Project import generated by Copybara.

GitOrigin-RevId: 943441cb588dcc8b2522538cb10f0662a37e7367
diff --git a/COPYING b/COPYING
index e90dfed..b1e3f5a 100644
--- a/COPYING
+++ b/COPYING
@@ -1,221 +1,397 @@
-		    GNU GENERAL PUBLIC LICENSE
-		       Version 2, June 1991
+		  GNU LESSER GENERAL PUBLIC LICENSE
+		       Version 2.1, February 1999
 
- Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ Copyright (C) 1991, 1999 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.
 
+[This is the first released version of the Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
 			    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.
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
 
-  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.
+  This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+  When we speak of free software, we are referring to freedom of use,
+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 and use pieces of
+it in new free programs; and that you are informed that 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.
+distributors to deny you these rights or to ask you to surrender these
+rights.  These restrictions translate to certain responsibilities for
+you if you distribute copies of the library 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.
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it.  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.
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
 
-  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.
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
 
-  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.
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard.  To achieve this, non-free programs must be
+allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
 
   The precise terms and conditions for copying, distribution and
-modification follow.
-
-		    GNU GENERAL PUBLIC LICENSE
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+		  GNU LESSER 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".
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
 
-Activities other than copying, distribution and modification are not
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, 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 library.
+
+  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.
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+  
+  1. You may copy and distribute verbatim copies of the Library's
+complete 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 distribute a copy of this License along with the
+Library.
 
-  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
+  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 Library or any portion
+of it, thus forming a work based on the Library, 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
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified 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) You must cause the whole of the work to be licensed 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.)
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
 
 These requirements apply to the modified work as a whole.  If
-identifiable sections of that work are not derived from the Program,
+identifiable sections of that work are not derived from the Library,
 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
+on the Library, 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.
+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.
+collective works based on the Library.
 
-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
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) 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:
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+
+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
 
-    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,
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
 
-    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,
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you 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.
 
-    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
+  If distribution of 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 satisfies the requirement to
+distribute the source code, even though third parties are not
 compelled to copy the source along with the object code.
 
-  4. You may not copy, modify, sublicense, or distribute the Program
-except as expressly provided under this License.  Any attempt
-otherwise to copy, modify, sublicense or distribute the Program is
-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. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
 
-  5. You are not required to accept this License, since you have not
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+  6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) Accompany the work with a written offer, valid for at
+    least three years, to give the same user the materials
+    specified in Subsection 6a, above, for a charge no more
+    than the cost of performing this distribution.
+
+    d) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    e) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the materials to be 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.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library 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.
+
+  9. 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
+distribute the Library 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
+modifying or distributing the Library (or any work based on the
+Library), 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.
+the Library 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
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+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
+You are not responsible for enforcing compliance by third parties with
 this License.
-
-  7. If, as a consequence of a court judgment or allegation of patent
+
+  11. 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
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library 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.
+refrain entirely from distribution of the Library.
 
-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.
+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
+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
@@ -226,115 +402,103 @@
 This section is intended to make thoroughly clear what is believed to
 be a consequence of the rest of this License.
 
-  8. If the distribution and/or use of the Program is restricted in
+  12. If the distribution and/or use of the Library 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.
+original copyright holder who places the Library 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.
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser 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.
+Each version is given a distinguishing version number.  If the Library
+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 Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+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.
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "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
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY 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.
+  16. 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 LIBRARY 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
+LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
 
 		     END OF TERMS AND CONDITIONS
+
+           How to Apply These Terms to Your New Libraries
 
-	    How to Apply These Terms to Your New Programs
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
 
-  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 apply these terms, attach the following notices to the library.  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.
 
-  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.>
+    <one line to give the library'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 library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
 
-    This program is distributed in the hope that it will be useful,
+    This library is distributed in the hope that it will be useful,
     but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Lesser General Public License for more details.
 
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; 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
+school, if any, to sign a "copyright disclaimer" for the library, 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.
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
 
-  <signature of Ty Coon>, 1 April 1989
+  <signature of Ty Coon>, 1 April 1990
   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.
+That's all there is to it!
+
+
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 0000000..22df356
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,69 @@
+* update to libtool 1.3.3
+
+0.1.3 -> 0.2.0
+
+* added snd_pcm_loopback_block_mode to PCM loopback interface
+* fixups in header files (according to documentation)
+* version is now compatible with driver
+
+0.1.2 -> 0.1.3
+
+* added PCM loopback interface
+
+0.1.1 -> 0.1.2
+
+* bug fixes in open() functions
+
+0.1.0 -> 0.1.1
+
+* added more switch functions to control interface
+
+0.0.9 -> 0.1.0
+
+* renamed soundlib.h to asoundlib.h
+* renamed libraries from libsound to libasound
+* big API changes
+  - added switches interfaces
+* added RawMIDI API
+
+0.0.8 -> 0.0.9
+
+* Makefile and configure.in changes
+  - added check for alsa driver package
+  - added spec file for RPM
+
+0.0.7 -> 0.0.8
+
+* added LGPL notice to all source and header files
+
+0.0.6 -> 0.0.7
+
+* added snd_cards_name function
+
+0.0.5 -> 0.0.6
+
+* fixed SND_PCM_OPEN constants
+
+0.0.4 -> 0.0.5
+
+* added snd_cards_mask function
+* added info functions for pcm playback/record in control interface
+* fixed Makefile bugs for shared library (added -fPIC)
+
+0.0.3 -> 0.0.4
+
+* changed COPYING policy from GPL to LGPL
+* fixed bug in snd_mixer_channel_read & write
+* added mixer exact support
+* added pcm time mode support
+* 'make install' is now possible
+
+0.0.2 -> 0.0.3
+
+* corrected documentation
+
+0.0.1 -> 0.0.2
+
+* added file COPYING
+* added documentation in sgml + plan.txt
+* minor changes in API for MIXER & PCM
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..91a8648
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,122 @@
+
+			ALSA library installation
+			=========================
+
+Installation from tarball
+-------------------------
+
+For installation you can use these commands:
+
+	./configure
+	make install
+
+
+Compilation from HG sources
+---------------------------
+
+You need also GNU packages automake and libtool installed in your system
+to compile HG (Mercurial) sources of alsa-lib package.
+
+For compilation you can use these commands:
+
+	libtoolize --force --copy --automake
+	aclocal
+	autoheader
+	automake --foreign --copy --add-missing
+	autoconf
+	./configure
+	make
+
+The included hgcompile script does this job for you.
+
+Note: Some automake packages have missing aclocal program. Use newer version
+      in the case.
+
+
+Compilation of static library
+-----------------------------
+
+If you would like to use the static ALSA library, you need to use these
+options for the configure script:
+
+	./configure --enable-shared=no --enable-static=yes
+
+Unfortunately, due to bug in the libtool script, the shared and static
+library cannot be built together.
+
+
+Partial Builds
+--------------
+
+You can choose the core components to build via --enable-* or --disable-*
+configure option for reducing the size of libasound.  The selectable
+components are: pcm, mixer, rawmidi, hwdep, seq and instr.
+For example, --disable-rawmidi will prevent to build the stuff related
+with raw MIDI.  As default, all components are enabled.
+
+The PCM plugins to build can be selected via --with-pcm-plugins
+configure option.  Multiple plugins can be passed by separation with
+comma.  For example, to select _only_ rate and linear plugins (and
+disable other plugins), pass
+	--with-pcm-plugins=rate,linear
+Note that "hw" plugin is always enabled.
+Passing "all" will select all available plugins (which is the default
+behavior).
+
+When you select "plug" plugin, copy and linear plugins will be
+automatically selected, too.  That is, the linear-format and
+access-type conversions are always available with plug layer.
+The other conversions of plug (channel shrink/expansion, rate,
+non-linear and float conversions) are enabled when the corresponding
+plugin is selected, too.
+
+
+Configuration for cross-compilation
+-----------------------------------
+
+When you would like to cross-compile ALSA library (e.g. compile on
+i686 host but for arm architecture) you will need to call ./configure
+script with additional parameters:
+
+CC=arm-linux-gcc ./configure --target=arm-linux
+
+In this example host where the library is build is guessed (should be
+given with --host=platform) and target for which is the library build is
+Linux on ARM architecture.  You should omit setting 'CC' variable and
+cross-compiler will be guessed too.
+
+So simplest version would be:
+
+./configure --target=arm-linux
+
+For platform names in the form cpu-vendor-os (or aliases for this)
+you should look in 'config.guess' script. Target and all paths
+used here are only examples and should not be directly applicable to
+your system.
+
+Configuration for machines without FPU
+--------------------------------------
+
+If your machine does not have FP unit, you should use '--with-softfloat'
+option. This option disables usage of float numbers in PCM route plugin.
+ALSA could then leave much more CPU cycles for your applications, but you 
+could still need some floating point emulator.
+
+Jack plugin
+-----------
+
+JACK plugin is moved to alsa-plugins package.
+
+Trouble Shooting
+----------------
+
+* Install path on Fedora Core 3
+
+  FC3 installs its system ALSA library to /lib instead of /usr/lib.
+  Specify --libdir=/lib to configure to overwrite it with the new library,
+  or run like
+
+	# ln -sf /usr/lib/libasound.so.2.0.0 /lib/libasound.so.2.0.0
+
+  to make symlink to the new path.
+  Note that /lib might be /lib64 on 64bit architecture.
diff --git a/LICENSE b/LICENSE
new file mode 120000
index 0000000..d24842f
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1 @@
+COPYING
\ No newline at end of file
diff --git a/MEMORY-LEAK b/MEMORY-LEAK
new file mode 100644
index 0000000..d9677d8
--- /dev/null
+++ b/MEMORY-LEAK
@@ -0,0 +1,13 @@
+
+
+				Memory leaks - really?
+				----------------------
+
+Note that some developers are thinking that the ALSA library has some memory
+leaks. Sure, it can be truth, but before contacting us, please, be sure that
+these leaks are not forced.
+
+The biggest reported leak is that the global configuration is cached for
+next usage. If you do not want this feature, simply, call
+snd_config_update_free_global() after all snd_*_open*() calls. This function
+will free the cache.
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..f0c39c1
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,37 @@
+ACLOCAL_AMFLAGS = -I m4
+
+SUBDIRS=doc include src
+if BUILD_MODULES
+SUBDIRS += modules
+endif
+if BUILD_PCM_PLUGIN_SHM
+SUBDIRS += aserver
+endif
+if BUILD_MIXER
+if BUILD_ALISP
+SUBDIRS += alsalisp
+endif
+endif
+SUBDIRS += test utils
+EXTRA_DIST=ChangeLog INSTALL TODO NOTES configure gitcompile libtool \
+	   depcomp version MEMORY-LEAK m4/attributes.m4
+AUTOMAKE_OPTIONS=foreign
+
+INCLUDES=-I$(top_srcdir)/include
+
+rpm: dist
+	$(MAKE) -C utils rpm
+
+dist-hook:
+	-chmod -R a+r $(distdir)
+	@if ! test -z "$(AMTAR)"; then \
+		$(AMTAR) --create --verbose --file=- $(distdir) | bzip2 -c -9 > $(distdir).tar.bz2 ; \
+	else \
+		$(TAR) --create --verbose --file=- $(distdir) | bzip2 -c -9 > $(distdir).tar.bz2 ; \
+	fi
+
+doc-dummy:
+
+doc: doc-dummy
+	$(MAKE) -C include all
+	$(MAKE) -C doc doc
diff --git a/NOTES b/NOTES
new file mode 100644
index 0000000..b318d1f
--- /dev/null
+++ b/NOTES
@@ -0,0 +1,56 @@
+Old versus new PCM API (values returned using indirect pointers)
+================================================================
+
+From the binary compatibility view, there is no change. For compilation,
+1.0 ALSA applications do not need any change. The older applications must
+use this include sequence:
+
+#define ALSA_PCM_OLD_HW_PARAMS_API
+#define ALSA_PCM_OLD_SW_PARAMS_API
+#include <alsa/asoundlib.h>
+
+If you use already the new API, you may remove old defines selecting
+this API, because they are no longer used:
+
+#define ALSA_PCM_NEW_HW_PARAMS_API
+#define ALSA_PCM_NEW_SW_PARAMS_API
+
+
+Verbose Error Messages
+======================
+
+Since version 1.0.8, assert() for some non-fatal errors are removed
+and error messages are no longer shown to stderr as default.  Instead,
+the error messages appear only when the environment variable
+LIBASOUND_DEBUG is set (to a non-empty value).
+
+When LIBASOUND_DEBUG=1 is set, the errors in hw_params configuration
+will be dumped to stderr.  Note that this will show even the non-fatal
+errors of plug layer (trial-and-error of parameters).
+
+This feature is disabled when --with-debug=no is passed to configure,
+i.e. no strict checking is done in alsa-lib.
+
+In addition, when --enable-debug-assert configure option is given and
+when LIBASOUND_DEBUG_ASSERT=1 is set, the default error message
+handler can call assert() to catch with a  debugger.  This feature was
+formerly activated via LIBASOUND_DEBUG=2.
+
+
+Blocking Open Mode
+==================
+
+The default behavior of blocking at snd_pcm_open is changed to
+non-blocking since version 1.0.11.  That is, snd_pcm_open() returns
+-EAGAIN immediately when the device is in use and cannot be opened,
+while the function was blocked in the former version.  This influences
+only on the opening behavior.  The behavior of the further access,
+read/write, poll or commit, are not changed.  They follow the extra
+flag argument of snd_pcm_open() as well as the former version.
+
+For taking back the compatible behavior of open blocking mode, set
+
+	defaults.pcm.nonblock 0
+
+in /etc/asound.conf or ~/.asoundrc file.
+
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..f4a99ff
--- /dev/null
+++ b/TODO
@@ -0,0 +1,4 @@
+H add serious PCM test application to test various period/buffer size
+  combinations to determine possible problems in the lowlevel drivers
+M think about xrun recovery helpers
+L move OSS emulation to user space? (pseudo device driver and daemon)
diff --git a/acinclude.m4 b/acinclude.m4
new file mode 100644
index 0000000..35486a0
--- /dev/null
+++ b/acinclude.m4
@@ -0,0 +1,24 @@
+AC_DEFUN([SAVE_LIBRARY_VERSION], [
+AC_MSG_CHECKING(for library version)
+SND_LIB_VERSION=$VERSION
+echo $VERSION > $srcdir/version
+AC_DEFINE_UNQUOTED(VERSION, "$SND_LIB_VERSION", [sound library version string])
+AC_SUBST(SND_LIB_VERSION)
+SND_LIB_MAJOR=`echo $VERSION | cut -d . -f 1`
+AC_SUBST(SND_LIB_MAJOR)
+SND_LIB_MINOR=`echo $VERSION | cut -d . -f 2`
+AC_SUBST(SND_LIB_MINOR)
+SND_LIB_SUBMINOR=`echo $VERSION | cut -d . -f 3 | sed -e 's/^\([[^[:alpha:]]]*\)\(.*\)$/\1/g'`
+AC_SUBST(SND_LIB_SUBMINOR)
+SND_LIB_EXTRASTR=`echo $VERSION | cut -d . -f 3 | sed -e 's/^\([[^[:alpha:]]]*\)\([[[:alpha:]]]*\)\([[[:digit:]]]*\)\(.*\)$/\2/g'`
+SND_LIB_EXTRAVER=`echo $VERSION | cut -d . -f 3 | sed -e 's/^\([[^[:alpha:]]]*\)\([[[:alpha:]]]*\)\([[[:digit:]]]*\)\(.*\)$/\3/g'`
+case "$SND_LIB_EXTRASTR" in
+  pre)   SND_LIB_EXTRAVER=`expr $SND_LIB_EXTRAVER + 00000` ;;
+  alpha) SND_LIB_EXTRAVER=`expr $SND_LIB_EXTRAVER + 10000` ;;
+  beta)  SND_LIB_EXTRAVER=`expr $SND_LIB_EXTRAVER + 20000` ;;
+  rc)	 SND_LIB_EXTRAVER=`expr $SND_LIB_EXTRAVER + 100000` ;;
+  *)     SND_LIB_EXTRAVER=1000000 ;;
+esac
+AC_MSG_RESULT(major $SND_LIB_MAJOR minor $SND_LIB_MINOR subminor $SND_LIB_SUBMINOR extrastr $SND_LIB_EXTRASTR extraver $SND_LIB_EXTRAVER)
+AC_SUBST(SND_LIB_EXTRAVER)
+])
diff --git a/alsalisp/Makefile.am b/alsalisp/Makefile.am
new file mode 100644
index 0000000..6df915a
--- /dev/null
+++ b/alsalisp/Makefile.am
@@ -0,0 +1,8 @@
+noinst_PROGRAMS = alsalisp
+
+alsalisp_SOURCES = alsalisp.c
+alsalisp_LDADD = ../src/libasound.la
+
+all: alsalisp
+
+INCLUDES=-I$(top_srcdir)/include -I$(top_srcdir)/src/alisp
diff --git a/alsalisp/alsalisp.c b/alsalisp/alsalisp.c
new file mode 100644
index 0000000..2a2a77b
--- /dev/null
+++ b/alsalisp/alsalisp.c
@@ -0,0 +1,110 @@
+/*
+ *  ALSA lisp implementation
+ *  Copyright (c) 2003 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <err.h>
+
+#include "asoundlib.h"
+#include "alisp.h"
+
+static int verbose = 0;
+static int warning = 0;
+static int debug = 0;
+
+static void interpret_filename(const char *file)
+{
+	struct alisp_cfg cfg;
+	snd_input_t *in;
+	snd_output_t *out;
+	int err;
+
+	memset(&cfg, 0, sizeof(cfg));
+	if (file != NULL && strcmp(file, "-") != 0) {
+		if ((err = snd_input_stdio_open(&in, file, "r")) < 0) {
+			fprintf(stderr, "unable to open filename '%s' (%s)\n", file, snd_strerror(err));
+			return;
+		}
+	} else {
+		if ((err = snd_input_stdio_attach(&in, stdin, 0)) < 0) {
+			fprintf(stderr, "unable to attach stdin '%s' (%s)\n", file, snd_strerror(err));
+			return;
+		}
+	}
+	if (snd_output_stdio_attach(&out, stdout, 0) < 0) {
+		snd_input_close(in);
+		fprintf(stderr, "unable to attach stdout (%s)\n", strerror(errno));
+		return;
+	}
+	cfg.verbose = verbose;
+	cfg.warning = warning;
+	cfg.debug = debug;
+	cfg.in = in;
+	cfg.out = cfg.eout = cfg.vout = cfg.wout = cfg.dout = out;
+	err = alsa_lisp(&cfg, NULL);
+	if (err < 0)
+		fprintf(stderr, "alsa lisp returned error %i (%s)\n", err, strerror(err));
+	else if (verbose)
+		printf("file %s passed ok via alsa lisp interpreter\n", file);
+	snd_output_close(out);
+	snd_input_close(in);
+}
+
+static void usage(void)
+{
+	fprintf(stderr, "usage: alsalisp [-vdw] [file...]\n");
+	exit(1);
+}
+
+int main(int argc, char **argv)
+{
+	int c;
+
+	while ((c = getopt(argc, argv, "vdw")) != -1) {
+		switch (c) {
+		case 'v':
+			verbose = 1;
+			break;
+		case 'd':
+			debug = 1;
+			break;
+		case 'w':
+			warning = 1;
+			break;
+		case '?':
+		default:
+			usage();
+			/* NOTREACHED */
+		}
+	}
+	argc -= optind;
+	argv += optind;
+
+	if (argc < 1)
+		interpret_filename(NULL);
+	else
+		while (*argv)
+			interpret_filename(*argv++);
+
+	return 0;
+}
diff --git a/alsalisp/hctl.lisp b/alsalisp/hctl.lisp
new file mode 100644
index 0000000..504050f
--- /dev/null
+++ b/alsalisp/hctl.lisp
@@ -0,0 +1,91 @@
+(setq card (Acall 'card_next -1))
+(setq card (Aresult card))
+(while (>= card 0)
+  (progn
+    (princ "found card: " card "\n")
+    (princ "  name    : " (Aresult (Acall 'card_get_name card)) "\n")
+    (princ "  longname: " (Aresult (Acall 'card_get_longname card)) "\n")
+    (setq card (Acall 'card_next card))
+    (setq card (Aresult card))
+  )
+)
+(unsetq card)
+
+(princ "card_get_index test (SI7018): " (Acall 'card_get_index "SI7018") "\n")
+(princ "card_get_index test (ABCD): " (Acall 'card_get_index "ABCD") "\n")
+
+(setq hctl (Acall 'hctl_open 'default nil))
+(if (= (Aerror hctl) 0)
+  (progn
+    (princ "open success: " hctl "\n")
+    (setq hctl (Ahandle hctl))
+    (princ "open hctl: " hctl "\n")
+    (setq hctl (Acall 'hctl_close hctl))
+    (if (= hctl 0)
+      (princ "close success\n")
+      (princ "close failed: " hctl "\n")
+    )
+  )
+  (progn
+    (princ "open failed: " hctl "\n")
+  )
+)
+(unsetq hctl)
+
+(setq ctl (Acall 'ctl_open 'default nil))
+(if (= (Aerror ctl) 0)
+  (progn
+    (princ "ctl open success: " ctl "\n")
+    (setq ctl (Ahandle ctl))
+    (setq info (Aresult (Acall 'ctl_card_info ctl)))
+    (princ "ctl card info: " info "\n")
+    (princ "ctl card info (mixername): " (cdr (assq "mixername" info)) "\n")
+    (unsetq info)
+    (setq hctl (Acall 'hctl_open_ctl ctl))
+    (if (= (Aerror hctl) 0)
+      (progn
+        (princ "hctl open success: " hctl "\n")
+        (setq hctl (Ahandle hctl))
+        (princ "open hctl: " hctl "\n")
+	(princ "load hctl: " (Acall 'hctl_load hctl) "\n")
+	(princ "first    : " (Acall 'hctl_first_elem hctl) "\n")
+	(princ "last     : " (Acall 'hctl_last_elem hctl) "\n")
+	(princ "next (first): " (Acall 'hctl_elem_next (Acall 'hctl_first_elem hctl)) "\n")
+	(princ "prev (last) : " (Acall 'hctl_elem_prev (Acall 'hctl_last_elem hctl)) "\n")
+	(setq elem (Acall 'hctl_first_elem hctl))
+	(while elem
+	  (progn
+	    (setq info (Acall 'hctl_elem_info elem))
+	    (princ info "\n")
+	    (setq value (Acall 'hctl_elem_read elem))
+	    (princ value "\n")
+	    (when (equal (cdr (assq "name" (car (cdr (assq "id" (Aresult info)))))) "Master Playback Volume")
+	      (princ "write Master: " (Acall 'hctl_elem_write elem (20 20)) "\n")
+	    )
+	    (unsetq info value)
+	    (gc)
+	    (setq elem (Acall 'hctl_elem_next elem))
+	  )
+        )
+	(unsetq elem)
+        (setq hctl (Acall 'hctl_close hctl))
+        (if (= hctl 0)
+          (princ "hctl close success\n")
+          (princ "hctl close failed: " hctl "\n")
+        )
+      )
+      (progn
+        (princ "hctl open failed: " hctl "\n")
+	(Acall 'ctl_close ctl)
+      )
+    )
+    (unsetq hctl)
+  )
+  (progn
+    (princ "ctl open failed: " ctl "\n")
+  )
+)
+(unsetq ctl)
+
+(&stat-memory)
+(&dump-memory "memory.dump")
diff --git a/alsalisp/hello.lisp b/alsalisp/hello.lisp
new file mode 100644
index 0000000..f04fc38
--- /dev/null
+++ b/alsalisp/hello.lisp
@@ -0,0 +1,26 @@
+(princ "Hello ALSA world\n")
+(princ "One " 1 "\n")
+(princ "Two " (+ 1 1) "\n")
+
+(defun myprinc (o) (progn (princ o)))
+(myprinc "Printed via myprinc function!\n")
+(unsetq myprinc)
+
+(defun printnum (from to) (while (<= from to) (princ " " from) (setq from (+ from 1))))
+(princ "Numbers 1-10: ") (printnum 1 10) (princ "\n")
+(unsetq printnum)
+
+(defun factorial (n) (if (> n 1) (* n (factorial (- n 1))) 1))
+(princ "Factorial of 10: " (factorial 10) "\n")
+(princ "Float test 1.1 + 1.35 = " (+ 1.1 1.35) "\n")
+(princ "Factorial of 10.0: " (factorial 10.0) "\n")
+(princ "Factorial of 20.0: " (factorial 20.0) "\n")
+(unsetq factorial)
+
+(setq alist '((one . first) (two . second) (three . third)))
+(princ "alist = " alist "\n")
+(princ "alist assoc one = " (assoc 'one alist) "\n")
+(princ "alist rassoc third = " (rassoc 'third alist) "\n")
+(unsetq alist)
+
+(&stat-memory)
diff --git a/alsalisp/itest.lisp b/alsalisp/itest.lisp
new file mode 100644
index 0000000..decd9ae
--- /dev/null
+++ b/alsalisp/itest.lisp
@@ -0,0 +1 @@
+(princ "itest.lisp file included!\n")
diff --git a/alsalisp/test.lisp b/alsalisp/test.lisp
new file mode 100644
index 0000000..5e3820f
--- /dev/null
+++ b/alsalisp/test.lisp
@@ -0,0 +1,382 @@
+;
+; Test code for all basic alsa lisp commands.
+; The test is indended to find memory leaks.
+;
+; Copyright (c) 2003 Jaroslav Kysela <perex@perex.cz>
+; License: GPL v2 (http://www.gnu.org/licenses/gpl.html)
+;
+
+;
+; Basic commands
+;
+
+(!=)				(&check-memory)
+(!= 0)				(&check-memory)
+(!= 0 1)			(&check-memory)
+(!= 1 1)			(&check-memory)
+(!= 0 1 2)			(&check-memory)
+(!= 'aaaa 'bbbb)		(&check-memory)
+
+(%)				(&check-memory)
+(% 11)				(&check-memory)
+(% 11 5)			(&check-memory)
+(% 11.5 5.1)			(&check-memory)
+(% 11.5 5.1 2.2)		(&check-memory)
+(% 'aaaa 'bbbb)			(&check-memory)
+
+(&check-memory)			(&check-memory)
+(&check-memory "abcd")		(&check-memory)
+(&dump-memory "-")		(&check-memory)
+(&dump-memory)			(&check-memory)
+(&dump-objects "-")		(&check-memory)
+(&dump-objects)			(&check-memory)
+(&stat-memory)			(&check-memory)
+(&stat-memory "abcd")		(&check-memory)
+
+(*)				(&check-memory)
+(* 1)				(&check-memory)
+(* 1 2)				(&check-memory)
+(* 1.1 2.2)			(&check-memory)
+(* 1.1 2.2 3.3)			(&check-memory)
+(* 'aaaa)			(&check-memory)
+
+(+)				(&check-memory)
+(+ 1)				(&check-memory)
+(+ 1 2)				(&check-memory)
+(+ 1.1 2.2)			(&check-memory)
+(+ 1.1 2.2 3.3)			(&check-memory)
+(+ 'aaaa)			(&check-memory)
+(+ 'aaaa 'bbbb)			(&check-memory)
+(+ "aaaa")			(&check-memory)
+(+ "aaaa" "bbbb")		(&check-memory)
+(+ "aaaa" "bbbb" "cccc")	(&check-memory)
+
+(-)				(&check-memory)
+(- 1)				(&check-memory)
+(- 1 2)				(&check-memory)
+(- 1.1 2.2)			(&check-memory)
+(- 1.1 2.2 3.3)			(&check-memory)
+(- 'aaaa)			(&check-memory)
+(- 'aaaa 'bbbb)			(&check-memory)
+
+(/)				(&check-memory)
+(/ 1)				(&check-memory)
+(/ 1 2)				(&check-memory)
+(/ 1.1 2.2)			(&check-memory)
+(/ 1.1 2.2 3.3)			(&check-memory)
+(/ 'aaaa)			(&check-memory)
+(/ 'aaaa 'bbbb)			(&check-memory)
+
+(<)				(&check-memory)
+(< 0)				(&check-memory)
+(< 0 1)				(&check-memory)
+(< 1 0)				(&check-memory)
+(< 0 1 2)			(&check-memory)
+
+(<=)				(&check-memory)
+(<= 0)				(&check-memory)
+(<= 0 1)			(&check-memory)
+(<= 1 0)			(&check-memory)
+(<= 0 1 2)			(&check-memory)
+
+(=)				(&check-memory)
+(= 0)				(&check-memory)
+(= 0 1)				(&check-memory)
+(= 1 1)				(&check-memory)
+(= 0 1 2)			(&check-memory)
+
+(>)				(&check-memory)
+(> 0)				(&check-memory)
+(> 0 1)				(&check-memory)
+(> 1 0)				(&check-memory)
+(> 0 1 2)			(&check-memory)
+
+(>= 0)				(&check-memory)
+(>= 0 1)			(&check-memory)
+(>= 1 0)			(&check-memory)
+(>= 0 1 2)			(&check-memory)
+
+(and)				(&check-memory)
+(and 0)				(&check-memory)
+(and 1)				(&check-memory)
+(and 0 0 0)			(&check-memory)
+
+(quote a)			(&check-memory)
+
+(assoc)							(&check-memory)
+(assoc 'one)						(&check-memory)
+(assoc 'one '((one . first)))				(&check-memory)
+(assoc 'one '((two . second)))				(&check-memory)
+(assoc 'one '((one . first) (two . second)))		(&check-memory)
+
+(assq)							(&check-memory)
+(assq 'one)						(&check-memory)
+(assq "one" '(("one" . "first")))			(&check-memory)
+(assq "one" '(("two" . "second")))			(&check-memory)
+(assq "one" '(("one" . "first") ("two" . "second")))	(&check-memory)
+
+(atom)				(&check-memory)
+(atom 'one)			(&check-memory)
+(atom "one")			(&check-memory)
+(atom "one" 'two)		(&check-memory)
+
+(funcall)			(&check-memory)
+
+(car)				(&check-memory)
+(car '(one . two))		(&check-memory)
+
+(cdr)				(&check-memory)
+(cdr '(one . two))		(&check-memory)
+
+(concat)			(&check-memory)
+(concat 'aaaa)			(&check-memory)
+(concat 'aaaa 'bbbb)		(&check-memory)
+(concat "aaaa")			(&check-memory)
+(concat "aaaa" "bbbb")		(&check-memory)
+(concat "aaaa" "bbbb" "cccc")	(&check-memory)
+
+(cond)				(&check-memory)
+(cond 0)			(&check-memory)
+(cond 0 1)			(&check-memory)
+(cond 0 1 2)			(&check-memory)
+(cond 0 1 2 3)			(&check-memory)
+(cond (0 'a) (1 'b) (0 'd))	(&check-memory)
+(cond 1)			(&check-memory)
+(cond 1 1)			(&check-memory)
+(cond 1 1 2)			(&check-memory)
+(cond 1 1 2 3)			(&check-memory)
+
+(cons)				(&check-memory)
+(cons "a")			(&check-memory)
+(cons "a" "b")			(&check-memory)
+(cons "a" "b" "c")		(&check-memory)
+
+(eq)				(&check-memory)
+(eq 1)				(&check-memory)
+(eq 0 0)			(&check-memory)
+(eq "a" "b")			(&check-memory)
+(eq "a" "b" "c")		(&check-memory)
+
+(equal)				(&check-memory)
+(equal 1)			(&check-memory)
+(equal 0 0)			(&check-memory)
+(equal "a" "b")			(&check-memory)
+(equal "a" "b" "c")		(&check-memory)
+
+(exfun)				(&check-memory)
+(exfun 'abcd)			(&check-memory)
+(exfun 'abcd 'ijkl)		(&check-memory)
+
+(format)			(&check-memory)
+(format 1)			(&check-memory)
+(format 'a)			(&check-memory)
+(format "a" "b" "c")		(&check-memory)
+(format "1.2")			(&check-memory)
+(format "%c" 43)		(&check-memory)
+(format "%d" 12)		(&check-memory)
+(format "%i" 12)		(&check-memory)
+(format "%f" 12.1)		(&check-memory)
+(format "%s" "abcd")		(&check-memory)
+(format "%s %i %i" "abcd" 1 2)	(&check-memory)
+
+(garbage-collect)		(&check-memory)
+(gc)				(&check-memory)
+
+(if)				(&check-memory)
+(if t)				(&check-memory)
+(if t 'a)			(&check-memory)
+(if t 'a 'b)			(&check-memory)
+(if nil)			(&check-memory)
+(if nil 'a)			(&check-memory)
+(if nil 'a 'b)			(&check-memory)
+
+(include "itest.lisp")		(&check-memory)
+
+(list)				(&check-memory)
+(list "a")			(&check-memory)
+(list "a" "b")			(&check-memory)
+(list "a" "b" "c")		(&check-memory)
+
+(not)				(&check-memory)
+(not 0)				(&check-memory)
+(not nil)			(&check-memory)
+(not t)				(&check-memory)
+(not 'a)			(&check-memory)
+(not 'a 'b 'c 'd)		(&check-memory)
+
+(nth)				(&check-memory)
+(nth 2)				(&check-memory)
+(nth 2 nil)			(&check-memory)
+(nth 2 '(('one 'two 'three)))	(&check-memory)
+
+(null)				(&check-memory)
+(null 0)			(&check-memory)
+(null nil)			(&check-memory)
+(null t)			(&check-memory)
+(null 'a)			(&check-memory)
+(null 'a 'b 'c 'd)		(&check-memory)
+
+(or)				(&check-memory)
+(or 0)				(&check-memory)
+(or 1)				(&check-memory)
+(or 0 0 0)			(&check-memory)
+
+(path)				(&check-memory)
+(path 0)			(&check-memory)
+(path 1)			(&check-memory)
+(path 0 0 0)			(&check-memory)
+(path "data")			(&check-memory)
+
+(princ)				(&check-memory)
+(princ "\nabcd\n")		(&check-memory)
+(princ "a" "b" "c\n")		(&check-memory)
+
+(prog1)				(&check-memory)
+(prog1 1)			(&check-memory)
+(prog1 1 2 3 4)			(&check-memory)
+
+(prog2)				(&check-memory)
+(prog2 1)			(&check-memory)
+(prog2 1 2 3 4)			(&check-memory)
+
+(progn)				(&check-memory)
+(progn 1)			(&check-memory)
+(progn 1 2 3 4)			(&check-memory)
+
+(quote)				(&check-memory)
+(quote a)			(&check-memory)
+
+(rassoc)						(&check-memory)
+(rassoc 'first)						(&check-memory)
+(rassoc 'first '((one . first)))			(&check-memory)
+(rassoc 'first '((two . second)))			(&check-memory)
+(rassoc 'first '((one . first) (two . second)))		(&check-memory)
+
+(rassq)							(&check-memory)
+(rassq "first")						(&check-memory)
+(rassq "first" '(("one" . "first")))			(&check-memory)
+(rassq "first" '(("two" . "second")))			(&check-memory)
+(rassq "first" '(("one" . "first") ("two" . "second")))	(&check-memory)
+
+(set)				(&check-memory)
+(set "a") (unset "a")		(&check-memory)
+(set "a" 1) (unset "a")		(&check-memory)
+(set a 1) (unset a)		(&check-memory)
+(set "a" 1 2) (unset "a")	(&check-memory)
+
+(setf)				(&check-memory)
+(setf a) (unsetf a)		(&check-memory)
+(setf a 1) (unsetf a)		(&check-memory)
+(setf a 1 2) (unsetf a)		(&check-memory)
+
+(setq)				(&check-memory)
+(setq a) (unsetq a)		(&check-memory)
+(setq a 1) (unsetq a)		(&check-memory)
+(setq a 1 2) (unsetq a)		(&check-memory)
+
+(string-equal)			(&check-memory)
+(string-equal 1)		(&check-memory)
+(string-equal "a")		(&check-memory)
+(string-equal "a" "a")		(&check-memory)
+(string-equal "a" "b")		(&check-memory)
+(string-equal "a" "b" "c")	(&check-memory)
+
+(string-to-integer)		(&check-memory)
+(string-to-integer 1)		(&check-memory)
+(string-to-integer 1.5)		(&check-memory)
+(string-to-integer "a")		(&check-memory)
+(string-to-integer "a" "a")	(&check-memory)
+(string-to-integer "a" "b")	(&check-memory)
+(string-to-integer "a" "b" "c")	(&check-memory)
+
+(string-to-float)		(&check-memory)
+(string-to-float 1)		(&check-memory)
+(string-to-float 1.5)		(&check-memory)
+(string-to-float "a")		(&check-memory)
+(string-to-float "a" "a")	(&check-memory)
+(string-to-float "a" "b")	(&check-memory)
+(string-to-float "a" "b" "c")	(&check-memory)
+
+(string=)			(&check-memory)
+(string= 1)			(&check-memory)
+(string= "a")			(&check-memory)
+(string= "a" "a")		(&check-memory)
+(string= "a" "b")		(&check-memory)
+(string= "a" "b" "c")		(&check-memory)
+
+(unless)			(&check-memory)
+(unless 1)			(&check-memory)
+(unless 0 1 2)			(&check-memory)
+(unless t 2 3 4)		(&check-memory)
+(unless nil 2 3 4)		(&check-memory)
+
+(unset)				(&check-memory)
+(unset "a")			(&check-memory)
+
+(unsetf)			(&check-memory)
+(unsetf a)			(&check-memory)
+(unsetf a b)			(&check-memory)
+
+(unsetq)			(&check-memory)
+(unsetq a)			(&check-memory)
+(unsetq a b)			(&check-memory)
+
+(when)				(&check-memory)
+(when 0)			(&check-memory)
+(when 0 1)			(&check-memory)
+(when t 1)			(&check-memory)
+(when nil 1)			(&check-memory)
+
+(while)				(&check-memory)
+(while nil)			(&check-memory)
+(while nil 1)			(&check-memory)
+(while nil 1 2 3 4)		(&check-memory)
+
+;
+; more complex command sequences
+;
+
+(setq abcd "abcd")
+(unsetq abcd)
+(&check-memory)
+
+(setq abcd (("abcd" . "efgh") ("1234" . "5678")))
+(unsetq abcd)
+(&check-memory)
+
+(defun myfun () (princ "a\n"))
+(exfun 'myfun)
+(unsetq myfun)
+(&check-memory)
+
+(defun myfun () (princ "a\n"))
+(funcall 'myfun)
+(funcall 'myfun 'aaaaa)
+(unsetq myfun)
+(&check-memory)
+
+(defun myfun (o) (princ o "a\n"))
+(funcall 'myfun)
+(funcall 'myfun 'aaaaa)
+(unsetq myfun)
+(&check-memory)
+
+(defun myfun (o p) (princ o p "\n"))
+(funcall 'myfun)
+(funcall 'myfun 'aaaaa)
+(funcall 'myfun 'aaaaa 'bbbbb)
+(unsetq myfun)
+(&check-memory)
+
+(defun printnum (from to) (while (<= from to) (princ " " from) (setq from (+ from 1))))
+(princ "Numbers 1-10:") (printnum 1 10) (princ "\n")
+(unsetq printnum)
+
+;
+; game over
+;
+
+(princ "*********************\n")
+(princ "OK, all tests passed!\n")
+(princ "*********************\n")
+(&stat-memory)
diff --git a/aserver/COPYING b/aserver/COPYING
new file mode 100644
index 0000000..b07ad0d
--- /dev/null
+++ b/aserver/COPYING
@@ -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 Lesser 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.
+
+		    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.)
+
+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.
+
+  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.
+
+  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
+
+	    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.1 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/aserver/Makefile.am b/aserver/Makefile.am
new file mode 100644
index 0000000..116f578
--- /dev/null
+++ b/aserver/Makefile.am
@@ -0,0 +1,12 @@
+bin_PROGRAMS = aserver
+aserver_SOURCES = aserver.c
+# aserver_LDADD = -lasound
+aserver_LDADD = ../src/libasound.la
+
+all: aserver
+
+INCLUDES=-I$(top_srcdir)/include -I$(top_srcdir)/src/pcm
+
+../src/libasound.la:
+	$(MAKE) -C ../src libasound.la
+
diff --git a/aserver/aserver.c b/aserver/aserver.c
new file mode 100644
index 0000000..73ea4e9
--- /dev/null
+++ b/aserver/aserver.c
@@ -0,0 +1,1104 @@
+/*
+ *  ALSA server
+ *  Copyright (c) by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <sys/shm.h>
+#include <sys/socket.h>
+#include <sys/poll.h>
+#include <sys/un.h>
+#include <sys/uio.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <getopt.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <limits.h>
+#include <signal.h>
+
+#include "aserver.h"
+
+char *command;
+
+#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95)
+#define ERROR(...) do {\
+	fprintf(stderr, "%s %s:%i:(%s) ", command, __FILE__, __LINE__, __FUNCTION__); \
+	fprintf(stderr, __VA_ARGS__); \
+	putc('\n', stderr); \
+} while (0)
+#else
+#define ERROR(args...) do {\
+	fprintf(stderr, "%s %s:%i:(%s) ", command, __FILE__, __LINE__, __FUNCTION__); \
+	fprintf(stderr, ##args); \
+	putc('\n', stderr); \
+} while (0)
+#endif	
+
+#define SYSERROR(string) ERROR(string ": %s", strerror(errno))
+
+static int make_local_socket(const char *filename)
+{
+	size_t l = strlen(filename);
+	size_t size = offsetof(struct sockaddr_un, sun_path) + l;
+	struct sockaddr_un *addr = alloca(size);
+	int sock;
+
+	sock = socket(PF_LOCAL, SOCK_STREAM, 0);
+	if (sock < 0) {
+		int result = -errno;
+		SYSERROR("socket failed");
+		return result;
+	}
+	
+	unlink(filename);
+
+	addr->sun_family = AF_LOCAL;
+	memcpy(addr->sun_path, filename, l);
+
+	if (bind(sock, (struct sockaddr *) addr, size) < 0) {
+		int result = -errno;
+		SYSERROR("bind failed");
+		return result;
+	}
+
+	return sock;
+}
+
+static int make_inet_socket(int port)
+{
+	struct sockaddr_in addr;
+	int sock;
+
+	sock = socket(PF_INET, SOCK_STREAM, 0);
+	if (sock < 0) {
+		int result = -errno;
+		SYSERROR("socket failed");
+		return result;
+	}
+	
+	addr.sin_family = AF_INET;
+	addr.sin_port = htons(port);
+	addr.sin_addr.s_addr = INADDR_ANY;
+
+	if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+		int result = -errno;
+		SYSERROR("bind failed");
+		return result;
+	}
+
+	return sock;
+}
+
+struct pollfd *pollfds;
+unsigned int pollfds_count = 0;
+typedef struct waiter waiter_t;
+typedef int (*waiter_handler_t)(waiter_t *waiter, unsigned short events);
+struct waiter {
+	int fd;
+	void *private_data;
+	waiter_handler_t handler;
+};
+waiter_t *waiters;
+
+static void add_waiter(int fd, unsigned short events, waiter_handler_t handler,
+		void *data)
+{
+	waiter_t *w = &waiters[fd];
+	struct pollfd *pfd = &pollfds[pollfds_count];
+	assert(!w->handler);
+	pfd->fd = fd;
+	pfd->events = events;
+	pfd->revents = 0;
+	w->fd = fd;
+	w->private_data = data;
+	w->handler = handler;
+	pollfds_count++;
+}
+
+static void del_waiter(int fd)
+{
+	waiter_t *w = &waiters[fd];
+	unsigned int k;
+	assert(w->handler);
+	w->handler = 0;
+	for (k = 0; k < pollfds_count; ++k) {
+		if (pollfds[k].fd == fd)
+			break;
+	}
+	assert(k < pollfds_count);
+	pollfds_count--;
+	memmove(&pollfds[k], &pollfds[k + 1], pollfds_count - k);
+}
+
+typedef struct client client_t;
+
+typedef struct {
+	int (*open)(client_t *client, int *cookie);
+	int (*cmd)(client_t *client);
+	int (*close)(client_t *client);
+} transport_ops_t;
+
+struct client {
+	struct list_head list;
+	int poll_fd;
+	int ctrl_fd;
+	int local;
+	int transport_type;
+	int dev_type;
+	char name[256];
+	int stream;
+	int mode;
+	transport_ops_t *ops;
+	snd_async_handler_t *async_handler;
+	int async_sig;
+	pid_t async_pid;
+	union {
+		struct {
+			snd_pcm_t *handle;
+			int fd;
+		} pcm;
+		struct {
+			snd_ctl_t *handle;
+			int fd;
+		} ctl;
+#if 0
+		struct {
+			snd_rawmidi_t *handle;
+		} rawmidi;
+		struct {
+			snd_timer_open_t *handle;
+		} timer;
+		struct {
+			snd_hwdep_t *handle;
+		} hwdep;
+		struct {
+			snd_seq_t *handle;
+		} seq;
+#endif
+	} device;
+	int polling;
+	int open;
+	int cookie;
+	union {
+		struct {
+			int ctrl_id;
+			void *ctrl;
+		} shm;
+	} transport;
+};
+
+LIST_HEAD(clients);
+
+typedef struct {
+	struct list_head list;
+	int fd;
+	uint32_t cookie;
+} inet_pending_t;
+LIST_HEAD(inet_pendings);
+
+#if 0
+static int pcm_handler(waiter_t *waiter, unsigned short events)
+{
+	client_t *client = waiter->private_data;
+	char buf[1];
+	ssize_t n;
+	if (events & POLLIN) {
+		n = write(client->poll_fd, buf, 1);
+		if (n != 1) {
+			SYSERROR("write failed");
+			return -errno;
+		}
+	} else if (events & POLLOUT) {
+		n = read(client->poll_fd, buf, 1);
+		if (n != 1) {
+			SYSERROR("read failed");
+			return -errno;
+		}
+	}
+	del_waiter(waiter->fd);
+	client->polling = 0;
+	return 0;
+}
+#endif
+
+static void pcm_shm_hw_ptr_changed(snd_pcm_t *pcm, snd_pcm_t *src ATTRIBUTE_UNUSED)
+{
+	client_t *client = pcm->hw.private_data;
+	volatile snd_pcm_shm_ctrl_t *ctrl = client->transport.shm.ctrl;
+	snd_pcm_t *loop;
+
+	ctrl->hw.changed = 1;
+	if (pcm->hw.fd >= 0) {
+		ctrl->hw.use_mmap = 1;
+		ctrl->hw.offset = pcm->hw.offset;
+		return;
+	}
+	ctrl->hw.use_mmap = 0;
+	ctrl->hw.ptr = pcm->hw.ptr ? *pcm->hw.ptr : 0;
+	for (loop = pcm->hw.master; loop; loop = loop->hw.master)
+		loop->hw.ptr = &ctrl->hw.ptr;
+	pcm->hw.ptr = &ctrl->hw.ptr;
+}
+
+static void pcm_shm_appl_ptr_changed(snd_pcm_t *pcm, snd_pcm_t *src ATTRIBUTE_UNUSED)
+{
+	client_t *client = pcm->appl.private_data;
+	volatile snd_pcm_shm_ctrl_t *ctrl = client->transport.shm.ctrl;
+	snd_pcm_t *loop;
+
+	ctrl->appl.changed = 1;
+	if (pcm->appl.fd >= 0) {
+		ctrl->appl.use_mmap = 1;
+		ctrl->appl.offset = pcm->appl.offset;
+		return;
+	}
+	ctrl->appl.use_mmap = 0;
+	ctrl->appl.ptr = pcm->appl.ptr ? *pcm->appl.ptr : 0;
+	for (loop = pcm->appl.master; loop; loop = loop->appl.master)
+		loop->appl.ptr = &ctrl->appl.ptr;
+	pcm->appl.ptr = &ctrl->appl.ptr;
+}
+
+static int pcm_shm_open(client_t *client, int *cookie)
+{
+	int shmid;
+	snd_pcm_t *pcm;
+	int err;
+	int result;
+	err = snd_pcm_open(&pcm, client->name, client->stream, SND_PCM_NONBLOCK);
+	if (err < 0)
+		return err;
+	client->device.pcm.handle = pcm;
+	client->device.pcm.fd = _snd_pcm_poll_descriptor(pcm);
+	pcm->hw.private_data = client;
+	pcm->hw.changed = pcm_shm_hw_ptr_changed;
+	pcm->appl.private_data = client;
+	pcm->appl.changed = pcm_shm_appl_ptr_changed;
+
+	shmid = shmget(IPC_PRIVATE, PCM_SHM_SIZE, 0666);
+	if (shmid < 0) {
+		result = -errno;
+		SYSERROR("shmget failed");
+		goto _err;
+	}
+	client->transport.shm.ctrl_id = shmid;
+	client->transport.shm.ctrl = shmat(shmid, 0, 0);
+	if (client->transport.shm.ctrl == (void*) -1) {
+		result = -errno;
+		shmctl(shmid, IPC_RMID, 0);
+		SYSERROR("shmat failed");
+		goto _err;
+	}
+	*cookie = shmid;
+	return 0;
+
+ _err:
+	snd_pcm_close(pcm);
+	return result;
+
+}
+
+static int pcm_shm_close(client_t *client)
+{
+	int err;
+	snd_pcm_shm_ctrl_t *ctrl = client->transport.shm.ctrl;
+	if (client->polling) {
+		del_waiter(client->device.pcm.fd);
+		client->polling = 0;
+	}
+	err = snd_pcm_close(client->device.pcm.handle);
+	ctrl->result = err;
+	if (err < 0) 
+		ERROR("snd_pcm_close");
+	if (client->transport.shm.ctrl) {
+		err = shmdt((void *)client->transport.shm.ctrl);
+		if (err < 0)
+			SYSERROR("shmdt failed");
+		err = shmctl(client->transport.shm.ctrl_id, IPC_RMID, 0);
+		if (err < 0)
+			SYSERROR("shmctl IPC_RMID failed");
+		client->transport.shm.ctrl = 0;
+	}
+	client->open = 0;
+	return 0;
+}
+
+static int shm_ack(client_t *client)
+{
+	struct pollfd pfd;
+	int err;
+	char buf[1];
+	pfd.fd = client->ctrl_fd;
+	pfd.events = POLLHUP;
+	if (poll(&pfd, 1, 0) == 1)
+		return -EBADFD;
+	err = write(client->ctrl_fd, buf, 1);
+	if (err != 1)
+		return -EBADFD;
+	return 0;
+}
+
+static int shm_ack_fd(client_t *client, int fd)
+{
+	struct pollfd pfd;
+	int err;
+	char buf[1];
+	pfd.fd = client->ctrl_fd;
+	pfd.events = POLLHUP;
+	if (poll(&pfd, 1, 0) == 1)
+		return -EBADFD;
+	err = snd_send_fd(client->ctrl_fd, buf, 1, fd);
+	if (err != 1)
+		return -EBADFD;
+	return 0;
+}
+
+static int shm_rbptr_fd(client_t *client, snd_pcm_rbptr_t *rbptr)
+{
+	if (rbptr->fd < 0)
+		return -EINVAL;
+	return shm_ack_fd(client, rbptr->fd);
+}
+
+static void async_handler(snd_async_handler_t *handler)
+{
+	client_t *client = snd_async_handler_get_callback_private(handler);
+	/* FIXME: use sigqueue */
+	kill(client->async_pid, client->async_sig);
+}
+
+static int pcm_shm_cmd(client_t *client)
+{
+	volatile snd_pcm_shm_ctrl_t *ctrl = client->transport.shm.ctrl;
+	char buf[1];
+	int err;
+	int cmd;
+	snd_pcm_t *pcm;
+	err = read(client->ctrl_fd, buf, 1);
+	if (err != 1)
+		return -EBADFD;
+	cmd = ctrl->cmd;
+	ctrl->cmd = 0;
+	pcm = client->device.pcm.handle;
+	switch (cmd) {
+	case SND_PCM_IOCTL_ASYNC:
+		ctrl->result = snd_pcm_async(pcm, ctrl->u.async.sig, ctrl->u.async.pid);
+		if (ctrl->result < 0)
+			break;
+		if (ctrl->u.async.sig >= 0) {
+			assert(client->async_sig < 0);
+			ctrl->result = snd_async_add_pcm_handler(&client->async_handler, pcm, async_handler, client);
+			if (ctrl->result < 0)
+				break;
+		} else {
+			assert(client->async_sig >= 0);
+			snd_async_del_handler(client->async_handler);
+		}
+		client->async_sig = ctrl->u.async.sig;
+		client->async_pid = ctrl->u.async.pid;
+		break;
+	case SNDRV_PCM_IOCTL_INFO:
+		ctrl->result = snd_pcm_info(pcm, (snd_pcm_info_t *) &ctrl->u.info);
+		break;
+	case SNDRV_PCM_IOCTL_HW_REFINE:
+		ctrl->result = snd_pcm_hw_refine(pcm, (snd_pcm_hw_params_t *) &ctrl->u.hw_refine);
+		break;
+	case SNDRV_PCM_IOCTL_HW_PARAMS:
+		ctrl->result = snd_pcm_hw_params(pcm, (snd_pcm_hw_params_t *) &ctrl->u.hw_params);
+		break;
+	case SNDRV_PCM_IOCTL_HW_FREE:
+		ctrl->result = snd_pcm_hw_free(pcm);
+		break;
+	case SNDRV_PCM_IOCTL_SW_PARAMS:
+		ctrl->result = snd_pcm_sw_params(pcm, (snd_pcm_sw_params_t *) &ctrl->u.sw_params);
+		break;
+	case SNDRV_PCM_IOCTL_STATUS:
+		ctrl->result = snd_pcm_status(pcm, (snd_pcm_status_t *) &ctrl->u.status);
+		break;
+	case SND_PCM_IOCTL_STATE:
+		ctrl->result = snd_pcm_state(pcm);
+		break;
+	case SND_PCM_IOCTL_HWSYNC:
+		ctrl->result = snd_pcm_hwsync(pcm);
+		break;
+	case SNDRV_PCM_IOCTL_DELAY:
+		ctrl->result = snd_pcm_delay(pcm, (snd_pcm_sframes_t *) &ctrl->u.delay.frames);
+		break;
+	case SND_PCM_IOCTL_AVAIL_UPDATE:
+		ctrl->result = snd_pcm_avail_update(pcm);
+		break;
+	case SNDRV_PCM_IOCTL_PREPARE:
+		ctrl->result = snd_pcm_prepare(pcm);
+		break;
+	case SNDRV_PCM_IOCTL_RESET:
+		ctrl->result = snd_pcm_reset(pcm);
+		break;
+	case SNDRV_PCM_IOCTL_START:
+		ctrl->result = snd_pcm_start(pcm);
+		break;
+	case SNDRV_PCM_IOCTL_DRAIN:
+		ctrl->result = snd_pcm_drain(pcm);
+		break;
+	case SNDRV_PCM_IOCTL_DROP:
+		ctrl->result = snd_pcm_drop(pcm);
+		break;
+	case SNDRV_PCM_IOCTL_PAUSE:
+		ctrl->result = snd_pcm_pause(pcm, ctrl->u.pause.enable);
+		break;
+	case SNDRV_PCM_IOCTL_CHANNEL_INFO:
+		ctrl->result = snd_pcm_channel_info(pcm, (snd_pcm_channel_info_t *) &ctrl->u.channel_info);
+		if (ctrl->result >= 0 &&
+		    ctrl->u.channel_info.type == SND_PCM_AREA_MMAP)
+			return shm_ack_fd(client, ctrl->u.channel_info.u.mmap.fd);
+		break;
+	case SNDRV_PCM_IOCTL_REWIND:
+		ctrl->result = snd_pcm_rewind(pcm, ctrl->u.rewind.frames);
+		break;
+	case SND_PCM_IOCTL_FORWARD:
+		ctrl->result = snd_pcm_forward(pcm, ctrl->u.forward.frames);
+		break;
+	case SNDRV_PCM_IOCTL_LINK:
+	{
+		/* FIXME */
+		ctrl->result = -ENOSYS;
+		break;
+	}
+	case SNDRV_PCM_IOCTL_UNLINK:
+		ctrl->result = snd_pcm_unlink(pcm);
+		break;
+	case SNDRV_PCM_IOCTL_RESUME:
+		ctrl->result = snd_pcm_resume(pcm);
+		break;
+	case SND_PCM_IOCTL_MMAP:
+	{
+		ctrl->result = snd_pcm_mmap(pcm);
+	}
+	case SND_PCM_IOCTL_MUNMAP:
+	{
+		ctrl->result = snd_pcm_munmap(pcm);
+		break;
+	}
+	case SND_PCM_IOCTL_MMAP_COMMIT:
+		ctrl->result = snd_pcm_mmap_commit(pcm,
+						   ctrl->u.mmap_commit.offset,
+						   ctrl->u.mmap_commit.frames);
+		break;
+	case SND_PCM_IOCTL_POLL_DESCRIPTOR:
+		ctrl->result = 0;
+		return shm_ack_fd(client, _snd_pcm_poll_descriptor(pcm));
+	case SND_PCM_IOCTL_CLOSE:
+		client->ops->close(client);
+		break;
+	case SND_PCM_IOCTL_HW_PTR_FD:
+		return shm_rbptr_fd(client, &pcm->hw);
+	case SND_PCM_IOCTL_APPL_PTR_FD:
+		return shm_rbptr_fd(client, &pcm->appl);
+	default:
+		ERROR("Bogus cmd: %x", ctrl->cmd);
+		ctrl->result = -ENOSYS;
+	}
+	return shm_ack(client);
+}
+
+transport_ops_t pcm_shm_ops = {
+	.open	= pcm_shm_open,
+	.cmd	= pcm_shm_cmd,
+	.close	= pcm_shm_close,
+};
+
+static int ctl_handler(waiter_t *waiter, unsigned short events)
+{
+	client_t *client = waiter->private_data;
+	char buf[1];
+	ssize_t n;
+	if (events & POLLIN) {
+		n = write(client->poll_fd, buf, 1);
+		if (n != 1) {
+			SYSERROR("write failed");
+			return -errno;
+		}
+	}
+	del_waiter(waiter->fd);
+	client->polling = 0;
+	return 0;
+}
+
+static int ctl_shm_open(client_t *client, int *cookie)
+{
+	int shmid;
+	snd_ctl_t *ctl;
+	int err;
+	int result;
+	err = snd_ctl_open(&ctl, client->name, SND_CTL_NONBLOCK);
+	if (err < 0)
+		return err;
+	client->device.ctl.handle = ctl;
+	client->device.ctl.fd = _snd_ctl_poll_descriptor(ctl);
+
+	shmid = shmget(IPC_PRIVATE, CTL_SHM_SIZE, 0666);
+	if (shmid < 0) {
+		result = -errno;
+		SYSERROR("shmget failed");
+		goto _err;
+	}
+	client->transport.shm.ctrl_id = shmid;
+	client->transport.shm.ctrl = shmat(shmid, 0, 0);
+	if (!client->transport.shm.ctrl) {
+		result = -errno;
+		shmctl(shmid, IPC_RMID, 0);
+		SYSERROR("shmat failed");
+		goto _err;
+	}
+	*cookie = shmid;
+	add_waiter(client->device.ctl.fd, POLLIN, ctl_handler, client);
+	client->polling = 1;
+	return 0;
+
+ _err:
+	snd_ctl_close(ctl);
+	return result;
+
+}
+
+static int ctl_shm_close(client_t *client)
+{
+	int err;
+	snd_ctl_shm_ctrl_t *ctrl = client->transport.shm.ctrl;
+	if (client->polling) {
+		del_waiter(client->device.ctl.fd);
+		client->polling = 0;
+	}
+	err = snd_ctl_close(client->device.ctl.handle);
+	ctrl->result = err;
+	if (err < 0) 
+		ERROR("snd_ctl_close");
+	if (client->transport.shm.ctrl) {
+		err = shmdt((void *)client->transport.shm.ctrl);
+		if (err < 0)
+			SYSERROR("shmdt failed");
+		err = shmctl(client->transport.shm.ctrl_id, IPC_RMID, 0);
+		if (err < 0)
+			SYSERROR("shmctl failed");
+		client->transport.shm.ctrl = 0;
+	}
+	client->open = 0;
+	return 0;
+}
+
+static int ctl_shm_cmd(client_t *client)
+{
+	snd_ctl_shm_ctrl_t *ctrl = client->transport.shm.ctrl;
+	char buf[1];
+	int err;
+	int cmd;
+	snd_ctl_t *ctl;
+	err = read(client->ctrl_fd, buf, 1);
+	if (err != 1)
+		return -EBADFD;
+	cmd = ctrl->cmd;
+	ctrl->cmd = 0;
+	ctl = client->device.ctl.handle;
+	switch (cmd) {
+	case SND_CTL_IOCTL_ASYNC:
+		ctrl->result = snd_ctl_async(ctl, ctrl->u.async.sig, ctrl->u.async.pid);
+		if (ctrl->result < 0)
+			break;
+		if (ctrl->u.async.sig >= 0) {
+			assert(client->async_sig < 0);
+			ctrl->result = snd_async_add_ctl_handler(&client->async_handler, ctl, async_handler, client);
+			if (ctrl->result < 0)
+				break;
+		} else {
+			assert(client->async_sig >= 0);
+			snd_async_del_handler(client->async_handler);
+		}
+		client->async_sig = ctrl->u.async.sig;
+		client->async_pid = ctrl->u.async.pid;
+		break;
+		break;
+	case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS:
+		ctrl->result = snd_ctl_subscribe_events(ctl, ctrl->u.subscribe_events);
+		break;
+	case SNDRV_CTL_IOCTL_CARD_INFO:
+		ctrl->result = snd_ctl_card_info(ctl, &ctrl->u.card_info);
+		break;
+	case SNDRV_CTL_IOCTL_ELEM_LIST:
+	{
+		size_t maxsize = CTL_SHM_DATA_MAXLEN;
+		if (ctrl->u.element_list.space * sizeof(*ctrl->u.element_list.pids) > maxsize) {
+			ctrl->result = -EFAULT;
+			break;
+		}
+		ctrl->u.element_list.pids = (snd_ctl_elem_id_t*) ctrl->data;
+		ctrl->result = snd_ctl_elem_list(ctl, &ctrl->u.element_list);
+		break;
+	}
+	case SNDRV_CTL_IOCTL_ELEM_INFO:
+		ctrl->result = snd_ctl_elem_info(ctl, &ctrl->u.element_info);
+		break;
+	case SNDRV_CTL_IOCTL_ELEM_READ:
+		ctrl->result = snd_ctl_elem_read(ctl, &ctrl->u.element_read);
+		break;
+	case SNDRV_CTL_IOCTL_ELEM_WRITE:
+		ctrl->result = snd_ctl_elem_write(ctl, &ctrl->u.element_write);
+		break;
+	case SNDRV_CTL_IOCTL_ELEM_LOCK:
+		ctrl->result = snd_ctl_elem_lock(ctl, &ctrl->u.element_lock);
+		break;
+	case SNDRV_CTL_IOCTL_ELEM_UNLOCK:
+		ctrl->result = snd_ctl_elem_unlock(ctl, &ctrl->u.element_unlock);
+		break;
+	case SNDRV_CTL_IOCTL_HWDEP_NEXT_DEVICE:
+		ctrl->result = snd_ctl_hwdep_next_device(ctl, &ctrl->u.device);
+		break;
+	case SNDRV_CTL_IOCTL_HWDEP_INFO:
+		ctrl->result = snd_ctl_hwdep_info(ctl, &ctrl->u.hwdep_info);
+		break;
+	case SNDRV_CTL_IOCTL_PCM_NEXT_DEVICE:
+		ctrl->result = snd_ctl_pcm_next_device(ctl, &ctrl->u.device);
+		break;
+	case SNDRV_CTL_IOCTL_PCM_INFO:
+		ctrl->result = snd_ctl_pcm_info(ctl, &ctrl->u.pcm_info);
+		break;
+	case SNDRV_CTL_IOCTL_PCM_PREFER_SUBDEVICE:
+		ctrl->result = snd_ctl_pcm_prefer_subdevice(ctl, ctrl->u.pcm_prefer_subdevice);
+		break;
+	case SNDRV_CTL_IOCTL_RAWMIDI_NEXT_DEVICE:
+		ctrl->result = snd_ctl_rawmidi_next_device(ctl, &ctrl->u.device);
+		break;
+	case SNDRV_CTL_IOCTL_RAWMIDI_INFO:
+		ctrl->result = snd_ctl_rawmidi_info(ctl, &ctrl->u.rawmidi_info);
+		break;
+	case SNDRV_CTL_IOCTL_RAWMIDI_PREFER_SUBDEVICE:
+		ctrl->result = snd_ctl_rawmidi_prefer_subdevice(ctl, ctrl->u.rawmidi_prefer_subdevice);
+		break;
+	case SNDRV_CTL_IOCTL_POWER:
+		ctrl->result = snd_ctl_set_power_state(ctl, ctrl->u.power_state);
+		break;
+	case SNDRV_CTL_IOCTL_POWER_STATE:
+		ctrl->result = snd_ctl_get_power_state(ctl, &ctrl->u.power_state);
+		break;
+	case SND_CTL_IOCTL_READ:
+		ctrl->result = snd_ctl_read(ctl, &ctrl->u.read);
+		break;
+	case SND_CTL_IOCTL_CLOSE:
+		client->ops->close(client);
+		break;
+	case SND_CTL_IOCTL_POLL_DESCRIPTOR:
+		ctrl->result = 0;
+		return shm_ack_fd(client, _snd_ctl_poll_descriptor(ctl));
+	default:
+		ERROR("Bogus cmd: %x", ctrl->cmd);
+		ctrl->result = -ENOSYS;
+	}
+	return shm_ack(client);
+}
+
+transport_ops_t ctl_shm_ops = {
+	.open	= ctl_shm_open,
+	.cmd	= ctl_shm_cmd,
+	.close	= ctl_shm_close,
+};
+
+static int snd_client_open(client_t *client)
+{
+	int err;
+	snd_client_open_request_t req;
+	snd_client_open_answer_t ans;
+	char *name;
+	memset(&ans, 0, sizeof(ans));
+	err = read(client->ctrl_fd, &req, sizeof(req));
+	if (err < 0) {
+		SYSERROR("read failed");
+		exit(1);
+	}
+	if (err != sizeof(req)) {
+		ans.result = -EINVAL;
+		goto _answer;
+	}
+	name = alloca(req.namelen);
+	err = read(client->ctrl_fd, name, req.namelen);
+	if (err < 0) {
+		SYSERROR("read failed");
+		exit(1);
+	}
+	if (err != req.namelen) {
+		ans.result = -EINVAL;
+		goto _answer;
+	}
+
+	switch (req.transport_type) {
+	case SND_TRANSPORT_TYPE_SHM:
+		if (!client->local) {
+			ans.result = -EINVAL;
+			goto _answer;
+		}
+		switch (req.dev_type) {
+		case SND_DEV_TYPE_PCM:
+			client->ops = &pcm_shm_ops;
+			break;
+		case SND_DEV_TYPE_CONTROL:
+			client->ops = &ctl_shm_ops;
+			break;
+		default:
+			ans.result = -EINVAL;
+			goto _answer;
+		}
+		break;
+	default:
+		ans.result = -EINVAL;
+		goto _answer;
+	}
+
+	name[req.namelen] = '\0';
+
+	client->transport_type = req.transport_type;
+	strcpy(client->name, name);
+	client->stream = req.stream;
+	client->mode = req.mode;
+
+	err = client->ops->open(client, &ans.cookie);
+	if (err < 0) {
+		ans.result = err;
+	} else {
+		client->open = 1;
+		ans.result = 0;
+	}
+
+ _answer:
+	err = write(client->ctrl_fd, &ans, sizeof(ans));
+	if (err != sizeof(ans)) {
+		SYSERROR("write failed");
+		exit(1);
+	}
+	return 0;
+}
+
+static int client_poll_handler(waiter_t *waiter, unsigned short events ATTRIBUTE_UNUSED)
+{
+	client_t *client = waiter->private_data;
+	if (client->open)
+		client->ops->close(client);
+	close(client->poll_fd);
+	close(client->ctrl_fd);
+	del_waiter(client->poll_fd);
+	del_waiter(client->ctrl_fd);
+	list_del(&client->list);
+	free(client);
+	return 0;
+}
+
+static int client_ctrl_handler(waiter_t *waiter, unsigned short events)
+{
+	client_t *client = waiter->private_data;
+	if (events & POLLHUP) {
+		if (client->open)
+			client->ops->close(client);
+		close(client->ctrl_fd);
+		del_waiter(client->ctrl_fd);
+		list_del(&client->list);
+		free(client);
+		return 0;
+	}
+	if (client->open)
+		return client->ops->cmd(client);
+	else
+		return snd_client_open(client);
+}
+
+static int inet_pending_handler(waiter_t *waiter, unsigned short events)
+{
+	inet_pending_t *pending = waiter->private_data;
+	inet_pending_t *pdata;
+	client_t *client;
+	uint32_t cookie;
+	struct list_head *item;
+	int remove = 0;
+	if (events & POLLHUP)
+		remove = 1;
+	else {
+		int err = read(waiter->fd, &cookie, sizeof(cookie));
+		if (err != sizeof(cookie))
+			remove = 1;
+		else {
+			err = write(waiter->fd, &cookie, sizeof(cookie));
+			if (err != sizeof(cookie))
+				remove = 1;
+		}
+	}
+	del_waiter(waiter->fd);
+	if (remove) {
+		close(waiter->fd);
+		list_del(&pending->list);
+		free(pending);
+		return 0;
+	}
+
+	list_for_each(item, &inet_pendings) {
+		pdata = list_entry(item, inet_pending_t, list);
+		if (pdata->cookie == cookie)
+			goto found;
+	}
+	pending->cookie = cookie;
+	return 0;
+
+ found:
+	client = calloc(1, sizeof(*client));
+	client->local = 0;
+	client->poll_fd = pdata->fd;
+	client->ctrl_fd = waiter->fd;
+	add_waiter(client->ctrl_fd, POLLIN | POLLHUP, client_ctrl_handler, client);
+	add_waiter(client->poll_fd, POLLHUP, client_poll_handler, client);
+	client->open = 0;
+	list_add_tail(&client->list, &clients);
+	list_del(&pending->list);
+	list_del(&pdata->list);
+	free(pending);
+	free(pdata);
+	return 0;
+}
+
+static int local_handler(waiter_t *waiter, unsigned short events ATTRIBUTE_UNUSED)
+{
+	int sock;
+	sock = accept(waiter->fd, 0, 0);
+	if (sock < 0) {
+		int result = -errno;
+		SYSERROR("accept failed");
+		return result;
+	} else {
+		client_t *client = calloc(1, sizeof(*client));
+		client->ctrl_fd = sock;
+		client->local = 1;
+		client->open = 0;
+		add_waiter(sock, POLLIN | POLLHUP, client_ctrl_handler, client);
+		list_add_tail(&client->list, &clients);
+	}
+	return 0;
+}
+
+static int inet_handler(waiter_t *waiter, unsigned short events ATTRIBUTE_UNUSED)
+{
+	int sock;
+	sock = accept(waiter->fd, 0, 0);
+	if (sock < 0) {
+		int result = -errno;
+		SYSERROR("accept failed");
+		return result;
+	} else {
+		inet_pending_t *pending = calloc(1, sizeof(*pending));
+		pending->fd = sock;
+		pending->cookie = 0;
+		add_waiter(sock, POLLIN, inet_pending_handler, pending);
+		list_add_tail(&pending->list, &inet_pendings);
+	}
+	return 0;
+}
+
+static int server(const char *sockname, int port)
+{
+	int err;
+	unsigned int k;
+	long open_max;
+	int result;
+
+	if (!sockname && port < 0)
+		return -EINVAL;
+	open_max = sysconf(_SC_OPEN_MAX);
+	if (open_max < 0) {
+		result = -errno;
+		SYSERROR("sysconf failed");
+		return result;
+	}
+	pollfds = calloc((size_t) open_max, sizeof(*pollfds));
+	waiters = calloc((size_t) open_max, sizeof(*waiters));
+
+	if (sockname) {
+		int sock = make_local_socket(sockname);
+		if (sock < 0)
+			return sock;
+		if (fcntl(sock, F_SETFL, O_NONBLOCK) < 0) {
+			result = -errno;
+			SYSERROR("fcntl O_NONBLOCK failed");
+			goto _end;
+		}
+		if (listen(sock, 4) < 0) {
+			result = -errno;
+			SYSERROR("listen failed");
+			goto _end;
+		}
+		add_waiter(sock, POLLIN, local_handler, NULL);
+	}
+	if (port >= 0) {
+		int sock = make_inet_socket(port);
+		if (sock < 0)
+			return sock;
+		if (fcntl(sock, F_SETFL, O_NONBLOCK) < 0) {
+			result = -errno;
+			SYSERROR("fcntl failed");
+			goto _end;
+		}
+		if (listen(sock, 4) < 0) {
+			result = -errno;
+			SYSERROR("listen failed");
+			goto _end;
+		}
+		add_waiter(sock, POLLIN, inet_handler, NULL);
+	}
+
+	while (1) {
+		struct pollfd pfds[open_max];
+		size_t pfds_count;
+		do {
+			err = poll(pollfds, pollfds_count, -1);
+		} while (err == 0);
+		if (err < 0) {
+			SYSERROR("poll failed");
+			continue;
+		}
+
+		pfds_count = pollfds_count;
+		memcpy(pfds, pollfds, sizeof(*pfds) * pfds_count);
+		for (k = 0; k < pfds_count; k++) {
+			struct pollfd *pfd = &pfds[k];
+			if (pfd->revents) {
+				waiter_t *w = &waiters[pfd->fd];
+				if (!w->handler)
+					continue;
+				err = w->handler(w, pfd->revents);
+				if (err < 0)
+					ERROR("waiter handler failed");
+			}
+		}
+	}
+ _end:
+	free(pollfds);
+	free(waiters);
+	return result;
+}
+					
+
+static void usage(void)
+{
+	fprintf(stderr,
+		"Usage: %s [OPTIONS] server\n"
+		"--help			help\n",
+		command);
+}
+
+int main(int argc, char **argv)
+{
+	static const struct option long_options[] = {
+		{"help", 0, 0, 'h'},
+		{ 0 , 0 , 0, 0 }
+	};
+	int c;
+	snd_config_t *conf;
+	snd_config_iterator_t i, next;
+	const char *sockname = NULL;
+	const char *host = NULL;
+	long port = -1;
+	int err;
+	char *srvname;
+	struct hostent *h;
+	command = argv[0];
+	while ((c = getopt_long(argc, argv, "h", long_options, 0)) != -1) {
+		switch (c) {
+		case 'h':
+			usage();
+			return 0;
+		default:
+			fprintf(stderr, "Try `%s --help' for more information\n", command);
+			return 1;
+		}
+	}
+	if (argc - optind != 1) {
+		ERROR("you need to specify server name");
+		return 1;
+	}
+	err = snd_config_update();
+	if (err < 0) {
+		ERROR("cannot read configuration file");
+		return 1;
+	}
+	srvname = argv[optind];
+	err = snd_config_search_definition(snd_config, "server", srvname, &conf);
+	if (err < 0) {
+		ERROR("Missing definition for server %s", srvname);
+		return 1;
+	}
+	if (snd_config_get_type(conf) != SND_CONFIG_TYPE_COMPOUND) {
+		SNDERR("Invalid type for server %s definition", srvname);
+		return -EINVAL;
+	}
+	snd_config_for_each(i, next, conf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (strcmp(id, "comment") == 0)
+			continue;
+		if (strcmp(id, "host") == 0) {
+			err = snd_config_get_string(n, &host);
+			if (err < 0) {
+				ERROR("Invalid type for %s", id);
+				return 1;
+			}
+			continue;
+		}
+		if (strcmp(id, "socket") == 0) {
+			err = snd_config_get_string(n, &sockname);
+			if (err < 0) {
+				ERROR("Invalid type for %s", id);
+				return 1;
+			}
+			continue;
+		}
+		if (strcmp(id, "port") == 0) {
+			err = snd_config_get_integer(n, &port);
+			if (err < 0) {
+				ERROR("Invalid type for %s", id);
+				return 1;
+			}
+			continue;
+		}
+		ERROR("Unknown field %s", id);
+		return 1;
+	}
+	if (!host) {
+		ERROR("host is not defined");
+		return 1;
+	}
+	h = gethostbyname(host);
+	if (!h) {
+		ERROR("Cannot resolve %s", host);
+		return 1;
+	}
+	if (!snd_is_local(h)) {
+		ERROR("%s is not the local host", host);
+		return 1;
+	}
+	if (!sockname && port < 0) {
+		ERROR("either socket or port need to be defined");
+		return 1;
+	}
+	server(sockname, port);
+	return 0;
+}
diff --git a/configure.in b/configure.in
new file mode 100644
index 0000000..4bcb0d6
--- /dev/null
+++ b/configure.in
@@ -0,0 +1,658 @@
+dnl Process this file with autoconf to produce a configure script.
+AC_PREREQ(2.59)
+AC_INIT(src/control/control.c)
+
+AC_CONFIG_MACRO_DIR([m4])
+
+dnl *************************************************
+dnl current:revision:age
+dnl change (without API) = c:r+1:a
+dnl change API = c+1:0:a
+dnl add API = c+1:0:a+1
+dnl remove API = c+1:0:0
+dnl *************************************************
+AC_CANONICAL_HOST
+AM_INIT_AUTOMAKE(alsa-lib, 1.0.25)
+eval LIBTOOL_VERSION_INFO="2:0:0"
+dnl *************************************************
+AM_CONDITIONAL(INSTALL_M4, test -n "${ACLOCAL}")
+
+# Test for new silent rules and enable only if they are available
+m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
+
+AC_PREFIX_DEFAULT(/usr)
+
+dnl Checks for programs.
+
+dnl try to gues cross-compiler if not set
+if test "x$host" != "x$build" -a -z "`echo $CC | grep -e '-gcc'`";
+then
+  AC_MSG_CHECKING(for cross-compiler)
+
+  which ${program_prefix}gcc >/dev/null 2>&1 && CC=${program_prefix}gcc
+  which ${host_cpu}-${host_os}-gcc >/dev/null 2>&1 \
+  && CC=${host_cpu}-${host-os}-gcc
+  which ${host_cpu}-${host_vendor}-${host_os}-gcc >/dev/null 2>&1 \
+  && CC=${host_cpu}-${host_vendor}-${host_os}-gcc
+
+  AC_MSG_RESULT($CC)
+fi
+	    
+CFLAGS="$CFLAGS -D_GNU_SOURCE"
+
+
+AC_PROG_CC
+AC_PROG_CPP
+AC_PROG_INSTALL
+AC_PROG_LN_S 
+AC_DISABLE_STATIC
+AC_LIBTOOL_DLOPEN
+AM_PROG_LIBTOOL
+
+CC_NOUNDEFINED
+
+dnl Checks for header files.
+AC_HEADER_STDC
+AM_CONFIG_HEADER(include/config.h)
+
+dnl Checks for typedefs, structures, and compiler characteristics.
+AC_C_CONST
+AC_C_INLINE
+AC_HEADER_TIME
+
+dnl Checks for library functions.
+AC_PROG_GCC_TRADITIONAL
+AC_CHECK_FUNC([hsearch_r], [HAVE_HSEARCH_R=yes])
+AM_CONDITIONAL(ALSA_HSEARCH_R, [test "x$HAVE_HSEARCH_R" != xyes])
+AC_CHECK_FUNCS([uselocale])
+
+SAVE_LIBRARY_VERSION
+AC_SUBST(LIBTOOL_VERSION_INFO)
+
+test "x$prefix" = xNONE && prefix=$ac_default_prefix
+
+dnl Do not build static and shared libraries together
+if test "$enable_static" = "$enable_shared" -a "$enable_static" = "yes"; then
+cat <<EOF
+  Please, do not try to compile static and shared libraries together.
+  See INSTALL file for more details (do not use --enable-shared=yes with
+  --enable-static=yes).
+EOF
+  exit 1
+fi
+
+dnl ALSA configuration directory
+AC_ARG_WITH(configdir,
+    AS_HELP_STRING([--with-configdir=dir],
+	[path where ALSA config files are stored]),
+    confdir="$withval", confdir="")
+if test -z "$confdir"; then
+    eval dir="$datadir"
+    case "$dir" in
+    /*) ;;
+    *) dir="$prefix/share"
+    esac
+    confdir="$dir/alsa"
+fi
+ALSA_CONFIG_DIR="$confdir"
+AC_DEFINE_UNQUOTED(ALSA_CONFIG_DIR, "$confdir", [directory containing ALSA configuration database])
+AC_SUBST(ALSA_CONFIG_DIR)
+
+dnl ALSA plugin directory
+test "x$exec_prefix" = xNONE && exec_prefix=$prefix
+
+AC_ARG_WITH(plugindir,
+    AS_HELP_STRING([--with-plugindir=dir],
+	[path where ALSA plugin files are stored]),
+    plugindir="$withval", plugindir="")
+if test -z "$plugindir"; then
+    eval dir="$libdir"
+    case "$dir" in
+    /*) ;;
+    *) dir="$dir"
+    esac
+    plugindir="$dir/$PACKAGE"
+fi
+AC_DEFINE_UNQUOTED(ALSA_PLUGIN_DIR, "$plugindir", [directory containing ALSA add-on modules])
+ALSA_PLUGIN_DIR="$plugindir"
+AC_SUBST(ALSA_PLUGIN_DIR)
+
+AC_ARG_WITH(pkgconfdir,
+    AS_HELP_STRING([--with-pkgconfdir=dir],
+	[path where pkgconfig files are stored]),
+    pkgconfdir="$withval", pkgconfdir="")
+if test -z "$pkgconfdir"; then
+    eval dir="$libdir"
+    case "$dir" in
+    /*) ;;
+    *) dir="$dir"
+    esac
+    pkgconfdir="$dir/pkgconfig"
+fi
+AC_DEFINE_UNQUOTED(ALSA_PKGCONF_DIR, "$pkgconfdir", [directory containing pkgconfig files])
+ALSA_PKGCONF_DIR="$pkgconfdir"
+AC_SUBST(ALSA_PKGCONF_DIR)
+
+dnl Check for versioned symbols
+AC_MSG_CHECKING(for versioned symbols)
+AC_ARG_WITH(versioned,
+  AS_HELP_STRING([--with-versioned],
+    [shared library will be compiled with versioned symbols (default = yes)]),
+  versioned="$withval", versioned="yes")
+if test "$versioned" = "yes"; then
+  # it seems that GNU ld versions since 2.10 are not broken
+  xres=`grep '^VERSION=' ${srcdir}/ltmain.sh | cut -d = -f 2 | cut -d \" -f 2`
+  major=`echo $xres | cut -d . -f 1`
+  minor=`echo $xres | cut -d . -f 2`
+  pass=0
+  if test $major -eq 1 && test $minor -gt 3; then
+    pass=1
+  else
+    if test $major -gt 1; then
+      pass=1
+    fi
+  fi
+  if test $pass -eq 1; then
+    AC_DEFINE(VERSIONED_SYMBOLS,,[compiled with versioned symbols])
+    AC_MSG_RESULT(yes)
+  else
+    AC_MSG_RESULT(broken libtool - use libtool v1.4+; no versions)
+  fi
+else
+  AC_MSG_RESULT(no)
+fi
+AM_CONDITIONAL(VERSIONED_SYMBOLS, test x$versioned = xyes)
+
+dnl Check for symbolic-functions
+AC_MSG_CHECKING(for symbolic-functions)
+AC_ARG_ENABLE(symbolic-functions,
+  AS_HELP_STRING([--enable-symbolic-functions],
+    [use -Bsymbolic-functions option if available (optmization for size and speed)]),
+  symfuncs="$enableval", symfuncs="no")
+if test "$symfuncs" = "yes"; then
+  if ld --help | grep -q -- '-Bsymbolic-functions'; then
+    AC_MSG_RESULT(yes)
+  else
+    AC_MSG_RESULT(not supported by ld)
+    symfuncs="no"
+  fi
+else
+  AC_MSG_RESULT(no)
+fi
+AM_CONDITIONAL(SYMBOLIC_FUNCTIONS, test x"$symfuncs" = xyes)
+
+dnl See if toolchain has a custom prefix for symbols ...
+AC_MSG_CHECKING(for custom symbol prefixes)
+SYMBOL_PREFIX=` \
+	echo "PREFIX=__USER_LABEL_PREFIX__" \
+		| ${CPP-${CC-gcc} -E} - 2>&1 \
+		| ${EGREP-grep} "^PREFIX=" \
+		| ${SED-sed} "s:^PREFIX=::"`
+AC_DEFINE_UNQUOTED([__SYMBOL_PREFIX], "$SYMBOL_PREFIX", [Toolchain Symbol Prefix])
+AC_SUBST(SYMBOL_PREFIX)
+AC_MSG_RESULT($SYMBOL_PREFIX)
+
+dnl Check for debug...
+AC_MSG_CHECKING(for debug)
+AC_ARG_WITH(debug,
+  AS_HELP_STRING([--with-debug],
+    [library will be compiled with asserts (default = yes)]),
+  debug="$withval", debug="yes")
+if test "$debug" = "yes"; then
+  AC_MSG_RESULT(yes)
+else
+  AC_DEFINE(NDEBUG,,[No assert debug])
+  AC_MSG_RESULT(no)
+fi
+
+if test "$debug" = "yes"; then
+  AC_MSG_CHECKING(for debug assert)
+  AC_ARG_ENABLE(debug-assert,
+    AS_HELP_STRING([--enable-debug],
+      [enable assert call at the default error message handler]),
+    debug_assert="$enableval", debug_assert="no")
+  if test "$debug_assert" = "yes"; then
+    AC_MSG_RESULT(yes)
+    AC_DEFINE(ALSA_DEBUG_ASSERT,,[Enable assert at error message handler])
+  else
+    AC_MSG_RESULT(no)
+  fi
+fi
+
+dnl Temporary directory
+AC_MSG_CHECKING(for tmpdir)
+AC_ARG_WITH(tmpdir,
+  AS_HELP_STRING([--with-tmpdir=directory],
+    [directory to put tmp socket files (/tmp)]),
+  tmpdir="$withval", tmpdir="/tmp")
+AC_MSG_RESULT($tmpdir)
+AC_DEFINE_UNQUOTED(TMPDIR, "$tmpdir", [directory to put tmp socket files])
+
+dnl Check for softfloat...
+AC_MSG_CHECKING(for softfloat)
+AC_ARG_WITH(softfloat,
+  AS_HELP_STRING([--with-softfloat],
+    [do you have floating point unit on this machine? (optional)]),
+  [case "$withval" in
+	y|yes) softfloat=yes ;;
+	*) softfloat=no ;;
+   esac],)
+if test "$softfloat" = "yes" ; then
+  AC_DEFINE(HAVE_SOFT_FLOAT, "1", [Avoid calculation in float])
+  AC_MSG_RESULT(yes)
+else
+  AC_MSG_RESULT(no)
+fi
+
+ALSA_DEPLIBS=""
+if test "$softfloat" != "yes"; then
+  ALSA_DEPLIBS="-lm"
+fi
+
+dnl Check for libdl
+AC_MSG_CHECKING(for libdl)
+AC_ARG_WITH(libdl,
+  AS_HELP_STRING([--with-libdl], [Use libdl for plugins (default = yes)]),
+  [ have_libdl="$withval" ], [ have_libdl="yes" ])
+if test "$have_libdl" = "yes"; then
+  AC_CHECK_LIB([dl], [dlsym], [HAVE_LIBDL="yes"])
+  if test "$HAVE_LIBDL" = "yes" ; then
+    ALSA_DEPLIBS="$ALSA_DEPLIBS -ldl"
+    AC_DEFINE([HAVE_LIBDL], 1, [Have libdl])
+  fi
+else
+  AC_MSG_RESULT(no)
+fi
+AM_CONDITIONAL(BUILD_MODULES, test "$HAVE_LIBDL"="yes")
+
+dnl Check for pthread
+AC_MSG_CHECKING(for pthread)
+AC_ARG_WITH(pthread,
+  AS_HELP_STRING([--with-pthread], [Use pthread (default = yes)]),
+  [ have_pthread="$withval" ], [ have_pthread="yes" ])
+if test "$have_pthread" = "yes"; then
+  AC_CHECK_LIB([pthread], [pthread_join], [HAVE_LIBPTHREAD="yes"])
+  if test "$HAVE_LIBPTHREAD" = "yes"; then
+    ALSA_DEPLIBS="$ALSA_DEPLIBS -lpthread"
+    AC_DEFINE([HAVE_LIBPTHREAD], 1, [Have libpthread])
+  fi
+else
+  AC_MSG_RESULT(no)
+fi
+
+dnl Check for librt
+AC_MSG_CHECKING(for librt)
+AC_ARG_WITH(librt,
+  AS_HELP_STRING([--with-librt], [Use librt for monotonic clock (default = yes)]),
+  [ have_librt="$withval" ], [ have_librt="yes" ])
+if test "$have_librt" = "yes"; then
+  AC_CHECK_LIB([rt], [clock_gettime], [HAVE_LIBRT="yes"])
+  if test "$HAVE_LIBRT" = "yes" ; then
+    ALSA_DEPLIBS="$ALSA_DEPLIBS -lrt"
+    AC_DEFINE([HAVE_LIBRT], 1, [Have librt])
+    AC_DEFINE([HAVE_CLOCK_GETTIME], 1, [Have clock gettime])
+  fi
+else
+  AC_MSG_RESULT(no)
+fi
+
+AC_SUBST(ALSA_DEPLIBS)
+
+dnl Check for architecture
+AC_MSG_CHECKING(for architecture)
+case "$host" in
+i?86*)
+  AC_MSG_RESULT(x86)
+  ;;
+x86_64*)
+  AC_MSG_RESULT(x86)
+  ;;
+alpha*)
+  AC_MSG_RESULT(alpha)
+  ;;
+powerpc*|ppc*)
+  AC_MSG_RESULT(ppc)
+  CPPFLAGS="$CPPFLAGS -D__ppc__"
+  ;;
+ia64*)
+  AC_MSG_RESULT(ia64)
+  CPPFLAGS="$CPPFLAGS -D__ia64__"
+  ;;
+mips*)
+  AC_MSG_RESULT(mips)
+  CPPFLAGS="$CPPFLAGS -D__mips__"
+  ;;
+arm*)
+  AC_MSG_RESULT(arm)
+  CPPFLAGS="$CPPFLAGS -D__arm__"
+  ;;
+*)
+  AC_MSG_RESULT($host_cpu)
+  echo "No atomic operations supported.." 
+  ;;
+esac
+
+dnl Check for wordexp.h
+AC_CHECK_HEADERS([wordexp.h])
+
+dnl Check for resmgr support...
+AC_MSG_CHECKING(for resmgr support)
+AC_ARG_ENABLE(resmgr,
+  AS_HELP_STRING([--enable-resmgr], [support resmgr (optional)]),
+  resmgr="$enableval", resmgr="no")
+AC_MSG_RESULT($resmgr)
+if test "$resmgr" = "yes"; then
+  AC_CHECK_LIB(resmgr, rsm_open_device,,
+    AC_ERROR([Cannot find libresmgr]))
+  AC_DEFINE(SUPPORT_RESMGR, "1", [Support resmgr with alsa-lib])
+fi
+
+dnl Check for aload* support...
+AC_MSG_CHECKING(for aload* support)
+AC_ARG_ENABLE(aload,
+  AS_HELP_STRING([--disable-aload], [disable reading /dev/aload*]),
+  aload="$enableval", aload="yes")
+AC_MSG_RESULT($aload)
+if test "$aload" = "yes"; then
+  AC_DEFINE(SUPPORT_ALOAD, "1", [Support /dev/aload* access for auto-loading])
+fi
+
+dnl Check for non-standard /dev directory
+AC_MSG_CHECKING([for ALSA device file directory])
+AC_ARG_WITH(alsa-devdir,
+  AS_HELP_STRING([--with-alsa-devdir=dir],
+    [directory with ALSA device files (default /dev/snd)]),
+  [alsa_dev_dir="$withval"],
+  [alsa_dev_dir="/dev/snd"])
+dnl make sure it has a trailing slash
+if echo "$alsa_dev_dir" | grep -v '/$' > /dev/null; then
+  alsa_dev_dir="$alsa_dev_dir/"
+fi
+AC_DEFINE_UNQUOTED(ALSA_DEVICE_DIRECTORY, "$alsa_dev_dir", [Directory with ALSA device files])
+AC_MSG_RESULT([$alsa_dev_dir])
+
+AC_MSG_CHECKING([for aload* device file directory])
+AC_ARG_WITH(aload-devdir,
+  AS_HELP_STRING([--with-aload-devdir=dir],
+    [directory with aload* device files (default /dev)]),
+  [aload_dev_dir="$withval"],
+  [aload_dev_dir="/dev"])
+if echo "$aload_dev_dir" | grep -v '/$' > /dev/null; then
+  aload_dev_dir="$aload_dev_dir/"
+fi
+AC_DEFINE_UNQUOTED(ALOAD_DEVICE_DIRECTORY, "$aload_dev_dir", [Directory with aload* device files])
+AC_MSG_RESULT([$aload_dev_dir])
+
+dnl Build conditions
+AC_ARG_ENABLE(mixer,
+  AS_HELP_STRING([--disable-mixer], [disable the mixer component]),
+  [build_mixer="$enableval"], [build_mixer="yes"])
+AC_ARG_ENABLE(pcm,
+  AS_HELP_STRING([--disable-pcm], [disable the PCM component]),
+  [build_pcm="$enableval"], [build_pcm="yes"])
+AC_ARG_ENABLE(rawmidi,
+  AS_HELP_STRING([--disable-rawmidi], [disable the raw MIDI component]),
+  [build_rawmidi="$enableval"], [build_rawmidi="yes"])
+AC_ARG_ENABLE(hwdep,
+  AS_HELP_STRING([--disable-hwdep], [disable the hwdep component]),
+  [build_hwdep="$enableval"], [build_hwdep="yes"])
+AC_ARG_ENABLE(seq,
+  AS_HELP_STRING([--disable-seq], [disable the sequencer component]),
+  [build_seq="$enableval"], [build_seq="yes"])
+AC_ARG_ENABLE(ucm,
+  AS_HELP_STRING([--disable-ucm], [disable the use-case-manager component]),
+  [build_ucm="$enableval"], [build_ucm="yes"])
+AC_ARG_ENABLE(alisp,
+  AS_HELP_STRING([--disable-alisp], [disable the alisp component]),
+  [build_alisp="$enableval"], [build_alisp="yes"])
+test "$softfloat" = "yes" && build_alisp="no"
+AC_ARG_ENABLE(old-symbols,
+  AS_HELP_STRING([--disable-old-symbols], [disable old obsoleted symbols]),
+  [keep_old_symbols="$enableval"], [keep_old_symbols="yes"])
+AM_CONDITIONAL(KEEP_OLD_SYMBOLS, test x$keep_old_symbols = xyes)
+
+AC_ARG_ENABLE(python,
+  AS_HELP_STRING([--disable-python], [disable the python components]),
+  [build_python="$enableval"], [build_python="yes"])
+PYTHON_LIBS=""
+PYTHON_INCLUDES=""
+if test "$build_python" = "yes"; then
+  AC_ARG_WITH(pythonlibs,
+    AS_HELP_STRING([--with-pythonlibs=ldflags],
+      [specify python libraries (-lpthread -lm -ldl -lpython2.4)]),
+    pythonlibs="$withval", pythonlibs=`python-config --libs`)
+  AC_ARG_WITH(pythonincludes,
+    AS_HELP_STRING([--with-pythonincludes=Cflags],
+      [specify python C header files (-I/usr/include/python)]),
+    pythonincludes="$withval", pythonincludes=`python-config --includes`)
+  if test -z "$pythonlibs"; then
+    echo "Unable to determine python libraries! Probably python-config is not"
+    echo "available on this system. Please, use --with-pythonlibs and"
+    echo "--with-pythonincludes options. Python components are disabled in this build."
+    build_python="no"
+  else
+    PYTHON_LIBS="$pythonlibs"
+    PYTHON_INCLUDES="$pythonincludes"
+  fi
+fi
+AC_SUBST(PYTHON_LIBS)
+AC_SUBST(PYTHON_INCLUDES)
+
+AM_CONDITIONAL(BUILD_MIXER, test x$build_mixer = xyes)
+AM_CONDITIONAL(BUILD_PCM, test x$build_pcm = xyes)
+AM_CONDITIONAL(BUILD_RAWMIDI, test x$build_rawmidi = xyes)
+AM_CONDITIONAL(BUILD_HWDEP, test x$build_hwdep = xyes)
+AM_CONDITIONAL(BUILD_SEQ, test x$build_seq = xyes)
+AM_CONDITIONAL(BUILD_UCM, test x$build_ucm = xyes)
+AM_CONDITIONAL(BUILD_ALISP, test x$build_alisp = xyes)
+AM_CONDITIONAL(BUILD_PYTHON, test x$build_python = xyes)
+
+if test "$build_mixer" = "yes"; then
+  AC_DEFINE([BUILD_MIXER], "1", [Build mixer component])
+fi
+if test "$build_pcm" = "yes"; then
+  AC_DEFINE([BUILD_PCM], "1", [Build PCM component])
+fi
+if test "$build_rawmidi" = "yes"; then
+  AC_DEFINE([BUILD_RAWMIDI], "1", [Build raw MIDI component])
+fi
+if test "$build_hwdep" = "yes"; then
+  AC_DEFINE([BUILD_HWDEP], "1", [Build hwdep component])
+fi
+if test "$build_seq" = "yes"; then
+  AC_DEFINE([BUILD_SEQ], "1", [Build sequencer component])
+fi
+if test "$build_ucm" = "yes"; then
+  AC_DEFINE([BUILD_UCM], "1", [Build UCM component])
+fi
+
+dnl PCM Plugins
+
+if test "$build_pcm" = "yes"; then
+AC_ARG_WITH(pcm-plugins,
+  AS_HELP_STRING([--with-pcm-plugins=<list>],
+    [build PCM plugins (default = all)]),
+  [pcm_plugins="$withval"], [pcm_plugins="all"])
+else
+pcm_plugins=""
+fi
+
+PCM_PLUGIN_LIST="copy linear route mulaw alaw adpcm rate plug multi shm file null empty share meter hooks lfloat ladspa dmix dshare dsnoop asym iec958 softvol extplug ioplug mmap_emul"
+
+build_pcm_plugin="no"
+for t in $PCM_PLUGIN_LIST; do
+  eval build_pcm_$t="no"
+done
+
+pcm_plugins=`echo $pcm_plugins | sed 's/,/ /g'`
+for p in $pcm_plugins; do
+  for t in $PCM_PLUGIN_LIST; do
+    if test "$p" = "$t" -o "$p" = "all"; then
+      eval build_pcm_$t="yes"
+      build_pcm_plugin="yes"
+    fi
+  done
+done
+
+dnl special dependencies
+if test "$build_pcm_plug" = "yes"; then
+  build_pcm_linear="yes"
+  build_pcm_copy="yes"
+fi
+
+if test "$build_pcm_ioplug" = "yes"; then
+  build_pcm_extplug="yes"
+fi
+
+if test "$HAVE_LIBDL" != "yes"; then
+  build_pcm_meter="no"
+  build_pcm_ladspa="no"
+  build_pcm_pcm_ioplug="no"
+  build_pcm_pcm_extplug="no"
+fi
+
+if test "$HAVE_LIBPTHREAD" != "yes"; then
+  build_pcm_share="no"
+fi
+
+if test "$softfloat" = "yes"; then
+  build_pcm_lfloat="no"
+  build_pcm_ladspa="no"
+fi
+
+AM_CONDITIONAL(BUILD_PCM_PLUGIN, test x$build_pcm_plugin = xyes)
+AM_CONDITIONAL(BUILD_PCM_PLUGIN_COPY, test x$build_pcm_copy = xyes)
+AM_CONDITIONAL(BUILD_PCM_PLUGIN_LINEAR, test x$build_pcm_linear = xyes)
+AM_CONDITIONAL(BUILD_PCM_PLUGIN_ROUTE, test x$build_pcm_route = xyes)
+AM_CONDITIONAL(BUILD_PCM_PLUGIN_MULAW, test x$build_pcm_mulaw = xyes)
+AM_CONDITIONAL(BUILD_PCM_PLUGIN_ALAW, test x$build_pcm_alaw = xyes)
+AM_CONDITIONAL(BUILD_PCM_PLUGIN_ADPCM, test x$build_pcm_adpcm = xyes)
+AM_CONDITIONAL(BUILD_PCM_PLUGIN_RATE, test x$build_pcm_rate = xyes)
+AM_CONDITIONAL(BUILD_PCM_PLUGIN_PLUG, test x$build_pcm_plug = xyes)
+AM_CONDITIONAL(BUILD_PCM_PLUGIN_MULTI, test x$build_pcm_multi = xyes)
+AM_CONDITIONAL(BUILD_PCM_PLUGIN_SHM, test x$build_pcm_shm = xyes)
+AM_CONDITIONAL(BUILD_PCM_PLUGIN_FILE, test x$build_pcm_file = xyes)
+AM_CONDITIONAL(BUILD_PCM_PLUGIN_NULL, test x$build_pcm_null = xyes)
+AM_CONDITIONAL(BUILD_PCM_PLUGIN_EMPTY, test x$build_pcm_empty = xyes)
+AM_CONDITIONAL(BUILD_PCM_PLUGIN_SHARE, test x$build_pcm_share = xyes)
+AM_CONDITIONAL(BUILD_PCM_PLUGIN_METER, test x$build_pcm_meter = xyes)
+AM_CONDITIONAL(BUILD_PCM_PLUGIN_HOOKS, test x$build_pcm_hooks = xyes)
+AM_CONDITIONAL(BUILD_PCM_PLUGIN_LFLOAT, test x$build_pcm_lfloat = xyes)
+AM_CONDITIONAL(BUILD_PCM_PLUGIN_LADSPA, test x$build_pcm_ladspa = xyes)
+AM_CONDITIONAL(BUILD_PCM_PLUGIN_DMIX, test x$build_pcm_dmix = xyes)
+AM_CONDITIONAL(BUILD_PCM_PLUGIN_DSHARE, test x$build_pcm_dshare = xyes)
+AM_CONDITIONAL(BUILD_PCM_PLUGIN_DSNOOP, test x$build_pcm_dsnoop = xyes)
+AM_CONDITIONAL(BUILD_PCM_PLUGIN_ASYM, test x$build_pcm_asym = xyes)
+AM_CONDITIONAL(BUILD_PCM_PLUGIN_IEC958, test x$build_pcm_iec958 = xyes)
+AM_CONDITIONAL(BUILD_PCM_PLUGIN_SOFTVOL, test x$build_pcm_softvol = xyes)
+AM_CONDITIONAL(BUILD_PCM_PLUGIN_EXTPLUG, test x$build_pcm_extplug = xyes)
+AM_CONDITIONAL(BUILD_PCM_PLUGIN_IOPLUG, test x$build_pcm_ioplug = xyes)
+AM_CONDITIONAL(BUILD_PCM_PLUGIN_MMAP_EMUL, test x$build_pcm_mmap_emul = xyes)
+
+dnl Defines for plug plugin
+if test "$build_pcm_rate" = "yes"; then
+  AC_DEFINE([BUILD_PCM_PLUGIN_RATE], "1", [Build PCM rate plugin])
+fi
+if test "$build_pcm_route" = "yes"; then
+  AC_DEFINE([BUILD_PCM_PLUGIN_ROUTE], "1", [Build PCM route plugin])
+fi
+if test "$build_pcm_lfloat" = "yes"; then
+  AC_DEFINE([BUILD_PCM_PLUGIN_LFLOAT], "1", [Build PCM lfloat plugin])
+fi
+if test "$build_pcm_adpcm" = "yes"; then
+  AC_DEFINE([BUILD_PCM_PLUGIN_ADPCM], "1", [Build PCM adpcm plugin])
+fi
+if test "$build_pcm_mulaw" = "yes"; then
+  AC_DEFINE([BUILD_PCM_PLUGIN_MULAW], "1", [Build PCM mulaw plugin])
+fi
+if test "$build_pcm_alaw" = "yes"; then
+  AC_DEFINE([BUILD_PCM_PLUGIN_ALAW], "1", [Build PCM alaw plugin])
+fi
+if test "$build_pcm_mmap_emul" = "yes"; then
+  AC_DEFINE([BUILD_PCM_PLUGIN_MMAP_EMUL], "1", [Build PCM mmap-emul plugin])
+fi
+
+
+dnl Create PCM plugin symbol list for static library
+rm -f "$srcdir"/src/pcm/pcm_symbols_list.c
+touch "$srcdir"/src/pcm/pcm_symbols_list.c
+for t in $PCM_PLUGIN_LIST; do
+  if eval test \$build_pcm_$t = yes; then
+    echo \&_snd_module_pcm_$t, >> "$srcdir"/src/pcm/pcm_symbols_list.c
+  fi
+done
+
+dnl Control Plugins
+
+AC_ARG_WITH(ctl-plugins,
+  AS_HELP_STRING([--with-ctl-plugins=<list>],
+    [build control plugins (default = all)]),
+  [ctl_plugins="$withval"], [ctl_plugins="all"])
+
+CTL_PLUGIN_LIST="shm ext"
+
+build_ctl_plugin="no"
+for t in $CTL_PLUGIN_LIST; do
+  eval build_ctl_$t="no"
+done
+
+ctl_plugins=`echo $ctl_plugins | sed 's/,/ /g'`
+for p in $ctl_plugins; do
+  for t in $CTL_PLUGIN_LIST; do
+    if test "$p" = "$t" -o "$p" = "all"; then
+      eval build_ctl_$t="yes"
+      build_ctl_plugin="yes"
+    fi
+  done
+done
+
+AM_CONDITIONAL(BUILD_CTL_PLUGIN, test x$build_ctl_plugin = xyes)
+AM_CONDITIONAL(BUILD_CTL_PLUGIN_SHM, test x$build_ctl_shm = xyes)
+AM_CONDITIONAL(BUILD_CTL_PLUGIN_EXT, test x$build_ctl_ext = xyes)
+
+dnl Create ctl plugin symbol list for static library
+rm -f "$srcdir"/src/control/ctl_symbols_list.c
+touch "$srcdir"/src/control/ctl_symbols_list.c
+for t in $CTL_PLUGIN_LIST; do
+  if eval test \$build_ctl_$t = yes; then
+    echo \&_snd_module_control_$t, >> "$srcdir"/src/control/ctl_symbols_list.c
+  fi
+done
+
+dnl Make a symlink for inclusion of alsa/xxx.h
+if test ! -L "$srcdir"/include/alsa ; then
+  echo "Making a symlink include/alsa"
+  rm -f "$srcdir"/include/alsa
+  ln -sf . "$srcdir"/include/alsa
+fi
+
+AC_OUTPUT(Makefile doc/Makefile doc/pictures/Makefile doc/doxygen.cfg \
+	  include/Makefile include/sound/Makefile src/Versions src/Makefile \
+          src/control/Makefile src/mixer/Makefile \
+	  src/pcm/Makefile src/pcm/scopes/Makefile \
+	  src/rawmidi/Makefile src/timer/Makefile \
+          src/hwdep/Makefile src/seq/Makefile src/ucm/Makefile \
+          src/compat/Makefile src/alisp/Makefile \
+	  src/conf/Makefile src/conf/alsa.conf.d/Makefile \
+	  src/conf/cards/Makefile \
+	  src/conf/pcm/Makefile \
+	  modules/Makefile modules/mixer/Makefile modules/mixer/simple/Makefile \
+	  alsalisp/Makefile aserver/Makefile \
+	  test/Makefile test/lsb/Makefile \
+	  utils/Makefile utils/alsa-lib.spec utils/alsa.pc)
+
+dnl Create asoundlib.h dynamically according to configure options
+echo "Creating asoundlib.h..."
+cp "$srcdir"/include/asoundlib-head.h include/asoundlib.h
+test "$build_pcm" = "yes" && echo "#include <alsa/pcm.h>" >> include/asoundlib.h
+test "$build_rawmidi" = "yes" && echo "#include <alsa/rawmidi.h>" >> include/asoundlib.h
+test "$build_pcm" = "yes" && echo "#include <alsa/timer.h>" >> include/asoundlib.h
+test "$build_hwdep" = "yes" && echo "#include <alsa/hwdep.h>" >> include/asoundlib.h
+echo "#include <alsa/control.h>" >> include/asoundlib.h
+test "$build_mixer" = "yes" && echo "#include <alsa/mixer.h>" >> include/asoundlib.h
+test "$build_seq" = "yes" && echo "#include <alsa/seq_event.h>" >> include/asoundlib.h
+test "$build_seq" = "yes" && echo "#include <alsa/seq.h>" >> include/asoundlib.h
+test "$build_seq" = "yes" && echo "#include <alsa/seqmid.h>" >> include/asoundlib.h
+test "$build_seq" = "yes" && echo "#include <alsa/seq_midi_event.h>" >> include/asoundlib.h
+cat "$srcdir"/include/asoundlib-tail.h >> include/asoundlib.h
+
diff --git a/doc/Makefile.am b/doc/Makefile.am
new file mode 100644
index 0000000..2cc250b
--- /dev/null
+++ b/doc/Makefile.am
@@ -0,0 +1,22 @@
+SUBDIRS=pictures
+
+EXTRA_DIST=README.1st asoundrc.txt doxygen.cfg index.doxygen
+
+INCLUDES=-I$(top_srcdir)/include
+
+doc:
+	test -e doxygen.cfg || sed s:[@]top_srcdir[@]:..:g doxygen.cfg.in > doxygen.cfg
+	doxygen doxygen.cfg
+
+doc-pack: doc
+	-chmod a+r $(top_srcdir)/doc/doxygen/html/*
+	-chmod a-w $(top_srcdir)/doc/doxygen/html/*
+	if ! test -z "$(AMTAR)"; then \
+		$(AMTAR) --create --directory=$(top_srcdir)/doc/doxygen/html --verbose --file=- . | bzip2 -c -9 > $(top_srcdir)/../alsa-lib-doc.tar.bz2 ; \
+	else \
+		$(TAR) --create --directory=$(top_srcdir)/doc/doxygen/html --verbose --file=- . | bzip2 -c -9 > $(top_srcdir)/../alsa-lib-doc.tar.bz2 ; \
+	fi
+	rm -f $(top_srcdir)/doc/doxygen/html/*
+
+doc-clean:
+	rm -f $(top_srcdir)/doc/doxygen/html/*
diff --git a/doc/README.1st b/doc/README.1st
new file mode 100644
index 0000000..f96b21c
--- /dev/null
+++ b/doc/README.1st
@@ -0,0 +1,2 @@
+The doxygen documentation is created with command 'make doc' in toplevel
+directory.
diff --git a/doc/asoundrc.txt b/doc/asoundrc.txt
new file mode 100644
index 0000000..f79d528
--- /dev/null
+++ b/doc/asoundrc.txt
@@ -0,0 +1,480 @@
+# Configuration file syntax
+
+# Include a new configuration file
+<filename>
+
+# Simple assign
+name [=] value [,|;]
+
+# Compound assign (first style)
+name [=] {
+	name1 [=] value [,|;]
+	...
+}
+
+# Compound assign (second style)
+name.name1 [=] value [,|;]
+
+# Array assign (first style)
+name [
+	value0 [,|;]
+	value1 [,|;]
+	...
+]
+
+# Array assign (second style)
+name.0 [=] value0 [,|;]
+name.1 [=] value1 [,|;]
+
+# ******************************************************************************
+
+# Server definition
+server.NAME {
+  host STR		# host where the server is located (if map to local address 
+			# server is local, and then it may be started automatically)
+  [socket STR]		# PF_LOCAL socket name to listen/connect
+  [port INT]		# PF_INET port number to listen/connect
+}
+
+# PCM type definition
+pcm_type.NAME {
+  [lib STR]		# Library file (default libasound.so)
+  [open	STR]		# Open function (default _snd_pcm_NAME_open)
+  [redirect {		# Redirect this PCM to an another
+     [filename STR]	# Configuration file specification
+     name STR		# PCM name specification
+  }]
+}
+
+# PCM scope type definition
+pcm_scope_type.NAME {
+  [lib STR]		# Library file (default libasound.so)
+  [open STR]		# Open function (default _snd_pcm_scope_NAME_open)
+}
+
+# PCM scope definition
+pcm_scope.NAME {
+  type STR		# Scope type
+  ...
+}
+
+# Slave PCM definition
+pcm_slave.NAME {
+  pcm STR		# PCM name
+  # or
+  pcm { }		# PCM definition
+  format STR		# Format
+  channels INT		# Channels
+  rate INT		# Rate
+  period_time INT	# Period time
+  buffer_time INT	# Buffer time
+  etc.
+}
+
+# Hook arguments definition
+hook_args.NAME {
+  ...			# Arbitrary arguments
+}
+
+# PCM hook type
+pcm_hook_type.NAME {
+  [lib STR]		# Library file (default libasound.so)
+  [install STR]		# Install function (default _snd_pcm_hook_NAME_install)
+}
+
+# PCM hook definition
+pcm_hook.NAME {
+  type STR		# PCM Hook type (see pcm_hook_type)
+  [args STR]		# Arguments for install function (see hook_args)
+  # or
+  [args { }]		# Arguments for install function
+}
+
+# PCM definition
+pcm.NAME {
+  type STR		# Type
+  [comment ANY]		# Saved comments
+
+
+# PCM types:
+  type hw 		# Kernel PCM
+  card INT/STR		# Card name or number
+  [device] INT		# Device number (default 0)	
+  [subdevice] INT	# Subdevice number, -1 first available (default -1)
+  mmap_emulation BOOL	# enable mmap emulation for ro/wo devices
+
+
+  type hooks 		# PCM with hooks
+  slave STR		# Slave name (see pcm_slave)
+  # or
+  slave {		# Slave definition
+    pcm STR		# Slave PCM name
+    # or
+    pcm { }		# Slave PCM definition
+  }
+  hooks {
+    ID STR		# Hook name (see pcm_hook)
+    # or
+    ID { }		# Hook definition (see pcm_hook)
+  }
+
+  type plug		# Format adjusted PCM
+  slave STR		# Slave name (see pcm_slave)
+  # or
+  slave {		# Slave definition
+    pcm STR		# Slave PCM name
+    # or
+    pcm { }		# Slave PCM definition
+    [format STR]	# Slave format (default nearest) or "unchanged"
+    [channels INT]	# Slave channels (default nearest) or "unchanged"
+    [rate INT]		# Slave rate (default nearest) or "unchanged"
+  }
+  route_policy STR	# route policy for automatic ttable generation
+			# STR can be 'default', 'average', 'copy', 'duplicate'
+			# average: result is average of input channels
+			# copy: only first channels are copied to destination
+			# duplicate: duplicate first set of channels
+			# default: copy policy, except for mono capture - sum
+  ttable {	 	# Transfer table (bidimensional compound of 
+	        	# cchannels * schannels numbers)
+    CCHANNEL {
+      SCHANNEL REAL	# route value (0.0 ... 1.0)
+    }
+  }
+
+
+  type copy		# Copy conversion PCM
+  slave STR		# Slave name (see pcm_slave)
+  # or
+  slave {		# Slave definition
+    pcm STR		# Slave PCM name
+    # or
+    pcm { }		# Slave PCM definition
+  }
+
+
+  type linear		# Linear format conversion PCM
+  type adpcm		# IMA-ADPCM format conversion PCM
+  type alaw		# A-Law format conversion PCM
+  type mulaw		# Mu-Law format conversion PCM
+  slave STR		# Slave name (see pcm_slave)
+  # or
+  slave {		# Slave definition
+    pcm STR		# Slave PCM name
+    # or
+    pcm { }		# Slave PCM definition
+    format STR		# Slave format
+  }
+
+
+  type rate		# Rate conversion PCM
+  slave STR		# Slave name (see pcm_slave)
+  # or
+  slave {		# Slave definition
+    pcm STR		# Slave PCM name
+    # or
+    pcm { }		# Slave PCM definition
+    [format STR]	# Slave format (default client format)
+    rate INT		# Slave rate
+  }
+
+
+  type route		# Attenuated static route PCM
+  slave STR		# Slave name (see pcm_slave)
+  # or
+  slave {		# Slave definition
+    pcm STR		# Slave PCM name
+    # or
+    pcm { }		# Slave PCM definition
+    [format STR]	# Slave format (default client format)
+    [channels INT]	# Slave channels (default client channels)
+  }
+  ttable {	 	# Transfer table (bidimensional compound of 
+	        	# cchannels * schannels numbers)
+    CCHANNEL {
+      SCHANNEL REAL	# route value (0.0 ... 1.0)
+    }
+  }
+
+
+  type multi		# Linked PCMs (exclusive access to selected channels)
+  slaves {		# Slaves definitions
+    ID STR		# Slave name for slave N (see pcm_slave)
+    # or
+    ID {		# Slave definition for slave N
+      pcm STR		# Slave PCM name
+    # or
+      pcm { }		# Slave PCM definition
+      channels INT	# Slave channels
+    }
+  }
+  bindings {		# Bindings table
+    N {			# Binding for client channel N
+      slave STR		# Slave key
+      channel INT	# Slave channel
+    }
+  }
+  [master INT]		# Define the master slave
+
+
+  type file		# File plugin
+  slave STR		# Slave name (see pcm_slave)
+  # or
+  slave {		# Slave definition
+    pcm STR		# Slave PCM name
+    # or
+    pcm { }		# Slave PCM definition
+  }
+  file STR		# File name
+  # or
+  file INT		# File descriptor
+  [format STR]		# File format (NYI)
+  [perm INT]		# File permission (default 0600)
+
+  type meter		# Meter PCM
+  slave STR		# Slave name (see pcm_slave)
+  # or
+  slave {		# Slave definition or name
+    pcm STR		# Slave PCM name
+    # or
+    pcm { }		# Slave PCM definition
+  }
+  [frequency INT]	# Updates per second
+  scopes {		# Scopes
+    ID STR		# Scope name (see pcm_scope)
+  # or
+    ID { }		# Scope definition (see pcm_scope)
+  }
+
+
+  type droute		# Attenuated dynamic route PCM (NYI)
+  slave STR		# Slave name (see pcm_slave)
+  # or
+  slave {		# Slave definition
+    pcm STR		# Slave PCM name
+    # or
+    pcm { }		# Slave PCM definition
+    [format STR]	# Slave format (default client format)
+    [channels INT]	# Slave channels (default client channels)
+  }
+  ctl STR		# Ctl name
+  bindings {		# Bindings table
+    ID {		# Binding entry
+      cchannels {	# Client channels
+        C INT		# Client channel
+      }
+      schannel {	# Slave channels
+        S INT		# Slave channel
+      }
+      control STR	# Control name of C * S (or C values: only if C == S)
+    }
+  }
+
+
+  type null		# Null endpoint plugin
+  [time INT]		# Time related or not (NYI)
+
+
+  type shm		# Shared memory client PCM
+  server STR		# Server name
+  pcm STR		# PCM name on server
+
+
+  type share		# Share PCM
+  slave STR		# Slave name (see pcm_slave)
+  bindings {		# Bindings table
+    N INT		# Slave channel for client channel N
+  }
+
+
+  type mix		# Mix PCM
+  slave STR		# Slave name (see pcm_slave)
+  bindings {		# Bindings table
+    N INT		# Slave channel for client channel N
+  }
+
+
+  type ladspa		# LADSPA plugin PCM
+  slave STR		# Slave name (see pcm_slave)
+  path STR		# Path or paths (delimited with ':')
+  plugins | playback_plugins | capture_plugins {
+    N {			# Configuration for LADSPA plugin N
+      id #		# LADSPA plugin ID (for example 1043)
+      label STR		# LADSPA plugin label (for example 'delay_5s')
+      filename STR	# Full filename of .so library with LADPA plugin code
+      policy STR	# Policy can be 'none' or 'duplicate'
+      input | output {
+        bindings {
+          C INT or STR  # C - channel, INT - audio port index, STR - audio port name
+        }
+        controls {
+          I INT or REAL # I - control port index, INT or REAL - control value
+        }
+      }
+    }
+  }
+
+  type dmix		# Direct mixing plugin
+  slave STR		# Slave name (see pcm_slave)
+  ipc_key INT		# Unique ipc key
+  ipc_perm INT		# ipc permissions (default 0600)
+  ipc_gid INT		# ipc gid (default -1 = disable)
+  ipc_key_add_uid BOOL  # Add current uid to ipc_key
+  bindings {		# Bindings table
+    N INT		# Slave channel for client channel N
+  }
+
+  type dsnoop		# Direct snoop (split one capture stream to more)
+  slave STR		# Slave name (see pcm_slave)
+  ipc_key INT		# Unique ipc key
+  ipc_perm INT		# ipc permissions (default 0600)
+  ipc_gid INT		# ipc gid (default -1 = disable)
+  ipc_key_add_uid BOOL  # Add current uid to ipc_key
+  bindings {		# Bindings table
+    N INT		# Slave channel for client channel N
+  }
+
+  type dshare		# Share channels from one stream
+  slave STR		# Slave name (see pcm_slave)
+  ipc_key INT		# Unique ipc key
+  ipc_perm INT		# ipc permissions (default 0600)
+  ipc_gid INT		# ipc gid (default -1 = disable)
+  ipc_key_add_uid BOOL  # Add current uid to ipc_key
+  bindings {		# Bindings table
+    N INT		# Slave channel for client channel N
+  }
+}
+
+# CTL type definition
+ctl_type.NAME {
+  [lib STR]		# Library file (default libasound.so)
+  [open STR]		# Open function (default _snd_ctl_NAME_open)
+}
+
+# CTL definition
+ctl.NAME {
+  type STR		# Type
+  [comment ANY]		# Saved comments
+
+# CTL types
+  type hw
+  card STR/INT		# Card name or number
+
+
+  type shm		# Shared memory client CTL
+  server STR		# Server name
+  ctl STR		# CTL name on server
+
+
+}
+
+
+# RAWMIDI type definition
+rawmidi_type.NAME {
+  [lib STR]		# Library file (default libasound.so)
+  [open STR]		# Open function (default _snd_rawmidi_NAME_open)
+}
+
+# RAWMIDI definition
+rawmidi.NAME {
+  type STR		# Type
+  [comment ANY]		# Saved comments
+
+# RAWMIDI types:
+  type hw 		# Kernel RAWMIDI
+  card INT/STR		# Card name or number
+  [device] INT		# Device number (default 0)	
+  [subdevice] INT	# Subdevice number, -1 first available (default -1)
+
+
+}
+
+# SEQ type definition
+seq_type.NAME {
+  [lib STR]		# Library file (default libasound.so)
+  [open STR]		# Open function (default _snd_seq_NAME_open)
+}
+
+# SEQ definition
+seq.NAME {
+  type STR		# Type
+  [comment ANY]		# Saved comments
+
+# SEQ types:
+  type hw 		# Kernel SEQ
+
+
+}
+
+# Aliases
+DEF.NAME1 NAME2		# DEF.NAME1 is an alias for DEF.NAME2
+
+Some examples:
+
+pcm.trident {
+	type hw
+	card 0
+	device 0
+}
+
+pcm.ice1712 {
+	type hw
+	card 1
+	device 0
+}
+
+pcm.ice1712_spdif {
+	type plug
+	ttable.0.8 1
+	ttable.1.9 1
+	slave.pcm ice1712
+}
+
+pcm_slave.rs {
+	pcm trident
+	rate 44100
+}
+
+pcm.r {
+	type rate
+	slave rs
+}
+
+pcm.m {
+	type meter
+	slave.pcm plug:trident
+	frequency 50
+	scopes [
+		{
+			type level
+		}
+	]
+}
+
+pcm_scope_type.level {
+	lib /home/abramo/scopes/scope-level.so
+} 
+
+# an example command is 'aplay -D plug:ladspa <filename>'
+# otherwise, the ladspa plugin expects FLOAT type which
+# is very rare
+pcm.ladspa {
+        type ladspa
+        slave.pcm "plughw:0,0";
+        path "/home/perex/src/ladspa_sdk/plugins";
+        plugins [
+                {
+                        label delay_5s
+                        input {
+                                controls [ 0.8 0.2 ]
+                        }
+                }
+        ]
+}
+
+# an example command for dmix plugin to force 44100Hz mixing rate:
+# aplay -D"plug:'dmix:RATE=44100'" <filename>
+# an example command for dmix plugin to force 44100Hz and hw:1,0 output device
+# aplay -Dplug:\'dmix:SLAVE=\"hw:1,0\",RATE=44100\' <filename>
+# an example command for dmix plugin to force 32-bit signed little endian format
+# aplay -D"plug:'dmix:FORMAT=S32_LE'" <filename>
diff --git a/doc/doxygen.cfg.in b/doc/doxygen.cfg.in
new file mode 100644
index 0000000..f4499d6
--- /dev/null
+++ b/doc/doxygen.cfg.in
@@ -0,0 +1,123 @@
+PROJECT_NAME     = "ALSA project - the C library reference"
+OUTPUT_DIRECTORY = doxygen
+GENERATE_LATEX   = NO
+GENERATE_MAN     = NO
+GENERATE_RTF	 = NO
+
+CASE_SENSE_NAMES = NO
+INPUT            = @top_srcdir@/doc/index.doxygen \
+		   @top_srcdir@/include/asoundlib.h \
+		   @top_srcdir@/include/version.h \
+		   @top_srcdir@/include/global.h \
+		   @top_srcdir@/include/asoundef.h \
+		   @top_srcdir@/include/input.h \
+		   @top_srcdir@/include/output.h \
+		   @top_srcdir@/include/error.h \
+		   @top_srcdir@/include/conf.h \
+		   @top_srcdir@/include/control.h \
+		   @top_srcdir@/include/pcm.h \
+		   @top_srcdir@/include/rawmidi.h \
+		   @top_srcdir@/include/timer.h \
+		   @top_srcdir@/include/hwdep.h \
+		   @top_srcdir@/include/seq.h \
+		   @top_srcdir@/include/seq_event.h \
+		   @top_srcdir@/include/seqmid.h \
+		   @top_srcdir@/include/seq_midi_event.h \
+		   @top_srcdir@/include/pcm_external.h \
+		   @top_srcdir@/include/pcm_extplug.h \
+		   @top_srcdir@/include/pcm_ioplug.h \
+		   @top_srcdir@/include/control_external.h \
+		   @top_srcdir@/include/mixer.h \
+		   @top_srcdir@/include/use-case.h \
+		   @top_srcdir@/src/error.c \
+		   @top_srcdir@/src/dlmisc.c \
+		   @top_srcdir@/src/async.c \
+		   @top_srcdir@/src/input.c \
+		   @top_srcdir@/src/output.c \
+		   @top_srcdir@/src/conf.c \
+		   @top_srcdir@/src/confmisc.c \
+		   @top_srcdir@/src/names.c \
+		   @top_srcdir@/src/shmarea.c \
+		   @top_srcdir@/src/userfile.c \
+		   @top_srcdir@/src/control \
+		   @top_srcdir@/src/mixer \
+		   @top_srcdir@/src/pcm/pcm.c \
+		   @top_srcdir@/src/pcm/pcm_mmap.c \
+		   @top_srcdir@/src/pcm/pcm_plugin.c \
+		   @top_srcdir@/src/pcm/pcm_hw.c \
+		   @top_srcdir@/src/pcm/pcm_mmap_emul.c \
+		   @top_srcdir@/src/pcm/pcm_shm.c \
+		   @top_srcdir@/src/pcm/pcm_null.c \
+		   @top_srcdir@/src/pcm/pcm_copy.c \
+		   @top_srcdir@/src/pcm/pcm_linear.c \
+		   @top_srcdir@/src/pcm/pcm_lfloat.c \
+		   @top_srcdir@/src/pcm/pcm_mulaw.c \
+		   @top_srcdir@/src/pcm/pcm_alaw.c \
+		   @top_srcdir@/src/pcm/pcm_adpcm.c \
+		   @top_srcdir@/src/pcm/pcm_route.c \
+		   @top_srcdir@/src/pcm/pcm_rate.c \
+		   @top_srcdir@/src/pcm/pcm_plug.c \
+		   @top_srcdir@/src/pcm/pcm_file.c \
+		   @top_srcdir@/src/pcm/pcm_multi.c \
+		   @top_srcdir@/src/pcm/pcm_share.c \
+		   @top_srcdir@/src/pcm/pcm_hooks.c \
+		   @top_srcdir@/src/pcm/pcm_dmix.c \
+		   @top_srcdir@/src/pcm/pcm_dshare.c \
+		   @top_srcdir@/src/pcm/pcm_dsnoop.c \
+		   @top_srcdir@/src/pcm/pcm_meter.c \
+		   @top_srcdir@/src/pcm/pcm_ladspa.c \
+		   @top_srcdir@/src/pcm/pcm_asym.c \
+		   @top_srcdir@/src/pcm/pcm_iec958.c \
+		   @top_srcdir@/src/pcm/pcm_softvol.c \
+		   @top_srcdir@/src/pcm/pcm_extplug.c \
+		   @top_srcdir@/src/pcm/pcm_ioplug.c \
+		   @top_srcdir@/src/pcm/pcm_empty.c \
+		   @top_srcdir@/src/pcm/pcm_misc.c \
+		   @top_srcdir@/src/pcm/pcm_simple.c \
+		   @top_srcdir@/src/rawmidi \
+		   @top_srcdir@/src/timer \
+		   @top_srcdir@/src/hwdep \
+		   @top_srcdir@/src/seq \
+		   @top_srcdir@/src/ucm
+EXCLUDE		 = @top_srcdir@/src/control/control_local.h \
+		   @top_srcdir@/src/pcm/atomic.h \
+		   @top_srcdir@/src/pcm/interval.h \
+		   @top_srcdir@/src/pcm/interval_inline.h \
+		   @top_srcdir@/src/pcm/mask.h \
+		   @top_srcdir@/src/pcm/mask_inline.h \
+		   @top_srcdir@/src/pcm/pcm_local.h \
+		   @top_srcdir@/src/pcm/pcm_meter.h \
+		   @top_srcdir@/src/pcm/pcm_plugin.h \
+		   @top_srcdir@/src/pcm/plugin_ops.h \
+		   @top_srcdir@/src/pcm/ladspa.h \
+		   @top_srcdir@/src/hwdep/hwdep_local.h \
+		   @top_srcdir@/src/mixer/mixer_local.h \
+		   @top_srcdir@/src/rawmidi/rawmidi_local.h \
+		   @top_srcdir@/src/seq/seq_local.h \
+		   @top_srcdir@/src/seq/ucm_local.h
+RECURSIVE	 = YES
+FILE_PATTERNS    = *.c *.h
+EXAMPLE_PATH     = @top_srcdir@/test
+IMAGE_PATH	 = pictures
+QUIET            = YES
+
+EXTRACT_ALL	 = NO
+EXTRACT_STATIC	 = NO
+SHOW_INCLUDE_FILES = NO
+JAVADOC_AUTOBRIEF = NO
+INHERIT_DOCS	 = YES
+ENABLED_SECTIONS = ""
+MACRO_EXPANSION  = YES
+EXPAND_ONLY_PREDEF = YES
+PREDEFINED	 = DOXYGEN PIC "DOC_HIDDEN" \
+		   "ATTRIBUTE_UNUSED=" \
+		   ALSA_PCM_NEW_HW_PARAMS_API \
+		   _POSIX_C_SOURCE \
+		   "use_default_symbol_version(x,y,z)=" \
+		   "link_warning(x,y)="
+
+OPTIMIZE_OUTPUT_FOR_C = YES	# doxygen 1.2.6 option
+TYPEDEF_HIDES_STRUCT = YES	# needed in doxygen >= 1.5.4
+
+#INPUT_FILTER	 = inputfilter
+#FILTER_SOURCE_FILES = YES
diff --git a/doc/index.doxygen b/doc/index.doxygen
new file mode 100644
index 0000000..f76cc10
--- /dev/null
+++ b/doc/index.doxygen
@@ -0,0 +1,54 @@
+/*! \page Index Preamble and License
+
+\author Jaroslav Kysela <perex@perex.cz>
+\author Abramo Bagnara <abramo@alsa-project.org>
+\author Takashi Iwai <tiwai@suse.de>
+\author Frank van de Pol <fvdpol@coil.demon.nl>
+
+<H2>Preface</H2>
+<P>The Advanced Linux Sound Architecture (\e ALSA) comes with a kernel
+API and a library API. This document describes the library API and how
+it interfaces with the kernel API.</P>
+
+<H2>Documentation License</H2>
+
+<P>This documentation is free; you can redistribute it without
+any restrictions. Modifications or derived work must retain
+the copyright and list all authors.</P>
+ 
+<P>This documentation 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.</P>
+
+<H2>API usage</H2>
+<P>Application programmers should use the library API rather than the
+kernel API. The library offers 100% of the functionality of the kernel API,
+but adds major improvements in usability, making the application code simpler
+and better looking. In addition, future fixes or compatibility code
+may be placed in the library code instead of the kernel driver.</P>
+
+<H2>API links</H2>
+
+<UL>
+  <LI>Page \ref control explains the primitive controls API.
+  <LI>Page \ref hcontrol explains the high-level primitive controls API.
+  <LI>Page \ref mixer explains the mixer controls API.
+  <LI>Page \ref pcm explains the design of the PCM (digital audio) API.
+  <LI>Page \ref pcm_plugins explains the design of PCM (digital audio) plugins.
+  <LI>Page \ref pcm_external_plugins explains the external PCM plugin SDK.
+  <LI>Page \ref ctl_external_plugins explains the external control plugin SDK.
+  <LI>Page \ref rawmidi explains the design of the RawMidi API.
+  <LI>Page \ref timer explains the design of the Timer API.
+  <LI>Page \ref seq explains the design of the Sequencer API.
+</UL>
+
+<H2>Configuration</H2>
+
+<UL>
+  <LI>Page \ref conf explains the syntax of library configuration files.
+  <LI>Page \ref confarg explains the run-time argument syntax.
+  <LI>Page \ref conffunc explains run-time function definitions and their usage.
+  <LI>Page \ref confhooks explains run-time hook definitions and their usage.
+</UL>
+
+*/
diff --git a/doc/pictures/Makefile.am b/doc/pictures/Makefile.am
new file mode 100644
index 0000000..17b6e12
--- /dev/null
+++ b/doc/pictures/Makefile.am
@@ -0,0 +1 @@
+EXTRA_DIST=wave1.gif wave2.gif
diff --git a/doc/pictures/wave1.gif b/doc/pictures/wave1.gif
new file mode 100644
index 0000000..e465df8
--- /dev/null
+++ b/doc/pictures/wave1.gif
Binary files differ
diff --git a/doc/pictures/wave2.gif b/doc/pictures/wave2.gif
new file mode 100644
index 0000000..ee91bf2
--- /dev/null
+++ b/doc/pictures/wave2.gif
Binary files differ
diff --git a/etc/asound.conf b/etc/asound.conf
deleted file mode 100644
index 34fd695..0000000
--- a/etc/asound.conf
+++ /dev/null
@@ -1,36 +0,0 @@
-# This PCM is used to test 6 channels via a stereo (2 channels) audio stream.
-pcm.ch51dup {
-	type route
-	slave.pcm surround51
-	slave.channels 6
-
-	ttable.0.0 1
-	ttable.1.1 1
-	ttable.0.2 1
-	ttable.1.3 1
-	ttable.0.4 1
-	ttable.1.5 1
-}
-
-# This PCM is used to test 4 channels via a stereo (2 channels) audio stream.
-pcm.ch40dup {
-	type route
-	slave.pcm surround40
-	slave.channels 4
-
-	ttable.0.0 1
-	ttable.1.1 1
-	ttable.0.2 1
-	ttable.1.3 1
-}
-
-# This PCM is used to swap a stereo (2 channels) audio stream
-pcm.ch2swp {
-	type route
-	slave.pcm "hw:0,0"
-	slave.channels 2
-
-	ttable.0.1 1
-	ttable.1.0 1
-}
-
diff --git a/gitcompile b/gitcompile
new file mode 100755
index 0000000..0b60aed
--- /dev/null
+++ b/gitcompile
@@ -0,0 +1,48 @@
+#!/bin/bash
+
+set -e
+
+bit32=
+if [ $# -ne 0 -a "$1" == "32" ]; then
+  bit32=yes
+  echo "Forced 32-bit library build..."
+fi
+if [ $# -ne 0 -a -z "$bit32" ]; then
+  args="$@"
+elif [ -r /etc/asound/library_args ]; then
+  args="`cat /etc/asound/library_args`"
+  if [ -z "$bit32" ]; then
+    test -r /etc/asound/library64_args && \
+      args="`cat /etc/asound/library64_args`"
+  fi
+else
+  prefix="/usr"
+  libdir="/usr/lib"
+  libdir2="/usr/lib"
+  if [ -z "$bit32" ]; then
+    test -d /usr/lib64 && libdir="/usr/lib64"
+    test -f /lib64/libasound.so.2 && libdir="/lib64"
+    test -d /usr/lib64 && libdir2="/usr/lib64"
+  else
+    test -f /lib/libasound.so.2 && libdir="/lib"
+  fi
+  args="--disable-aload --prefix=$prefix --libdir=$libdir"
+  args="$args --with-plugindir=$libdir2/alsa-lib"
+  args="$args --with-pkgconfdir=$libdir2/pkgconfig"
+fi
+      
+touch ltconfig
+libtoolize --force --copy --automake
+aclocal $ACLOCAL_FLAGS
+autoheader
+automake --foreign --copy --add-missing
+touch depcomp		# seems to be missing for old automake
+autoconf
+export CFLAGS='-O2 -Wall -W -pipe -g'
+echo "CFLAGS=$CFLAGS"
+echo "./configure $args"
+./configure $args || exit 1
+unset CFLAGS
+if [ -z "$GITCOMPILE_NO_MAKE" ]; then
+  make
+fi
diff --git a/include/Makefile.am b/include/Makefile.am
new file mode 100644
index 0000000..de37f2c
--- /dev/null
+++ b/include/Makefile.am
@@ -0,0 +1,90 @@
+SUBDIRS = sound
+
+sysincludedir = ${includedir}/sys
+alsaincludedir = ${includedir}/alsa
+
+alsainclude_HEADERS = asoundlib.h asoundef.h \
+		      version.h global.h input.h output.h error.h \
+		      conf.h control.h iatomic.h use-case.h
+
+if BUILD_CTL_PLUGIN_EXT
+alsainclude_HEADERS += control_external.h
+endif
+
+if BUILD_PCM
+alsainclude_HEADERS += pcm.h pcm_old.h timer.h
+if BUILD_PCM_PLUGIN
+alsainclude_HEADERS += pcm_plugin.h
+endif
+if BUILD_PCM_PLUGIN_RATE
+alsainclude_HEADERS += pcm_rate.h 
+endif
+if BUILD_PCM_PLUGIN_EXTPLUG
+alsainclude_HEADERS += pcm_external.h pcm_extplug.h
+endif
+if BUILD_PCM_PLUGIN_IOPLUG
+if !BUILD_PCM_PLUGIN_EXTPLUG
+alsainclude_HEADERS += pcm_external.h
+endif
+alsainclude_HEADERS += pcm_ioplug.h
+endif
+endif
+
+if BUILD_RAWMIDI
+alsainclude_HEADERS += rawmidi.h
+endif
+
+if BUILD_HWDEP
+alsainclude_HEADERS += hwdep.h
+endif
+
+if BUILD_MIXER
+alsainclude_HEADERS += mixer.h mixer_abst.h
+endif
+
+if BUILD_SEQ
+alsainclude_HEADERS += seq_event.h seq.h seqmid.h seq_midi_event.h
+endif
+
+if BUILD_ALISP
+alsainclude_HEADERS += alisp.h
+endif
+
+noinst_HEADERS = alsa sys.h search.h list.h aserver.h local.h alsa-symbols.h \
+	asoundlib-head.h asoundlib-tail.h
+
+DISTCLEANFILES = stamp-vh version.h alsa asoundlib.h
+
+alsa:
+	ln -s $(top_srcdir)/include alsa
+
+version.h: stamp-vh alsa
+	@:
+
+stamp-vh: $(top_builddir)/configure.in
+	@echo "/*" > ver.tmp
+	@echo " *  version.h" >> ver.tmp
+	@echo " */" >> ver.tmp
+	@echo "" >> ver.tmp
+	@echo "#define SND_LIB_MAJOR		$(SND_LIB_MAJOR) /**< major number of library version */" >> ver.tmp
+	@echo "#define SND_LIB_MINOR		$(SND_LIB_MINOR) /**< minor number of library version */" >> ver.tmp
+	@echo "#define SND_LIB_SUBMINOR	$(SND_LIB_SUBMINOR) /**< subminor number of library version */" >> ver.tmp
+	@echo "#define SND_LIB_EXTRAVER	$(SND_LIB_EXTRAVER) /**< extra version number, used mainly for betas */" >> ver.tmp
+	@echo "/** library version */" >> ver.tmp
+	@echo "#define SND_LIB_VERSION		((SND_LIB_MAJOR<<16)|\\" >> ver.tmp
+	@echo "				 (SND_LIB_MINOR<<8)|\\" >> ver.tmp
+	@echo "				  SND_LIB_SUBMINOR)" >> ver.tmp
+	@echo "/** library version (string) */" >> ver.tmp
+	@echo "#define SND_LIB_VERSION_STR	\"$(SND_LIB_VERSION)\"" >> ver.tmp
+	@echo >> ver.tmp
+	@cmp -s version.h ver.tmp \
+          || (echo "Updating version.h"; \
+              cp ver.tmp version.h; \
+              echo timestamp > stamp-vh)
+	-@rm -f ver.tmp
+
+INCLUDES=-I$(top_srcdir)/include
+
+install-data-hook:
+	test -d $(DESTDIR)$(sysincludedir) || mkdir -p $(DESTDIR)$(sysincludedir)
+	$(INSTALL_DATA) $(srcdir)/sys.h $(DESTDIR)$(sysincludedir)/asoundlib.h
diff --git a/include/alisp.h b/include/alisp.h
new file mode 100644
index 0000000..407ed64
--- /dev/null
+++ b/include/alisp.h
@@ -0,0 +1,55 @@
+/*
+ *  ALSA lisp implementation
+ *  Copyright (c) 2003 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+struct alisp_cfg {
+	int verbose: 1,
+	    warning: 1,
+	    debug: 1;
+	snd_input_t *in;	/* program code */
+	snd_output_t *out;	/* program output */
+	snd_output_t *eout;	/* error output */
+	snd_output_t *vout;	/* verbose output */
+	snd_output_t *wout;	/* warning output */
+	snd_output_t *dout;	/* debug output */
+};
+
+struct alisp_instance;
+struct alisp_object;
+struct alisp_seq_iterator;
+
+struct alisp_cfg *alsa_lisp_default_cfg(snd_input_t *input);
+void alsa_lisp_default_cfg_free(struct alisp_cfg *cfg);
+int alsa_lisp(struct alisp_cfg *cfg, struct alisp_instance **instance);
+void alsa_lisp_free(struct alisp_instance *instance);
+int alsa_lisp_function(struct alisp_instance *instance, struct alisp_seq_iterator **result,
+		       const char *id, const char *args, ...)
+#ifndef DOC_HIDDEN
+		       __attribute__ ((format (printf, 4, 5)))
+#endif
+		       ;
+void alsa_lisp_result_free(struct alisp_instance *instance,
+			   struct alisp_seq_iterator *result);
+int alsa_lisp_seq_first(struct alisp_instance *instance, const char *id,
+			struct alisp_seq_iterator **seq);
+int alsa_lisp_seq_next(struct alisp_seq_iterator **seq);
+int alsa_lisp_seq_count(struct alisp_seq_iterator *seq);
+int alsa_lisp_seq_integer(struct alisp_seq_iterator *seq, long *val);
+int alsa_lisp_seq_pointer(struct alisp_seq_iterator *seq, const char *ptr_id, void **ptr);
diff --git a/include/alsa-symbols.h b/include/alsa-symbols.h
new file mode 100644
index 0000000..51cb982
--- /dev/null
+++ b/include/alsa-symbols.h
@@ -0,0 +1,72 @@
+/*
+ *  ALSA lib - dynamic symbol versions
+ *  Copyright (c) 2002 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#ifndef __ALSA_SYMBOLS_H
+#define __ALSA_SYMBOLS_H
+
+#if defined(PIC) && defined(VERSIONED_SYMBOLS) /* might be also configurable */
+#define USE_VERSIONED_SYMBOLS
+#endif
+
+#define INTERNAL_CONCAT2_2(Pre, Post) Pre##Post
+#define INTERNAL(Name) INTERNAL_CONCAT2_2(__, Name)
+
+#ifdef __powerpc64__
+# define symbol_version(real, name, version) 			\
+	__asm__ (".symver " ASM_NAME(#real) "," ASM_NAME(#name) "@" #version);	\
+	__asm__ (".symver ." ASM_NAME(#real) ",." ASM_NAME(#name) "@" #version)
+# define default_symbol_version(real, name, version) 		\
+	__asm__ (".symver " ASM_NAME(#real) "," ASM_NAME(#name) "@@" #version);	\
+	__asm__ (".symver ." ASM_NAME(#real) ",." ASM_NAME(#name) "@@" #version)
+#else
+# define symbol_version(real, name, version) \
+	__asm__ (".symver " ASM_NAME(#real) "," ASM_NAME(#name) "@" #version)
+# define default_symbol_version(real, name, version) \
+	__asm__ (".symver " ASM_NAME(#real) "," ASM_NAME(#name) "@@" #version)
+#endif
+
+#ifdef USE_VERSIONED_SYMBOLS
+#define use_symbol_version(real, name, version) \
+		symbol_version(real, name, version)
+#define use_default_symbol_version(real, name, version) \
+		default_symbol_version(real, name, version)
+#else
+#define use_symbol_version(real, name, version) /* nothing */
+#ifdef __powerpc64__
+#define use_default_symbol_version(real, name, version) \
+	__asm__ (".weak " ASM_NAME(#name)); 			\
+	__asm__ (".weak ." ASM_NAME(#name)); 			\
+	__asm__ (".set " ASM_NAME(#name) "," ASM_NAME(#real));		\
+	__asm__ (".set ." ASM_NAME(#name) ",." ASM_NAME(#real))
+#else
+#if defined(__alpha__) || defined(__mips__)
+#define use_default_symbol_version(real, name, version) \
+        __asm__ (".weak " ASM_NAME(#name)); \
+        __asm__ (ASM_NAME(#name) " = " ASM_NAME(#real))
+#else
+#define use_default_symbol_version(real, name, version) \
+	__asm__ (".weak " ASM_NAME(#name)); \
+	__asm__ (".set " ASM_NAME(#name) "," ASM_NAME(#real))
+#endif
+#endif
+#endif
+
+#endif /* __ALSA_SYMBOLS_H */
diff --git a/include/aserver.h b/include/aserver.h
new file mode 100644
index 0000000..2586832
--- /dev/null
+++ b/include/aserver.h
@@ -0,0 +1,160 @@
+/*
+ *  ALSA client/server header file
+ *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+#include <netdb.h>
+#include "../src/pcm/pcm_local.h"
+#include "../src/control/control_local.h"
+
+int snd_receive_fd(int sock, void *data, size_t len, int *fd);
+int snd_is_local(struct hostent *hent);
+
+typedef enum _snd_dev_type {
+	SND_DEV_TYPE_PCM,
+	SND_DEV_TYPE_CONTROL,
+	SND_DEV_TYPE_RAWMIDI,
+	SND_DEV_TYPE_TIMER,
+	SND_DEV_TYPE_HWDEP,
+	SND_DEV_TYPE_SEQ,
+} snd_dev_type_t;
+
+typedef enum _snd_transport_type {
+	SND_TRANSPORT_TYPE_SHM,
+	SND_TRANSPORT_TYPE_TCP,
+} snd_transport_type_t;
+
+#define SND_PCM_IOCTL_HWSYNC		_IO ('A', 0x22)
+#define SND_PCM_IOCTL_STATE		_IO ('A', 0xf1)
+#define SND_PCM_IOCTL_MMAP		_IO ('A', 0xf2)
+#define SND_PCM_IOCTL_MUNMAP		_IO ('A', 0xf3)
+#define SND_PCM_IOCTL_MMAP_COMMIT	_IO ('A', 0xf4)
+#define SND_PCM_IOCTL_AVAIL_UPDATE	_IO ('A', 0xf5)
+#define SND_PCM_IOCTL_ASYNC		_IO ('A', 0xf6)
+#define SND_PCM_IOCTL_CLOSE		_IO ('A', 0xf7)
+#define SND_PCM_IOCTL_POLL_DESCRIPTOR	_IO ('A', 0xf8)
+#define SND_PCM_IOCTL_HW_PTR_FD		_IO ('A', 0xf9)
+#define SND_PCM_IOCTL_APPL_PTR_FD	_IO ('A', 0xfa)
+#define SND_PCM_IOCTL_FORWARD		_IO ('A', 0xfb)
+
+typedef struct {
+	snd_pcm_uframes_t ptr;
+	int use_mmap;
+	off_t offset;		/* for mmap */
+	int changed;
+} snd_pcm_shm_rbptr_t;
+
+typedef struct {
+	long result;
+	int cmd;
+	snd_pcm_shm_rbptr_t hw;
+	snd_pcm_shm_rbptr_t appl;
+	union {
+		struct {
+			int sig;
+			pid_t pid;
+		} async;
+		snd_pcm_info_t info;
+		snd_pcm_hw_params_t hw_refine;
+		snd_pcm_hw_params_t hw_params;
+		snd_pcm_sw_params_t sw_params;
+		snd_pcm_status_t status;
+		struct {
+			snd_pcm_uframes_t frames;
+		} avail;
+		struct {
+			snd_pcm_sframes_t frames;
+		} delay;
+		struct {
+			int enable;
+		} pause;
+		snd_pcm_channel_info_t channel_info;
+		struct {
+			snd_pcm_uframes_t frames;
+		} rewind;
+		struct {
+			snd_pcm_uframes_t frames;
+		} forward;
+		struct {
+			int fd;
+		} link;
+		struct {
+			snd_pcm_uframes_t offset;
+			snd_pcm_uframes_t frames;
+		} mmap_commit;
+		struct {
+			char use_mmap;
+			int shmid;
+			off_t offset;
+		} rbptr;
+	} u;
+	char data[0];
+} snd_pcm_shm_ctrl_t;
+
+#define PCM_SHM_SIZE sizeof(snd_pcm_shm_ctrl_t)
+		
+#define SND_CTL_IOCTL_READ		_IOR('U', 0xf1, snd_ctl_event_t)
+#define SND_CTL_IOCTL_CLOSE		_IO ('U', 0xf2)
+#define SND_CTL_IOCTL_POLL_DESCRIPTOR	_IO ('U', 0xf3)
+#define SND_CTL_IOCTL_ASYNC		_IO ('U', 0xf4)
+
+typedef struct {
+	int result;
+	int cmd;
+	union {
+		struct {
+			int sig;
+			pid_t pid;
+		} async;
+		int device;
+		int subscribe_events;
+		snd_ctl_card_info_t card_info;
+		snd_ctl_elem_list_t element_list;
+		snd_ctl_elem_info_t element_info;
+		snd_ctl_elem_value_t element_read;
+		snd_ctl_elem_value_t element_write;
+		snd_ctl_elem_id_t element_lock;
+		snd_ctl_elem_id_t element_unlock;
+		snd_hwdep_info_t hwdep_info;
+		snd_pcm_info_t pcm_info;
+		int pcm_prefer_subdevice;
+		snd_rawmidi_info_t rawmidi_info;
+		int rawmidi_prefer_subdevice;
+		unsigned int power_state;
+		snd_ctl_event_t read;
+	} u;
+	char data[0];
+} snd_ctl_shm_ctrl_t;
+
+#define CTL_SHM_SIZE 65536
+#define CTL_SHM_DATA_MAXLEN (CTL_SHM_SIZE - offsetof(snd_ctl_shm_ctrl_t, data))
+
+typedef struct {
+	unsigned char dev_type;
+	unsigned char transport_type;
+	unsigned char stream;
+	unsigned char mode;
+	unsigned char namelen;
+	char name[0];
+} snd_client_open_request_t;
+
+typedef struct {
+	long result;
+	int cookie;
+} snd_client_open_answer_t;
+
diff --git a/include/asoundef.h b/include/asoundef.h
new file mode 100644
index 0000000..c6c4eec
--- /dev/null
+++ b/include/asoundef.h
@@ -0,0 +1,310 @@
+/**
+ * \file include/asoundef.h
+ * \brief Application interface library for the ALSA driver
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \author Takashi Iwai <tiwai@suse.de>
+ * \date 1998-2001
+ *
+ * Definitions of constants for the ALSA driver
+ */
+/*
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#ifndef __ALSA_ASOUNDEF_H
+#define __ALSA_ASOUNDEF_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \defgroup Digital_Audio_Interface Constants for Digital Audio Interfaces
+ * AES/IEC958 channel status bits.
+ * \{
+ */
+
+#define IEC958_AES0_PROFESSIONAL	(1<<0)	/**< 0 = consumer, 1 = professional */
+#define IEC958_AES0_NONAUDIO		(1<<1)	/**< 0 = audio, 1 = non-audio */
+#define IEC958_AES0_PRO_EMPHASIS	(7<<2)	/**< mask - emphasis */
+#define IEC958_AES0_PRO_EMPHASIS_NOTID	(0<<2)	/**< emphasis not indicated */
+#define IEC958_AES0_PRO_EMPHASIS_NONE	(1<<2)	/**< no emphasis */
+#define IEC958_AES0_PRO_EMPHASIS_5015	(3<<2)	/**< 50/15us emphasis */
+#define IEC958_AES0_PRO_EMPHASIS_CCITT	(7<<2)	/**< CCITT J.17 emphasis */
+#define IEC958_AES0_PRO_FREQ_UNLOCKED	(1<<5)	/**< source sample frequency: 0 = locked, 1 = unlocked */
+#define IEC958_AES0_PRO_FS		(3<<6)	/**< mask - sample frequency */
+#define IEC958_AES0_PRO_FS_NOTID	(0<<6)	/**< fs not indicated */
+#define IEC958_AES0_PRO_FS_44100	(1<<6)	/**< 44.1kHz */
+#define IEC958_AES0_PRO_FS_48000	(2<<6)	/**< 48kHz */
+#define IEC958_AES0_PRO_FS_32000	(3<<6)	/**< 32kHz */
+#define IEC958_AES0_CON_NOT_COPYRIGHT	(1<<2)	/**< 0 = copyright, 1 = not copyright */
+#define IEC958_AES0_CON_EMPHASIS	(7<<3)	/**< mask - emphasis */
+#define IEC958_AES0_CON_EMPHASIS_NONE	(0<<3)	/**< no emphasis */
+#define IEC958_AES0_CON_EMPHASIS_5015	(1<<3)	/**< 50/15us emphasis */
+#define IEC958_AES0_CON_MODE		(3<<6)	/**< mask - mode */
+#define IEC958_AES1_PRO_MODE		(15<<0)	/**< mask - channel mode */
+#define IEC958_AES1_PRO_MODE_NOTID	(0<<0)	/**< mode not indicated */
+#define IEC958_AES1_PRO_MODE_STEREOPHONIC (2<<0) /**< stereophonic - ch A is left */
+#define IEC958_AES1_PRO_MODE_SINGLE	(4<<0)	/**< single channel */
+#define IEC958_AES1_PRO_MODE_TWO	(8<<0)	/**< two channels */
+#define IEC958_AES1_PRO_MODE_PRIMARY	(12<<0)	/**< primary/secondary */
+#define IEC958_AES1_PRO_MODE_BYTE3	(15<<0)	/**< vector to byte 3 */
+#define IEC958_AES1_PRO_USERBITS	(15<<4)	/**< mask - user bits */
+#define IEC958_AES1_PRO_USERBITS_NOTID	(0<<4)	/**< user bits not indicated */
+#define IEC958_AES1_PRO_USERBITS_192	(8<<4)	/**< 192-bit structure */
+#define IEC958_AES1_PRO_USERBITS_UDEF	(12<<4)	/**< user defined application */
+#define IEC958_AES1_CON_CATEGORY	0x7f	/**< consumer category */
+#define IEC958_AES1_CON_GENERAL		0x00	/**< general category */
+#define IEC958_AES1_CON_LASEROPT_MASK	0x07	/**< Laser-optical mask */
+#define IEC958_AES1_CON_LASEROPT_ID	0x01	/**< Laser-optical ID */
+#define IEC958_AES1_CON_IEC908_CD	(IEC958_AES1_CON_LASEROPT_ID|0x00)	/**< IEC958 CD compatible device */
+#define IEC958_AES1_CON_NON_IEC908_CD	(IEC958_AES1_CON_LASEROPT_ID|0x08)	/**< non-IEC958 CD compatible device */
+#define IEC958_AES1_CON_MINI_DISC	(IEC958_AES1_CON_LASEROPT_ID|0x48)	/**< Mini-Disc device */
+#define IEC958_AES1_CON_DVD		(IEC958_AES1_CON_LASEROPT_ID|0x18)	/**< DVD device */
+#define IEC958_AES1_CON_LASTEROPT_OTHER	(IEC958_AES1_CON_LASEROPT_ID|0x78)	/**< Other laser-optical product */
+#define IEC958_AES1_CON_DIGDIGCONV_MASK 0x07	/**< digital<->digital converter mask */
+#define IEC958_AES1_CON_DIGDIGCONV_ID	0x02	/**< digital<->digital converter id */
+#define IEC958_AES1_CON_PCM_CODER	(IEC958_AES1_CON_DIGDIGCONV_ID|0x00)	/**< PCM coder */
+#define IEC958_AES1_CON_MIXER		(IEC958_AES1_CON_DIGDIGCONV_ID|0x10)	/**< Digital signal mixer */
+#define IEC958_AES1_CON_RATE_CONVERTER	(IEC958_AES1_CON_DIGDIGCONV_ID|0x18)	/**< Rate converter */
+#define IEC958_AES1_CON_SAMPLER		(IEC958_AES1_CON_DIGDIGCONV_ID|0x20)	/**< PCM sampler */
+#define IEC958_AES1_CON_DSP		(IEC958_AES1_CON_DIGDIGCONV_ID|0x28)	/**< Digital sound processor */
+#define IEC958_AES1_CON_DIGDIGCONV_OTHER (IEC958_AES1_CON_DIGDIGCONV_ID|0x78)	/**< Other digital<->digital product */
+#define IEC958_AES1_CON_MAGNETIC_MASK	0x07	/**< Magnetic device mask */
+#define IEC958_AES1_CON_MAGNETIC_ID	0x03	/**< Magnetic device ID */
+#define IEC958_AES1_CON_DAT		(IEC958_AES1_CON_MAGNETIC_ID|0x00)	/**< Digital Audio Tape */
+#define IEC958_AES1_CON_VCR		(IEC958_AES1_CON_MAGNETIC_ID|0x08)	/**< Video recorder */
+#define IEC958_AES1_CON_DCC		(IEC958_AES1_CON_MAGNETIC_ID|0x40)	/**< Digital compact cassette */
+#define IEC958_AES1_CON_MAGNETIC_DISC	(IEC958_AES1_CON_MAGNETIC_ID|0x18)	/**< Magnetic disc digital audio device */
+#define IEC958_AES1_CON_MAGNETIC_OTHER	(IEC958_AES1_CON_MAGNETIC_ID|0x78)	/**< Other magnetic device */
+#define IEC958_AES1_CON_BROADCAST1_MASK 0x07	/**< Broadcast mask */
+#define IEC958_AES1_CON_BROADCAST1_ID	0x04	/**< Broadcast ID */
+#define IEC958_AES1_CON_DAB_JAPAN	(IEC958_AES1_CON_BROADCAST1_ID|0x00)	/**< Digital audio broadcast (Japan) */
+#define IEC958_AES1_CON_DAB_EUROPE	(IEC958_AES1_CON_BROADCAST1_ID|0x08)	/**< Digital audio broadcast (Europe) */
+#define IEC958_AES1_CON_DAB_USA		(IEC958_AES1_CON_BROADCAST1_ID|0x60)	/**< Digital audio broadcast (USA) */
+#define IEC958_AES1_CON_SOFTWARE	(IEC958_AES1_CON_BROADCAST1_ID|0x40)	/**< Electronic software delivery */
+#define IEC958_AES1_CON_IEC62105	(IEC958_AES1_CON_BROADCAST1_ID|0x20)	/**< Used by another standard (IEC 62105) */
+#define IEC958_AES1_CON_BROADCAST1_OTHER (IEC958_AES1_CON_BROADCAST1_ID|0x78)	/**< Other broadcast product */
+#define IEC958_AES1_CON_BROADCAST2_MASK 0x0f	/**< Broadcast alternative mask */
+#define IEC958_AES1_CON_BROADCAST2_ID	0x0e	/**< Broadcast alternative ID */
+#define IEC958_AES1_CON_MUSICAL_MASK	0x07	/**< Musical device mask */
+#define IEC958_AES1_CON_MUSICAL_ID	0x05	/**< Musical device ID */
+#define IEC958_AES1_CON_SYNTHESIZER	(IEC958_AES1_CON_MUSICAL_ID|0x00)	/**< Synthesizer */
+#define IEC958_AES1_CON_MICROPHONE	(IEC958_AES1_CON_MUSICAL_ID|0x08)	/**< Microphone */
+#define IEC958_AES1_CON_MUSICAL_OTHER	(IEC958_AES1_CON_MUSICAL_ID|0x78)	/**< Other musical device */
+#define IEC958_AES1_CON_ADC_MASK	0x1f	/**< ADC Mask */
+#define IEC958_AES1_CON_ADC_ID		0x06	/**< ADC ID */
+#define IEC958_AES1_CON_ADC		(IEC958_AES1_CON_ADC_ID|0x00)	/**< ADC without copyright information */
+#define IEC958_AES1_CON_ADC_OTHER	(IEC958_AES1_CON_ADC_ID|0x60)	/**< Other ADC product (with no copyright information) */
+#define IEC958_AES1_CON_ADC_COPYRIGHT_MASK 0x1f	/**< ADC Copyright mask */
+#define IEC958_AES1_CON_ADC_COPYRIGHT_ID 0x16	/**< ADC Copyright ID */
+#define IEC958_AES1_CON_ADC_COPYRIGHT	(IEC958_AES1_CON_ADC_COPYRIGHT_ID|0x00)	/**< ADC with copyright information */
+#define IEC958_AES1_CON_ADC_COPYRIGHT_OTHER (IEC958_AES1_CON_ADC_COPYRIGHT_ID|0x60)	/**< Other ADC with copyright information product */
+#define IEC958_AES1_CON_SOLIDMEM_MASK	0x0f	/**< Solid memory based products mask */
+#define IEC958_AES1_CON_SOLIDMEM_ID	0x08	/**< Solid memory based products ID */
+#define IEC958_AES1_CON_SOLIDMEM_DIGITAL_RECORDER_PLAYER (IEC958_AES1_CON_SOLIDMEM_ID|0x00)	/**< Digital audio recorder and player using solid state memory */
+#define IEC958_AES1_CON_SOLIDMEM_OTHER	(IEC958_AES1_CON_SOLIDMEM_ID|0x70)	/**< Other solid state memory based product */
+#define IEC958_AES1_CON_EXPERIMENTAL	0x40	/**< experimental category */
+#define IEC958_AES1_CON_ORIGINAL	(1<<7)	/**< this bits depends on the category code */
+#define IEC958_AES2_PRO_SBITS		(7<<0)	/**< mask - sample bits */
+#define IEC958_AES2_PRO_SBITS_20	(2<<0)	/**< 20-bit - coordination */
+#define IEC958_AES2_PRO_SBITS_24	(4<<0)	/**< 24-bit - main audio */
+#define IEC958_AES2_PRO_SBITS_UDEF	(6<<0)	/**< user defined application */
+#define IEC958_AES2_PRO_WORDLEN		(7<<3)	/**< mask - source word length */
+#define IEC958_AES2_PRO_WORDLEN_NOTID	(0<<3)	/**< source word length not indicated */
+#define IEC958_AES2_PRO_WORDLEN_22_18	(2<<3)	/**< 22-bit or 18-bit */
+#define IEC958_AES2_PRO_WORDLEN_23_19	(4<<3)	/**< 23-bit or 19-bit */
+#define IEC958_AES2_PRO_WORDLEN_24_20	(5<<3)	/**< 24-bit or 20-bit */
+#define IEC958_AES2_PRO_WORDLEN_20_16	(6<<3)	/**< 20-bit or 16-bit */
+#define IEC958_AES2_CON_SOURCE		(15<<0)	/**< mask - source number */
+#define IEC958_AES2_CON_SOURCE_UNSPEC	(0<<0)	/**< source number unspecified */
+#define IEC958_AES2_CON_CHANNEL		(15<<4)	/**< mask - channel number */
+#define IEC958_AES2_CON_CHANNEL_UNSPEC	(0<<4)	/**< channel number unspecified */
+#define IEC958_AES3_CON_FS		(15<<0)	/**< mask - sample frequency */
+#define IEC958_AES3_CON_FS_44100	(0<<0)	/**< 44.1kHz */
+#define IEC958_AES3_CON_FS_NOTID	(1<<0)	/**< sample frequency non indicated */
+#define IEC958_AES3_CON_FS_48000	(2<<0)	/**< 48kHz */
+#define IEC958_AES3_CON_FS_32000	(3<<0)	/**< 32kHz */
+#define IEC958_AES3_CON_FS_22050	(4<<0)	/**< 22.05kHz */
+#define IEC958_AES3_CON_FS_24000	(6<<0)	/**< 24kHz */
+#define IEC958_AES3_CON_FS_88200	(8<<0)	/**< 88.2kHz */
+#define IEC958_AES3_CON_FS_768000	(9<<0)	/**< 768kHz */
+#define IEC958_AES3_CON_FS_96000	(10<<0)	/**< 96kHz */
+#define IEC958_AES3_CON_FS_176400	(12<<0)	/**< 176.4kHz */
+#define IEC958_AES3_CON_FS_192000	(14<<0)	/**< 192kHz */
+#define IEC958_AES3_CON_CLOCK		(3<<4)	/**< mask - clock accuracy */
+#define IEC958_AES3_CON_CLOCK_1000PPM	(0<<4)	/**< 1000 ppm */
+#define IEC958_AES3_CON_CLOCK_50PPM	(1<<4)	/**< 50 ppm */
+#define IEC958_AES3_CON_CLOCK_VARIABLE	(2<<4)	/**< variable pitch */
+#define IEC958_AES4_CON_MAX_WORDLEN_24	(1<<0)	/**< 0 = 20-bit, 1 = 24-bit */
+#define IEC958_AES4_CON_WORDLEN		(7<<1)	/**< mask - sample word length */
+#define IEC958_AES4_CON_WORDLEN_NOTID	(0<<1)	/**< not indicated */
+#define IEC958_AES4_CON_WORDLEN_20_16	(1<<1)	/**< 20-bit or 16-bit */
+#define IEC958_AES4_CON_WORDLEN_22_18	(2<<1)	/**< 22-bit or 18-bit */
+#define IEC958_AES4_CON_WORDLEN_23_19	(4<<1)	/**< 23-bit or 19-bit */
+#define IEC958_AES4_CON_WORDLEN_24_20	(5<<1)	/**< 24-bit or 20-bit */
+#define IEC958_AES4_CON_WORDLEN_21_17	(6<<1)	/**< 21-bit or 17-bit */
+#define IEC958_AES4_CON_ORIGFS		(15<<4)	/**< mask - original sample frequency */
+#define IEC958_AES4_CON_ORIGFS_NOTID	(0<<4)	/**< original sample frequency not indicated */
+#define IEC958_AES4_CON_ORIGFS_192000	(1<<4)	/**< 192kHz */
+#define IEC958_AES4_CON_ORIGFS_12000	(2<<4)	/**< 12kHz */
+#define IEC958_AES4_CON_ORIGFS_176400	(3<<4)	/**< 176.4kHz */
+#define IEC958_AES4_CON_ORIGFS_96000	(5<<4)	/**< 96kHz */
+#define IEC958_AES4_CON_ORIGFS_8000	(6<<4)	/**< 8kHz */
+#define IEC958_AES4_CON_ORIGFS_88200	(7<<4)	/**< 88.2kHz */
+#define IEC958_AES4_CON_ORIGFS_16000	(8<<4)	/**< 16kHz */
+#define IEC958_AES4_CON_ORIGFS_24000	(9<<4)	/**< 24kHz */
+#define IEC958_AES4_CON_ORIGFS_11025	(10<<4)	/**< 11.025kHz */
+#define IEC958_AES4_CON_ORIGFS_22050	(11<<4)	/**< 22.05kHz */
+#define IEC958_AES4_CON_ORIGFS_32000	(12<<4)	/**< 32kHz */
+#define IEC958_AES4_CON_ORIGFS_48000	(13<<4)	/**< 48kHz */
+#define IEC958_AES4_CON_ORIGFS_44100	(15<<4)	/**< 44.1kHz */
+#define IEC958_AES5_CON_CGMSA		(3<<0)	/**< mask - CGMS-A */
+#define IEC958_AES5_CON_CGMSA_COPYFREELY (0<<0)	/**< copying is permitted without restriction */
+#define IEC958_AES5_CON_CGMSA_COPYONCE	(1<<0)	/**< one generation of copies may be made */
+#define IEC958_AES5_CON_CGMSA_COPYNOMORE (2<<0)	/**< condition not be used */
+#define IEC958_AES5_CON_CGMSA_COPYNEVER	(3<<0)	/**< no copying is permitted */
+
+/** \} */
+
+/**
+ * \defgroup MIDI_Interface Constants for MIDI v1.0
+ * Constants for MIDI v1.0.
+ * \{
+ */
+
+#define MIDI_CHANNELS			16	/**< Number of channels per port/cable. */
+#define MIDI_GM_DRUM_CHANNEL		(10-1)	/**< Channel number for GM drums. */
+
+/**
+ * \defgroup MIDI_Commands MIDI Commands
+ * MIDI command codes.
+ * \{
+ */
+
+#define MIDI_CMD_NOTE_OFF		0x80	/**< note off */
+#define MIDI_CMD_NOTE_ON		0x90	/**< note on */
+#define MIDI_CMD_NOTE_PRESSURE		0xa0	/**< key pressure */
+#define MIDI_CMD_CONTROL		0xb0	/**< control change */
+#define MIDI_CMD_PGM_CHANGE		0xc0	/**< program change */
+#define MIDI_CMD_CHANNEL_PRESSURE	0xd0	/**< channel pressure */
+#define MIDI_CMD_BENDER			0xe0	/**< pitch bender */
+
+#define MIDI_CMD_COMMON_SYSEX		0xf0	/**< sysex (system exclusive) begin */
+#define MIDI_CMD_COMMON_MTC_QUARTER	0xf1	/**< MTC quarter frame */
+#define MIDI_CMD_COMMON_SONG_POS	0xf2	/**< song position */
+#define MIDI_CMD_COMMON_SONG_SELECT	0xf3	/**< song select */
+#define MIDI_CMD_COMMON_TUNE_REQUEST	0xf6	/**< tune request */
+#define MIDI_CMD_COMMON_SYSEX_END	0xf7	/**< end of sysex */
+#define MIDI_CMD_COMMON_CLOCK		0xf8	/**< clock */
+#define MIDI_CMD_COMMON_START		0xfa	/**< start */
+#define MIDI_CMD_COMMON_CONTINUE	0xfb	/**< continue */
+#define MIDI_CMD_COMMON_STOP		0xfc	/**< stop */
+#define MIDI_CMD_COMMON_SENSING		0xfe	/**< active sensing */
+#define MIDI_CMD_COMMON_RESET		0xff	/**< reset */
+
+/** \} */
+
+/**
+ * \defgroup MIDI_Controllers MIDI Controllers
+ * MIDI controller numbers.
+ * \{
+ */
+
+#define MIDI_CTL_MSB_BANK		0x00	/**< Bank selection */
+#define MIDI_CTL_MSB_MODWHEEL         	0x01	/**< Modulation */
+#define MIDI_CTL_MSB_BREATH           	0x02	/**< Breath */
+#define MIDI_CTL_MSB_FOOT             	0x04	/**< Foot */
+#define MIDI_CTL_MSB_PORTAMENTO_TIME 	0x05	/**< Portamento time */
+#define MIDI_CTL_MSB_DATA_ENTRY		0x06	/**< Data entry */
+#define MIDI_CTL_MSB_MAIN_VOLUME      	0x07	/**< Main volume */
+#define MIDI_CTL_MSB_BALANCE          	0x08	/**< Balance */
+#define MIDI_CTL_MSB_PAN              	0x0a	/**< Panpot */
+#define MIDI_CTL_MSB_EXPRESSION       	0x0b	/**< Expression */
+#define MIDI_CTL_MSB_EFFECT1		0x0c	/**< Effect1 */
+#define MIDI_CTL_MSB_EFFECT2		0x0d	/**< Effect2 */
+#define MIDI_CTL_MSB_GENERAL_PURPOSE1 	0x10	/**< General purpose 1 */
+#define MIDI_CTL_MSB_GENERAL_PURPOSE2 	0x11	/**< General purpose 2 */
+#define MIDI_CTL_MSB_GENERAL_PURPOSE3 	0x12	/**< General purpose 3 */
+#define MIDI_CTL_MSB_GENERAL_PURPOSE4 	0x13	/**< General purpose 4 */
+#define MIDI_CTL_LSB_BANK		0x20	/**< Bank selection */
+#define MIDI_CTL_LSB_MODWHEEL        	0x21	/**< Modulation */
+#define MIDI_CTL_LSB_BREATH           	0x22	/**< Breath */
+#define MIDI_CTL_LSB_FOOT             	0x24	/**< Foot */
+#define MIDI_CTL_LSB_PORTAMENTO_TIME 	0x25	/**< Portamento time */
+#define MIDI_CTL_LSB_DATA_ENTRY		0x26	/**< Data entry */
+#define MIDI_CTL_LSB_MAIN_VOLUME      	0x27	/**< Main volume */
+#define MIDI_CTL_LSB_BALANCE          	0x28	/**< Balance */
+#define MIDI_CTL_LSB_PAN              	0x2a	/**< Panpot */
+#define MIDI_CTL_LSB_EXPRESSION       	0x2b	/**< Expression */
+#define MIDI_CTL_LSB_EFFECT1		0x2c	/**< Effect1 */
+#define MIDI_CTL_LSB_EFFECT2		0x2d	/**< Effect2 */
+#define MIDI_CTL_LSB_GENERAL_PURPOSE1 	0x30	/**< General purpose 1 */
+#define MIDI_CTL_LSB_GENERAL_PURPOSE2 	0x31	/**< General purpose 2 */
+#define MIDI_CTL_LSB_GENERAL_PURPOSE3 	0x32	/**< General purpose 3 */
+#define MIDI_CTL_LSB_GENERAL_PURPOSE4 	0x33	/**< General purpose 4 */
+#define MIDI_CTL_SUSTAIN              	0x40	/**< Sustain pedal */
+#define MIDI_CTL_PORTAMENTO           	0x41	/**< Portamento */
+#define MIDI_CTL_SOSTENUTO            	0x42	/**< Sostenuto */
+#define MIDI_CTL_SUSTENUTO            	0x42	/**< Sostenuto (a typo in the older version) */
+#define MIDI_CTL_SOFT_PEDAL           	0x43	/**< Soft pedal */
+#define MIDI_CTL_LEGATO_FOOTSWITCH	0x44	/**< Legato foot switch */
+#define MIDI_CTL_HOLD2                	0x45	/**< Hold2 */
+#define MIDI_CTL_SC1_SOUND_VARIATION	0x46	/**< SC1 Sound Variation */
+#define MIDI_CTL_SC2_TIMBRE		0x47	/**< SC2 Timbre */
+#define MIDI_CTL_SC3_RELEASE_TIME	0x48	/**< SC3 Release Time */
+#define MIDI_CTL_SC4_ATTACK_TIME	0x49	/**< SC4 Attack Time */
+#define MIDI_CTL_SC5_BRIGHTNESS		0x4a	/**< SC5 Brightness */
+#define MIDI_CTL_SC6			0x4b	/**< SC6 */
+#define MIDI_CTL_SC7			0x4c	/**< SC7 */
+#define MIDI_CTL_SC8			0x4d	/**< SC8 */
+#define MIDI_CTL_SC9			0x4e	/**< SC9 */
+#define MIDI_CTL_SC10			0x4f	/**< SC10 */
+#define MIDI_CTL_GENERAL_PURPOSE5     	0x50	/**< General purpose 5 */
+#define MIDI_CTL_GENERAL_PURPOSE6     	0x51	/**< General purpose 6 */
+#define MIDI_CTL_GENERAL_PURPOSE7     	0x52	/**< General purpose 7 */
+#define MIDI_CTL_GENERAL_PURPOSE8     	0x53	/**< General purpose 8 */
+#define MIDI_CTL_PORTAMENTO_CONTROL	0x54	/**< Portamento control */
+#define MIDI_CTL_E1_REVERB_DEPTH	0x5b	/**< E1 Reverb Depth */
+#define MIDI_CTL_E2_TREMOLO_DEPTH	0x5c	/**< E2 Tremolo Depth */
+#define MIDI_CTL_E3_CHORUS_DEPTH	0x5d	/**< E3 Chorus Depth */
+#define MIDI_CTL_E4_DETUNE_DEPTH	0x5e	/**< E4 Detune Depth */
+#define MIDI_CTL_E5_PHASER_DEPTH	0x5f	/**< E5 Phaser Depth */
+#define MIDI_CTL_DATA_INCREMENT       	0x60	/**< Data Increment */
+#define MIDI_CTL_DATA_DECREMENT       	0x61	/**< Data Decrement */
+#define MIDI_CTL_NONREG_PARM_NUM_LSB  	0x62	/**< Non-registered parameter number */
+#define MIDI_CTL_NONREG_PARM_NUM_MSB  	0x63	/**< Non-registered parameter number */
+#define MIDI_CTL_REGIST_PARM_NUM_LSB  	0x64	/**< Registered parameter number */
+#define MIDI_CTL_REGIST_PARM_NUM_MSB	0x65	/**< Registered parameter number */
+#define MIDI_CTL_ALL_SOUNDS_OFF		0x78	/**< All sounds off */
+#define MIDI_CTL_RESET_CONTROLLERS	0x79	/**< Reset Controllers */
+#define MIDI_CTL_LOCAL_CONTROL_SWITCH	0x7a	/**< Local control switch */
+#define MIDI_CTL_ALL_NOTES_OFF		0x7b	/**< All notes off */
+#define MIDI_CTL_OMNI_OFF		0x7c	/**< Omni off */
+#define MIDI_CTL_OMNI_ON		0x7d	/**< Omni on */
+#define MIDI_CTL_MONO1			0x7e	/**< Mono1 */
+#define MIDI_CTL_MONO2			0x7f	/**< Mono2 */
+
+/** \} */
+
+/** \} */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __ALSA_ASOUNDEF_H */
diff --git a/include/asoundlib-head.h b/include/asoundlib-head.h
new file mode 100644
index 0000000..20c8a68
--- /dev/null
+++ b/include/asoundlib-head.h
@@ -0,0 +1,48 @@
+/**
+ * \file include/asoundlib.h
+ * \brief Application interface library for the ALSA driver
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \author Takashi Iwai <tiwai@suse.de>
+ * \date 1998-2001
+ *
+ * Application interface library for the ALSA driver
+ */
+/*
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#ifndef __ASOUNDLIB_H
+#define __ASOUNDLIB_H
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <endian.h>
+#include <sys/poll.h>
+#include <errno.h>
+#include <stdarg.h>
+
+#include <alsa/asoundef.h>
+#include <alsa/version.h>
+#include <alsa/global.h>
+#include <alsa/input.h>
+#include <alsa/output.h>
+#include <alsa/error.h>
+#include <alsa/conf.h>
diff --git a/include/asoundlib-tail.h b/include/asoundlib-tail.h
new file mode 100644
index 0000000..e20f5ad
--- /dev/null
+++ b/include/asoundlib-tail.h
@@ -0,0 +1,2 @@
+
+#endif /* __ASOUNDLIB_H */
diff --git a/include/conf.h b/include/conf.h
new file mode 100644
index 0000000..ff270f6
--- /dev/null
+++ b/include/conf.h
@@ -0,0 +1,207 @@
+/**
+ * \file include/conf.h
+ * \brief Application interface library for the ALSA driver
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \author Takashi Iwai <tiwai@suse.de>
+ * \date 1998-2001
+ *
+ * Application interface library for the ALSA driver
+ */
+/*
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#ifndef __ALSA_CONF_H
+#define __ALSA_CONF_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ *  \defgroup Config Configuration Interface
+ *  The configuration functions and types allow you to read, enumerate,
+ *  modify and write the contents of ALSA configuration files.
+ *  \{
+ */
+
+/** \brief \c dlsym version for the config evaluate callback. */
+#define SND_CONFIG_DLSYM_VERSION_EVALUATE	_dlsym_config_evaluate_001
+/** \brief \c dlsym version for the config hook callback. */
+#define SND_CONFIG_DLSYM_VERSION_HOOK		_dlsym_config_hook_001
+
+/** \brief Configuration node type. */
+typedef enum _snd_config_type {
+	/** Integer number. */
+        SND_CONFIG_TYPE_INTEGER,
+	/** 64-bit integer number. */
+        SND_CONFIG_TYPE_INTEGER64,
+	/** Real number. */
+        SND_CONFIG_TYPE_REAL,
+	/** Character string. */
+        SND_CONFIG_TYPE_STRING,
+        /** Pointer (runtime only, cannot be saved). */
+        SND_CONFIG_TYPE_POINTER,
+	/** Compound node. */
+	SND_CONFIG_TYPE_COMPOUND = 1024
+} snd_config_type_t;
+
+/**
+ * \brief Internal structure for a configuration node object.
+ *
+ * The ALSA library uses a pointer to this structure as a handle to a
+ * configuration node. Applications don't access its contents directly.
+ */
+typedef struct _snd_config snd_config_t;
+/**
+ * \brief Type for a configuration compound iterator.
+ *
+ * The ALSA library uses this pointer type as a handle to a configuration
+ * compound iterator. Applications don't directly access the contents of
+ * the structure pointed to by this type.
+ */
+typedef struct _snd_config_iterator *snd_config_iterator_t;
+/**
+ * \brief Internal structure for a configuration private update object.
+ *
+ * The ALSA library uses this structure to save private update information.
+ */
+typedef struct _snd_config_update snd_config_update_t;
+
+extern snd_config_t *snd_config;
+
+int snd_config_top(snd_config_t **config);
+
+int snd_config_load(snd_config_t *config, snd_input_t *in);
+int snd_config_load_override(snd_config_t *config, snd_input_t *in);
+int snd_config_save(snd_config_t *config, snd_output_t *out);
+int snd_config_update(void);
+int snd_config_update_r(snd_config_t **top, snd_config_update_t **update, const char *path);
+int snd_config_update_free(snd_config_update_t *update);
+int snd_config_update_free_global(void);
+
+int snd_config_search(snd_config_t *config, const char *key,
+		      snd_config_t **result);
+int snd_config_searchv(snd_config_t *config, 
+		       snd_config_t **result, ...);
+int snd_config_search_definition(snd_config_t *config,
+				 const char *base, const char *key,
+				 snd_config_t **result);
+
+int snd_config_expand(snd_config_t *config, snd_config_t *root,
+		      const char *args, snd_config_t *private_data,
+		      snd_config_t **result);
+int snd_config_evaluate(snd_config_t *config, snd_config_t *root,
+			snd_config_t *private_data, snd_config_t **result);
+
+int snd_config_add(snd_config_t *config, snd_config_t *leaf);
+int snd_config_delete(snd_config_t *config);
+int snd_config_delete_compound_members(const snd_config_t *config);
+int snd_config_copy(snd_config_t **dst, snd_config_t *src);
+
+int snd_config_make(snd_config_t **config, const char *key,
+		    snd_config_type_t type);
+int snd_config_make_integer(snd_config_t **config, const char *key);
+int snd_config_make_integer64(snd_config_t **config, const char *key);
+int snd_config_make_real(snd_config_t **config, const char *key);
+int snd_config_make_string(snd_config_t **config, const char *key);
+int snd_config_make_pointer(snd_config_t **config, const char *key);
+int snd_config_make_compound(snd_config_t **config, const char *key, int join);
+
+int snd_config_imake_integer(snd_config_t **config, const char *key, const long value);
+int snd_config_imake_integer64(snd_config_t **config, const char *key, const long long value);
+int snd_config_imake_real(snd_config_t **config, const char *key, const double value);
+int snd_config_imake_string(snd_config_t **config, const char *key, const char *ascii);
+int snd_config_imake_pointer(snd_config_t **config, const char *key, const void *ptr);
+
+snd_config_type_t snd_config_get_type(const snd_config_t *config);
+
+int snd_config_set_id(snd_config_t *config, const char *id);
+int snd_config_set_integer(snd_config_t *config, long value);
+int snd_config_set_integer64(snd_config_t *config, long long value);
+int snd_config_set_real(snd_config_t *config, double value);
+int snd_config_set_string(snd_config_t *config, const char *value);
+int snd_config_set_ascii(snd_config_t *config, const char *ascii);
+int snd_config_set_pointer(snd_config_t *config, const void *ptr);
+int snd_config_get_id(const snd_config_t *config, const char **value);
+int snd_config_get_integer(const snd_config_t *config, long *value);
+int snd_config_get_integer64(const snd_config_t *config, long long *value);
+int snd_config_get_real(const snd_config_t *config, double *value);
+int snd_config_get_ireal(const snd_config_t *config, double *value);
+int snd_config_get_string(const snd_config_t *config, const char **value);
+int snd_config_get_ascii(const snd_config_t *config, char **value);
+int snd_config_get_pointer(const snd_config_t *config, const void **value);
+int snd_config_test_id(const snd_config_t *config, const char *id);
+
+snd_config_iterator_t snd_config_iterator_first(const snd_config_t *node);
+snd_config_iterator_t snd_config_iterator_next(const snd_config_iterator_t iterator);
+snd_config_iterator_t snd_config_iterator_end(const snd_config_t *node);
+snd_config_t *snd_config_iterator_entry(const snd_config_iterator_t iterator);
+
+/**
+ * \brief Helper macro to iterate over the children of a compound node.
+ * \param[in,out] pos Iterator variable for the current node.
+ * \param[in,out] next Temporary iterator variable for the next node.
+ * \param[in] node Handle to the compound configuration node to iterate over.
+ *
+ * Use this macro like a \c for statement, e.g.:
+ * \code
+ * snd_config_iterator_t pos, next;
+ * snd_config_for_each(pos, next, node) {
+ *     snd_config_t *entry = snd_config_iterator_entry(pos);
+ *     ...
+ * }
+ * \endcode
+ *
+ * This macro allows deleting or removing the current node.
+ */
+#define snd_config_for_each(pos, next, node) \
+	for (pos = snd_config_iterator_first(node), next = snd_config_iterator_next(pos); pos != snd_config_iterator_end(node); pos = next, next = snd_config_iterator_next(pos))
+
+/* Misc functions */
+
+int snd_config_get_bool_ascii(const char *ascii);
+int snd_config_get_bool(const snd_config_t *conf);
+int snd_config_get_ctl_iface_ascii(const char *ascii);
+int snd_config_get_ctl_iface(const snd_config_t *conf);
+
+/* Names functions */
+
+/**
+ * Device-name list element
+ */
+typedef struct snd_devname snd_devname_t;
+
+/**
+ * Device-name list element (definition)
+ */
+struct snd_devname {
+	char *name;	/**< Device name string */
+	char *comment;	/**< Comments */
+	snd_devname_t *next;	/**< Next pointer */
+};
+
+int snd_names_list(const char *iface, snd_devname_t **list);
+void snd_names_list_free(snd_devname_t *list);
+
+/** \} */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __ALSA_CONF_H */
diff --git a/include/control.h b/include/control.h
new file mode 100644
index 0000000..488629d
--- /dev/null
+++ b/include/control.h
@@ -0,0 +1,590 @@
+/**
+ * \file include/control.h
+ * \brief Application interface library for the ALSA driver
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \author Takashi Iwai <tiwai@suse.de>
+ * \date 1998-2001
+ *
+ * Application interface library for the ALSA driver
+ */
+/*
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#ifndef __ALSA_CONTROL_H
+#define __ALSA_CONTROL_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ *  \defgroup Control Control Interface
+ *  The control interface.
+ *  See \ref control page for more details.
+ *  \{
+ */
+
+/** dlsym version for interface entry callback */
+#define SND_CONTROL_DLSYM_VERSION	_dlsym_control_001
+
+/** IEC958 structure */
+typedef struct snd_aes_iec958 {
+	unsigned char status[24];	/**< AES/IEC958 channel status bits */
+	unsigned char subcode[147];	/**< AES/IEC958 subcode bits */
+	unsigned char pad;		/**< nothing */
+	unsigned char dig_subframe[4];	/**< AES/IEC958 subframe bits */
+} snd_aes_iec958_t;
+
+/** CTL card info container */
+typedef struct _snd_ctl_card_info snd_ctl_card_info_t;
+
+/** CTL element identifier container */
+typedef struct _snd_ctl_elem_id snd_ctl_elem_id_t;
+
+/** CTL element identifier list container */
+typedef struct _snd_ctl_elem_list snd_ctl_elem_list_t;
+
+/** CTL element info container */
+typedef struct _snd_ctl_elem_info snd_ctl_elem_info_t;
+
+/** CTL element value container */
+typedef struct _snd_ctl_elem_value snd_ctl_elem_value_t;
+
+/** CTL event container */
+typedef struct _snd_ctl_event snd_ctl_event_t;
+
+/** CTL element type */
+typedef enum _snd_ctl_elem_type {
+	/** Invalid type */
+	SND_CTL_ELEM_TYPE_NONE = 0,
+	/** Boolean contents */
+	SND_CTL_ELEM_TYPE_BOOLEAN,
+	/** Integer contents */
+	SND_CTL_ELEM_TYPE_INTEGER,
+	/** Enumerated contents */
+	SND_CTL_ELEM_TYPE_ENUMERATED,
+	/** Bytes contents */
+	SND_CTL_ELEM_TYPE_BYTES,
+	/** IEC958 (S/PDIF) setting content */
+	SND_CTL_ELEM_TYPE_IEC958,
+	/** 64-bit integer contents */
+	SND_CTL_ELEM_TYPE_INTEGER64,
+	SND_CTL_ELEM_TYPE_LAST = SND_CTL_ELEM_TYPE_INTEGER64
+} snd_ctl_elem_type_t;
+
+/** CTL related interface */
+typedef enum _snd_ctl_elem_iface {
+	/** Card level */
+	SND_CTL_ELEM_IFACE_CARD = 0,
+	/** Hardware dependent device */
+	SND_CTL_ELEM_IFACE_HWDEP,
+	/** Mixer */
+	SND_CTL_ELEM_IFACE_MIXER,
+	/** PCM */
+	SND_CTL_ELEM_IFACE_PCM,
+	/** RawMidi */
+	SND_CTL_ELEM_IFACE_RAWMIDI,
+	/** Timer */
+	SND_CTL_ELEM_IFACE_TIMER,
+	/** Sequencer */
+	SND_CTL_ELEM_IFACE_SEQUENCER,
+	SND_CTL_ELEM_IFACE_LAST = SND_CTL_ELEM_IFACE_SEQUENCER
+} snd_ctl_elem_iface_t;
+
+/** Event class */
+typedef enum _snd_ctl_event_type {
+	/** Elements related event */
+	SND_CTL_EVENT_ELEM = 0,
+	SND_CTL_EVENT_LAST = SND_CTL_EVENT_ELEM
+}snd_ctl_event_type_t;
+
+/** Element has been removed (Warning: test this first and if set don't
+  * test the other masks) \hideinitializer */
+#define SND_CTL_EVENT_MASK_REMOVE 	(~0U)
+/** Element value has been changed \hideinitializer */
+#define SND_CTL_EVENT_MASK_VALUE	(1<<0)
+/** Element info has been changed \hideinitializer */
+#define SND_CTL_EVENT_MASK_INFO		(1<<1)
+/** Element has been added \hideinitializer */
+#define SND_CTL_EVENT_MASK_ADD		(1<<2)
+/** Element's TLV value has been changed \hideinitializer */
+#define SND_CTL_EVENT_MASK_TLV		(1<<3)
+
+/** CTL name helper */
+#define SND_CTL_NAME_NONE				""
+/** CTL name helper */
+#define SND_CTL_NAME_PLAYBACK				"Playback "
+/** CTL name helper */
+#define SND_CTL_NAME_CAPTURE				"Capture "
+
+/** CTL name helper */
+#define SND_CTL_NAME_IEC958_NONE			""
+/** CTL name helper */
+#define SND_CTL_NAME_IEC958_SWITCH			"Switch"
+/** CTL name helper */
+#define SND_CTL_NAME_IEC958_VOLUME			"Volume"
+/** CTL name helper */
+#define SND_CTL_NAME_IEC958_DEFAULT			"Default"
+/** CTL name helper */
+#define SND_CTL_NAME_IEC958_MASK			"Mask"
+/** CTL name helper */
+#define SND_CTL_NAME_IEC958_CON_MASK			"Con Mask"
+/** CTL name helper */
+#define SND_CTL_NAME_IEC958_PRO_MASK			"Pro Mask"
+/** CTL name helper */
+#define SND_CTL_NAME_IEC958_PCM_STREAM			"PCM Stream"
+/** Element name for IEC958 (S/PDIF) */
+#define SND_CTL_NAME_IEC958(expl,direction,what)	"IEC958 " expl SND_CTL_NAME_##direction SND_CTL_NAME_IEC958_##what
+
+/** Mask for the major Power State identifier */
+#define SND_CTL_POWER_MASK		0xff00
+/** ACPI/PCI Power State D0 */
+#define SND_CTL_POWER_D0          	0x0000
+/** ACPI/PCI Power State D1 */
+#define SND_CTL_POWER_D1     	     	0x0100
+/** ACPI/PCI Power State D2 */
+#define SND_CTL_POWER_D2 	        0x0200
+/** ACPI/PCI Power State D3 */
+#define SND_CTL_POWER_D3         	0x0300
+/** ACPI/PCI Power State D3hot */
+#define SND_CTL_POWER_D3hot		(SND_CTL_POWER_D3|0x0000)
+/** ACPI/PCI Power State D3cold */
+#define SND_CTL_POWER_D3cold	      	(SND_CTL_POWER_D3|0x0001)
+
+/** TLV type - Container */
+#define SND_CTL_TLVT_CONTAINER		0x0000
+/** TLV type - basic dB scale */
+#define SND_CTL_TLVT_DB_SCALE		0x0001
+/** TLV type - linear volume */
+#define SND_CTL_TLVT_DB_LINEAR		0x0002
+/** TLV type - dB range container */
+#define SND_CTL_TLVT_DB_RANGE		0x0003
+/** TLV type - dB scale specified by min/max values */
+#define SND_CTL_TLVT_DB_MINMAX		0x0004
+/** TLV type - dB scale specified by min/max values (with mute) */
+#define SND_CTL_TLVT_DB_MINMAX_MUTE	0x0005
+
+/** Mute state */
+#define SND_CTL_TLV_DB_GAIN_MUTE	-9999999
+
+/** CTL type */
+typedef enum _snd_ctl_type {
+	/** Kernel level CTL */
+	SND_CTL_TYPE_HW,
+	/** Shared memory client CTL */
+	SND_CTL_TYPE_SHM,
+	/** INET client CTL (not yet implemented) */
+	SND_CTL_TYPE_INET,
+	/** External control plugin */
+	SND_CTL_TYPE_EXT
+} snd_ctl_type_t;
+
+/** Non blocking mode (flag for open mode) \hideinitializer */
+#define SND_CTL_NONBLOCK		0x0001
+
+/** Async notification (flag for open mode) \hideinitializer */
+#define SND_CTL_ASYNC			0x0002
+
+/** Read only (flag for open mode) \hideinitializer */
+#define SND_CTL_READONLY		0x0004
+
+/** CTL handle */
+typedef struct _snd_ctl snd_ctl_t;
+
+/** Don't destroy the ctl handle when close */
+#define SND_SCTL_NOFREE			0x0001
+
+/** SCTL type */
+typedef struct _snd_sctl snd_sctl_t;
+
+int snd_card_load(int card);
+int snd_card_next(int *card);
+int snd_card_get_index(const char *name);
+int snd_card_get_name(int card, char **name);
+int snd_card_get_longname(int card, char **name);
+
+int snd_device_name_hint(int card, const char *iface, void ***hints);
+int snd_device_name_free_hint(void **hints);
+char *snd_device_name_get_hint(const void *hint, const char *id);
+
+int snd_ctl_open(snd_ctl_t **ctl, const char *name, int mode);
+int snd_ctl_open_lconf(snd_ctl_t **ctl, const char *name, int mode, snd_config_t *lconf);
+int snd_ctl_open_fallback(snd_ctl_t **ctl, snd_config_t *root, const char *name, const char *orig_name, int mode);
+int snd_ctl_close(snd_ctl_t *ctl);
+int snd_ctl_nonblock(snd_ctl_t *ctl, int nonblock);
+int snd_async_add_ctl_handler(snd_async_handler_t **handler, snd_ctl_t *ctl, 
+			      snd_async_callback_t callback, void *private_data);
+snd_ctl_t *snd_async_handler_get_ctl(snd_async_handler_t *handler);
+int snd_ctl_poll_descriptors_count(snd_ctl_t *ctl);
+int snd_ctl_poll_descriptors(snd_ctl_t *ctl, struct pollfd *pfds, unsigned int space);
+int snd_ctl_poll_descriptors_revents(snd_ctl_t *ctl, struct pollfd *pfds, unsigned int nfds, unsigned short *revents);
+int snd_ctl_subscribe_events(snd_ctl_t *ctl, int subscribe);
+int snd_ctl_card_info(snd_ctl_t *ctl, snd_ctl_card_info_t *info);
+int snd_ctl_elem_list(snd_ctl_t *ctl, snd_ctl_elem_list_t *list);
+int snd_ctl_elem_info(snd_ctl_t *ctl, snd_ctl_elem_info_t *info);
+int snd_ctl_elem_read(snd_ctl_t *ctl, snd_ctl_elem_value_t *value);
+int snd_ctl_elem_write(snd_ctl_t *ctl, snd_ctl_elem_value_t *value);
+int snd_ctl_elem_lock(snd_ctl_t *ctl, snd_ctl_elem_id_t *id);
+int snd_ctl_elem_unlock(snd_ctl_t *ctl, snd_ctl_elem_id_t *id);
+int snd_ctl_elem_tlv_read(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
+			  unsigned int *tlv, unsigned int tlv_size);
+int snd_ctl_elem_tlv_write(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
+			   const unsigned int *tlv);
+int snd_ctl_elem_tlv_command(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
+			     const unsigned int *tlv);
+#ifdef __ALSA_HWDEP_H
+int snd_ctl_hwdep_next_device(snd_ctl_t *ctl, int * device);
+int snd_ctl_hwdep_info(snd_ctl_t *ctl, snd_hwdep_info_t * info);
+#endif
+#ifdef __ALSA_PCM_H
+int snd_ctl_pcm_next_device(snd_ctl_t *ctl, int *device);
+int snd_ctl_pcm_info(snd_ctl_t *ctl, snd_pcm_info_t * info);
+int snd_ctl_pcm_prefer_subdevice(snd_ctl_t *ctl, int subdev);
+#endif
+#ifdef __ALSA_RAWMIDI_H
+int snd_ctl_rawmidi_next_device(snd_ctl_t *ctl, int * device);
+int snd_ctl_rawmidi_info(snd_ctl_t *ctl, snd_rawmidi_info_t * info);
+int snd_ctl_rawmidi_prefer_subdevice(snd_ctl_t *ctl, int subdev);
+#endif
+int snd_ctl_set_power_state(snd_ctl_t *ctl, unsigned int state);
+int snd_ctl_get_power_state(snd_ctl_t *ctl, unsigned int *state);
+
+int snd_ctl_read(snd_ctl_t *ctl, snd_ctl_event_t *event);
+int snd_ctl_wait(snd_ctl_t *ctl, int timeout);
+const char *snd_ctl_name(snd_ctl_t *ctl);
+snd_ctl_type_t snd_ctl_type(snd_ctl_t *ctl);
+
+const char *snd_ctl_elem_type_name(snd_ctl_elem_type_t type);
+const char *snd_ctl_elem_iface_name(snd_ctl_elem_iface_t iface);
+const char *snd_ctl_event_type_name(snd_ctl_event_type_t type);
+
+unsigned int snd_ctl_event_elem_get_mask(const snd_ctl_event_t *obj);
+unsigned int snd_ctl_event_elem_get_numid(const snd_ctl_event_t *obj);
+void snd_ctl_event_elem_get_id(const snd_ctl_event_t *obj, snd_ctl_elem_id_t *ptr);
+snd_ctl_elem_iface_t snd_ctl_event_elem_get_interface(const snd_ctl_event_t *obj);
+unsigned int snd_ctl_event_elem_get_device(const snd_ctl_event_t *obj);
+unsigned int snd_ctl_event_elem_get_subdevice(const snd_ctl_event_t *obj);
+const char *snd_ctl_event_elem_get_name(const snd_ctl_event_t *obj);
+unsigned int snd_ctl_event_elem_get_index(const snd_ctl_event_t *obj);
+
+int snd_ctl_elem_list_alloc_space(snd_ctl_elem_list_t *obj, unsigned int entries);
+void snd_ctl_elem_list_free_space(snd_ctl_elem_list_t *obj);
+
+char *snd_ctl_ascii_elem_id_get(snd_ctl_elem_id_t *id);
+int snd_ctl_ascii_elem_id_parse(snd_ctl_elem_id_t *dst, const char *str);
+int snd_ctl_ascii_value_parse(snd_ctl_t *handle,
+			      snd_ctl_elem_value_t *dst,
+			      snd_ctl_elem_info_t *info,
+			      const char *value);
+
+size_t snd_ctl_elem_id_sizeof(void);
+/** \hideinitializer
+ * \brief allocate an invalid #snd_ctl_elem_id_t using standard alloca
+ * \param ptr returned pointer
+ */
+#define snd_ctl_elem_id_alloca(ptr) __snd_alloca(ptr, snd_ctl_elem_id)
+int snd_ctl_elem_id_malloc(snd_ctl_elem_id_t **ptr);
+void snd_ctl_elem_id_free(snd_ctl_elem_id_t *obj);
+void snd_ctl_elem_id_clear(snd_ctl_elem_id_t *obj);
+void snd_ctl_elem_id_copy(snd_ctl_elem_id_t *dst, const snd_ctl_elem_id_t *src);
+unsigned int snd_ctl_elem_id_get_numid(const snd_ctl_elem_id_t *obj);
+snd_ctl_elem_iface_t snd_ctl_elem_id_get_interface(const snd_ctl_elem_id_t *obj);
+unsigned int snd_ctl_elem_id_get_device(const snd_ctl_elem_id_t *obj);
+unsigned int snd_ctl_elem_id_get_subdevice(const snd_ctl_elem_id_t *obj);
+const char *snd_ctl_elem_id_get_name(const snd_ctl_elem_id_t *obj);
+unsigned int snd_ctl_elem_id_get_index(const snd_ctl_elem_id_t *obj);
+void snd_ctl_elem_id_set_numid(snd_ctl_elem_id_t *obj, unsigned int val);
+void snd_ctl_elem_id_set_interface(snd_ctl_elem_id_t *obj, snd_ctl_elem_iface_t val);
+void snd_ctl_elem_id_set_device(snd_ctl_elem_id_t *obj, unsigned int val);
+void snd_ctl_elem_id_set_subdevice(snd_ctl_elem_id_t *obj, unsigned int val);
+void snd_ctl_elem_id_set_name(snd_ctl_elem_id_t *obj, const char *val);
+void snd_ctl_elem_id_set_index(snd_ctl_elem_id_t *obj, unsigned int val);
+
+size_t snd_ctl_card_info_sizeof(void);
+/** \hideinitializer
+ * \brief allocate an invalid #snd_ctl_card_info_t using standard alloca
+ * \param ptr returned pointer
+ */
+#define snd_ctl_card_info_alloca(ptr) __snd_alloca(ptr, snd_ctl_card_info)
+int snd_ctl_card_info_malloc(snd_ctl_card_info_t **ptr);
+void snd_ctl_card_info_free(snd_ctl_card_info_t *obj);
+void snd_ctl_card_info_clear(snd_ctl_card_info_t *obj);
+void snd_ctl_card_info_copy(snd_ctl_card_info_t *dst, const snd_ctl_card_info_t *src);
+int snd_ctl_card_info_get_card(const snd_ctl_card_info_t *obj);
+const char *snd_ctl_card_info_get_id(const snd_ctl_card_info_t *obj);
+const char *snd_ctl_card_info_get_driver(const snd_ctl_card_info_t *obj);
+const char *snd_ctl_card_info_get_name(const snd_ctl_card_info_t *obj);
+const char *snd_ctl_card_info_get_longname(const snd_ctl_card_info_t *obj);
+const char *snd_ctl_card_info_get_mixername(const snd_ctl_card_info_t *obj);
+const char *snd_ctl_card_info_get_components(const snd_ctl_card_info_t *obj);
+
+size_t snd_ctl_event_sizeof(void);
+/** \hideinitializer
+ * \brief allocate an invalid #snd_ctl_event_t using standard alloca
+ * \param ptr returned pointer
+ */
+#define snd_ctl_event_alloca(ptr) __snd_alloca(ptr, snd_ctl_event)
+int snd_ctl_event_malloc(snd_ctl_event_t **ptr);
+void snd_ctl_event_free(snd_ctl_event_t *obj);
+void snd_ctl_event_clear(snd_ctl_event_t *obj);
+void snd_ctl_event_copy(snd_ctl_event_t *dst, const snd_ctl_event_t *src);
+snd_ctl_event_type_t snd_ctl_event_get_type(const snd_ctl_event_t *obj);
+
+size_t snd_ctl_elem_list_sizeof(void);
+/** \hideinitializer
+ * \brief allocate an invalid #snd_ctl_elem_list_t using standard alloca
+ * \param ptr returned pointer
+ */
+#define snd_ctl_elem_list_alloca(ptr) __snd_alloca(ptr, snd_ctl_elem_list)
+int snd_ctl_elem_list_malloc(snd_ctl_elem_list_t **ptr);
+void snd_ctl_elem_list_free(snd_ctl_elem_list_t *obj);
+void snd_ctl_elem_list_clear(snd_ctl_elem_list_t *obj);
+void snd_ctl_elem_list_copy(snd_ctl_elem_list_t *dst, const snd_ctl_elem_list_t *src);
+void snd_ctl_elem_list_set_offset(snd_ctl_elem_list_t *obj, unsigned int val);
+unsigned int snd_ctl_elem_list_get_used(const snd_ctl_elem_list_t *obj);
+unsigned int snd_ctl_elem_list_get_count(const snd_ctl_elem_list_t *obj);
+void snd_ctl_elem_list_get_id(const snd_ctl_elem_list_t *obj, unsigned int idx, snd_ctl_elem_id_t *ptr);
+unsigned int snd_ctl_elem_list_get_numid(const snd_ctl_elem_list_t *obj, unsigned int idx);
+snd_ctl_elem_iface_t snd_ctl_elem_list_get_interface(const snd_ctl_elem_list_t *obj, unsigned int idx);
+unsigned int snd_ctl_elem_list_get_device(const snd_ctl_elem_list_t *obj, unsigned int idx);
+unsigned int snd_ctl_elem_list_get_subdevice(const snd_ctl_elem_list_t *obj, unsigned int idx);
+const char *snd_ctl_elem_list_get_name(const snd_ctl_elem_list_t *obj, unsigned int idx);
+unsigned int snd_ctl_elem_list_get_index(const snd_ctl_elem_list_t *obj, unsigned int idx);
+
+size_t snd_ctl_elem_info_sizeof(void);
+/** \hideinitializer
+ * \brief allocate an invalid #snd_ctl_elem_info_t using standard alloca
+ * \param ptr returned pointer
+ */
+#define snd_ctl_elem_info_alloca(ptr) __snd_alloca(ptr, snd_ctl_elem_info)
+int snd_ctl_elem_info_malloc(snd_ctl_elem_info_t **ptr);
+void snd_ctl_elem_info_free(snd_ctl_elem_info_t *obj);
+void snd_ctl_elem_info_clear(snd_ctl_elem_info_t *obj);
+void snd_ctl_elem_info_copy(snd_ctl_elem_info_t *dst, const snd_ctl_elem_info_t *src);
+snd_ctl_elem_type_t snd_ctl_elem_info_get_type(const snd_ctl_elem_info_t *obj);
+int snd_ctl_elem_info_is_readable(const snd_ctl_elem_info_t *obj);
+int snd_ctl_elem_info_is_writable(const snd_ctl_elem_info_t *obj);
+int snd_ctl_elem_info_is_volatile(const snd_ctl_elem_info_t *obj);
+int snd_ctl_elem_info_is_inactive(const snd_ctl_elem_info_t *obj);
+int snd_ctl_elem_info_is_locked(const snd_ctl_elem_info_t *obj);
+int snd_ctl_elem_info_is_tlv_readable(const snd_ctl_elem_info_t *obj);
+int snd_ctl_elem_info_is_tlv_writable(const snd_ctl_elem_info_t *obj);
+int snd_ctl_elem_info_is_tlv_commandable(const snd_ctl_elem_info_t *obj);
+int snd_ctl_elem_info_is_owner(const snd_ctl_elem_info_t *obj);
+int snd_ctl_elem_info_is_user(const snd_ctl_elem_info_t *obj);
+pid_t snd_ctl_elem_info_get_owner(const snd_ctl_elem_info_t *obj);
+unsigned int snd_ctl_elem_info_get_count(const snd_ctl_elem_info_t *obj);
+long snd_ctl_elem_info_get_min(const snd_ctl_elem_info_t *obj);
+long snd_ctl_elem_info_get_max(const snd_ctl_elem_info_t *obj);
+long snd_ctl_elem_info_get_step(const snd_ctl_elem_info_t *obj);
+long long snd_ctl_elem_info_get_min64(const snd_ctl_elem_info_t *obj);
+long long snd_ctl_elem_info_get_max64(const snd_ctl_elem_info_t *obj);
+long long snd_ctl_elem_info_get_step64(const snd_ctl_elem_info_t *obj);
+unsigned int snd_ctl_elem_info_get_items(const snd_ctl_elem_info_t *obj);
+void snd_ctl_elem_info_set_item(snd_ctl_elem_info_t *obj, unsigned int val);
+const char *snd_ctl_elem_info_get_item_name(const snd_ctl_elem_info_t *obj);
+int snd_ctl_elem_info_get_dimensions(const snd_ctl_elem_info_t *obj);
+int snd_ctl_elem_info_get_dimension(const snd_ctl_elem_info_t *obj, unsigned int idx);
+void snd_ctl_elem_info_get_id(const snd_ctl_elem_info_t *obj, snd_ctl_elem_id_t *ptr);
+unsigned int snd_ctl_elem_info_get_numid(const snd_ctl_elem_info_t *obj);
+snd_ctl_elem_iface_t snd_ctl_elem_info_get_interface(const snd_ctl_elem_info_t *obj);
+unsigned int snd_ctl_elem_info_get_device(const snd_ctl_elem_info_t *obj);
+unsigned int snd_ctl_elem_info_get_subdevice(const snd_ctl_elem_info_t *obj);
+const char *snd_ctl_elem_info_get_name(const snd_ctl_elem_info_t *obj);
+unsigned int snd_ctl_elem_info_get_index(const snd_ctl_elem_info_t *obj);
+void snd_ctl_elem_info_set_id(snd_ctl_elem_info_t *obj, const snd_ctl_elem_id_t *ptr);
+void snd_ctl_elem_info_set_numid(snd_ctl_elem_info_t *obj, unsigned int val);
+void snd_ctl_elem_info_set_interface(snd_ctl_elem_info_t *obj, snd_ctl_elem_iface_t val);
+void snd_ctl_elem_info_set_device(snd_ctl_elem_info_t *obj, unsigned int val);
+void snd_ctl_elem_info_set_subdevice(snd_ctl_elem_info_t *obj, unsigned int val);
+void snd_ctl_elem_info_set_name(snd_ctl_elem_info_t *obj, const char *val);
+void snd_ctl_elem_info_set_index(snd_ctl_elem_info_t *obj, unsigned int val);
+
+int snd_ctl_elem_add_integer(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id, unsigned int count, long imin, long imax, long istep);
+int snd_ctl_elem_add_integer64(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id, unsigned int count, long long imin, long long imax, long long istep);
+int snd_ctl_elem_add_boolean(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id, unsigned int count);
+int snd_ctl_elem_add_enumerated(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id, unsigned int count, unsigned int items, const char *const names[]);
+int snd_ctl_elem_add_iec958(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id);
+int snd_ctl_elem_remove(snd_ctl_t *ctl, snd_ctl_elem_id_t *id);
+
+size_t snd_ctl_elem_value_sizeof(void);
+/** \hideinitializer
+ * \brief allocate an invalid #snd_ctl_elem_value_t using standard alloca
+ * \param ptr returned pointer
+ */
+#define snd_ctl_elem_value_alloca(ptr) __snd_alloca(ptr, snd_ctl_elem_value)
+int snd_ctl_elem_value_malloc(snd_ctl_elem_value_t **ptr);
+void snd_ctl_elem_value_free(snd_ctl_elem_value_t *obj);
+void snd_ctl_elem_value_clear(snd_ctl_elem_value_t *obj);
+void snd_ctl_elem_value_copy(snd_ctl_elem_value_t *dst, const snd_ctl_elem_value_t *src);
+int snd_ctl_elem_value_compare(snd_ctl_elem_value_t *left, const snd_ctl_elem_value_t *right);
+void snd_ctl_elem_value_get_id(const snd_ctl_elem_value_t *obj, snd_ctl_elem_id_t *ptr);
+unsigned int snd_ctl_elem_value_get_numid(const snd_ctl_elem_value_t *obj);
+snd_ctl_elem_iface_t snd_ctl_elem_value_get_interface(const snd_ctl_elem_value_t *obj);
+unsigned int snd_ctl_elem_value_get_device(const snd_ctl_elem_value_t *obj);
+unsigned int snd_ctl_elem_value_get_subdevice(const snd_ctl_elem_value_t *obj);
+const char *snd_ctl_elem_value_get_name(const snd_ctl_elem_value_t *obj);
+unsigned int snd_ctl_elem_value_get_index(const snd_ctl_elem_value_t *obj);
+void snd_ctl_elem_value_set_id(snd_ctl_elem_value_t *obj, const snd_ctl_elem_id_t *ptr);
+void snd_ctl_elem_value_set_numid(snd_ctl_elem_value_t *obj, unsigned int val);
+void snd_ctl_elem_value_set_interface(snd_ctl_elem_value_t *obj, snd_ctl_elem_iface_t val);
+void snd_ctl_elem_value_set_device(snd_ctl_elem_value_t *obj, unsigned int val);
+void snd_ctl_elem_value_set_subdevice(snd_ctl_elem_value_t *obj, unsigned int val);
+void snd_ctl_elem_value_set_name(snd_ctl_elem_value_t *obj, const char *val);
+void snd_ctl_elem_value_set_index(snd_ctl_elem_value_t *obj, unsigned int val);
+int snd_ctl_elem_value_get_boolean(const snd_ctl_elem_value_t *obj, unsigned int idx);
+long snd_ctl_elem_value_get_integer(const snd_ctl_elem_value_t *obj, unsigned int idx);
+long long snd_ctl_elem_value_get_integer64(const snd_ctl_elem_value_t *obj, unsigned int idx);
+unsigned int snd_ctl_elem_value_get_enumerated(const snd_ctl_elem_value_t *obj, unsigned int idx);
+unsigned char snd_ctl_elem_value_get_byte(const snd_ctl_elem_value_t *obj, unsigned int idx);
+void snd_ctl_elem_value_set_boolean(snd_ctl_elem_value_t *obj, unsigned int idx, long val);
+void snd_ctl_elem_value_set_integer(snd_ctl_elem_value_t *obj, unsigned int idx, long val);
+void snd_ctl_elem_value_set_integer64(snd_ctl_elem_value_t *obj, unsigned int idx, long long val);
+void snd_ctl_elem_value_set_enumerated(snd_ctl_elem_value_t *obj, unsigned int idx, unsigned int val);
+void snd_ctl_elem_value_set_byte(snd_ctl_elem_value_t *obj, unsigned int idx, unsigned char val);
+void snd_ctl_elem_set_bytes(snd_ctl_elem_value_t *obj, void *data, size_t size);
+const void * snd_ctl_elem_value_get_bytes(const snd_ctl_elem_value_t *obj);
+void snd_ctl_elem_value_get_iec958(const snd_ctl_elem_value_t *obj, snd_aes_iec958_t *ptr);
+void snd_ctl_elem_value_set_iec958(snd_ctl_elem_value_t *obj, const snd_aes_iec958_t *ptr);
+
+int snd_tlv_parse_dB_info(unsigned int *tlv, unsigned int tlv_size,
+			  unsigned int **db_tlvp);
+int snd_tlv_get_dB_range(unsigned int *tlv, long rangemin, long rangemax,
+			 long *min, long *max);
+int snd_tlv_convert_to_dB(unsigned int *tlv, long rangemin, long rangemax,
+			  long volume, long *db_gain);
+int snd_tlv_convert_from_dB(unsigned int *tlv, long rangemin, long rangemax,
+			    long db_gain, long *value, int xdir);
+int snd_ctl_get_dB_range(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
+			 long *min, long *max);
+int snd_ctl_convert_to_dB(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
+			  long volume, long *db_gain);
+int snd_ctl_convert_from_dB(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
+			    long db_gain, long *value, int xdir);
+
+/**
+ *  \defgroup HControl High level Control Interface
+ *  \ingroup Control
+ *  The high level control interface.
+ *  See \ref hcontrol page for more details.
+ *  \{
+ */
+
+/** HCTL element handle */
+typedef struct _snd_hctl_elem snd_hctl_elem_t;
+
+/** HCTL handle */
+typedef struct _snd_hctl snd_hctl_t;
+
+/**
+ * \brief Compare function for sorting HCTL elements
+ * \param e1 First element
+ * \param e2 Second element
+ * \return -1 if e1 < e2, 0 if e1 == e2, 1 if e1 > e2
+ */
+typedef int (*snd_hctl_compare_t)(const snd_hctl_elem_t *e1,
+				  const snd_hctl_elem_t *e2);
+int snd_hctl_compare_fast(const snd_hctl_elem_t *c1,
+			  const snd_hctl_elem_t *c2);
+/** 
+ * \brief HCTL callback function
+ * \param hctl HCTL handle
+ * \param mask event mask
+ * \param elem related HCTL element (if any)
+ * \return 0 on success otherwise a negative error code
+ */
+typedef int (*snd_hctl_callback_t)(snd_hctl_t *hctl,
+				   unsigned int mask,
+				   snd_hctl_elem_t *elem);
+/** 
+ * \brief HCTL element callback function
+ * \param elem HCTL element
+ * \param mask event mask
+ * \return 0 on success otherwise a negative error code
+ */
+typedef int (*snd_hctl_elem_callback_t)(snd_hctl_elem_t *elem,
+					unsigned int mask);
+
+int snd_hctl_open(snd_hctl_t **hctl, const char *name, int mode);
+int snd_hctl_open_ctl(snd_hctl_t **hctlp, snd_ctl_t *ctl);
+int snd_hctl_close(snd_hctl_t *hctl);
+int snd_hctl_nonblock(snd_hctl_t *hctl, int nonblock);
+int snd_hctl_poll_descriptors_count(snd_hctl_t *hctl);
+int snd_hctl_poll_descriptors(snd_hctl_t *hctl, struct pollfd *pfds, unsigned int space);
+int snd_hctl_poll_descriptors_revents(snd_hctl_t *ctl, struct pollfd *pfds, unsigned int nfds, unsigned short *revents);
+unsigned int snd_hctl_get_count(snd_hctl_t *hctl);
+int snd_hctl_set_compare(snd_hctl_t *hctl, snd_hctl_compare_t hsort);
+snd_hctl_elem_t *snd_hctl_first_elem(snd_hctl_t *hctl);
+snd_hctl_elem_t *snd_hctl_last_elem(snd_hctl_t *hctl);
+snd_hctl_elem_t *snd_hctl_find_elem(snd_hctl_t *hctl, const snd_ctl_elem_id_t *id);
+void snd_hctl_set_callback(snd_hctl_t *hctl, snd_hctl_callback_t callback);
+void snd_hctl_set_callback_private(snd_hctl_t *hctl, void *data);
+void *snd_hctl_get_callback_private(snd_hctl_t *hctl);
+int snd_hctl_load(snd_hctl_t *hctl);
+int snd_hctl_free(snd_hctl_t *hctl);
+int snd_hctl_handle_events(snd_hctl_t *hctl);
+const char *snd_hctl_name(snd_hctl_t *hctl);
+int snd_hctl_wait(snd_hctl_t *hctl, int timeout);
+snd_ctl_t *snd_hctl_ctl(snd_hctl_t *hctl);
+
+snd_hctl_elem_t *snd_hctl_elem_next(snd_hctl_elem_t *elem);
+snd_hctl_elem_t *snd_hctl_elem_prev(snd_hctl_elem_t *elem);
+int snd_hctl_elem_info(snd_hctl_elem_t *elem, snd_ctl_elem_info_t * info);
+int snd_hctl_elem_read(snd_hctl_elem_t *elem, snd_ctl_elem_value_t * value);
+int snd_hctl_elem_write(snd_hctl_elem_t *elem, snd_ctl_elem_value_t * value);
+int snd_hctl_elem_tlv_read(snd_hctl_elem_t *elem, unsigned int *tlv, unsigned int tlv_size);
+int snd_hctl_elem_tlv_write(snd_hctl_elem_t *elem, const unsigned int *tlv);
+int snd_hctl_elem_tlv_command(snd_hctl_elem_t *elem, const unsigned int *tlv);
+
+snd_hctl_t *snd_hctl_elem_get_hctl(snd_hctl_elem_t *elem);
+
+void snd_hctl_elem_get_id(const snd_hctl_elem_t *obj, snd_ctl_elem_id_t *ptr);
+unsigned int snd_hctl_elem_get_numid(const snd_hctl_elem_t *obj);
+snd_ctl_elem_iface_t snd_hctl_elem_get_interface(const snd_hctl_elem_t *obj);
+unsigned int snd_hctl_elem_get_device(const snd_hctl_elem_t *obj);
+unsigned int snd_hctl_elem_get_subdevice(const snd_hctl_elem_t *obj);
+const char *snd_hctl_elem_get_name(const snd_hctl_elem_t *obj);
+unsigned int snd_hctl_elem_get_index(const snd_hctl_elem_t *obj);
+void snd_hctl_elem_set_callback(snd_hctl_elem_t *obj, snd_hctl_elem_callback_t val);
+void * snd_hctl_elem_get_callback_private(const snd_hctl_elem_t *obj);
+void snd_hctl_elem_set_callback_private(snd_hctl_elem_t *obj, void * val);
+
+/** \} */
+
+/** \} */
+
+/**
+ *  \defgroup SControl Setup Control Interface
+ *  \ingroup Control
+ *  The setup control interface - set or modify control elements from a configuration file.
+ *  \{
+ */
+
+int snd_sctl_build(snd_sctl_t **ctl, snd_ctl_t *handle, snd_config_t *config,
+		   snd_config_t *private_data, int mode);
+int snd_sctl_free(snd_sctl_t *handle);
+int snd_sctl_install(snd_sctl_t *handle);
+int snd_sctl_remove(snd_sctl_t *handle);
+
+/** \} */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __ALSA_CONTROL_H */
diff --git a/include/control_external.h b/include/control_external.h
new file mode 100644
index 0000000..7c066cf
--- /dev/null
+++ b/include/control_external.h
@@ -0,0 +1,265 @@
+/**
+ * \file include/control_external.h
+ * \brief External control plugin SDK
+ * \author Takashi Iwai <tiwai@suse.de>
+ * \date 2005
+ *
+ * External control plugin SDK.
+ */
+
+/*
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+#ifndef __ALSA_CONTROL_EXTERNAL_H
+#define __ALSA_CONTROL_EXTERNAL_H
+
+#include "control.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ *  \defgroup CtlPlugin_SDK External Control Plugin SDK
+ *  \{
+ */
+
+/**
+ * Define the object entry for external control plugins
+ */
+#define SND_CTL_PLUGIN_ENTRY(name) _snd_ctl_##name##_open
+
+/**
+ * Define the symbols of the given control plugin with versions
+ */
+#define SND_CTL_PLUGIN_SYMBOL(name) SND_DLSYM_BUILD_VERSION(SND_CTL_PLUGIN_ENTRY(name), SND_CONTROL_DLSYM_VERSION);
+
+/**
+ * Define the control plugin
+ */
+#define SND_CTL_PLUGIN_DEFINE_FUNC(plugin) \
+int SND_CTL_PLUGIN_ENTRY(plugin) (snd_ctl_t **handlep, const char *name,\
+				  snd_config_t *root, snd_config_t *conf, int mode)
+
+/** External control plugin handle */
+typedef struct snd_ctl_ext snd_ctl_ext_t;
+/** Callback table of control ext */
+typedef struct snd_ctl_ext_callback snd_ctl_ext_callback_t;
+/** Key to access a control pointer */
+typedef unsigned long snd_ctl_ext_key_t;
+
+/*
+ * Protocol version
+ */
+#define SND_CTL_EXT_VERSION_MAJOR	1	/**< Protocol major version */
+#define SND_CTL_EXT_VERSION_MINOR	0	/**< Protocol minor version */
+#define SND_CTL_EXT_VERSION_TINY	0	/**< Protocol tiny version */
+/**
+ * external plugin protocol version
+ */
+#define SND_CTL_EXT_VERSION		((SND_CTL_EXT_VERSION_MAJOR<<16) |\
+					 (SND_CTL_EXT_VERSION_MINOR<<8) |\
+					 (SND_CTL_EXT_VERSION_TINY))
+
+/** Handle of control ext */
+struct snd_ctl_ext {
+	/**
+	 * protocol version; #SND_CTL_EXT_VERSION must be filled here
+	 * before calling #snd_ctl_ext_create()
+	 */
+	unsigned int version;
+	/**
+	 * Index of this card; must be filled before calling #snd_ctl_ext_create()
+	 */
+	int card_idx;
+	/**
+	 * ID string of this card; must be filled before calling #snd_ctl_ext_create()
+	 */
+	char id[16];
+	/**
+	 * Driver name of this card; must be filled before calling #snd_ctl_ext_create()
+	 */
+	char driver[16];
+	/**
+	 * short name of this card; must be filled before calling #snd_ctl_ext_create()
+	 */
+	char name[32];
+	/**
+	 * Long name of this card; must be filled before calling #snd_ctl_ext_create()
+	 */
+	char longname[80];
+	/**
+	 * Mixer name of this card; must be filled before calling #snd_ctl_ext_create()
+	 */
+	char mixername[80];
+	/**
+	 * poll descriptor
+	 */
+	int poll_fd;
+
+	/**
+	 * callbacks of this plugin; must be filled before calling #snd_pcm_ioplug_create()
+	 */
+	const snd_ctl_ext_callback_t *callback;
+	/**
+	 * private data, which can be used freely in the driver callbacks
+	 */
+	void *private_data;
+	/**
+	 * control handle filled by #snd_ctl_ext_create()
+	 */
+	snd_ctl_t *handle;
+
+	int nonblock;			/**< non-block mode; read-only */
+	int subscribed;			/**< events subscribed; read-only */
+};
+
+/** Callback table of ext */
+struct snd_ctl_ext_callback {
+	/**
+	 * close the control handle; optional
+	 */
+	void (*close)(snd_ctl_ext_t *ext);
+	/**
+	 * return the total number of elements; required
+	 */
+	int (*elem_count)(snd_ctl_ext_t *ext);
+	/**
+	 * return the element id of the given offset (array index); required
+	 */
+	int (*elem_list)(snd_ctl_ext_t *ext, unsigned int offset, snd_ctl_elem_id_t *id);
+	/**
+	 * convert the element id to a search key; required
+	 */
+	snd_ctl_ext_key_t (*find_elem)(snd_ctl_ext_t *ext, const snd_ctl_elem_id_t *id);
+	/**
+	 * the destructor of the key; optional
+	 */
+	void (*free_key)(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key);
+	/**
+	 * get the attribute of the element; required
+	 */
+	int (*get_attribute)(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key,
+			     int *type, unsigned int *acc, unsigned int *count);
+	/**
+	 * get the element information of integer type
+	 */
+	int (*get_integer_info)(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key,
+				long *imin, long *imax, long *istep);
+	/**
+	 * get the element information of integer64 type
+	 */
+	int (*get_integer64_info)(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key,
+				  int64_t *imin, int64_t *imax, int64_t *istep);
+	/**
+	 * get the element information of enumerated type
+	 */
+	int (*get_enumerated_info)(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, unsigned int *items);
+	/**
+	 * get the name of the enumerated item
+	 */
+	int (*get_enumerated_name)(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, unsigned int item,
+				   char *name, size_t name_max_len);
+	/**
+	 * read the current values of integer type
+	 */
+	int (*read_integer)(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, long *value);
+	/**
+	 * read the current values of integer64 type
+	 */
+	int (*read_integer64)(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, int64_t *value);
+	/**
+	 * read the current values of enumerated type
+	 */
+	int (*read_enumerated)(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, unsigned int *items);
+	/**
+	 * read the current values of bytes type
+	 */
+	int (*read_bytes)(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, unsigned char *data,
+			  size_t max_bytes);
+	/**
+	 * read the current values of iec958 type
+	 */
+	int (*read_iec958)(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, snd_aes_iec958_t *iec958);
+	/**
+	 * update the current values of integer type with the given values
+	 */
+	int (*write_integer)(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, long *value);
+	/**
+	 * update the current values of integer64 type with the given values
+	 */
+	int (*write_integer64)(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, int64_t *value);
+	/**
+	 * update the current values of enumerated type with the given values
+	 */
+	int (*write_enumerated)(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, unsigned int *items);
+	/**
+	 * update the current values of bytes type with the given values
+	 */
+	int (*write_bytes)(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, unsigned char *data,
+			   size_t max_bytes);
+	/**
+	 * update the current values of iec958 type with the given values
+	 */
+	int (*write_iec958)(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, snd_aes_iec958_t *iec958);
+	/**
+	 * subscribe/unsubscribe the event notification; optional
+	 */
+	void (*subscribe_events)(snd_ctl_ext_t *ext, int subscribe);
+	/**
+	 * read a pending notification event; optional
+	 */
+	int (*read_event)(snd_ctl_ext_t *ext, snd_ctl_elem_id_t *id, unsigned int *event_mask);
+	/**
+	 * return the number of poll descriptors; optional
+	 */
+	int (*poll_descriptors_count)(snd_ctl_ext_t *ext);
+	/**
+	 * fill the poll descriptors; optional
+	 */
+	int (*poll_descriptors)(snd_ctl_ext_t *ext, struct pollfd *pfds, unsigned int space);
+	/**
+	 * mangle the revents of poll descriptors
+	 */
+	int (*poll_revents)(snd_ctl_ext_t *ext, struct pollfd *pfds, unsigned int nfds, unsigned short *revents);
+};
+
+/**
+ * The access type bits stored in get_attribute callback
+ */
+typedef enum snd_ctl_ext_access {
+	SND_CTL_EXT_ACCESS_READ = (1<<0),
+	SND_CTL_EXT_ACCESS_WRITE = (1<<1),
+	SND_CTL_EXT_ACCESS_READWRITE = (3<<0),
+	SND_CTL_EXT_ACCESS_VOLATILE = (1<<2),
+	SND_CTL_EXT_ACCESS_INACTIVE = (1<<8),
+} snd_ctl_ext_access_t;
+
+/**
+ * find_elem callback returns this if no matching control element is found
+ */
+#define SND_CTL_EXT_KEY_NOT_FOUND	(snd_ctl_ext_key_t)(-1)
+
+int snd_ctl_ext_create(snd_ctl_ext_t *ext, const char *name, int mode);
+int snd_ctl_ext_delete(snd_ctl_ext_t *ext);
+
+/** \} */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __ALSA_CONTROL_EXTERNAL_H */
diff --git a/include/error.h b/include/error.h
new file mode 100644
index 0000000..6d27083
--- /dev/null
+++ b/include/error.h
@@ -0,0 +1,78 @@
+/**
+ * \file include/error.h
+ * \brief Application interface library for the ALSA driver
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \author Takashi Iwai <tiwai@suse.de>
+ * \date 1998-2001
+ *
+ * Application interface library for the ALSA driver
+ */
+/*
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#ifndef __ALSA_ERROR_H
+#define __ALSA_ERROR_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ *  \defgroup Error Error handling
+ *  Error handling macros and functions.
+ *  \{
+ */
+
+#define SND_ERROR_BEGIN				500000			/**< Lower boundary of sound error codes. */
+#define SND_ERROR_INCOMPATIBLE_VERSION		(SND_ERROR_BEGIN+0)	/**< Kernel/library protocols are not compatible. */
+#define SND_ERROR_ALISP_NIL			(SND_ERROR_BEGIN+1)	/**< Lisp encountered an error during acall. */
+
+const char *snd_strerror(int errnum);
+
+/**
+ * \brief Error handler callback.
+ * \param file Source file name.
+ * \param line Line number.
+ * \param function Function name.
+ * \param err Value of \c errno, or 0 if not relevant.
+ * \param fmt \c printf(3) format.
+ * \param ... \c printf(3) arguments.
+ *
+ * A function of this type is called by the ALSA library when an error occurs.
+ * This function usually shows the message on the screen, and/or logs it.
+ */
+typedef void (*snd_lib_error_handler_t)(const char *file, int line, const char *function, int err, const char *fmt, ...) /* __attribute__ ((format (printf, 5, 6))) */;
+extern snd_lib_error_handler_t snd_lib_error;
+extern int snd_lib_error_set_handler(snd_lib_error_handler_t handler);
+
+#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 95)
+#define SNDERR(...) snd_lib_error(__FILE__, __LINE__, __FUNCTION__, 0, __VA_ARGS__) /**< Shows a sound error message. */
+#define SYSERR(...) snd_lib_error(__FILE__, __LINE__, __FUNCTION__, errno, __VA_ARGS__) /**< Shows a system error message (related to \c errno). */
+#else
+#define SNDERR(args...) snd_lib_error(__FILE__, __LINE__, __FUNCTION__, 0, ##args) /**< Shows a sound error message. */
+#define SYSERR(args...) snd_lib_error(__FILE__, __LINE__, __FUNCTION__, errno, ##args) /**< Shows a system error message (related to \c errno). */
+#endif
+
+/** \} */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __ALSA_ERROR_H */
+
diff --git a/include/global.h b/include/global.h
new file mode 100644
index 0000000..3e3680f
--- /dev/null
+++ b/include/global.h
@@ -0,0 +1,159 @@
+/**
+ * \file include/global.h
+ * \brief Application interface library for the ALSA driver
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \author Takashi Iwai <tiwai@suse.de>
+ * \date 1998-2001
+ *
+ * Application interface library for the ALSA driver
+ */
+/*
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#ifndef __ALSA_GLOBAL_H_
+#define __ALSA_GLOBAL_H_
+
+/* for timeval and timespec */
+#include <time.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ *  \defgroup Global Global defines and functions
+ *  Global defines and functions.
+ *  \par
+ *  The ALSA library implementation uses these macros and functions.
+ *  Most applications probably do not need them.
+ *  \{
+ */
+
+const char *snd_asoundlib_version(void);
+
+#ifndef ATTRIBUTE_UNUSED
+/** do not print warning (gcc) when function parameter is not used */
+#define ATTRIBUTE_UNUSED __attribute__ ((__unused__))
+#endif
+
+#ifdef PIC /* dynamic build */
+
+/** \hideinitializer \brief Helper macro for #SND_DLSYM_BUILD_VERSION. */
+#define __SND_DLSYM_VERSION(name, version) _ ## name ## version
+/**
+ * \hideinitializer
+ * \brief Appends the build version to the name of a versioned dynamic symbol.
+ */
+#define SND_DLSYM_BUILD_VERSION(name, version) char __SND_DLSYM_VERSION(name, version);
+
+#else /* static build */
+
+struct snd_dlsym_link {
+	struct snd_dlsym_link *next;
+	const char *dlsym_name;
+	const void *dlsym_ptr;
+};
+
+extern struct snd_dlsym_link *snd_dlsym_start;
+
+/** \hideinitializer \brief Helper macro for #SND_DLSYM_BUILD_VERSION. */
+#define __SND_DLSYM_VERSION(prefix, name, version) _ ## prefix ## name ## version
+/**
+ * \hideinitializer
+ * \brief Appends the build version to the name of a versioned dynamic symbol.
+ */
+#define SND_DLSYM_BUILD_VERSION(name, version) \
+  static struct snd_dlsym_link __SND_DLSYM_VERSION(snd_dlsym_, name, version); \
+  void __SND_DLSYM_VERSION(snd_dlsym_constructor_, name, version) (void) __attribute__ ((constructor)); \
+  void __SND_DLSYM_VERSION(snd_dlsym_constructor_, name, version) (void) { \
+    __SND_DLSYM_VERSION(snd_dlsym_, name, version).next = snd_dlsym_start; \
+    __SND_DLSYM_VERSION(snd_dlsym_, name, version).dlsym_name = # name; \
+    __SND_DLSYM_VERSION(snd_dlsym_, name, version).dlsym_ptr = (void *)&name; \
+    snd_dlsym_start = &__SND_DLSYM_VERSION(snd_dlsym_, name, version); \
+  }
+
+#endif
+
+#ifndef __STRING
+/** \brief Return 'x' argument as string */
+#define __STRING(x)     #x
+#endif
+
+/** \brief Returns the version of a dynamic symbol as a string. */
+#define SND_DLSYM_VERSION(version) __STRING(version)
+
+void *snd_dlopen(const char *file, int mode);
+void *snd_dlsym(void *handle, const char *name, const char *version);
+int snd_dlclose(void *handle);
+
+
+/** \brief alloca helper macro. */
+#define __snd_alloca(ptr,type) do { *ptr = (type##_t *) alloca(type##_sizeof()); memset(*ptr, 0, type##_sizeof()); } while (0)
+
+/**
+ * \brief Internal structure for an async notification client handler.
+ *
+ * The ALSA library uses a pointer to this structure as a handle to an async
+ * notification object. Applications don't access its contents directly.
+ */
+typedef struct _snd_async_handler snd_async_handler_t;
+
+/**
+ * \brief Async notification callback.
+ *
+ * See the #snd_async_add_handler function for details.
+ */
+typedef void (*snd_async_callback_t)(snd_async_handler_t *handler);
+
+int snd_async_add_handler(snd_async_handler_t **handler, int fd, 
+			  snd_async_callback_t callback, void *private_data);
+int snd_async_del_handler(snd_async_handler_t *handler);
+int snd_async_handler_get_fd(snd_async_handler_t *handler);
+int snd_async_handler_get_signo(snd_async_handler_t *handler);
+void *snd_async_handler_get_callback_private(snd_async_handler_t *handler);
+
+struct snd_shm_area *snd_shm_area_create(int shmid, void *ptr);
+struct snd_shm_area *snd_shm_area_share(struct snd_shm_area *area);
+int snd_shm_area_destroy(struct snd_shm_area *area);
+
+int snd_user_file(const char *file, char **result);
+
+#if !defined(_POSIX_C_SOURCE) && !defined(_POSIX_SOURCE)
+struct timeval {
+	time_t		tv_sec;		/* seconds */
+	long		tv_usec;	/* microseconds */
+};
+
+struct timespec {
+	time_t		tv_sec;		/* seconds */
+	long		tv_nsec;	/* nanoseconds */
+};
+#endif
+
+/** Timestamp */
+typedef struct timeval snd_timestamp_t;
+/** Hi-res timestamp */
+typedef struct timespec snd_htimestamp_t;
+
+/** \} */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __ALSA_GLOBAL_H */
diff --git a/include/hwdep.h b/include/hwdep.h
new file mode 100644
index 0000000..ab12822
--- /dev/null
+++ b/include/hwdep.h
@@ -0,0 +1,160 @@
+/**
+ * \file include/hwdep.h
+ * \brief Application interface library for the ALSA driver
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \author Takashi Iwai <tiwai@suse.de>
+ * \date 1998-2001
+ *
+ * Application interface library for the ALSA driver
+ */
+/*
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#ifndef __ALSA_HWDEP_H
+#define __ALSA_HWDEP_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ *  \defgroup HwDep Hardware Dependant Interface
+ *  The Hardware Dependant Interface.
+ *  \{
+ */
+
+/** dlsym version for interface entry callback */
+#define SND_HWDEP_DLSYM_VERSION		_dlsym_hwdep_001
+
+/** HwDep information container */
+typedef struct _snd_hwdep_info snd_hwdep_info_t;
+
+/** HwDep DSP status container */
+typedef struct _snd_hwdep_dsp_status snd_hwdep_dsp_status_t;
+
+/** HwDep DSP image container */
+typedef struct _snd_hwdep_dsp_image snd_hwdep_dsp_image_t;
+
+/** HwDep interface */
+typedef enum _snd_hwdep_iface {
+	SND_HWDEP_IFACE_OPL2 = 0,	/**< OPL2 raw driver */
+	SND_HWDEP_IFACE_OPL3,		/**< OPL3 raw driver */
+	SND_HWDEP_IFACE_OPL4,		/**< OPL4 raw driver */
+	SND_HWDEP_IFACE_SB16CSP,	/**< SB16CSP driver */
+	SND_HWDEP_IFACE_EMU10K1,	/**< EMU10K1 driver */
+	SND_HWDEP_IFACE_YSS225,		/**< YSS225 driver */
+	SND_HWDEP_IFACE_ICS2115,	/**< ICS2115 driver */
+	SND_HWDEP_IFACE_SSCAPE,		/**< Ensoniq SoundScape ISA card (MC68EC000) */
+	SND_HWDEP_IFACE_VX,		/**< Digigram VX cards */
+	SND_HWDEP_IFACE_MIXART,		/**< Digigram miXart cards */
+	SND_HWDEP_IFACE_USX2Y,		/**< Tascam US122, US224 & US428 usb */
+	SND_HWDEP_IFACE_EMUX_WAVETABLE,	/**< EmuX wavetable */
+	SND_HWDEP_IFACE_BLUETOOTH,	/**< Bluetooth audio */
+	SND_HWDEP_IFACE_USX2Y_PCM,	/**< Tascam US122, US224 & US428 raw USB PCM */
+	SND_HWDEP_IFACE_PCXHR,		/**< Digigram PCXHR */
+	SND_HWDEP_IFACE_SB_RC,		/**< SB Extigy/Audigy2NX remote control */
+                
+	SND_HWDEP_IFACE_LAST = SND_HWDEP_IFACE_SB_RC  /**< last known hwdep interface */
+} snd_hwdep_iface_t;
+
+/** open for reading */
+#define SND_HWDEP_OPEN_READ		(O_RDONLY)
+/** open for writing */
+#define SND_HWDEP_OPEN_WRITE		(O_WRONLY)
+/** open for reading and writing */
+#define SND_HWDEP_OPEN_DUPLEX		(O_RDWR)
+/** open mode flag: open in nonblock mode */
+#define SND_HWDEP_OPEN_NONBLOCK		(O_NONBLOCK)
+
+/** HwDep handle type */
+typedef enum _snd_hwdep_type {
+	/** Kernel level HwDep */
+	SND_HWDEP_TYPE_HW,
+	/** Shared memory client HwDep (not yet implemented) */
+	SND_HWDEP_TYPE_SHM,
+	/** INET client HwDep (not yet implemented) */
+	SND_HWDEP_TYPE_INET
+} snd_hwdep_type_t;
+
+/** HwDep handle */
+typedef struct _snd_hwdep snd_hwdep_t;
+
+int snd_hwdep_open(snd_hwdep_t **hwdep, const char *name, int mode);
+int snd_hwdep_close(snd_hwdep_t *hwdep);
+int snd_hwdep_poll_descriptors(snd_hwdep_t *hwdep, struct pollfd *pfds, unsigned int space);
+int snd_hwdep_poll_descriptors_revents(snd_hwdep_t *hwdep, struct pollfd *pfds, unsigned int nfds, unsigned short *revents);
+int snd_hwdep_nonblock(snd_hwdep_t *hwdep, int nonblock);
+int snd_hwdep_info(snd_hwdep_t *hwdep, snd_hwdep_info_t * info);
+int snd_hwdep_dsp_status(snd_hwdep_t *hwdep, snd_hwdep_dsp_status_t *status);
+int snd_hwdep_dsp_load(snd_hwdep_t *hwdep, snd_hwdep_dsp_image_t *block);
+int snd_hwdep_ioctl(snd_hwdep_t *hwdep, unsigned int request, void * arg);
+ssize_t snd_hwdep_write(snd_hwdep_t *hwdep, const void *buffer, size_t size);
+ssize_t snd_hwdep_read(snd_hwdep_t *hwdep, void *buffer, size_t size);
+
+size_t snd_hwdep_info_sizeof(void);
+/** allocate #snd_hwdep_info_t container on stack */
+#define snd_hwdep_info_alloca(ptr) __snd_alloca(ptr, snd_hwdep_info)
+int snd_hwdep_info_malloc(snd_hwdep_info_t **ptr);
+void snd_hwdep_info_free(snd_hwdep_info_t *obj);
+void snd_hwdep_info_copy(snd_hwdep_info_t *dst, const snd_hwdep_info_t *src);
+
+unsigned int snd_hwdep_info_get_device(const snd_hwdep_info_t *obj);
+int snd_hwdep_info_get_card(const snd_hwdep_info_t *obj);
+const char *snd_hwdep_info_get_id(const snd_hwdep_info_t *obj);
+const char *snd_hwdep_info_get_name(const snd_hwdep_info_t *obj);
+snd_hwdep_iface_t snd_hwdep_info_get_iface(const snd_hwdep_info_t *obj);
+void snd_hwdep_info_set_device(snd_hwdep_info_t *obj, unsigned int val);
+
+size_t snd_hwdep_dsp_status_sizeof(void);
+/** allocate #snd_hwdep_dsp_status_t container on stack */
+#define snd_hwdep_dsp_status_alloca(ptr) __snd_alloca(ptr, snd_hwdep_dsp_status)
+int snd_hwdep_dsp_status_malloc(snd_hwdep_dsp_status_t **ptr);
+void snd_hwdep_dsp_status_free(snd_hwdep_dsp_status_t *obj);
+void snd_hwdep_dsp_status_copy(snd_hwdep_dsp_status_t *dst, const snd_hwdep_dsp_status_t *src);
+
+unsigned int snd_hwdep_dsp_status_get_version(const snd_hwdep_dsp_status_t *obj);
+const char *snd_hwdep_dsp_status_get_id(const snd_hwdep_dsp_status_t *obj);
+unsigned int snd_hwdep_dsp_status_get_num_dsps(const snd_hwdep_dsp_status_t *obj);
+unsigned int snd_hwdep_dsp_status_get_dsp_loaded(const snd_hwdep_dsp_status_t *obj);
+unsigned int snd_hwdep_dsp_status_get_chip_ready(const snd_hwdep_dsp_status_t *obj);
+
+size_t snd_hwdep_dsp_image_sizeof(void);
+/** allocate #snd_hwdep_dsp_image_t container on stack */
+#define snd_hwdep_dsp_image_alloca(ptr) __snd_alloca(ptr, snd_hwdep_dsp_image)
+int snd_hwdep_dsp_image_malloc(snd_hwdep_dsp_image_t **ptr);
+void snd_hwdep_dsp_image_free(snd_hwdep_dsp_image_t *obj);
+void snd_hwdep_dsp_image_copy(snd_hwdep_dsp_image_t *dst, const snd_hwdep_dsp_image_t *src);
+
+unsigned int snd_hwdep_dsp_image_get_index(const snd_hwdep_dsp_image_t *obj);
+const char *snd_hwdep_dsp_image_get_name(const snd_hwdep_dsp_image_t *obj);
+const void *snd_hwdep_dsp_image_get_image(const snd_hwdep_dsp_image_t *obj);
+size_t snd_hwdep_dsp_image_get_length(const snd_hwdep_dsp_image_t *obj);
+
+void snd_hwdep_dsp_image_set_index(snd_hwdep_dsp_image_t *obj, unsigned int _index);
+void snd_hwdep_dsp_image_set_name(snd_hwdep_dsp_image_t *obj, const char *name);
+void snd_hwdep_dsp_image_set_image(snd_hwdep_dsp_image_t *obj, void *buffer);
+void snd_hwdep_dsp_image_set_length(snd_hwdep_dsp_image_t *obj, size_t length);
+
+/** \} */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __ALSA_HWDEP_H */
+
diff --git a/include/iatomic.h b/include/iatomic.h
new file mode 100644
index 0000000..e92dbfd
--- /dev/null
+++ b/include/iatomic.h
@@ -0,0 +1,1198 @@
+#ifndef __ALSA_IATOMIC_H
+#define __ALSA_IATOMIC_H
+
+#if defined(__i386__) || defined(__x86_64__)
+
+/*
+ * Atomic operations that C can't guarantee us.  Useful for
+ * resource counting etc..
+ */
+
+#define ATOMIC_SMP_LOCK "lock ; "
+
+/*
+ * Make sure gcc doesn't try to be clever and move things around
+ * on us. We need to use _exactly_ the address the user gave us,
+ * not some alias that contains the same information.
+ */
+typedef struct { volatile int counter; } atomic_t;
+
+#define ATOMIC_INIT(i)	{ (i) }
+
+/**
+ * atomic_read - read atomic variable
+ * @v: pointer of type atomic_t
+ * 
+ * Atomically reads the value of @v.  Note that the guaranteed
+ * useful range of an atomic_t is only 24 bits.
+ */ 
+#define atomic_read(v)		((v)->counter)
+
+/**
+ * atomic_set - set atomic variable
+ * @v: pointer of type atomic_t
+ * @i: required value
+ * 
+ * Atomically sets the value of @v to @i.  Note that the guaranteed
+ * useful range of an atomic_t is only 24 bits.
+ */ 
+#define atomic_set(v,i)		(((v)->counter) = (i))
+
+/**
+ * atomic_add - add integer to atomic variable
+ * @i: integer value to add
+ * @v: pointer of type atomic_t
+ * 
+ * Atomically adds @i to @v.  Note that the guaranteed useful range
+ * of an atomic_t is only 24 bits.
+ */
+static __inline__ void atomic_add(int i, atomic_t *v)
+{
+	__asm__ __volatile__(
+		ATOMIC_SMP_LOCK "addl %1,%0"
+		:"=m" (v->counter)
+		:"ir" (i), "m" (v->counter));
+}
+
+/**
+ * atomic_sub - subtract the atomic variable
+ * @i: integer value to subtract
+ * @v: pointer of type atomic_t
+ * 
+ * Atomically subtracts @i from @v.  Note that the guaranteed
+ * useful range of an atomic_t is only 24 bits.
+ */
+static __inline__ void atomic_sub(int i, atomic_t *v)
+{
+	__asm__ __volatile__(
+		ATOMIC_SMP_LOCK "subl %1,%0"
+		:"=m" (v->counter)
+		:"ir" (i), "m" (v->counter));
+}
+
+/**
+ * atomic_sub_and_test - subtract value from variable and test result
+ * @i: integer value to subtract
+ * @v: pointer of type atomic_t
+ * 
+ * Atomically subtracts @i from @v and returns
+ * true if the result is zero, or false for all
+ * other cases.  Note that the guaranteed
+ * useful range of an atomic_t is only 24 bits.
+ */
+static __inline__ int atomic_sub_and_test(int i, atomic_t *v)
+{
+	unsigned char c;
+
+	__asm__ __volatile__(
+		ATOMIC_SMP_LOCK "subl %2,%0; sete %1"
+		:"=m" (v->counter), "=qm" (c)
+		:"ir" (i), "m" (v->counter) : "memory");
+	return c;
+}
+
+/**
+ * atomic_inc - increment atomic variable
+ * @v: pointer of type atomic_t
+ * 
+ * Atomically increments @v by 1.  Note that the guaranteed
+ * useful range of an atomic_t is only 24 bits.
+ */ 
+static __inline__ void atomic_inc(atomic_t *v)
+{
+	__asm__ __volatile__(
+		ATOMIC_SMP_LOCK "incl %0"
+		:"=m" (v->counter)
+		:"m" (v->counter));
+}
+
+/**
+ * atomic_dec - decrement atomic variable
+ * @v: pointer of type atomic_t
+ * 
+ * Atomically decrements @v by 1.  Note that the guaranteed
+ * useful range of an atomic_t is only 24 bits.
+ */ 
+static __inline__ void atomic_dec(atomic_t *v)
+{
+	__asm__ __volatile__(
+		ATOMIC_SMP_LOCK "decl %0"
+		:"=m" (v->counter)
+		:"m" (v->counter));
+}
+
+/**
+ * atomic_dec_and_test - decrement and test
+ * @v: pointer of type atomic_t
+ * 
+ * Atomically decrements @v by 1 and
+ * returns true if the result is 0, or false for all other
+ * cases.  Note that the guaranteed
+ * useful range of an atomic_t is only 24 bits.
+ */ 
+static __inline__ int atomic_dec_and_test(atomic_t *v)
+{
+	unsigned char c;
+
+	__asm__ __volatile__(
+		ATOMIC_SMP_LOCK "decl %0; sete %1"
+		:"=m" (v->counter), "=qm" (c)
+		:"m" (v->counter) : "memory");
+	return c != 0;
+}
+
+/**
+ * atomic_inc_and_test - increment and test 
+ * @v: pointer of type atomic_t
+ * 
+ * Atomically increments @v by 1
+ * and returns true if the result is zero, or false for all
+ * other cases.  Note that the guaranteed
+ * useful range of an atomic_t is only 24 bits.
+ */ 
+static __inline__ int atomic_inc_and_test(atomic_t *v)
+{
+	unsigned char c;
+
+	__asm__ __volatile__(
+		ATOMIC_SMP_LOCK "incl %0; sete %1"
+		:"=m" (v->counter), "=qm" (c)
+		:"m" (v->counter) : "memory");
+	return c != 0;
+}
+
+/**
+ * atomic_add_negative - add and test if negative
+ * @v: pointer of type atomic_t
+ * @i: integer value to add
+ * 
+ * Atomically adds @i to @v and returns true
+ * if the result is negative, or false when
+ * result is greater than or equal to zero.  Note that the guaranteed
+ * useful range of an atomic_t is only 24 bits.
+ */ 
+static __inline__ int atomic_add_negative(int i, atomic_t *v)
+{
+	unsigned char c;
+
+	__asm__ __volatile__(
+		ATOMIC_SMP_LOCK "addl %2,%0; sets %1"
+		:"=m" (v->counter), "=qm" (c)
+		:"ir" (i), "m" (v->counter) : "memory");
+	return c;
+}
+
+/* These are x86-specific, used by some header files */
+#define atomic_clear_mask(mask, addr) \
+__asm__ __volatile__(ATOMIC_SMP_LOCK "andl %0,%1" \
+: : "r" (~(mask)),"m" (*addr) : "memory")
+
+#define atomic_set_mask(mask, addr) \
+__asm__ __volatile__(ATOMIC_SMP_LOCK "orl %0,%1" \
+: : "r" (mask),"m" (*addr) : "memory")
+
+/*
+ * Force strict CPU ordering.
+ * And yes, this is required on UP too when we're talking
+ * to devices.
+ *
+ * For now, "wmb()" doesn't actually do anything, as all
+ * Intel CPU's follow what Intel calls a *Processor Order*,
+ * in which all writes are seen in the program order even
+ * outside the CPU.
+ *
+ * I expect future Intel CPU's to have a weaker ordering,
+ * but I'd also expect them to finally get their act together
+ * and add some real memory barriers if so.
+ */
+ 
+#ifdef __i386__
+#define mb() 	__asm__ __volatile__ ("lock; addl $0,0(%%esp)": : :"memory")
+#define rmb()	mb()
+#define wmb()	__asm__ __volatile__ ("": : :"memory")
+#else
+#define mb() 	asm volatile("mfence":::"memory")
+#define rmb()	asm volatile("lfence":::"memory")
+#define wmb()	asm volatile("sfence":::"memory")
+#endif
+
+#undef ATOMIC_SMP_LOCK
+
+#define IATOMIC_DEFINED		1
+
+#endif /* __i386__ */
+
+#ifdef __ia64__
+
+/*
+ * On IA-64, counter must always be volatile to ensure that that the
+ * memory accesses are ordered.
+ */
+typedef struct { volatile int counter; } atomic_t;
+
+#define ATOMIC_INIT(i)		((atomic_t) { (i) })
+
+#define atomic_read(v)		((v)->counter)
+#define atomic_set(v,i)		(((v)->counter) = (i))
+
+/* stripped version - we need only 4byte version */
+#define ia64_cmpxchg(sem,ptr,old,new,size) \
+({ \
+	__typeof__(ptr) _p_ = (ptr); \
+	__typeof__(new) _n_ = (new); \
+	unsigned long _o_, _r_; \
+	_o_ = (unsigned int) (long) (old); \
+	__asm__ __volatile__ ("mov ar.ccv=%0;;" :: "rO"(_o_)); \
+	__asm__ __volatile__ ("cmpxchg4."sem" %0=[%1],%2,ar.ccv" \
+			      : "=r"(_r_) : "r"(_p_), "r"(_n_) : "memory"); \
+	(__typeof__(old)) _r_; \
+})
+
+static __inline__ int
+ia64_atomic_add (int i, atomic_t *v)
+{
+	int old, new;
+	// CMPXCHG_BUGCHECK_DECL
+
+	do {
+		// CMPXCHG_BUGCHECK(v);
+		old = atomic_read(v);
+		new = old + i;
+	} while (ia64_cmpxchg("acq", v, old, old + i, sizeof(atomic_t)) != old);
+	return new;
+}
+
+static __inline__ int
+ia64_atomic_sub (int i, atomic_t *v)
+{
+	int old, new;
+	// CMPXCHG_BUGCHECK_DECL
+
+	do {
+		// CMPXCHG_BUGCHECK(v);
+		old = atomic_read(v);
+		new = old - i;
+	} while (ia64_cmpxchg("acq", v, old, new, sizeof(atomic_t)) != old);
+	return new;
+}
+
+#define IA64_FETCHADD(tmp,v,n,sz)						\
+({										\
+	switch (sz) {								\
+	      case 4:								\
+		__asm__ __volatile__ ("fetchadd4.rel %0=[%1],%2"		\
+				      : "=r"(tmp) : "r"(v), "i"(n) : "memory");	\
+		break;								\
+										\
+	      case 8:								\
+		__asm__ __volatile__ ("fetchadd8.rel %0=[%1],%2"		\
+				      : "=r"(tmp) : "r"(v), "i"(n) : "memory");	\
+		break;								\
+	}									\
+})
+
+#define ia64_fetch_and_add(i,v)							\
+({										\
+	unsigned long _tmp;								\
+	volatile __typeof__(*(v)) *_v = (v);					\
+	switch (i) {								\
+	      case -16:	IA64_FETCHADD(_tmp, _v, -16, sizeof(*(v))); break;	\
+	      case  -8:	IA64_FETCHADD(_tmp, _v,  -8, sizeof(*(v))); break;	\
+	      case  -4:	IA64_FETCHADD(_tmp, _v,  -4, sizeof(*(v))); break;	\
+	      case  -1:	IA64_FETCHADD(_tmp, _v,  -1, sizeof(*(v))); break;	\
+	      case   1:	IA64_FETCHADD(_tmp, _v,   1, sizeof(*(v))); break;	\
+	      case   4:	IA64_FETCHADD(_tmp, _v,   4, sizeof(*(v))); break;	\
+	      case   8:	IA64_FETCHADD(_tmp, _v,   8, sizeof(*(v))); break;	\
+	      case  16:	IA64_FETCHADD(_tmp, _v,  16, sizeof(*(v))); break;	\
+	}									\
+	(__typeof__(*v)) (_tmp + (i));	/* return new value */			\
+})
+
+/*
+ * Atomically add I to V and return TRUE if the resulting value is
+ * negative.
+ */
+static __inline__ int
+atomic_add_negative (int i, atomic_t *v)
+{
+	return ia64_atomic_add(i, v) < 0;
+}
+
+#define atomic_add_return(i,v)						\
+	((__builtin_constant_p(i) &&					\
+	  (   (i ==  1) || (i ==  4) || (i ==  8) || (i ==  16)		\
+	   || (i == -1) || (i == -4) || (i == -8) || (i == -16)))	\
+	 ? ia64_fetch_and_add(i, &(v)->counter)				\
+	 : ia64_atomic_add(i, v))
+
+#define atomic_sub_return(i,v)						\
+	((__builtin_constant_p(i) &&					\
+	  (   (i ==  1) || (i ==  4) || (i ==  8) || (i ==  16)		\
+	   || (i == -1) || (i == -4) || (i == -8) || (i == -16)))	\
+	 ? ia64_fetch_and_add(-(i), &(v)->counter)			\
+	 : ia64_atomic_sub(i, v))
+
+#define atomic_dec_return(v)		atomic_sub_return(1, (v))
+#define atomic_inc_return(v)		atomic_add_return(1, (v))
+
+#define atomic_sub_and_test(i,v)	(atomic_sub_return((i), (v)) == 0)
+#define atomic_dec_and_test(v)		(atomic_sub_return(1, (v)) == 0)
+#define atomic_inc_and_test(v)		(atomic_add_return(1, (v)) != 0)
+
+#define atomic_add(i,v)			atomic_add_return((i), (v))
+#define atomic_sub(i,v)			atomic_sub_return((i), (v))
+#define atomic_inc(v)			atomic_add(1, (v))
+#define atomic_dec(v)			atomic_sub(1, (v))
+
+/*
+ * Macros to force memory ordering.  In these descriptions, "previous"
+ * and "subsequent" refer to program order; "visible" means that all
+ * architecturally visible effects of a memory access have occurred
+ * (at a minimum, this means the memory has been read or written).
+ *
+ *   wmb():	Guarantees that all preceding stores to memory-
+ *		like regions are visible before any subsequent
+ *		stores and that all following stores will be
+ *		visible only after all previous stores.
+ *   rmb():	Like wmb(), but for reads.
+ *   mb():	wmb()/rmb() combo, i.e., all previous memory
+ *		accesses are visible before all subsequent
+ *		accesses and vice versa.  This is also known as
+ *		a "fence."
+ *
+ * Note: "mb()" and its variants cannot be used as a fence to order
+ * accesses to memory mapped I/O registers.  For that, mf.a needs to
+ * be used.  However, we don't want to always use mf.a because (a)
+ * it's (presumably) much slower than mf and (b) mf.a is supported for
+ * sequential memory pages only.
+ */
+#define mb()	__asm__ __volatile__ ("mf" ::: "memory")
+#define rmb()	mb()
+#define wmb()	mb()
+
+#define IATOMIC_DEFINED		1
+
+#endif /* __ia64__ */
+
+#ifdef __alpha__
+
+/*
+ * Atomic operations that C can't guarantee us.  Useful for
+ * resource counting etc...
+ *
+ * But use these as seldom as possible since they are much slower
+ * than regular operations.
+ */
+
+
+/*
+ * Counter is volatile to make sure gcc doesn't try to be clever
+ * and move things around on us. We need to use _exactly_ the address
+ * the user gave us, not some alias that contains the same information.
+ */
+typedef struct { volatile int counter; } atomic_t;
+
+#define ATOMIC_INIT(i)	( (atomic_t) { (i) } )
+
+#define atomic_read(v)		((v)->counter)
+#define atomic_set(v,i)		((v)->counter = (i))
+
+/*
+ * To get proper branch prediction for the main line, we must branch
+ * forward to code at the end of this object's .text section, then
+ * branch back to restart the operation.
+ */
+
+static __inline__ void atomic_add(int i, atomic_t * v)
+{
+	unsigned long temp;
+	__asm__ __volatile__(
+	"1:	ldl_l %0,%1\n"
+	"	addl %0,%2,%0\n"
+	"	stl_c %0,%1\n"
+	"	beq %0,2f\n"
+	".subsection 2\n"
+	"2:	br 1b\n"
+	".previous"
+	:"=&r" (temp), "=m" (v->counter)
+	:"Ir" (i), "m" (v->counter));
+}
+
+static __inline__ void atomic_sub(int i, atomic_t * v)
+{
+	unsigned long temp;
+	__asm__ __volatile__(
+	"1:	ldl_l %0,%1\n"
+	"	subl %0,%2,%0\n"
+	"	stl_c %0,%1\n"
+	"	beq %0,2f\n"
+	".subsection 2\n"
+	"2:	br 1b\n"
+	".previous"
+	:"=&r" (temp), "=m" (v->counter)
+	:"Ir" (i), "m" (v->counter));
+}
+
+/*
+ * Same as above, but return the result value
+ */
+static __inline__ long atomic_add_return(int i, atomic_t * v)
+{
+	long temp, result;
+	__asm__ __volatile__(
+	"1:	ldl_l %0,%1\n"
+	"	addl %0,%3,%2\n"
+	"	addl %0,%3,%0\n"
+	"	stl_c %0,%1\n"
+	"	beq %0,2f\n"
+	"	mb\n"
+	".subsection 2\n"
+	"2:	br 1b\n"
+	".previous"
+	:"=&r" (temp), "=m" (v->counter), "=&r" (result)
+	:"Ir" (i), "m" (v->counter) : "memory");
+	return result;
+}
+
+static __inline__ long atomic_sub_return(int i, atomic_t * v)
+{
+	long temp, result;
+	__asm__ __volatile__(
+	"1:	ldl_l %0,%1\n"
+	"	subl %0,%3,%2\n"
+	"	subl %0,%3,%0\n"
+	"	stl_c %0,%1\n"
+	"	beq %0,2f\n"
+	"	mb\n"
+	".subsection 2\n"
+	"2:	br 1b\n"
+	".previous"
+	:"=&r" (temp), "=m" (v->counter), "=&r" (result)
+	:"Ir" (i), "m" (v->counter) : "memory");
+	return result;
+}
+
+#define atomic_dec_return(v) atomic_sub_return(1,(v))
+#define atomic_inc_return(v) atomic_add_return(1,(v))
+
+#define atomic_sub_and_test(i,v) (atomic_sub_return((i), (v)) == 0)
+#define atomic_dec_and_test(v) (atomic_sub_return(1, (v)) == 0)
+
+#define atomic_inc(v) atomic_add(1,(v))
+#define atomic_dec(v) atomic_sub(1,(v))
+
+#define mb() \
+__asm__ __volatile__("mb": : :"memory")
+
+#define rmb() \
+__asm__ __volatile__("mb": : :"memory")
+
+#define wmb() \
+__asm__ __volatile__("wmb": : :"memory")
+
+#define IATOMIC_DEFINED		1
+
+#endif /* __alpha__ */
+
+#ifdef __powerpc__
+
+typedef struct { volatile int counter; } atomic_t;
+
+#define ATOMIC_INIT(i)	{ (i) }
+
+#define atomic_read(v)		((v)->counter)
+#define atomic_set(v,i)		(((v)->counter) = (i))
+
+extern void atomic_clear_mask(unsigned long mask, unsigned long *addr);
+extern void atomic_set_mask(unsigned long mask, unsigned long *addr);
+
+#define SMP_ISYNC	"\n\tisync"
+
+static __inline__ void atomic_add(int a, atomic_t *v)
+{
+	int t;
+
+	__asm__ __volatile__(
+"1:	lwarx	%0,0,%3		# atomic_add\n\
+	add	%0,%2,%0\n\
+	stwcx.	%0,0,%3\n\
+	bne-	1b"
+	: "=&r" (t), "=m" (v->counter)
+	: "r" (a), "r" (&v->counter), "m" (v->counter)
+	: "cc");
+}
+
+static __inline__ int atomic_add_return(int a, atomic_t *v)
+{
+	int t;
+
+	__asm__ __volatile__(
+"1:	lwarx	%0,0,%2		# atomic_add_return\n\
+	add	%0,%1,%0\n\
+	stwcx.	%0,0,%2\n\
+	bne-	1b"
+	SMP_ISYNC
+	: "=&r" (t)
+	: "r" (a), "r" (&v->counter)
+	: "cc", "memory");
+
+	return t;
+}
+
+static __inline__ void atomic_sub(int a, atomic_t *v)
+{
+	int t;
+
+	__asm__ __volatile__(
+"1:	lwarx	%0,0,%3		# atomic_sub\n\
+	subf	%0,%2,%0\n\
+	stwcx.	%0,0,%3\n\
+	bne-	1b"
+	: "=&r" (t), "=m" (v->counter)
+	: "r" (a), "r" (&v->counter), "m" (v->counter)
+	: "cc");
+}
+
+static __inline__ int atomic_sub_return(int a, atomic_t *v)
+{
+	int t;
+
+	__asm__ __volatile__(
+"1:	lwarx	%0,0,%2		# atomic_sub_return\n\
+	subf	%0,%1,%0\n\
+	stwcx.	%0,0,%2\n\
+	bne-	1b"
+	SMP_ISYNC
+	: "=&r" (t)
+	: "r" (a), "r" (&v->counter)
+	: "cc", "memory");
+
+	return t;
+}
+
+static __inline__ void atomic_inc(atomic_t *v)
+{
+	int t;
+
+	__asm__ __volatile__(
+"1:	lwarx	%0,0,%2		# atomic_inc\n\
+	addic	%0,%0,1\n\
+	stwcx.	%0,0,%2\n\
+	bne-	1b"
+	: "=&r" (t), "=m" (v->counter)
+	: "r" (&v->counter), "m" (v->counter)
+	: "cc");
+}
+
+static __inline__ int atomic_inc_return(atomic_t *v)
+{
+	int t;
+
+	__asm__ __volatile__(
+"1:	lwarx	%0,0,%1		# atomic_inc_return\n\
+	addic	%0,%0,1\n\
+	stwcx.	%0,0,%1\n\
+	bne-	1b"
+	SMP_ISYNC
+	: "=&r" (t)
+	: "r" (&v->counter)
+	: "cc", "memory");
+
+	return t;
+}
+
+static __inline__ void atomic_dec(atomic_t *v)
+{
+	int t;
+
+	__asm__ __volatile__(
+"1:	lwarx	%0,0,%2		# atomic_dec\n\
+	addic	%0,%0,-1\n\
+	stwcx.	%0,0,%2\n\
+	bne-	1b"
+	: "=&r" (t), "=m" (v->counter)
+	: "r" (&v->counter), "m" (v->counter)
+	: "cc");
+}
+
+static __inline__ int atomic_dec_return(atomic_t *v)
+{
+	int t;
+
+	__asm__ __volatile__(
+"1:	lwarx	%0,0,%1		# atomic_dec_return\n\
+	addic	%0,%0,-1\n\
+	stwcx.	%0,0,%1\n\
+	bne-	1b"
+	SMP_ISYNC
+	: "=&r" (t)
+	: "r" (&v->counter)
+	: "cc", "memory");
+
+	return t;
+}
+
+#define atomic_sub_and_test(a, v)	(atomic_sub_return((a), (v)) == 0)
+#define atomic_dec_and_test(v)		(atomic_dec_return((v)) == 0)
+
+/*
+ * Atomically test *v and decrement if it is greater than 0.
+ * The function returns the old value of *v minus 1.
+ */
+static __inline__ int atomic_dec_if_positive(atomic_t *v)
+{
+	int t;
+
+	__asm__ __volatile__(
+"1:	lwarx	%0,0,%1		# atomic_dec_if_positive\n\
+	addic.	%0,%0,-1\n\
+	blt-	2f\n\
+	stwcx.	%0,0,%1\n\
+	bne-	1b"
+	SMP_ISYNC
+	"\n\
+2:"	: "=&r" (t)
+	: "r" (&v->counter)
+	: "cc", "memory");
+
+	return t;
+}
+
+/*
+ * Memory barrier.
+ * The sync instruction guarantees that all memory accesses initiated
+ * by this processor have been performed (with respect to all other
+ * mechanisms that access memory).  The eieio instruction is a barrier
+ * providing an ordering (separately) for (a) cacheable stores and (b)
+ * loads and stores to non-cacheable memory (e.g. I/O devices).
+ *
+ * mb() prevents loads and stores being reordered across this point.
+ * rmb() prevents loads being reordered across this point.
+ * wmb() prevents stores being reordered across this point.
+ *
+ * We can use the eieio instruction for wmb, but since it doesn't
+ * give any ordering guarantees about loads, we have to use the
+ * stronger but slower sync instruction for mb and rmb.
+ */
+#define mb()  __asm__ __volatile__ ("sync" : : : "memory")
+#define rmb()  __asm__ __volatile__ ("sync" : : : "memory")
+#define wmb()  __asm__ __volatile__ ("eieio" : : : "memory")
+
+#define IATOMIC_DEFINED		1
+
+#endif /* __powerpc__ */
+
+#ifdef __mips__
+
+typedef struct { volatile int counter; } atomic_t;
+
+#define ATOMIC_INIT(i)    { (i) }
+
+/*
+ * atomic_read - read atomic variable
+ * @v: pointer of type atomic_t
+ *
+ * Atomically reads the value of @v.  Note that the guaranteed
+ * useful range of an atomic_t is only 24 bits.
+ */
+#define atomic_read(v)	((v)->counter)
+
+/*
+ * atomic_set - set atomic variable
+ * @v: pointer of type atomic_t
+ * @i: required value
+ *
+ * Atomically sets the value of @v to @i.  Note that the guaranteed
+ * useful range of an atomic_t is only 24 bits.
+ */
+#define atomic_set(v,i)	((v)->counter = (i))
+
+/*
+ * for MIPS II and better we can use ll/sc instruction, and kernel 2.4.3+
+ * will emulate it on MIPS I.
+ */
+
+/*
+ * atomic_add - add integer to atomic variable
+ * @i: integer value to add
+ * @v: pointer of type atomic_t
+ *
+ * Atomically adds @i to @v.  Note that the guaranteed useful range
+ * of an atomic_t is only 24 bits.
+ */
+extern __inline__ void atomic_add(int i, atomic_t * v)
+{
+	unsigned long temp;
+
+	__asm__ __volatile__(
+		".set push                            \n"
+		".set mips2                           \n"
+		"1:   ll      %0, %1      # atomic_add\n"
+		"     addu    %0, %2                  \n"
+		"     sc      %0, %1                  \n"
+		"     beqz    %0, 1b                  \n"
+		".set pop                             \n"
+		: "=&r" (temp), "=m" (v->counter)
+		: "Ir" (i), "m" (v->counter));
+}
+
+/*
+ * atomic_sub - subtract the atomic variable
+ * @i: integer value to subtract
+ * @v: pointer of type atomic_t
+ *
+ * Atomically subtracts @i from @v.  Note that the guaranteed
+ * useful range of an atomic_t is only 24 bits.
+ */
+extern __inline__ void atomic_sub(int i, atomic_t * v)
+{
+	unsigned long temp;
+
+	__asm__ __volatile__(
+		".set push                            \n"
+		".set mips2                           \n"
+		"1:   ll      %0, %1      # atomic_sub\n"
+		"     subu    %0, %2                  \n"
+		"     sc      %0, %1                  \n"
+		"     beqz    %0, 1b                  \n"
+		".set pop                             \n"
+		: "=&r" (temp), "=m" (v->counter)
+		: "Ir" (i), "m" (v->counter));
+}
+
+/*
+ * Same as above, but return the result value
+ */
+extern __inline__ int atomic_add_return(int i, atomic_t * v)
+{
+	unsigned long temp, result;
+
+	__asm__ __volatile__(
+		".set push               # atomic_add_return\n"
+		".set noreorder                             \n"
+		".set mips2                                 \n"
+		"1:   ll      %1, %2                        \n"
+		"     addu    %0, %1, %3                    \n"
+		"     sc      %0, %2                        \n"
+		"     beqz    %0, 1b                        \n"
+		"     addu    %0, %1, %3                    \n"
+		".set pop                                   \n"
+		: "=&r" (result), "=&r" (temp), "=m" (v->counter)
+		: "Ir" (i), "m" (v->counter)
+		: "memory");
+
+	return result;
+}
+
+extern __inline__ int atomic_sub_return(int i, atomic_t * v)
+{
+	unsigned long temp, result;
+
+	__asm__ __volatile__(
+		".set push                                   \n"
+		".set mips2                                  \n"
+		".set noreorder           # atomic_sub_return\n"
+		"1:   ll    %1, %2                           \n"
+		"     subu  %0, %1, %3                       \n"
+		"     sc    %0, %2                           \n"
+		"     beqz  %0, 1b                           \n"
+		"     subu  %0, %1, %3                       \n"
+		".set pop                                    \n"
+		: "=&r" (result), "=&r" (temp), "=m" (v->counter)
+		: "Ir" (i), "m" (v->counter)
+		: "memory");
+
+	return result;
+}
+
+#define atomic_dec_return(v) atomic_sub_return(1,(v))
+#define atomic_inc_return(v) atomic_add_return(1,(v))
+
+/*
+ * atomic_sub_and_test - subtract value from variable and test result
+ * @i: integer value to subtract
+ * @v: pointer of type atomic_t
+ *
+ * Atomically subtracts @i from @v and returns
+ * true if the result is zero, or false for all
+ * other cases.  Note that the guaranteed
+ * useful range of an atomic_t is only 24 bits.
+ */
+#define atomic_sub_and_test(i,v) (atomic_sub_return((i), (v)) == 0)
+
+/*
+ * atomic_inc_and_test - increment and test
+ * @v: pointer of type atomic_t
+ *
+ * Atomically increments @v by 1
+ * and returns true if the result is zero, or false for all
+ * other cases.  Note that the guaranteed
+ * useful range of an atomic_t is only 24 bits.
+ */
+#define atomic_inc_and_test(v) (atomic_inc_return(1, (v)) == 0)
+
+/*
+ * atomic_dec_and_test - decrement by 1 and test
+ * @v: pointer of type atomic_t
+ *
+ * Atomically decrements @v by 1 and
+ * returns true if the result is 0, or false for all other
+ * cases.  Note that the guaranteed
+ * useful range of an atomic_t is only 24 bits.
+ */
+#define atomic_dec_and_test(v) (atomic_sub_return(1, (v)) == 0)
+
+/*
+ * atomic_inc - increment atomic variable
+ * @v: pointer of type atomic_t
+ *
+ * Atomically increments @v by 1.  Note that the guaranteed
+ * useful range of an atomic_t is only 24 bits.
+ */
+#define atomic_inc(v) atomic_add(1,(v))
+
+/*
+ * atomic_dec - decrement and test
+ * @v: pointer of type atomic_t
+ *
+ * Atomically decrements @v by 1.  Note that the guaranteed
+ * useful range of an atomic_t is only 24 bits.
+ */
+#define atomic_dec(v) atomic_sub(1,(v))
+
+/*
+ * atomic_add_negative - add and test if negative
+ * @v: pointer of type atomic_t
+ * @i: integer value to add
+ *
+ * Atomically adds @i to @v and returns true
+ * if the result is negative, or false when
+ * result is greater than or equal to zero.  Note that the guaranteed
+ * useful range of an atomic_t is only 24 bits.
+ *
+ * Currently not implemented for MIPS.
+ */
+
+#define mb()						\
+__asm__ __volatile__(					\
+	"# prevent instructions being moved around\n\t"	\
+	".set\tnoreorder\n\t"				\
+	"# 8 nops to fool the R4400 pipeline\n\t"	\
+	"nop;nop;nop;nop;nop;nop;nop;nop\n\t"		\
+	".set\treorder"					\
+	: /* no output */				\
+	: /* no input */				\
+	: "memory")
+#define rmb() mb()
+#define wmb() mb()
+
+#define IATOMIC_DEFINED		1
+
+#endif /* __mips__ */
+
+#ifdef __arm__
+
+/*
+ * FIXME: bellow code is valid only for SA11xx
+ */
+
+/*
+ * Save the current interrupt enable state & disable IRQs
+ */
+#define local_irq_save(x)					\
+	({							\
+		unsigned long temp;				\
+	__asm__ __volatile__(					\
+	"mrs	%0, cpsr		@ local_irq_save\n"	\
+"	orr	%1, %0, #128\n"					\
+"	msr	cpsr_c, %1"					\
+	: "=r" (x), "=r" (temp)					\
+	:							\
+	: "memory");						\
+	})
+
+/*
+ * restore saved IRQ & FIQ state
+ */
+#define local_irq_restore(x)					\
+	__asm__ __volatile__(					\
+	"msr	cpsr_c, %0		@ local_irq_restore\n"	\
+	:							\
+	: "r" (x)						\
+	: "memory")
+
+#define __save_flags_cli(x) local_irq_save(x)
+#define __restore_flags(x) local_irq_restore(x)
+
+typedef struct { volatile int counter; } atomic_t;
+
+#define ATOMIC_INIT(i)	{ (i) }
+
+#define atomic_read(v)	((v)->counter)
+#define atomic_set(v,i)	(((v)->counter) = (i))
+
+static __inline__ void atomic_add(int i, volatile atomic_t *v)
+{
+	unsigned long flags;
+
+	__save_flags_cli(flags);
+	v->counter += i;
+	__restore_flags(flags);
+}
+
+static __inline__ void atomic_sub(int i, volatile atomic_t *v)
+{
+	unsigned long flags;
+
+	__save_flags_cli(flags);
+	v->counter -= i;
+	__restore_flags(flags);
+}
+
+static __inline__ void atomic_inc(volatile atomic_t *v)
+{
+	unsigned long flags;
+
+	__save_flags_cli(flags);
+	v->counter += 1;
+	__restore_flags(flags);
+}
+
+static __inline__ void atomic_dec(volatile atomic_t *v)
+{
+	unsigned long flags;
+
+	__save_flags_cli(flags);
+	v->counter -= 1;
+	__restore_flags(flags);
+}
+
+static __inline__ int atomic_dec_and_test(volatile atomic_t *v)
+{
+	unsigned long flags;
+	int result;
+
+	__save_flags_cli(flags);
+	v->counter -= 1;
+	result = (v->counter == 0);
+	__restore_flags(flags);
+
+	return result;
+}
+
+static inline int atomic_add_negative(int i, volatile atomic_t *v)
+{
+	unsigned long flags;
+	int result;
+
+	__save_flags_cli(flags);
+	v->counter += i;
+	result = (v->counter < 0);
+	__restore_flags(flags);
+
+	return result;
+}
+
+static __inline__ void atomic_clear_mask(unsigned long mask, unsigned long *addr)
+{
+	unsigned long flags;
+
+	__save_flags_cli(flags);
+	*addr &= ~mask;
+	__restore_flags(flags);
+}
+
+#define mb() __asm__ __volatile__ ("" : : : "memory")
+#define rmb() mb()
+#define wmb() mb()
+
+#define IATOMIC_DEFINED		1
+
+#endif /* __arm__ */
+
+#ifdef __sh__
+
+typedef struct { volatile int counter; } atomic_t;
+
+#define ATOMIC_INIT(i)	{ (i) }
+
+#define atomic_read(v)			((v)->counter)
+#define atomic_set(v,i)			(((v)->counter) = (i))
+
+#define atomic_dec_return(v)		atomic_sub_return(1,(v))
+#define atomic_inc_return(v)		atomic_add_return(1,(v))
+
+#define atomic_sub_and_test(i,v)	(atomic_sub_return((i), (v)) == 0)
+#define atomic_dec_and_test(v)		(atomic_sub_return(1, (v)) == 0)
+#define atomic_inc_and_test(v)		(atomic_add_return(1, (v)) != 0)
+
+#define atomic_add(i,v)			atomic_add_return((i),(v))
+#define atomic_sub(i,v)			atomic_sub_return((i),(v))
+#define atomic_inc(v)			atomic_add(1,(v))
+#define atomic_dec(v)			atomic_sub(1,(v))
+
+static __inline__ int atomic_add_return(int i, volatile atomic_t *v)
+{
+	int result;
+
+	asm volatile (
+	"	.align	2\n"
+	"	mova	99f, r0\n"
+	"	mov	r15, r1\n"
+	"	mov	#-6, r15\n"
+	"	mov.l	@%2, %0\n"
+	"	add	%1, %0\n"
+	"	mov.l	%0, @%2\n"
+	"99:	mov	r1, r15"
+	: "=&r"(result)
+	: "r"(i), "r"(v)
+	: "r0", "r1");
+
+	return result;
+}
+
+static __inline__ int atomic_sub_return(int i, volatile atomic_t *v)
+{
+	int result;
+
+	asm volatile (
+	"	.align	2\n"
+	"	mova	99f, r0\n"
+	"	mov	r15, r1\n"
+	"	mov	#-6, r15\n"
+	"	mov.l	@%2, %0\n"
+	"	sub	%1, %0\n"
+	"	mov.l	%0, @%2\n"
+	"99:	mov	r1, r15"
+	: "=&r"(result)
+	: "r"(i), "r"(v)
+	: "r0", "r1");
+
+	return result;
+}
+
+#define mb() __asm__ __volatile__ ("" : : : "memory")
+#define rmb() mb()
+#define wmb() mb()
+
+#define IATOMIC_DEFINED		1
+
+#endif /* __sh__ */
+
+#ifdef __bfin__
+
+#include <bfin_fixed_code.h>
+
+typedef struct { volatile int counter; } atomic_t;
+
+#define ATOMIC_INIT(i)   { (i) }
+
+#define atomic_read(v)   ((v)->counter)
+#define atomic_set(v,i)  (((v)->counter) = (i))
+#define atomic_add(i,v)  bfin_atomic_add32(&(v)->counter, i)
+#define atomic_sub(i,v)  bfin_atomic_sub32(&(v)->counter, i)
+#define atomic_inc(v)    bfin_atomic_inc32(&(v)->counter);
+#define atomic_dec(v)    bfin_atomic_dec32(&(v)->counter);
+
+#define mb() __asm__ __volatile__ ("" : : : "memory")
+#define rmb() mb()
+#define wmb() mb()
+
+#define IATOMIC_DEFINED 1
+
+#endif /* __bfin__ */
+
+#ifndef IATOMIC_DEFINED
+/*
+ * non supported architecture.
+ */
+#warning "Atomic operations are not supported on this architecture."
+
+typedef struct { volatile int counter; } atomic_t;
+
+#define ATOMIC_INIT(i)	{ (i) }
+
+#define atomic_read(v)	((v)->counter)
+#define atomic_set(v,i)	(((v)->counter) = (i))
+#define atomic_add(i,v) (((v)->counter) += (i))
+#define atomic_sub(i,v) (((v)->counter) -= (i))
+#define atomic_inc(v)   (((v)->counter)++)
+#define atomic_dec(v)   (((v)->counter)--)
+
+#define mb()
+#define rmb()
+#define wmb()
+
+#define IATOMIC_DEFINED		1
+
+#endif /* IATOMIC_DEFINED */
+
+/*
+ *  Atomic read/write
+ *  Copyright (c) 2001 by Abramo Bagnara <abramo@alsa-project.org>
+ */
+
+/* Max number of times we must spin on a spin-lock calling sched_yield().
+   After MAX_SPIN_COUNT iterations, we put the calling thread to sleep. */
+
+#ifndef MAX_SPIN_COUNT
+#define MAX_SPIN_COUNT 50
+#endif
+
+/* Duration of sleep (in nanoseconds) when we can't acquire a spin-lock
+   after MAX_SPIN_COUNT iterations of sched_yield().
+   This MUST BE > 2ms.
+   (Otherwise the kernel does busy-waiting for real-time threads,
+    giving other threads no chance to run.) */
+
+#ifndef SPIN_SLEEP_DURATION
+#define SPIN_SLEEP_DURATION 2000001
+#endif
+
+typedef struct {
+	unsigned int begin, end;
+} snd_atomic_write_t;
+
+typedef struct {
+	volatile const snd_atomic_write_t *write;
+	unsigned int end;
+} snd_atomic_read_t;
+
+void snd_atomic_read_wait(snd_atomic_read_t *t);
+
+static inline void snd_atomic_write_init(snd_atomic_write_t *w)
+{
+	w->begin = 0;
+	w->end = 0;
+}
+
+static inline void snd_atomic_write_begin(snd_atomic_write_t *w)
+{
+	w->begin++;
+	wmb();
+}
+
+static inline void snd_atomic_write_end(snd_atomic_write_t *w)
+{
+	wmb();
+	w->end++;
+}
+
+static inline void snd_atomic_read_init(snd_atomic_read_t *r, snd_atomic_write_t *w)
+{
+	r->write = w;
+}
+
+static inline void snd_atomic_read_begin(snd_atomic_read_t *r)
+{
+	r->end = r->write->end;
+	rmb();
+}
+
+static inline int snd_atomic_read_ok(snd_atomic_read_t *r)
+{
+	rmb();
+	return r->end == r->write->begin;
+}
+
+#endif /* __ALSA_IATOMIC_H */
diff --git a/include/input.h b/include/input.h
new file mode 100644
index 0000000..fc5d0e6
--- /dev/null
+++ b/include/input.h
@@ -0,0 +1,83 @@
+/**
+ * \file include/input.h
+ * \brief Application interface library for the ALSA driver
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \author Takashi Iwai <tiwai@suse.de>
+ * \date 1998-2001
+ *
+ * Application interface library for the ALSA driver
+ */
+/*
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#ifndef __ALSA_INPUT_H
+#define __ALSA_INPUT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ *  \defgroup Input Input Interface
+ *
+ *  The input functions present an interface similar to the stdio functions
+ *  on top of different underlying input sources.
+ *
+ *  The #snd_config_load function uses such an input handle to be able to
+ *  load configurations not only from standard files but also from other
+ *  sources, e.g. from memory buffers.
+ *
+ *  \{
+ */
+
+/**
+ * \brief Internal structure for an input object.
+ *
+ * The ALSA library uses a pointer to this structure as a handle to an
+ * input object. Applications don't access its contents directly.
+ */
+typedef struct _snd_input snd_input_t;
+
+/** Input type. */
+typedef enum _snd_input_type {
+	/** Input from a stdio stream. */
+	SND_INPUT_STDIO,
+	/** Input from a memory buffer. */
+	SND_INPUT_BUFFER
+} snd_input_type_t;
+
+int snd_input_stdio_open(snd_input_t **inputp, const char *file, const char *mode);
+int snd_input_stdio_attach(snd_input_t **inputp, FILE *fp, int _close);
+int snd_input_buffer_open(snd_input_t **inputp, const char *buffer, ssize_t size);
+int snd_input_close(snd_input_t *input);
+int snd_input_scanf(snd_input_t *input, const char *format, ...)
+#ifndef DOC_HIDDEN
+	__attribute__ ((format (scanf, 2, 3)))
+#endif
+	;
+char *snd_input_gets(snd_input_t *input, char *str, size_t size);
+int snd_input_getc(snd_input_t *input);
+int snd_input_ungetc(snd_input_t *input, int c);
+
+/** \} */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __ALSA_INPUT_H */
diff --git a/include/list.h b/include/list.h
new file mode 100644
index 0000000..4d9895f
--- /dev/null
+++ b/include/list.h
@@ -0,0 +1,174 @@
+#ifndef _LIST_H
+#define _LIST_H
+
+/*
+ * This code was taken from the Linux 2.4.0 kernel. [jaroslav]
+ */
+
+/*
+ * Simple doubly linked list implementation.
+ *
+ * Some of the internal functions ("__xxx") are useful when
+ * manipulating whole lists rather than single entries, as
+ * sometimes we already know the next/prev entries and we can
+ * generate better code by using them directly rather than
+ * using the generic single-entry routines.
+ */
+
+#ifndef LIST_HEAD_IS_DEFINED
+struct list_head {
+	struct list_head *next, *prev;
+};
+#endif
+
+#define LIST_HEAD_INIT(name) { &(name), &(name) }
+
+#define LIST_HEAD(name) \
+	struct list_head name = LIST_HEAD_INIT(name)
+
+#define INIT_LIST_HEAD(ptr) do { \
+	(ptr)->next = (ptr); (ptr)->prev = (ptr); \
+} while (0)
+
+/*
+ * Insert a new entry between two known consecutive entries. 
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static __inline__ void __list_add(struct list_head * _new,
+				  struct list_head * prev,
+				  struct list_head * next)
+{
+	next->prev = _new;
+	_new->next = next;
+	_new->prev = prev;
+	prev->next = _new;
+}
+
+/**
+ * list_add - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it after
+ *
+ * Insert a new entry after the specified head.
+ * This is good for implementing stacks.
+ */
+static __inline__ void list_add(struct list_head *_new, struct list_head *head)
+{
+	__list_add(_new, head, head->next);
+}
+
+/**
+ * list_add_tail - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it before
+ *
+ * Insert a new entry before the specified head.
+ * This is useful for implementing queues.
+ */
+static __inline__ void list_add_tail(struct list_head *_new, struct list_head *head)
+{
+	__list_add(_new, head->prev, head);
+}
+
+/*
+ * Delete a list entry by making the prev/next entries
+ * point to each other.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static __inline__ void __list_del(struct list_head * prev,
+				  struct list_head * next)
+{
+	next->prev = prev;
+	prev->next = next;
+}
+
+/**
+ * list_del - deletes entry from list.
+ * @entry: the element to delete from the list.
+ * Note: list_empty on entry does not return true after this, the entry is in an undefined state.
+ */
+static __inline__ void list_del(struct list_head *entry)
+{
+	__list_del(entry->prev, entry->next);
+}
+
+/**
+ * list_del_init - deletes entry from list and reinitialize it.
+ * @entry: the element to delete from the list.n 
+ */
+static __inline__ void list_del_init(struct list_head *entry)
+{
+	__list_del(entry->prev, entry->next);
+	INIT_LIST_HEAD(entry); 
+}
+
+/**
+ * list_empty - tests whether a list is empty
+ * @head: the list to test.
+ */
+static __inline__ int list_empty(struct list_head *head)
+{
+	return head->next == head;
+}
+
+/**
+ * list_splice - join two lists
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ */
+static __inline__ void list_splice(struct list_head *list, struct list_head *head)
+{
+	struct list_head *first = list->next;
+
+	if (first != list) {
+		struct list_head *last = list->prev;
+		struct list_head *at = head->next;
+
+		first->prev = head;
+		head->next = first;
+
+		last->next = at;
+		at->prev = last;
+	}
+}
+
+/**
+ * list_for_each	-	iterate over a list
+ * @pos:	the &struct list_head to use as a loop counter.
+ * @head:	the head for your list.
+ */
+#define list_for_each(pos, head) \
+	for (pos = (head)->next ; pos != (head); pos = pos->next)
+
+/**
+ * list_for_each_safe	-	iterate over a list safely (actual pointer can be invalidated)
+ * @pos:	the &struct list_head to use as a loop counter.
+ * @next:	the &struct list_head to use to save next.
+ * @head:	the head for your list.
+ */
+#define list_for_each_safe(pos, npos, head) \
+	for (pos = (head)->next, npos = pos->next ; pos != (head); pos = npos, npos = pos->next)
+
+/**
+ * list_entry - get the struct for this entry
+ * @ptr:	the &struct list_head pointer.
+ * @type:	the type of the struct this is embedded in.
+ * @member:	the name of the list_struct within the struct.
+ */
+#define list_entry(ptr, type, member) \
+	((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))
+
+/**
+ * list_entry - get the struct for this entry
+ * @ptr:	the &struct list_head pointer.
+ * @type:	the type of the struct this is embedded in.
+ * @offset:	offset of entry inside a struct
+ */
+#define list_entry_offset(ptr, type, offset) \
+	((type *)((char *)(ptr)-(offset)))
+
+#endif /* _LIST_H */
diff --git a/include/local.h b/include/local.h
new file mode 100644
index 0000000..4dc6562
--- /dev/null
+++ b/include/local.h
@@ -0,0 +1,284 @@
+/*
+ *  ALSA lib - local header file
+ *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#ifndef __LOCAL_H
+#define __LOCAL_H
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <endian.h>
+#include <stdarg.h>
+#include <sys/poll.h>
+#include <errno.h>
+
+#include "config.h"
+#ifdef SUPPORT_RESMGR
+#include <resmgr.h>
+#endif
+#ifdef HAVE_LIBDL
+#include <dlfcn.h>
+#else
+#define RTLD_NOW	0
+#endif
+
+#define _snd_config_iterator list_head
+#define _snd_interval sndrv_interval
+#define _snd_pcm_info sndrv_pcm_info
+#define _snd_pcm_hw_params sndrv_pcm_hw_params
+#define _snd_pcm_sw_params sndrv_pcm_sw_params
+#define _snd_pcm_status sndrv_pcm_status
+
+#define _snd_ctl_card_info sndrv_ctl_card_info
+#define _snd_ctl_elem_id sndrv_ctl_elem_id
+#define _snd_ctl_elem_list sndrv_ctl_elem_list
+#define _snd_ctl_elem_info sndrv_ctl_elem_info
+#define _snd_ctl_elem_value sndrv_ctl_elem_value
+#define _snd_ctl_event sndrv_ctl_event
+
+#define _snd_rawmidi_info sndrv_rawmidi_info
+#define _snd_rawmidi_params sndrv_rawmidi_params
+#define _snd_rawmidi_status sndrv_rawmidi_status
+
+#define _snd_hwdep_info sndrv_hwdep_info
+#define _snd_hwdep_dsp_status sndrv_hwdep_dsp_status
+#define _snd_hwdep_dsp_image sndrv_hwdep_dsp_image
+
+#define _snd_seq_queue_tempo sndrv_seq_queue_tempo
+#define _snd_seq_client_info sndrv_seq_client_info
+#define _snd_seq_port_info sndrv_seq_port_info
+#define _snd_seq_system_info sndrv_seq_system_info
+#define _snd_seq_queue_info sndrv_seq_queue_info
+#define _snd_seq_queue_status sndrv_seq_queue_status
+#define _snd_seq_queue_timer sndrv_seq_queue_timer
+#define _snd_seq_port_subscribe sndrv_seq_port_subscribe
+#define _snd_seq_query_subscribe sndrv_seq_query_subs
+#define _snd_seq_client_pool sndrv_seq_client_pool
+#define _snd_seq_remove_events sndrv_seq_remove_events
+
+#define sndrv_seq_addr	snd_seq_addr
+#define sndrv_seq_tick_time_t	snd_seq_tick_time_t
+#define sndrv_seq_real_time	snd_seq_real_time
+#define sndrv_seq_timestamp	snd_seq_timestamp
+#define sndrv_seq_event		snd_seq_event
+
+#if 0
+typedef struct sndrv_seq_addr snd_seq_addr_t;
+#define snd_seq_tick_time_t sndrv_seq_tick_time_t
+typedef struct sndrv_seq_real_time snd_seq_real_time_t;
+typedef union sndrv_seq_timestamp snd_seq_timestamp_t;
+typedef struct sndrv_seq_event snd_seq_event_t;
+#endif
+
+#define _snd_timer_id sndrv_timer_id
+#define _snd_timer_ginfo sndrv_timer_ginfo
+#define _snd_timer_gparams sndrv_timer_gparams
+#define _snd_timer_gstatus sndrv_timer_gstatus
+#define _snd_timer_select sndrv_timer_select
+#define _snd_timer_info sndrv_timer_info
+#define _snd_timer_params sndrv_timer_params
+#define _snd_timer_status sndrv_timer_status
+
+#define ALSA_LIBRARY_BUILD
+
+#include <sound/asound.h>
+#include <sound/asoundef.h>
+#include "alsa-symbols.h"
+#include "version.h"
+#include "global.h"
+#include "input.h"
+#include "output.h"
+#include "error.h"
+#include "conf.h"
+#include "pcm.h"
+#include "pcm_plugin.h"
+#include "rawmidi.h"
+#include "timer.h"
+#include "hwdep.h"
+#include "control.h"
+#include "mixer.h"
+#include "seq_event.h"
+#include "seq.h"
+#include <sound/asequencer.h>
+#include "seqmid.h"
+#include "seq_midi_event.h"
+#include "list.h"
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+#define SND_LITTLE_ENDIAN
+#endif
+#if __BYTE_ORDER == __BIG_ENDIAN
+#define SND_BIG_ENDIAN
+#endif
+
+struct _snd_async_handler {
+	enum {
+		SND_ASYNC_HANDLER_GENERIC,
+		SND_ASYNC_HANDLER_CTL,
+		SND_ASYNC_HANDLER_PCM,
+		SND_ASYNC_HANDLER_TIMER,
+	} type;
+	int fd;
+	union {
+		snd_ctl_t *ctl;
+		snd_pcm_t *pcm;
+		snd_timer_t *timer;
+	} u;
+	snd_async_callback_t callback;
+	void *private_data;
+	struct list_head glist;
+	struct list_head hlist;
+};
+
+typedef enum _snd_set_mode {
+	SND_CHANGE,
+	SND_TRY,
+	SND_TEST,
+} snd_set_mode_t;
+
+size_t page_align(size_t size);
+size_t page_size(void);
+size_t page_ptr(size_t object_offset, size_t object_size, size_t *offset, size_t *mmap_offset);
+
+int safe_strtol(const char *str, long *val);
+
+int snd_send_fd(int sock, void *data, size_t len, int fd);
+int snd_receive_fd(int sock, void *data, size_t len, int *fd);
+
+/*
+ * error messages
+ */
+#ifndef NDEBUG
+#define CHECK_SANITY(x) x
+extern snd_lib_error_handler_t snd_err_msg;
+#define SNDMSG(args...) snd_err_msg(__FILE__, __LINE__, __FUNCTION__, 0, ##args)
+#define SYSMSG(args...) snd_err_msg(__FILE__, __LINE__, __FUNCTION__, errno, ##args)
+#else
+#define CHECK_SANITY(x) 0 /* not evaluated */
+#define SNDMSG(args...) /* nop */
+#define SYSMSG(args...) /* nop */
+#endif
+
+/*
+ */
+#define HAVE_GNU_LD
+#define HAVE_ELF
+#define HAVE_ASM_PREVIOUS_DIRECTIVE
+
+/* Stolen from libc-symbols.h in GNU glibc */
+
+/* When a reference to SYMBOL is encountered, the linker will emit a
+   warning message MSG.  */
+
+#define ASM_NAME(name) __SYMBOL_PREFIX name
+
+#ifdef HAVE_GNU_LD
+# ifdef HAVE_ELF
+
+/* We want the .gnu.warning.SYMBOL section to be unallocated.  */
+#  ifdef HAVE_ASM_PREVIOUS_DIRECTIVE
+#   define __make_section_unallocated(section_string)	\
+  asm (".section " section_string "\n\t.previous");
+#  elif defined HAVE_ASM_POPSECTION_DIRECTIVE
+#   define __make_section_unallocated(section_string)	\
+  asm (".pushsection " section_string "\n\t.popsection");
+#  else
+#   define __make_section_unallocated(section_string)
+#  endif
+
+/* Tacking on "\n\t#" to the section name makes gcc put it's bogus
+   section attributes on what looks like a comment to the assembler.  */
+#  ifdef HAVE_SECTION_QUOTES
+#   define link_warning(symbol, msg) \
+  __make_section_unallocated (".gnu.warning." ASM_NAME(#symbol)) \
+  static const char __evoke_link_warning_##symbol[]	\
+    __attribute__ ((section (".gnu.warning." ASM_NAME(#symbol) "\"\n\t#\""))) = msg;
+#  else
+#   define link_warning(symbol, msg) \
+  __make_section_unallocated (".gnu.warning." ASM_NAME(#symbol)) \
+  static const char __evoke_link_warning_##symbol[]	\
+    __attribute__ ((section (".gnu.warning." ASM_NAME(#symbol) "\n\t#"))) = msg;
+#  endif
+# else
+#  define link_warning(symbol, msg)		\
+  asm (".stabs \"" msg "\",30,0,0,0\n\t"	\
+       ".stabs \"" ASM_NAME(#symbol) "\",1,0,0,0\n");
+# endif
+#else
+/* We will never be heard; they will all die horribly.  */
+# define link_warning(symbol, msg)
+#endif
+
+static inline int snd_open_device(const char *filename, int fmode)
+{
+	int fd;
+
+#ifdef O_CLOEXEC
+	fmode |= O_CLOEXEC;
+#endif
+	fd = open(filename, fmode);
+
+/* open with resmgr */
+#ifdef SUPPORT_RESMGR
+	if (fd < 0) {
+		if (errno == EAGAIN || errno == EBUSY)
+			return fd;
+		if (! access(filename, F_OK))
+			fd = rsm_open_device(filename, fmode);
+	}
+#endif
+	if (fd >= 0)
+		fcntl(fd, F_SETFD, FD_CLOEXEC);
+	return fd;
+}
+
+/* make local functions really local */
+#define snd_dlobj_cache_get \
+	snd1_dlobj_cache_get
+#define snd_dlobj_cache_put \
+	snd1_dlobj_cache_put
+#define snd_dlobj_cache_cleanup \
+	snd1_dlobj_cache_cleanup
+#define snd_config_set_hop \
+	snd1_config_set_hop
+#define snd_config_check_hop \
+	snd1_config_check_hop
+#define snd_config_search_alias_hooks \
+	snd1_config_search_alias_hooks
+
+/* dlobj cache */
+void *snd_dlobj_cache_get(const char *lib, const char *name, const char *version, int verbose);
+int snd_dlobj_cache_put(void *open_func);
+void snd_dlobj_cache_cleanup(void);
+
+/* for recursive checks */
+void snd_config_set_hop(snd_config_t *conf, int hop);
+int snd_config_check_hop(snd_config_t *conf);
+#define SND_CONF_MAX_HOPS	64
+
+int snd_config_search_alias_hooks(snd_config_t *config,
+                                  const char *base, const char *key,
+				  snd_config_t **result);
+
+#endif
diff --git a/include/mixer.h b/include/mixer.h
new file mode 100644
index 0000000..58256a6
--- /dev/null
+++ b/include/mixer.h
@@ -0,0 +1,317 @@
+/**
+ * \file include/mixer.h
+ * \brief Application interface library for the ALSA driver
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \author Takashi Iwai <tiwai@suse.de>
+ * \date 1998-2001
+ *
+ * Application interface library for the ALSA driver
+ */
+/*
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#ifndef __ALSA_MIXER_H
+#define __ALSA_MIXER_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ *  \defgroup Mixer Mixer Interface
+ *  The mixer interface.
+ *  \{
+ */
+
+/** Mixer handle */
+typedef struct _snd_mixer snd_mixer_t;
+/** Mixer elements class handle */
+typedef struct _snd_mixer_class snd_mixer_class_t;
+/** Mixer element handle */
+typedef struct _snd_mixer_elem snd_mixer_elem_t;
+
+/** 
+ * \brief Mixer callback function
+ * \param mixer Mixer handle
+ * \param mask event mask
+ * \param elem related mixer element (if any)
+ * \return 0 on success otherwise a negative error code
+ */
+typedef int (*snd_mixer_callback_t)(snd_mixer_t *ctl,
+				    unsigned int mask,
+				    snd_mixer_elem_t *elem);
+
+/** 
+ * \brief Mixer element callback function
+ * \param elem Mixer element
+ * \param mask event mask
+ * \return 0 on success otherwise a negative error code
+ */
+typedef int (*snd_mixer_elem_callback_t)(snd_mixer_elem_t *elem,
+					 unsigned int mask);
+
+/**
+ * \brief Compare function for sorting mixer elements
+ * \param e1 First element
+ * \param e2 Second element
+ * \return -1 if e1 < e2, 0 if e1 == e2, 1 if e1 > e2
+ */
+typedef int (*snd_mixer_compare_t)(const snd_mixer_elem_t *e1,
+				   const snd_mixer_elem_t *e2);
+
+/**
+ * \brief Event callback for the mixer class
+ * \param class_ Mixer class
+ * \param mask Event mask (SND_CTL_EVENT_*)
+ * \param helem HCTL element which invoked the event
+ * \param melem Mixer element associated to HCTL element
+ * \return zero if success, otherwise a negative error value
+ */
+typedef int (*snd_mixer_event_t)(snd_mixer_class_t *class_, unsigned int mask,
+				 snd_hctl_elem_t *helem, snd_mixer_elem_t *melem);
+
+
+/** Mixer element type */
+typedef enum _snd_mixer_elem_type {
+	/* Simple mixer elements */
+	SND_MIXER_ELEM_SIMPLE,
+	SND_MIXER_ELEM_LAST = SND_MIXER_ELEM_SIMPLE
+} snd_mixer_elem_type_t;
+
+int snd_mixer_open(snd_mixer_t **mixer, int mode);
+int snd_mixer_close(snd_mixer_t *mixer);
+snd_mixer_elem_t *snd_mixer_first_elem(snd_mixer_t *mixer);
+snd_mixer_elem_t *snd_mixer_last_elem(snd_mixer_t *mixer);
+int snd_mixer_handle_events(snd_mixer_t *mixer);
+int snd_mixer_attach(snd_mixer_t *mixer, const char *name);
+int snd_mixer_attach_hctl(snd_mixer_t *mixer, snd_hctl_t *hctl);
+int snd_mixer_detach(snd_mixer_t *mixer, const char *name);
+int snd_mixer_detach_hctl(snd_mixer_t *mixer, snd_hctl_t *hctl);
+int snd_mixer_get_hctl(snd_mixer_t *mixer, const char *name, snd_hctl_t **hctl);
+int snd_mixer_poll_descriptors_count(snd_mixer_t *mixer);
+int snd_mixer_poll_descriptors(snd_mixer_t *mixer, struct pollfd *pfds, unsigned int space);
+int snd_mixer_poll_descriptors_revents(snd_mixer_t *mixer, struct pollfd *pfds, unsigned int nfds, unsigned short *revents);
+int snd_mixer_load(snd_mixer_t *mixer);
+void snd_mixer_free(snd_mixer_t *mixer);
+int snd_mixer_wait(snd_mixer_t *mixer, int timeout);
+int snd_mixer_set_compare(snd_mixer_t *mixer, snd_mixer_compare_t msort);
+void snd_mixer_set_callback(snd_mixer_t *obj, snd_mixer_callback_t val);
+void * snd_mixer_get_callback_private(const snd_mixer_t *obj);
+void snd_mixer_set_callback_private(snd_mixer_t *obj, void * val);
+unsigned int snd_mixer_get_count(const snd_mixer_t *obj);
+int snd_mixer_class_unregister(snd_mixer_class_t *clss);
+
+snd_mixer_elem_t *snd_mixer_elem_next(snd_mixer_elem_t *elem);
+snd_mixer_elem_t *snd_mixer_elem_prev(snd_mixer_elem_t *elem);
+void snd_mixer_elem_set_callback(snd_mixer_elem_t *obj, snd_mixer_elem_callback_t val);
+void * snd_mixer_elem_get_callback_private(const snd_mixer_elem_t *obj);
+void snd_mixer_elem_set_callback_private(snd_mixer_elem_t *obj, void * val);
+snd_mixer_elem_type_t snd_mixer_elem_get_type(const snd_mixer_elem_t *obj);
+
+int snd_mixer_class_register(snd_mixer_class_t *class_, snd_mixer_t *mixer);
+int snd_mixer_elem_new(snd_mixer_elem_t **elem,
+		       snd_mixer_elem_type_t type,
+		       int compare_weight,
+		       void *private_data,
+		       void (*private_free)(snd_mixer_elem_t *elem));
+int snd_mixer_elem_add(snd_mixer_elem_t *elem, snd_mixer_class_t *class_);
+int snd_mixer_elem_remove(snd_mixer_elem_t *elem);
+void snd_mixer_elem_free(snd_mixer_elem_t *elem);
+int snd_mixer_elem_info(snd_mixer_elem_t *elem);
+int snd_mixer_elem_value(snd_mixer_elem_t *elem);
+int snd_mixer_elem_attach(snd_mixer_elem_t *melem, snd_hctl_elem_t *helem);
+int snd_mixer_elem_detach(snd_mixer_elem_t *melem, snd_hctl_elem_t *helem);
+int snd_mixer_elem_empty(snd_mixer_elem_t *melem);
+void *snd_mixer_elem_get_private(const snd_mixer_elem_t *melem);
+
+size_t snd_mixer_class_sizeof(void);
+/** \hideinitializer
+ * \brief allocate an invalid #snd_mixer_class_t using standard alloca
+ * \param ptr returned pointer
+ */
+#define snd_mixer_class_alloca(ptr) __snd_alloca(ptr, snd_mixer_class)
+int snd_mixer_class_malloc(snd_mixer_class_t **ptr);
+void snd_mixer_class_free(snd_mixer_class_t *obj);
+void snd_mixer_class_copy(snd_mixer_class_t *dst, const snd_mixer_class_t *src);
+snd_mixer_t *snd_mixer_class_get_mixer(const snd_mixer_class_t *class_);
+snd_mixer_event_t snd_mixer_class_get_event(const snd_mixer_class_t *class_);
+void *snd_mixer_class_get_private(const snd_mixer_class_t *class_);
+snd_mixer_compare_t snd_mixer_class_get_compare(const snd_mixer_class_t *class_);
+int snd_mixer_class_set_event(snd_mixer_class_t *class_, snd_mixer_event_t event);
+int snd_mixer_class_set_private(snd_mixer_class_t *class_, void *private_data);
+int snd_mixer_class_set_private_free(snd_mixer_class_t *class_, void (*private_free)(snd_mixer_class_t *class_));
+int snd_mixer_class_set_compare(snd_mixer_class_t *class_, snd_mixer_compare_t compare);
+
+/**
+ *  \defgroup SimpleMixer Simple Mixer Interface
+ *  \ingroup Mixer
+ *  The simple mixer interface.
+ *  \{
+ */
+
+/* Simple mixer elements API */
+
+/** Mixer simple element channel identifier */
+typedef enum _snd_mixer_selem_channel_id {
+	/** Unknown */
+	SND_MIXER_SCHN_UNKNOWN = -1,
+	/** Front left */
+	SND_MIXER_SCHN_FRONT_LEFT = 0,
+	/** Front right */
+	SND_MIXER_SCHN_FRONT_RIGHT,
+	/** Rear left */
+	SND_MIXER_SCHN_REAR_LEFT,
+	/** Rear right */
+	SND_MIXER_SCHN_REAR_RIGHT,
+	/** Front center */
+	SND_MIXER_SCHN_FRONT_CENTER,
+	/** Woofer */
+	SND_MIXER_SCHN_WOOFER,
+	/** Side Left */
+	SND_MIXER_SCHN_SIDE_LEFT,
+	/** Side Right */
+	SND_MIXER_SCHN_SIDE_RIGHT,
+	/** Rear Center */
+	SND_MIXER_SCHN_REAR_CENTER,
+	SND_MIXER_SCHN_LAST = 31,
+	/** Mono (Front left alias) */
+	SND_MIXER_SCHN_MONO = SND_MIXER_SCHN_FRONT_LEFT
+} snd_mixer_selem_channel_id_t;
+
+/** Mixer simple element - register options - abstraction level */
+enum snd_mixer_selem_regopt_abstract {
+	/** no abstraction - try use all universal controls from driver */
+	SND_MIXER_SABSTRACT_NONE = 0,
+	/** basic abstraction - Master,PCM,CD,Aux,Record-Gain etc. */
+	SND_MIXER_SABSTRACT_BASIC,
+};
+
+/** Mixer simple element - register options */
+struct snd_mixer_selem_regopt {
+	/** structure version */
+	int ver;
+	/** v1: abstract layer selection */
+	enum snd_mixer_selem_regopt_abstract abstract;
+	/** v1: device name (must be NULL when playback_pcm or capture_pcm != NULL) */
+	const char *device;
+	/** v1: playback PCM connected to mixer device (NULL == none) */
+	snd_pcm_t *playback_pcm;
+	/** v1: capture PCM connected to mixer device (NULL == none) */
+	snd_pcm_t *capture_pcm;
+};
+
+/** Mixer simple element identifier */
+typedef struct _snd_mixer_selem_id snd_mixer_selem_id_t;
+
+const char *snd_mixer_selem_channel_name(snd_mixer_selem_channel_id_t channel);
+
+int snd_mixer_selem_register(snd_mixer_t *mixer,
+			     struct snd_mixer_selem_regopt *options,
+			     snd_mixer_class_t **classp);
+void snd_mixer_selem_get_id(snd_mixer_elem_t *element,
+			    snd_mixer_selem_id_t *id);
+const char *snd_mixer_selem_get_name(snd_mixer_elem_t *elem);
+unsigned int snd_mixer_selem_get_index(snd_mixer_elem_t *elem);
+snd_mixer_elem_t *snd_mixer_find_selem(snd_mixer_t *mixer,
+				       const snd_mixer_selem_id_t *id);
+
+int snd_mixer_selem_is_active(snd_mixer_elem_t *elem);
+int snd_mixer_selem_is_playback_mono(snd_mixer_elem_t *elem);
+int snd_mixer_selem_has_playback_channel(snd_mixer_elem_t *obj, snd_mixer_selem_channel_id_t channel);
+int snd_mixer_selem_is_capture_mono(snd_mixer_elem_t *elem);
+int snd_mixer_selem_has_capture_channel(snd_mixer_elem_t *obj, snd_mixer_selem_channel_id_t channel);
+int snd_mixer_selem_get_capture_group(snd_mixer_elem_t *elem);
+int snd_mixer_selem_has_common_volume(snd_mixer_elem_t *elem);
+int snd_mixer_selem_has_playback_volume(snd_mixer_elem_t *elem);
+int snd_mixer_selem_has_playback_volume_joined(snd_mixer_elem_t *elem);
+int snd_mixer_selem_has_capture_volume(snd_mixer_elem_t *elem);
+int snd_mixer_selem_has_capture_volume_joined(snd_mixer_elem_t *elem);
+int snd_mixer_selem_has_common_switch(snd_mixer_elem_t *elem);
+int snd_mixer_selem_has_playback_switch(snd_mixer_elem_t *elem);
+int snd_mixer_selem_has_playback_switch_joined(snd_mixer_elem_t *elem);
+int snd_mixer_selem_has_capture_switch(snd_mixer_elem_t *elem);
+int snd_mixer_selem_has_capture_switch_joined(snd_mixer_elem_t *elem);
+int snd_mixer_selem_has_capture_switch_exclusive(snd_mixer_elem_t *elem);
+
+int snd_mixer_selem_ask_playback_vol_dB(snd_mixer_elem_t *elem, long value, long *dBvalue);
+int snd_mixer_selem_ask_capture_vol_dB(snd_mixer_elem_t *elem, long value, long *dBvalue);
+int snd_mixer_selem_ask_playback_dB_vol(snd_mixer_elem_t *elem, long dBvalue, int dir, long *value);
+int snd_mixer_selem_ask_capture_dB_vol(snd_mixer_elem_t *elem, long dBvalue, int dir, long *value);
+int snd_mixer_selem_get_playback_volume(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, long *value);
+int snd_mixer_selem_get_capture_volume(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, long *value);
+int snd_mixer_selem_get_playback_dB(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, long *value);
+int snd_mixer_selem_get_capture_dB(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, long *value);
+int snd_mixer_selem_get_playback_switch(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, int *value);
+int snd_mixer_selem_get_capture_switch(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, int *value);
+int snd_mixer_selem_set_playback_volume(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, long value);
+int snd_mixer_selem_set_capture_volume(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, long value);
+int snd_mixer_selem_set_playback_dB(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, long value, int dir);
+int snd_mixer_selem_set_capture_dB(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, long value, int dir);
+int snd_mixer_selem_set_playback_volume_all(snd_mixer_elem_t *elem, long value);
+int snd_mixer_selem_set_capture_volume_all(snd_mixer_elem_t *elem, long value);
+int snd_mixer_selem_set_playback_dB_all(snd_mixer_elem_t *elem, long value, int dir);
+int snd_mixer_selem_set_capture_dB_all(snd_mixer_elem_t *elem, long value, int dir);
+int snd_mixer_selem_set_playback_switch(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, int value);
+int snd_mixer_selem_set_capture_switch(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, int value);
+int snd_mixer_selem_set_playback_switch_all(snd_mixer_elem_t *elem, int value);
+int snd_mixer_selem_set_capture_switch_all(snd_mixer_elem_t *elem, int value);
+int snd_mixer_selem_get_playback_volume_range(snd_mixer_elem_t *elem, 
+					      long *min, long *max);
+int snd_mixer_selem_get_playback_dB_range(snd_mixer_elem_t *elem, 
+					  long *min, long *max);
+int snd_mixer_selem_set_playback_volume_range(snd_mixer_elem_t *elem, 
+					      long min, long max);
+int snd_mixer_selem_get_capture_volume_range(snd_mixer_elem_t *elem, 
+					     long *min, long *max);
+int snd_mixer_selem_get_capture_dB_range(snd_mixer_elem_t *elem, 
+					 long *min, long *max);
+int snd_mixer_selem_set_capture_volume_range(snd_mixer_elem_t *elem, 
+					     long min, long max);
+
+int snd_mixer_selem_is_enumerated(snd_mixer_elem_t *elem);
+int snd_mixer_selem_is_enum_playback(snd_mixer_elem_t *elem);
+int snd_mixer_selem_is_enum_capture(snd_mixer_elem_t *elem);
+int snd_mixer_selem_get_enum_items(snd_mixer_elem_t *elem);
+int snd_mixer_selem_get_enum_item_name(snd_mixer_elem_t *elem, unsigned int idx, size_t maxlen, char *str);
+int snd_mixer_selem_get_enum_item(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, unsigned int *idxp);
+int snd_mixer_selem_set_enum_item(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, unsigned int idx);
+
+size_t snd_mixer_selem_id_sizeof(void);
+/** \hideinitializer
+ * \brief allocate an invalid #snd_mixer_selem_id_t using standard alloca
+ * \param ptr returned pointer
+ */
+#define snd_mixer_selem_id_alloca(ptr) __snd_alloca(ptr, snd_mixer_selem_id)
+int snd_mixer_selem_id_malloc(snd_mixer_selem_id_t **ptr);
+void snd_mixer_selem_id_free(snd_mixer_selem_id_t *obj);
+void snd_mixer_selem_id_copy(snd_mixer_selem_id_t *dst, const snd_mixer_selem_id_t *src);
+const char *snd_mixer_selem_id_get_name(const snd_mixer_selem_id_t *obj);
+unsigned int snd_mixer_selem_id_get_index(const snd_mixer_selem_id_t *obj);
+void snd_mixer_selem_id_set_name(snd_mixer_selem_id_t *obj, const char *val);
+void snd_mixer_selem_id_set_index(snd_mixer_selem_id_t *obj, unsigned int val);
+
+/** \} */
+
+/** \} */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __ALSA_MIXER_H */
+
diff --git a/include/mixer_abst.h b/include/mixer_abst.h
new file mode 100644
index 0000000..7844b19
--- /dev/null
+++ b/include/mixer_abst.h
@@ -0,0 +1,112 @@
+/**
+ * \file include/mixer_abst.h
+ * \brief Mixer abstract implementation interface library for the ALSA library
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \date 2005
+ *
+ * Mixer abstact implementation interface library for the ALSA library
+ */
+/*
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#ifndef __ALSA_MIXER_ABST_H
+#define __ALSA_MIXER_ABST_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ *  \defgroup Mixer_Abstract Mixer Abstact Module Interface
+ *  The mixer abstact module interface.
+ *  \{
+ */
+
+#define	SM_PLAY			0
+#define SM_CAPT			1
+
+#define SM_CAP_GVOLUME		(1<<1)
+#define SM_CAP_GSWITCH		(1<<2)
+#define SM_CAP_PVOLUME		(1<<3)
+#define SM_CAP_PVOLUME_JOIN	(1<<4)
+#define SM_CAP_PSWITCH		(1<<5) 
+#define SM_CAP_PSWITCH_JOIN	(1<<6) 
+#define SM_CAP_CVOLUME		(1<<7) 
+#define SM_CAP_CVOLUME_JOIN	(1<<8) 
+#define SM_CAP_CSWITCH		(1<<9) 
+#define SM_CAP_CSWITCH_JOIN	(1<<10)
+#define SM_CAP_CSWITCH_EXCL	(1<<11)
+#define SM_CAP_PENUM		(1<<12)
+#define SM_CAP_CENUM		(1<<13)
+/* SM_CAP_* 24-31 => private for module use */
+
+#define SM_OPS_IS_ACTIVE	0
+#define SM_OPS_IS_MONO		1
+#define SM_OPS_IS_CHANNEL	2
+#define SM_OPS_IS_ENUMERATED	3
+#define SM_OPS_IS_ENUMCNT	4
+
+#define sm_selem(x)		((sm_selem_t *)((x)->private_data))
+#define sm_selem_ops(x)		((sm_selem_t *)((x)->private_data))->ops
+
+typedef struct _sm_selem {
+	snd_mixer_selem_id_t *id;
+	struct sm_elem_ops *ops;
+	unsigned int caps;
+	unsigned int capture_group;
+} sm_selem_t;
+
+typedef struct _sm_class_basic {
+	char *device;
+	snd_ctl_t *ctl;
+	snd_hctl_t *hctl;
+	snd_ctl_card_info_t *info;
+} sm_class_basic_t;
+
+struct sm_elem_ops {	
+	int (*is)(snd_mixer_elem_t *elem, int dir, int cmd, int val);
+	int (*get_range)(snd_mixer_elem_t *elem, int dir, long *min, long *max);
+	int (*set_range)(snd_mixer_elem_t *elem, int dir, long min, long max);
+	int (*get_dB_range)(snd_mixer_elem_t *elem, int dir, long *min, long *max);
+	int (*ask_vol_dB)(snd_mixer_elem_t *elem, int dir, long value, long *dbValue);
+	int (*ask_dB_vol)(snd_mixer_elem_t *elem, int dir, long dbValue, long *value, int xdir);
+	int (*get_volume)(snd_mixer_elem_t *elem, int dir, snd_mixer_selem_channel_id_t channel, long *value);
+	int (*get_dB)(snd_mixer_elem_t *elem, int dir, snd_mixer_selem_channel_id_t channel, long *value);
+	int (*set_volume)(snd_mixer_elem_t *elem, int dir, snd_mixer_selem_channel_id_t channel, long value);
+	int (*set_dB)(snd_mixer_elem_t *elem, int dir, snd_mixer_selem_channel_id_t channel, long value, int xdir);
+	int (*get_switch)(snd_mixer_elem_t *elem, int dir, snd_mixer_selem_channel_id_t channel, int *value);
+	int (*set_switch)(snd_mixer_elem_t *elem, int dir, snd_mixer_selem_channel_id_t channel, int value);
+	int (*enum_item_name)(snd_mixer_elem_t *elem, unsigned int item, size_t maxlen, char *buf);
+	int (*get_enum_item)(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, unsigned int *itemp);
+	int (*set_enum_item)(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, unsigned int item);
+};
+
+int snd_mixer_selem_compare(const snd_mixer_elem_t *c1, const snd_mixer_elem_t *c2);
+
+int snd_mixer_sbasic_info(const snd_mixer_class_t *class, sm_class_basic_t *info);
+void *snd_mixer_sbasic_get_private(const snd_mixer_class_t *class);
+void snd_mixer_sbasic_set_private(const snd_mixer_class_t *class, void *private_data);
+void snd_mixer_sbasic_set_private_free(const snd_mixer_class_t *class, void (*private_free)(snd_mixer_class_t *class));
+
+/** \} */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __ALSA_MIXER_ABST_H */
+
diff --git a/include/output.h b/include/output.h
new file mode 100644
index 0000000..5279aa2
--- /dev/null
+++ b/include/output.h
@@ -0,0 +1,86 @@
+/**
+ * \file include/output.h
+ * \brief Application interface library for the ALSA driver
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \author Takashi Iwai <tiwai@suse.de>
+ * \date 1998-2001
+ *
+ * Application interface library for the ALSA driver
+ */
+/*
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#ifndef __ALSA_OUTPUT_H
+#define __ALSA_OUTPUT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ *  \defgroup Output Output Interface
+ *
+ *  The output functions present an interface similar to the stdio functions
+ *  on top of different underlying output destinations.
+ *
+ *  Many PCM debugging functions (\c snd_pcm_xxx_dump_xxx) use such an output
+ *  handle to be able to write not only to the screen but also to other
+ *  destinations, e.g. to files or to memory buffers.
+ *
+ *  \{
+ */
+
+/**
+ * \brief Internal structure for an output object.
+ *
+ * The ALSA library uses a pointer to this structure as a handle to an
+ * output object. Applications don't access its contents directly.
+ */
+typedef struct _snd_output snd_output_t;
+
+/** Output type. */
+typedef enum _snd_output_type {
+	/** Output to a stdio stream. */
+	SND_OUTPUT_STDIO,
+	/** Output to a memory buffer. */
+	SND_OUTPUT_BUFFER
+} snd_output_type_t;
+
+int snd_output_stdio_open(snd_output_t **outputp, const char *file, const char *mode);
+int snd_output_stdio_attach(snd_output_t **outputp, FILE *fp, int _close);
+int snd_output_buffer_open(snd_output_t **outputp);
+size_t snd_output_buffer_string(snd_output_t *output, char **buf);
+int snd_output_close(snd_output_t *output);
+int snd_output_printf(snd_output_t *output, const char *format, ...)
+#ifndef DOC_HIDDEN
+	__attribute__ ((format (printf, 2, 3)))
+#endif
+	;
+int snd_output_vprintf(snd_output_t *output, const char *format, va_list args);
+int snd_output_puts(snd_output_t *output, const char *str);
+int snd_output_putc(snd_output_t *output, int c);
+int snd_output_flush(snd_output_t *output);
+
+/** \} */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __ALSA_OUTPUT_H */
+
diff --git a/include/pcm.h b/include/pcm.h
new file mode 100644
index 0000000..4997557
--- /dev/null
+++ b/include/pcm.h
@@ -0,0 +1,1160 @@
+/**
+ * \file include/pcm.h
+ * \brief Application interface library for the ALSA driver
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \author Takashi Iwai <tiwai@suse.de>
+ * \date 1998-2001
+ *
+ * Application interface library for the ALSA driver.
+ * See the \ref pcm page for more details.
+ */
+/*
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#ifndef __ALSA_PCM_H
+#define __ALSA_PCM_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ *  \defgroup PCM PCM Interface
+ *  See the \ref pcm page for more details.
+ *  \{
+ */
+
+/** dlsym version for interface entry callback */
+#define SND_PCM_DLSYM_VERSION		_dlsym_pcm_001
+
+/** PCM generic info container */
+typedef struct _snd_pcm_info snd_pcm_info_t;
+
+/** PCM hardware configuration space container
+ *
+ *  snd_pcm_hw_params_t is an opaque structure which contains a set of possible
+ *  PCM hardware configurations. For example, a given instance might include a
+ *  range of buffer sizes, a range of period sizes, and a set of several sample
+ *  formats. Some subset of all possible combinations these sets may be valid,
+ *  but not necessarily any combination will be valid.
+ *
+ *  When a parameter is set or restricted using a snd_pcm_hw_params_set*
+ *  function, all of the other ranges will be updated to exclude as many
+ *  impossible configurations as possible. Attempting to set a parameter
+ *  outside of its acceptable range will result in the function failing
+ *  and an error code being returned.
+ */
+typedef struct _snd_pcm_hw_params snd_pcm_hw_params_t;
+
+/** PCM software configuration container */
+typedef struct _snd_pcm_sw_params snd_pcm_sw_params_t;
+/** PCM status container */
+ typedef struct _snd_pcm_status snd_pcm_status_t;
+/** PCM access types mask */
+typedef struct _snd_pcm_access_mask snd_pcm_access_mask_t;
+/** PCM formats mask */
+typedef struct _snd_pcm_format_mask snd_pcm_format_mask_t;
+/** PCM subformats mask */
+typedef struct _snd_pcm_subformat_mask snd_pcm_subformat_mask_t;
+
+/** PCM class */
+typedef enum _snd_pcm_class {
+	/** standard device */
+
+	SND_PCM_CLASS_GENERIC = 0,
+	/** multichannel device */
+	SND_PCM_CLASS_MULTI,
+	/** software modem device */
+	SND_PCM_CLASS_MODEM,
+	/** digitizer device */
+	SND_PCM_CLASS_DIGITIZER,
+	SND_PCM_CLASS_LAST = SND_PCM_CLASS_DIGITIZER
+} snd_pcm_class_t;
+
+/** PCM subclass */
+typedef enum _snd_pcm_subclass {
+	/** subdevices are mixed together */
+	SND_PCM_SUBCLASS_GENERIC_MIX = 0,
+	/** multichannel subdevices are mixed together */
+	SND_PCM_SUBCLASS_MULTI_MIX,
+	SND_PCM_SUBCLASS_LAST = SND_PCM_SUBCLASS_MULTI_MIX
+} snd_pcm_subclass_t;
+
+/** PCM stream (direction) */
+typedef enum _snd_pcm_stream {
+	/** Playback stream */
+	SND_PCM_STREAM_PLAYBACK = 0,
+	/** Capture stream */
+	SND_PCM_STREAM_CAPTURE,
+	SND_PCM_STREAM_LAST = SND_PCM_STREAM_CAPTURE
+} snd_pcm_stream_t;
+
+/** PCM access type */
+typedef enum _snd_pcm_access {
+	/** mmap access with simple interleaved channels */
+	SND_PCM_ACCESS_MMAP_INTERLEAVED = 0,
+	/** mmap access with simple non interleaved channels */
+	SND_PCM_ACCESS_MMAP_NONINTERLEAVED,
+	/** mmap access with complex placement */
+	SND_PCM_ACCESS_MMAP_COMPLEX,
+	/** snd_pcm_readi/snd_pcm_writei access */
+	SND_PCM_ACCESS_RW_INTERLEAVED,
+	/** snd_pcm_readn/snd_pcm_writen access */
+	SND_PCM_ACCESS_RW_NONINTERLEAVED,
+	SND_PCM_ACCESS_LAST = SND_PCM_ACCESS_RW_NONINTERLEAVED
+} snd_pcm_access_t;
+
+/** PCM sample format */
+typedef enum _snd_pcm_format {
+	/** Unknown */
+	SND_PCM_FORMAT_UNKNOWN = -1,
+	/** Signed 8 bit */
+	SND_PCM_FORMAT_S8 = 0,
+	/** Unsigned 8 bit */
+	SND_PCM_FORMAT_U8,
+	/** Signed 16 bit Little Endian */
+	SND_PCM_FORMAT_S16_LE,
+	/** Signed 16 bit Big Endian */
+	SND_PCM_FORMAT_S16_BE,
+	/** Unsigned 16 bit Little Endian */
+	SND_PCM_FORMAT_U16_LE,
+	/** Unsigned 16 bit Big Endian */
+	SND_PCM_FORMAT_U16_BE,
+	/** Signed 24 bit Little Endian using low three bytes in 32-bit word */
+	SND_PCM_FORMAT_S24_LE,
+	/** Signed 24 bit Big Endian using low three bytes in 32-bit word */
+	SND_PCM_FORMAT_S24_BE,
+	/** Unsigned 24 bit Little Endian using low three bytes in 32-bit word */
+	SND_PCM_FORMAT_U24_LE,
+	/** Unsigned 24 bit Big Endian using low three bytes in 32-bit word */
+	SND_PCM_FORMAT_U24_BE,
+	/** Signed 32 bit Little Endian */
+	SND_PCM_FORMAT_S32_LE,
+	/** Signed 32 bit Big Endian */
+	SND_PCM_FORMAT_S32_BE,
+	/** Unsigned 32 bit Little Endian */
+	SND_PCM_FORMAT_U32_LE,
+	/** Unsigned 32 bit Big Endian */
+	SND_PCM_FORMAT_U32_BE,
+	/** Float 32 bit Little Endian, Range -1.0 to 1.0 */
+	SND_PCM_FORMAT_FLOAT_LE,
+	/** Float 32 bit Big Endian, Range -1.0 to 1.0 */
+	SND_PCM_FORMAT_FLOAT_BE,
+	/** Float 64 bit Little Endian, Range -1.0 to 1.0 */
+	SND_PCM_FORMAT_FLOAT64_LE,
+	/** Float 64 bit Big Endian, Range -1.0 to 1.0 */
+	SND_PCM_FORMAT_FLOAT64_BE,
+	/** IEC-958 Little Endian */
+	SND_PCM_FORMAT_IEC958_SUBFRAME_LE,
+	/** IEC-958 Big Endian */
+	SND_PCM_FORMAT_IEC958_SUBFRAME_BE,
+	/** Mu-Law */
+	SND_PCM_FORMAT_MU_LAW,
+	/** A-Law */
+	SND_PCM_FORMAT_A_LAW,
+	/** Ima-ADPCM */
+	SND_PCM_FORMAT_IMA_ADPCM,
+	/** MPEG */
+	SND_PCM_FORMAT_MPEG,
+	/** GSM */
+	SND_PCM_FORMAT_GSM,
+	/** Special */
+	SND_PCM_FORMAT_SPECIAL = 31,
+	/** Signed 24bit Little Endian in 3bytes format */
+	SND_PCM_FORMAT_S24_3LE = 32,
+	/** Signed 24bit Big Endian in 3bytes format */
+	SND_PCM_FORMAT_S24_3BE,
+	/** Unsigned 24bit Little Endian in 3bytes format */
+	SND_PCM_FORMAT_U24_3LE,
+	/** Unsigned 24bit Big Endian in 3bytes format */
+	SND_PCM_FORMAT_U24_3BE,
+	/** Signed 20bit Little Endian in 3bytes format */
+	SND_PCM_FORMAT_S20_3LE,
+	/** Signed 20bit Big Endian in 3bytes format */
+	SND_PCM_FORMAT_S20_3BE,
+	/** Unsigned 20bit Little Endian in 3bytes format */
+	SND_PCM_FORMAT_U20_3LE,
+	/** Unsigned 20bit Big Endian in 3bytes format */
+	SND_PCM_FORMAT_U20_3BE,
+	/** Signed 18bit Little Endian in 3bytes format */
+	SND_PCM_FORMAT_S18_3LE,
+	/** Signed 18bit Big Endian in 3bytes format */
+	SND_PCM_FORMAT_S18_3BE,
+	/** Unsigned 18bit Little Endian in 3bytes format */
+	SND_PCM_FORMAT_U18_3LE,
+	/** Unsigned 18bit Big Endian in 3bytes format */
+	SND_PCM_FORMAT_U18_3BE,
+	SND_PCM_FORMAT_LAST = SND_PCM_FORMAT_U18_3BE,
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+	/** Signed 16 bit CPU endian */
+	SND_PCM_FORMAT_S16 = SND_PCM_FORMAT_S16_LE,
+	/** Unsigned 16 bit CPU endian */
+	SND_PCM_FORMAT_U16 = SND_PCM_FORMAT_U16_LE,
+	/** Signed 24 bit CPU endian */
+	SND_PCM_FORMAT_S24 = SND_PCM_FORMAT_S24_LE,
+	/** Unsigned 24 bit CPU endian */
+	SND_PCM_FORMAT_U24 = SND_PCM_FORMAT_U24_LE,
+	/** Signed 32 bit CPU endian */
+	SND_PCM_FORMAT_S32 = SND_PCM_FORMAT_S32_LE,
+	/** Unsigned 32 bit CPU endian */
+	SND_PCM_FORMAT_U32 = SND_PCM_FORMAT_U32_LE,
+	/** Float 32 bit CPU endian */
+	SND_PCM_FORMAT_FLOAT = SND_PCM_FORMAT_FLOAT_LE,
+	/** Float 64 bit CPU endian */
+	SND_PCM_FORMAT_FLOAT64 = SND_PCM_FORMAT_FLOAT64_LE,
+	/** IEC-958 CPU Endian */
+	SND_PCM_FORMAT_IEC958_SUBFRAME = SND_PCM_FORMAT_IEC958_SUBFRAME_LE
+#elif __BYTE_ORDER == __BIG_ENDIAN
+	/** Signed 16 bit CPU endian */
+	SND_PCM_FORMAT_S16 = SND_PCM_FORMAT_S16_BE,
+	/** Unsigned 16 bit CPU endian */
+	SND_PCM_FORMAT_U16 = SND_PCM_FORMAT_U16_BE,
+	/** Signed 24 bit CPU endian */
+	SND_PCM_FORMAT_S24 = SND_PCM_FORMAT_S24_BE,
+	/** Unsigned 24 bit CPU endian */
+	SND_PCM_FORMAT_U24 = SND_PCM_FORMAT_U24_BE,
+	/** Signed 32 bit CPU endian */
+	SND_PCM_FORMAT_S32 = SND_PCM_FORMAT_S32_BE,
+	/** Unsigned 32 bit CPU endian */
+	SND_PCM_FORMAT_U32 = SND_PCM_FORMAT_U32_BE,
+	/** Float 32 bit CPU endian */
+	SND_PCM_FORMAT_FLOAT = SND_PCM_FORMAT_FLOAT_BE,
+	/** Float 64 bit CPU endian */
+	SND_PCM_FORMAT_FLOAT64 = SND_PCM_FORMAT_FLOAT64_BE,
+	/** IEC-958 CPU Endian */
+	SND_PCM_FORMAT_IEC958_SUBFRAME = SND_PCM_FORMAT_IEC958_SUBFRAME_BE
+#else
+#error "Unknown endian"
+#endif
+} snd_pcm_format_t;
+
+/** PCM sample subformat */
+typedef enum _snd_pcm_subformat {
+	/** Standard */
+	SND_PCM_SUBFORMAT_STD = 0,
+	SND_PCM_SUBFORMAT_LAST = SND_PCM_SUBFORMAT_STD
+} snd_pcm_subformat_t;
+
+/** PCM state */
+typedef enum _snd_pcm_state {
+	/** Open */
+	SND_PCM_STATE_OPEN = 0,
+	/** Setup installed */ 
+	SND_PCM_STATE_SETUP,
+	/** Ready to start */
+	SND_PCM_STATE_PREPARED,
+	/** Running */
+	SND_PCM_STATE_RUNNING,
+	/** Stopped: underrun (playback) or overrun (capture) detected */
+	SND_PCM_STATE_XRUN,
+	/** Draining: running (playback) or stopped (capture) */
+	SND_PCM_STATE_DRAINING,
+	/** Paused */
+	SND_PCM_STATE_PAUSED,
+	/** Hardware is suspended */
+	SND_PCM_STATE_SUSPENDED,
+	/** Hardware is disconnected */
+	SND_PCM_STATE_DISCONNECTED,
+	SND_PCM_STATE_LAST = SND_PCM_STATE_DISCONNECTED
+} snd_pcm_state_t;
+
+/** PCM start mode */
+typedef enum _snd_pcm_start {
+	/** Automatic start on data read/write */
+	SND_PCM_START_DATA = 0,
+	/** Explicit start */
+	SND_PCM_START_EXPLICIT,
+	SND_PCM_START_LAST = SND_PCM_START_EXPLICIT
+} snd_pcm_start_t;
+
+/** PCM xrun mode */
+typedef enum _snd_pcm_xrun {
+	/** Xrun detection disabled */
+	SND_PCM_XRUN_NONE = 0,
+	/** Stop on xrun detection */
+	SND_PCM_XRUN_STOP,
+	SND_PCM_XRUN_LAST = SND_PCM_XRUN_STOP
+} snd_pcm_xrun_t;
+
+/** PCM timestamp mode */
+typedef enum _snd_pcm_tstamp {
+	/** No timestamp */
+	SND_PCM_TSTAMP_NONE = 0,
+	/** Update timestamp at every hardware position update */
+	SND_PCM_TSTAMP_ENABLE,
+	/** Equivalent with #SND_PCM_TSTAMP_ENABLE,
+	 * just for compatibility with older versions
+	 */
+	SND_PCM_TSTAMP_MMAP = SND_PCM_TSTAMP_ENABLE,
+	SND_PCM_TSTAMP_LAST = SND_PCM_TSTAMP_ENABLE
+} snd_pcm_tstamp_t;
+
+/** Unsigned frames quantity */
+typedef unsigned long snd_pcm_uframes_t;
+/** Signed frames quantity */
+typedef long snd_pcm_sframes_t;
+
+/** Non blocking mode (flag for open mode) \hideinitializer */
+#define SND_PCM_NONBLOCK		0x00000001
+/** Async notification (flag for open mode) \hideinitializer */
+#define SND_PCM_ASYNC			0x00000002
+/** Disable automatic (but not forced!) rate resamplinig */
+#define SND_PCM_NO_AUTO_RESAMPLE	0x00010000
+/** Disable automatic (but not forced!) channel conversion */
+#define SND_PCM_NO_AUTO_CHANNELS	0x00020000
+/** Disable automatic (but not forced!) format conversion */
+#define SND_PCM_NO_AUTO_FORMAT		0x00040000
+/** Disable soft volume control */
+#define SND_PCM_NO_SOFTVOL		0x00080000
+
+/** PCM handle */
+typedef struct _snd_pcm snd_pcm_t;
+
+/** PCM type */
+enum _snd_pcm_type {
+	/** Kernel level PCM */
+	SND_PCM_TYPE_HW = 0,
+	/** Hooked PCM */
+	SND_PCM_TYPE_HOOKS,
+	/** One or more linked PCM with exclusive access to selected
+	    channels */
+	SND_PCM_TYPE_MULTI,
+	/** File writing plugin */
+	SND_PCM_TYPE_FILE,
+	/** Null endpoint PCM */
+	SND_PCM_TYPE_NULL,
+	/** Shared memory client PCM */
+	SND_PCM_TYPE_SHM,
+	/** INET client PCM (not yet implemented) */
+	SND_PCM_TYPE_INET,
+	/** Copying plugin */
+	SND_PCM_TYPE_COPY,
+	/** Linear format conversion PCM */
+	SND_PCM_TYPE_LINEAR,
+	/** A-Law format conversion PCM */
+	SND_PCM_TYPE_ALAW,
+	/** Mu-Law format conversion PCM */
+	SND_PCM_TYPE_MULAW,
+	/** IMA-ADPCM format conversion PCM */
+	SND_PCM_TYPE_ADPCM,
+	/** Rate conversion PCM */
+	SND_PCM_TYPE_RATE,
+	/** Attenuated static route PCM */
+	SND_PCM_TYPE_ROUTE,
+	/** Format adjusted PCM */
+	SND_PCM_TYPE_PLUG,
+	/** Sharing PCM */
+	SND_PCM_TYPE_SHARE,
+	/** Meter plugin */
+	SND_PCM_TYPE_METER,
+	/** Mixing PCM */
+	SND_PCM_TYPE_MIX,
+	/** Attenuated dynamic route PCM (not yet implemented) */
+	SND_PCM_TYPE_DROUTE,
+	/** Loopback server plugin (not yet implemented) */
+	SND_PCM_TYPE_LBSERVER,
+	/** Linear Integer <-> Linear Float format conversion PCM */
+	SND_PCM_TYPE_LINEAR_FLOAT,
+	/** LADSPA integration plugin */
+	SND_PCM_TYPE_LADSPA,
+	/** Direct Mixing plugin */
+	SND_PCM_TYPE_DMIX,
+	/** Jack Audio Connection Kit plugin */
+	SND_PCM_TYPE_JACK,
+	/** Direct Snooping plugin */
+	SND_PCM_TYPE_DSNOOP,
+	/** Direct Sharing plugin */
+	SND_PCM_TYPE_DSHARE,
+	/** IEC958 subframe plugin */
+	SND_PCM_TYPE_IEC958,
+	/** Soft volume plugin */
+	SND_PCM_TYPE_SOFTVOL,
+	/** External I/O plugin */
+	SND_PCM_TYPE_IOPLUG,
+	/** External filter plugin */
+	SND_PCM_TYPE_EXTPLUG,
+	/** Mmap-emulation plugin */
+	SND_PCM_TYPE_MMAP_EMUL,
+	SND_PCM_TYPE_LAST = SND_PCM_TYPE_MMAP_EMUL
+};
+
+/** PCM type */
+typedef enum _snd_pcm_type snd_pcm_type_t;
+
+/** PCM area specification */
+typedef struct _snd_pcm_channel_area {
+	/** base address of channel samples */
+	void *addr;
+	/** offset to first sample in bits */
+	unsigned int first;
+	/** samples distance in bits */
+	unsigned int step;
+} snd_pcm_channel_area_t;
+
+/** PCM synchronization ID */
+typedef union _snd_pcm_sync_id {
+	/** 8-bit ID */
+	unsigned char id[16];
+	/** 16-bit ID */
+	unsigned short id16[8];
+	/** 32-bit ID */
+	unsigned int id32[4];
+} snd_pcm_sync_id_t;
+
+/** #SND_PCM_TYPE_METER scope handle */
+typedef struct _snd_pcm_scope snd_pcm_scope_t;
+
+int snd_pcm_open(snd_pcm_t **pcm, const char *name, 
+		 snd_pcm_stream_t stream, int mode);
+int snd_pcm_open_lconf(snd_pcm_t **pcm, const char *name, 
+		       snd_pcm_stream_t stream, int mode,
+		       snd_config_t *lconf);
+int snd_pcm_open_fallback(snd_pcm_t **pcm, snd_config_t *root,
+			  const char *name, const char *orig_name,
+			  snd_pcm_stream_t stream, int mode);
+
+int snd_pcm_close(snd_pcm_t *pcm);
+const char *snd_pcm_name(snd_pcm_t *pcm);
+snd_pcm_type_t snd_pcm_type(snd_pcm_t *pcm);
+snd_pcm_stream_t snd_pcm_stream(snd_pcm_t *pcm);
+int snd_pcm_poll_descriptors_count(snd_pcm_t *pcm);
+int snd_pcm_poll_descriptors(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int space);
+int snd_pcm_poll_descriptors_revents(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int nfds, unsigned short *revents);
+int snd_pcm_nonblock(snd_pcm_t *pcm, int nonblock);
+int snd_async_add_pcm_handler(snd_async_handler_t **handler, snd_pcm_t *pcm, 
+			      snd_async_callback_t callback, void *private_data);
+snd_pcm_t *snd_async_handler_get_pcm(snd_async_handler_t *handler);
+int snd_pcm_info(snd_pcm_t *pcm, snd_pcm_info_t *info);
+int snd_pcm_hw_params_current(snd_pcm_t *pcm, snd_pcm_hw_params_t *params);
+int snd_pcm_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t *params);
+int snd_pcm_hw_free(snd_pcm_t *pcm);
+int snd_pcm_sw_params_current(snd_pcm_t *pcm, snd_pcm_sw_params_t *params);
+int snd_pcm_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t *params);
+int snd_pcm_prepare(snd_pcm_t *pcm);
+int snd_pcm_reset(snd_pcm_t *pcm);
+int snd_pcm_status(snd_pcm_t *pcm, snd_pcm_status_t *status);
+int snd_pcm_start(snd_pcm_t *pcm);
+int snd_pcm_drop(snd_pcm_t *pcm);
+int snd_pcm_drain(snd_pcm_t *pcm);
+int snd_pcm_pause(snd_pcm_t *pcm, int enable);
+snd_pcm_state_t snd_pcm_state(snd_pcm_t *pcm);
+int snd_pcm_hwsync(snd_pcm_t *pcm);
+int snd_pcm_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp);
+int snd_pcm_resume(snd_pcm_t *pcm);
+int snd_pcm_htimestamp(snd_pcm_t *pcm, snd_pcm_uframes_t *avail, snd_htimestamp_t *tstamp);
+snd_pcm_sframes_t snd_pcm_avail(snd_pcm_t *pcm);
+snd_pcm_sframes_t snd_pcm_avail_update(snd_pcm_t *pcm);
+int snd_pcm_avail_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *availp, snd_pcm_sframes_t *delayp);
+snd_pcm_sframes_t snd_pcm_rewindable(snd_pcm_t *pcm);
+snd_pcm_sframes_t snd_pcm_rewind(snd_pcm_t *pcm, snd_pcm_uframes_t frames);
+snd_pcm_sframes_t snd_pcm_forwardable(snd_pcm_t *pcm);
+snd_pcm_sframes_t snd_pcm_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames);
+snd_pcm_sframes_t snd_pcm_writei(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size);
+snd_pcm_sframes_t snd_pcm_readi(snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t size);
+snd_pcm_sframes_t snd_pcm_writen(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size);
+snd_pcm_sframes_t snd_pcm_readn(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size);
+int snd_pcm_wait(snd_pcm_t *pcm, int timeout);
+
+int snd_pcm_link(snd_pcm_t *pcm1, snd_pcm_t *pcm2);
+int snd_pcm_unlink(snd_pcm_t *pcm);
+
+//int snd_pcm_mixer_element(snd_pcm_t *pcm, snd_mixer_t *mixer, snd_mixer_elem_t **elem);
+
+/*
+ * application helpers - these functions are implemented on top
+ * of the basic API
+ */
+
+int snd_pcm_recover(snd_pcm_t *pcm, int err, int silent);
+int snd_pcm_set_params(snd_pcm_t *pcm,
+                       snd_pcm_format_t format,
+                       snd_pcm_access_t access,
+                       unsigned int channels,
+                       unsigned int rate,
+                       int soft_resample,
+                       unsigned int latency);
+int snd_pcm_get_params(snd_pcm_t *pcm,
+                       snd_pcm_uframes_t *buffer_size,
+                       snd_pcm_uframes_t *period_size);
+
+/** \} */
+
+/**
+ * \defgroup PCM_Info Stream Information
+ * \ingroup PCM
+ * See the \ref pcm page for more details.
+ * \{
+ */
+
+size_t snd_pcm_info_sizeof(void);
+/** \hideinitializer
+ * \brief allocate an invalid #snd_pcm_info_t using standard alloca
+ * \param ptr returned pointer
+ */
+#define snd_pcm_info_alloca(ptr) __snd_alloca(ptr, snd_pcm_info)
+int snd_pcm_info_malloc(snd_pcm_info_t **ptr);
+void snd_pcm_info_free(snd_pcm_info_t *obj);
+void snd_pcm_info_copy(snd_pcm_info_t *dst, const snd_pcm_info_t *src);
+unsigned int snd_pcm_info_get_device(const snd_pcm_info_t *obj);
+unsigned int snd_pcm_info_get_subdevice(const snd_pcm_info_t *obj);
+snd_pcm_stream_t snd_pcm_info_get_stream(const snd_pcm_info_t *obj);
+int snd_pcm_info_get_card(const snd_pcm_info_t *obj);
+const char *snd_pcm_info_get_id(const snd_pcm_info_t *obj);
+const char *snd_pcm_info_get_name(const snd_pcm_info_t *obj);
+const char *snd_pcm_info_get_subdevice_name(const snd_pcm_info_t *obj);
+snd_pcm_class_t snd_pcm_info_get_class(const snd_pcm_info_t *obj);
+snd_pcm_subclass_t snd_pcm_info_get_subclass(const snd_pcm_info_t *obj);
+unsigned int snd_pcm_info_get_subdevices_count(const snd_pcm_info_t *obj);
+unsigned int snd_pcm_info_get_subdevices_avail(const snd_pcm_info_t *obj);
+snd_pcm_sync_id_t snd_pcm_info_get_sync(const snd_pcm_info_t *obj);
+void snd_pcm_info_set_device(snd_pcm_info_t *obj, unsigned int val);
+void snd_pcm_info_set_subdevice(snd_pcm_info_t *obj, unsigned int val);
+void snd_pcm_info_set_stream(snd_pcm_info_t *obj, snd_pcm_stream_t val);
+
+/** \} */
+
+/**
+ * \defgroup PCM_HW_Params Hardware Parameters
+ * \ingroup PCM
+ * See the \ref pcm page for more details.
+ * \{
+ */
+
+int snd_pcm_hw_params_any(snd_pcm_t *pcm, snd_pcm_hw_params_t *params);
+
+int snd_pcm_hw_params_can_mmap_sample_resolution(const snd_pcm_hw_params_t *params);
+int snd_pcm_hw_params_is_double(const snd_pcm_hw_params_t *params);
+int snd_pcm_hw_params_is_batch(const snd_pcm_hw_params_t *params);
+int snd_pcm_hw_params_is_block_transfer(const snd_pcm_hw_params_t *params);
+int snd_pcm_hw_params_is_monotonic(const snd_pcm_hw_params_t *params);
+int snd_pcm_hw_params_can_overrange(const snd_pcm_hw_params_t *params);
+int snd_pcm_hw_params_can_pause(const snd_pcm_hw_params_t *params);
+int snd_pcm_hw_params_can_resume(const snd_pcm_hw_params_t *params);
+int snd_pcm_hw_params_is_half_duplex(const snd_pcm_hw_params_t *params);
+int snd_pcm_hw_params_is_joint_duplex(const snd_pcm_hw_params_t *params);
+int snd_pcm_hw_params_can_sync_start(const snd_pcm_hw_params_t *params);
+int snd_pcm_hw_params_can_disable_period_wakeup(const snd_pcm_hw_params_t *params);
+int snd_pcm_hw_params_get_rate_numden(const snd_pcm_hw_params_t *params,
+				      unsigned int *rate_num,
+				      unsigned int *rate_den);
+int snd_pcm_hw_params_get_sbits(const snd_pcm_hw_params_t *params);
+int snd_pcm_hw_params_get_fifo_size(const snd_pcm_hw_params_t *params);
+
+#if 0
+typedef struct _snd_pcm_hw_strategy snd_pcm_hw_strategy_t;
+
+/* choices need to be sorted on ascending badness */
+typedef struct _snd_pcm_hw_strategy_simple_choices_list {
+	unsigned int value;
+	unsigned int badness;
+} snd_pcm_hw_strategy_simple_choices_list_t;
+
+int snd_pcm_hw_params_strategy(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
+			       const snd_pcm_hw_strategy_t *strategy,
+			       unsigned int badness_min,
+			       unsigned int badness_max);
+
+void snd_pcm_hw_strategy_free(snd_pcm_hw_strategy_t *strategy);
+int snd_pcm_hw_strategy_simple(snd_pcm_hw_strategy_t **strategyp,
+			       unsigned int badness_min,
+			       unsigned int badness_max);
+int snd_pcm_hw_params_try_explain_failure(snd_pcm_t *pcm,
+					  snd_pcm_hw_params_t *fail,
+					  snd_pcm_hw_params_t *success,
+					  unsigned int depth,
+					  snd_output_t *out);
+
+#endif
+
+size_t snd_pcm_hw_params_sizeof(void);
+/** \hideinitializer
+ * \brief allocate an invalid #snd_pcm_hw_params_t using standard alloca
+ * \param ptr returned pointer
+ */
+#define snd_pcm_hw_params_alloca(ptr) __snd_alloca(ptr, snd_pcm_hw_params)
+int snd_pcm_hw_params_malloc(snd_pcm_hw_params_t **ptr);
+void snd_pcm_hw_params_free(snd_pcm_hw_params_t *obj);
+void snd_pcm_hw_params_copy(snd_pcm_hw_params_t *dst, const snd_pcm_hw_params_t *src);
+
+#if !defined(ALSA_LIBRARY_BUILD) && !defined(ALSA_PCM_OLD_HW_PARAMS_API)
+
+int snd_pcm_hw_params_get_access(const snd_pcm_hw_params_t *params, snd_pcm_access_t *_access);
+int snd_pcm_hw_params_test_access(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_access_t _access);
+int snd_pcm_hw_params_set_access(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_access_t _access);
+int snd_pcm_hw_params_set_access_first(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_access_t *_access);
+int snd_pcm_hw_params_set_access_last(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_access_t *_access);
+int snd_pcm_hw_params_set_access_mask(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_access_mask_t *mask);
+int snd_pcm_hw_params_get_access_mask(snd_pcm_hw_params_t *params, snd_pcm_access_mask_t *mask);
+
+int snd_pcm_hw_params_get_format(const snd_pcm_hw_params_t *params, snd_pcm_format_t *val);
+int snd_pcm_hw_params_test_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_format_t val);
+int snd_pcm_hw_params_set_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_format_t val);
+int snd_pcm_hw_params_set_format_first(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_format_t *format);
+int snd_pcm_hw_params_set_format_last(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_format_t *format);
+int snd_pcm_hw_params_set_format_mask(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_format_mask_t *mask);
+void snd_pcm_hw_params_get_format_mask(snd_pcm_hw_params_t *params, snd_pcm_format_mask_t *mask);
+
+int snd_pcm_hw_params_get_subformat(const snd_pcm_hw_params_t *params, snd_pcm_subformat_t *subformat);
+int snd_pcm_hw_params_test_subformat(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_subformat_t subformat);
+int snd_pcm_hw_params_set_subformat(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_subformat_t subformat);
+int snd_pcm_hw_params_set_subformat_first(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_subformat_t *subformat);
+int snd_pcm_hw_params_set_subformat_last(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_subformat_t *subformat);
+int snd_pcm_hw_params_set_subformat_mask(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_subformat_mask_t *mask);
+void snd_pcm_hw_params_get_subformat_mask(snd_pcm_hw_params_t *params, snd_pcm_subformat_mask_t *mask);
+
+int snd_pcm_hw_params_get_channels(const snd_pcm_hw_params_t *params, unsigned int *val);
+int snd_pcm_hw_params_get_channels_min(const snd_pcm_hw_params_t *params, unsigned int *val);
+int snd_pcm_hw_params_get_channels_max(const snd_pcm_hw_params_t *params, unsigned int *val);
+int snd_pcm_hw_params_test_channels(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val);
+int snd_pcm_hw_params_set_channels(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val);
+int snd_pcm_hw_params_set_channels_min(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val);
+int snd_pcm_hw_params_set_channels_max(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val);
+int snd_pcm_hw_params_set_channels_minmax(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *min, unsigned int *max);
+int snd_pcm_hw_params_set_channels_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val);
+int snd_pcm_hw_params_set_channels_first(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val);
+int snd_pcm_hw_params_set_channels_last(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val);
+
+int snd_pcm_hw_params_get_rate(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_get_rate_min(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_get_rate_max(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_test_rate(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int dir);
+int snd_pcm_hw_params_set_rate(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int dir);
+int snd_pcm_hw_params_set_rate_min(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_set_rate_max(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_set_rate_minmax(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *min, int *mindir, unsigned int *max, int *maxdir);
+int snd_pcm_hw_params_set_rate_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_set_rate_first(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_set_rate_last(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_set_rate_resample(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val);
+int snd_pcm_hw_params_get_rate_resample(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val);
+int snd_pcm_hw_params_set_export_buffer(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val);
+int snd_pcm_hw_params_get_export_buffer(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val);
+int snd_pcm_hw_params_set_period_wakeup(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val);
+int snd_pcm_hw_params_get_period_wakeup(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val);
+
+int snd_pcm_hw_params_get_period_time(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_get_period_time_min(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_get_period_time_max(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_test_period_time(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int dir);
+int snd_pcm_hw_params_set_period_time(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int dir);
+int snd_pcm_hw_params_set_period_time_min(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_set_period_time_max(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_set_period_time_minmax(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *min, int *mindir, unsigned int *max, int *maxdir);
+int snd_pcm_hw_params_set_period_time_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_set_period_time_first(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_set_period_time_last(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+
+int snd_pcm_hw_params_get_period_size(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *frames, int *dir);
+int snd_pcm_hw_params_get_period_size_min(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *frames, int *dir);
+int snd_pcm_hw_params_get_period_size_max(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *frames, int *dir);
+int snd_pcm_hw_params_test_period_size(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t val, int dir);
+int snd_pcm_hw_params_set_period_size(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t val, int dir);
+int snd_pcm_hw_params_set_period_size_min(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val, int *dir);
+int snd_pcm_hw_params_set_period_size_max(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val, int *dir);
+int snd_pcm_hw_params_set_period_size_minmax(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *min, int *mindir, snd_pcm_uframes_t *max, int *maxdir);
+int snd_pcm_hw_params_set_period_size_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val, int *dir);
+int snd_pcm_hw_params_set_period_size_first(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val, int *dir);
+int snd_pcm_hw_params_set_period_size_last(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val, int *dir);
+int snd_pcm_hw_params_set_period_size_integer(snd_pcm_t *pcm, snd_pcm_hw_params_t *params);
+
+int snd_pcm_hw_params_get_periods(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_get_periods_min(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_get_periods_max(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_test_periods(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int dir);
+int snd_pcm_hw_params_set_periods(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int dir);
+int snd_pcm_hw_params_set_periods_min(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_set_periods_max(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_set_periods_minmax(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *min, int *mindir, unsigned int *max, int *maxdir);
+int snd_pcm_hw_params_set_periods_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_set_periods_first(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_set_periods_last(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_set_periods_integer(snd_pcm_t *pcm, snd_pcm_hw_params_t *params);
+
+int snd_pcm_hw_params_get_buffer_time(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_get_buffer_time_min(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_get_buffer_time_max(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_test_buffer_time(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int dir);
+int snd_pcm_hw_params_set_buffer_time(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int dir);
+int snd_pcm_hw_params_set_buffer_time_min(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_set_buffer_time_max(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_set_buffer_time_minmax(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *min, int *mindir, unsigned int *max, int *maxdir);
+int snd_pcm_hw_params_set_buffer_time_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_set_buffer_time_first(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_set_buffer_time_last(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+
+int snd_pcm_hw_params_get_buffer_size(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val);
+int snd_pcm_hw_params_get_buffer_size_min(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val);
+int snd_pcm_hw_params_get_buffer_size_max(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val);
+int snd_pcm_hw_params_test_buffer_size(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t val);
+int snd_pcm_hw_params_set_buffer_size(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t val);
+int snd_pcm_hw_params_set_buffer_size_min(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val);
+int snd_pcm_hw_params_set_buffer_size_max(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val);
+int snd_pcm_hw_params_set_buffer_size_minmax(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *min, snd_pcm_uframes_t *max);
+int snd_pcm_hw_params_set_buffer_size_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val);
+int snd_pcm_hw_params_set_buffer_size_first(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val);
+int snd_pcm_hw_params_set_buffer_size_last(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val);
+
+#endif /* !ALSA_LIBRARY_BUILD && !ALSA_PCM_OLD_HW_PARAMS_API */
+
+int snd_pcm_hw_params_get_min_align(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val);
+
+/** \} */
+
+/**
+ * \defgroup PCM_SW_Params Software Parameters
+ * \ingroup PCM
+ * See the \ref pcm page for more details.
+ * \{
+ */
+
+size_t snd_pcm_sw_params_sizeof(void);
+/** \hideinitializer
+ * \brief allocate an invalid #snd_pcm_sw_params_t using standard alloca
+ * \param ptr returned pointer
+ */
+#define snd_pcm_sw_params_alloca(ptr) __snd_alloca(ptr, snd_pcm_sw_params)
+int snd_pcm_sw_params_malloc(snd_pcm_sw_params_t **ptr);
+void snd_pcm_sw_params_free(snd_pcm_sw_params_t *obj);
+void snd_pcm_sw_params_copy(snd_pcm_sw_params_t *dst, const snd_pcm_sw_params_t *src);
+int snd_pcm_sw_params_get_boundary(const snd_pcm_sw_params_t *params, snd_pcm_uframes_t *val);
+
+#if !defined(ALSA_LIBRARY_BUILD) && !defined(ALSA_PCM_OLD_SW_PARAMS_API)
+
+int snd_pcm_sw_params_set_tstamp_mode(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_tstamp_t val);
+int snd_pcm_sw_params_get_tstamp_mode(const snd_pcm_sw_params_t *params, snd_pcm_tstamp_t *val);
+int snd_pcm_sw_params_set_avail_min(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val);
+int snd_pcm_sw_params_get_avail_min(const snd_pcm_sw_params_t *params, snd_pcm_uframes_t *val);
+int snd_pcm_sw_params_set_period_event(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, int val);
+int snd_pcm_sw_params_get_period_event(const snd_pcm_sw_params_t *params, int *val);
+int snd_pcm_sw_params_set_start_threshold(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val);
+int snd_pcm_sw_params_get_start_threshold(const snd_pcm_sw_params_t *paramsm, snd_pcm_uframes_t *val);
+int snd_pcm_sw_params_set_stop_threshold(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val);
+int snd_pcm_sw_params_get_stop_threshold(const snd_pcm_sw_params_t *params, snd_pcm_uframes_t *val);
+int snd_pcm_sw_params_set_silence_threshold(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val);
+int snd_pcm_sw_params_get_silence_threshold(const snd_pcm_sw_params_t *params, snd_pcm_uframes_t *val);
+int snd_pcm_sw_params_set_silence_size(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val);
+int snd_pcm_sw_params_get_silence_size(const snd_pcm_sw_params_t *params, snd_pcm_uframes_t *val);
+
+#endif /* !ALSA_LIBRARY_BUILD && !ALSA_PCM_OLD_SW_PARAMS_API */
+
+/** \} */
+
+/* include old API */
+#ifndef ALSA_LIBRARY_BUILD
+#if defined(ALSA_PCM_OLD_HW_PARAMS_API) || defined(ALSA_PCM_OLD_SW_PARAMS_API)
+#include "pcm_old.h"
+#endif
+#endif
+
+/**
+ * \defgroup PCM_Access Access Mask Functions
+ * \ingroup PCM
+ * See the \ref pcm page for more details.
+ * \{
+ */
+
+size_t snd_pcm_access_mask_sizeof(void);
+/** \hideinitializer
+ * \brief allocate an empty #snd_pcm_access_mask_t using standard alloca
+ * \param ptr returned pointer
+ */
+#define snd_pcm_access_mask_alloca(ptr) __snd_alloca(ptr, snd_pcm_access_mask)
+int snd_pcm_access_mask_malloc(snd_pcm_access_mask_t **ptr);
+void snd_pcm_access_mask_free(snd_pcm_access_mask_t *obj);
+void snd_pcm_access_mask_copy(snd_pcm_access_mask_t *dst, const snd_pcm_access_mask_t *src);
+void snd_pcm_access_mask_none(snd_pcm_access_mask_t *mask);
+void snd_pcm_access_mask_any(snd_pcm_access_mask_t *mask);
+int snd_pcm_access_mask_test(const snd_pcm_access_mask_t *mask, snd_pcm_access_t val);
+int snd_pcm_access_mask_empty(const snd_pcm_access_mask_t *mask);
+void snd_pcm_access_mask_set(snd_pcm_access_mask_t *mask, snd_pcm_access_t val);
+void snd_pcm_access_mask_reset(snd_pcm_access_mask_t *mask, snd_pcm_access_t val);
+
+/** \} */
+
+/**
+ * \defgroup PCM_Format Format Mask Functions
+ * \ingroup PCM
+ * See the \ref pcm page for more details.
+ * \{
+ */
+
+size_t snd_pcm_format_mask_sizeof(void);
+/** \hideinitializer
+ * \brief allocate an empty #snd_pcm_format_mask_t using standard alloca
+ * \param ptr returned pointer
+ */
+#define snd_pcm_format_mask_alloca(ptr) __snd_alloca(ptr, snd_pcm_format_mask)
+int snd_pcm_format_mask_malloc(snd_pcm_format_mask_t **ptr);
+void snd_pcm_format_mask_free(snd_pcm_format_mask_t *obj);
+void snd_pcm_format_mask_copy(snd_pcm_format_mask_t *dst, const snd_pcm_format_mask_t *src);
+void snd_pcm_format_mask_none(snd_pcm_format_mask_t *mask);
+void snd_pcm_format_mask_any(snd_pcm_format_mask_t *mask);
+int snd_pcm_format_mask_test(const snd_pcm_format_mask_t *mask, snd_pcm_format_t val);
+int snd_pcm_format_mask_empty(const snd_pcm_format_mask_t *mask);
+void snd_pcm_format_mask_set(snd_pcm_format_mask_t *mask, snd_pcm_format_t val);
+void snd_pcm_format_mask_reset(snd_pcm_format_mask_t *mask, snd_pcm_format_t val);
+
+/** \} */
+
+/**
+ * \defgroup PCM_SubFormat Subformat Mask Functions
+ * \ingroup PCM
+ * See the \ref pcm page for more details.
+ * \{
+ */
+
+size_t snd_pcm_subformat_mask_sizeof(void);
+/** \hideinitializer
+ * \brief allocate an empty #snd_pcm_subformat_mask_t using standard alloca
+ * \param ptr returned pointer
+ */
+#define snd_pcm_subformat_mask_alloca(ptr) __snd_alloca(ptr, snd_pcm_subformat_mask)
+int snd_pcm_subformat_mask_malloc(snd_pcm_subformat_mask_t **ptr);
+void snd_pcm_subformat_mask_free(snd_pcm_subformat_mask_t *obj);
+void snd_pcm_subformat_mask_copy(snd_pcm_subformat_mask_t *dst, const snd_pcm_subformat_mask_t *src);
+void snd_pcm_subformat_mask_none(snd_pcm_subformat_mask_t *mask);
+void snd_pcm_subformat_mask_any(snd_pcm_subformat_mask_t *mask);
+int snd_pcm_subformat_mask_test(const snd_pcm_subformat_mask_t *mask, snd_pcm_subformat_t val);
+int snd_pcm_subformat_mask_empty(const snd_pcm_subformat_mask_t *mask);
+void snd_pcm_subformat_mask_set(snd_pcm_subformat_mask_t *mask, snd_pcm_subformat_t val);
+void snd_pcm_subformat_mask_reset(snd_pcm_subformat_mask_t *mask, snd_pcm_subformat_t val);
+
+/** \} */
+
+/**
+ * \defgroup PCM_Status Status Functions
+ * \ingroup PCM
+ * See the \ref pcm page for more details.
+ * \{
+ */
+
+size_t snd_pcm_status_sizeof(void);
+/** \hideinitializer
+ * \brief allocate an invalid #snd_pcm_status_t using standard alloca
+ * \param ptr returned pointer
+ */
+#define snd_pcm_status_alloca(ptr) __snd_alloca(ptr, snd_pcm_status)
+int snd_pcm_status_malloc(snd_pcm_status_t **ptr);
+void snd_pcm_status_free(snd_pcm_status_t *obj);
+void snd_pcm_status_copy(snd_pcm_status_t *dst, const snd_pcm_status_t *src);
+snd_pcm_state_t snd_pcm_status_get_state(const snd_pcm_status_t *obj);
+void snd_pcm_status_get_trigger_tstamp(const snd_pcm_status_t *obj, snd_timestamp_t *ptr);
+void snd_pcm_status_get_trigger_htstamp(const snd_pcm_status_t *obj, snd_htimestamp_t *ptr);
+void snd_pcm_status_get_tstamp(const snd_pcm_status_t *obj, snd_timestamp_t *ptr);
+void snd_pcm_status_get_htstamp(const snd_pcm_status_t *obj, snd_htimestamp_t *ptr);
+snd_pcm_sframes_t snd_pcm_status_get_delay(const snd_pcm_status_t *obj);
+snd_pcm_uframes_t snd_pcm_status_get_avail(const snd_pcm_status_t *obj);
+snd_pcm_uframes_t snd_pcm_status_get_avail_max(const snd_pcm_status_t *obj);
+snd_pcm_uframes_t snd_pcm_status_get_overrange(const snd_pcm_status_t *obj);
+
+/** \} */
+
+/**
+ * \defgroup PCM_Description Description Functions
+ * \ingroup PCM
+ * See the \ref pcm page for more details.
+ * \{
+ */
+
+const char *snd_pcm_type_name(snd_pcm_type_t type);
+const char *snd_pcm_stream_name(const snd_pcm_stream_t stream);
+const char *snd_pcm_access_name(const snd_pcm_access_t _access);
+const char *snd_pcm_format_name(const snd_pcm_format_t format);
+const char *snd_pcm_format_description(const snd_pcm_format_t format);
+const char *snd_pcm_subformat_name(const snd_pcm_subformat_t subformat);
+const char *snd_pcm_subformat_description(const snd_pcm_subformat_t subformat);
+snd_pcm_format_t snd_pcm_format_value(const char* name);
+const char *snd_pcm_tstamp_mode_name(const snd_pcm_tstamp_t mode);
+const char *snd_pcm_state_name(const snd_pcm_state_t state);
+
+/** \} */
+
+/**
+ * \defgroup PCM_Dump Debug Functions
+ * \ingroup PCM
+ * See the \ref pcm page for more details.
+ * \{
+ */
+
+int snd_pcm_dump(snd_pcm_t *pcm, snd_output_t *out);
+int snd_pcm_dump_hw_setup(snd_pcm_t *pcm, snd_output_t *out);
+int snd_pcm_dump_sw_setup(snd_pcm_t *pcm, snd_output_t *out);
+int snd_pcm_dump_setup(snd_pcm_t *pcm, snd_output_t *out);
+int snd_pcm_hw_params_dump(snd_pcm_hw_params_t *params, snd_output_t *out);
+int snd_pcm_sw_params_dump(snd_pcm_sw_params_t *params, snd_output_t *out);
+int snd_pcm_status_dump(snd_pcm_status_t *status, snd_output_t *out);
+
+/** \} */
+
+/**
+ * \defgroup PCM_Direct Direct Access (MMAP) Functions
+ * \ingroup PCM
+ * See the \ref pcm page for more details.
+ * \{
+ */
+
+int snd_pcm_mmap_begin(snd_pcm_t *pcm,
+		       const snd_pcm_channel_area_t **areas,
+		       snd_pcm_uframes_t *offset,
+		       snd_pcm_uframes_t *frames);
+snd_pcm_sframes_t snd_pcm_mmap_commit(snd_pcm_t *pcm,
+				      snd_pcm_uframes_t offset,
+				      snd_pcm_uframes_t frames);
+snd_pcm_sframes_t snd_pcm_mmap_writei(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size);
+snd_pcm_sframes_t snd_pcm_mmap_readi(snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t size);
+snd_pcm_sframes_t snd_pcm_mmap_writen(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size);
+snd_pcm_sframes_t snd_pcm_mmap_readn(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size);                                                                
+
+/** \} */
+
+/**
+ * \defgroup PCM_Helpers Helper Functions
+ * \ingroup PCM
+ * See the \ref pcm page for more details.
+ * \{
+ */
+
+int snd_pcm_format_signed(snd_pcm_format_t format);
+int snd_pcm_format_unsigned(snd_pcm_format_t format);
+int snd_pcm_format_linear(snd_pcm_format_t format);
+int snd_pcm_format_float(snd_pcm_format_t format);
+int snd_pcm_format_little_endian(snd_pcm_format_t format);
+int snd_pcm_format_big_endian(snd_pcm_format_t format);
+int snd_pcm_format_cpu_endian(snd_pcm_format_t format);
+int snd_pcm_format_width(snd_pcm_format_t format);			/* in bits */
+int snd_pcm_format_physical_width(snd_pcm_format_t format);		/* in bits */
+snd_pcm_format_t snd_pcm_build_linear_format(int width, int pwidth, int unsignd, int big_endian);
+ssize_t snd_pcm_format_size(snd_pcm_format_t format, size_t samples);
+u_int8_t snd_pcm_format_silence(snd_pcm_format_t format);
+u_int16_t snd_pcm_format_silence_16(snd_pcm_format_t format);
+u_int32_t snd_pcm_format_silence_32(snd_pcm_format_t format);
+u_int64_t snd_pcm_format_silence_64(snd_pcm_format_t format);
+int snd_pcm_format_set_silence(snd_pcm_format_t format, void *buf, unsigned int samples);
+
+snd_pcm_sframes_t snd_pcm_bytes_to_frames(snd_pcm_t *pcm, ssize_t bytes);
+ssize_t snd_pcm_frames_to_bytes(snd_pcm_t *pcm, snd_pcm_sframes_t frames);
+long snd_pcm_bytes_to_samples(snd_pcm_t *pcm, ssize_t bytes);
+ssize_t snd_pcm_samples_to_bytes(snd_pcm_t *pcm, long samples);
+
+int snd_pcm_area_silence(const snd_pcm_channel_area_t *dst_channel, snd_pcm_uframes_t dst_offset,
+			 unsigned int samples, snd_pcm_format_t format);
+int snd_pcm_areas_silence(const snd_pcm_channel_area_t *dst_channels, snd_pcm_uframes_t dst_offset,
+			  unsigned int channels, snd_pcm_uframes_t frames, snd_pcm_format_t format);
+int snd_pcm_area_copy(const snd_pcm_channel_area_t *dst_channel, snd_pcm_uframes_t dst_offset,
+		      const snd_pcm_channel_area_t *src_channel, snd_pcm_uframes_t src_offset,
+		      unsigned int samples, snd_pcm_format_t format);
+int snd_pcm_areas_copy(const snd_pcm_channel_area_t *dst_channels, snd_pcm_uframes_t dst_offset,
+		       const snd_pcm_channel_area_t *src_channels, snd_pcm_uframes_t src_offset,
+		       unsigned int channels, snd_pcm_uframes_t frames, snd_pcm_format_t format);
+
+/** \} */
+
+/**
+ * \defgroup PCM_Hook Hook Extension
+ * \ingroup PCM
+ * See the \ref pcm page for more details.
+ * \{
+ */
+
+/** type of pcm hook */
+typedef enum _snd_pcm_hook_type {
+	SND_PCM_HOOK_TYPE_HW_PARAMS = 0,
+	SND_PCM_HOOK_TYPE_HW_FREE,
+	SND_PCM_HOOK_TYPE_CLOSE,
+	SND_PCM_HOOK_TYPE_LAST = SND_PCM_HOOK_TYPE_CLOSE
+} snd_pcm_hook_type_t;
+
+/** PCM hook container */
+typedef struct _snd_pcm_hook snd_pcm_hook_t;
+/** PCM hook callback function */
+typedef int (*snd_pcm_hook_func_t)(snd_pcm_hook_t *hook);
+snd_pcm_t *snd_pcm_hook_get_pcm(snd_pcm_hook_t *hook);
+void *snd_pcm_hook_get_private(snd_pcm_hook_t *hook);
+void snd_pcm_hook_set_private(snd_pcm_hook_t *hook, void *private_data);
+int snd_pcm_hook_add(snd_pcm_hook_t **hookp, snd_pcm_t *pcm,
+		     snd_pcm_hook_type_t type,
+		     snd_pcm_hook_func_t func, void *private_data);
+int snd_pcm_hook_remove(snd_pcm_hook_t *hook);
+
+/** \} */
+
+/**
+ * \defgroup PCM_Scope Scope Plugin Extension
+ * \ingroup PCM
+ * See the \ref pcm page for more details.
+ * \{
+ */
+
+/** #SND_PCM_TYPE_METER scope functions */
+typedef struct _snd_pcm_scope_ops {
+	/** \brief Enable and prepare it using current params
+	 * \param scope scope handle
+	 */
+	int (*enable)(snd_pcm_scope_t *scope);
+	/** \brief Disable
+	 * \param scope scope handle
+	 */
+	void (*disable)(snd_pcm_scope_t *scope);
+	/** \brief PCM has been started
+	 * \param scope scope handle
+	 */
+	void (*start)(snd_pcm_scope_t *scope);
+	/** \brief PCM has been stopped
+	 * \param scope scope handle
+	 */
+	void (*stop)(snd_pcm_scope_t *scope);
+	/** \brief New frames are present
+	 * \param scope scope handle
+	 */
+	void (*update)(snd_pcm_scope_t *scope);
+	/** \brief Reset status
+	 * \param scope scope handle
+	 */
+	void (*reset)(snd_pcm_scope_t *scope);
+	/** \brief PCM is closing
+	 * \param scope scope handle
+	 */
+	void (*close)(snd_pcm_scope_t *scope);
+} snd_pcm_scope_ops_t;
+
+snd_pcm_uframes_t snd_pcm_meter_get_bufsize(snd_pcm_t *pcm);
+unsigned int snd_pcm_meter_get_channels(snd_pcm_t *pcm);
+unsigned int snd_pcm_meter_get_rate(snd_pcm_t *pcm);
+snd_pcm_uframes_t snd_pcm_meter_get_now(snd_pcm_t *pcm);
+snd_pcm_uframes_t snd_pcm_meter_get_boundary(snd_pcm_t *pcm);
+int snd_pcm_meter_add_scope(snd_pcm_t *pcm, snd_pcm_scope_t *scope);
+snd_pcm_scope_t *snd_pcm_meter_search_scope(snd_pcm_t *pcm, const char *name);
+int snd_pcm_scope_malloc(snd_pcm_scope_t **ptr);
+void snd_pcm_scope_set_ops(snd_pcm_scope_t *scope,
+			   const snd_pcm_scope_ops_t *val);
+void snd_pcm_scope_set_name(snd_pcm_scope_t *scope, const char *val);
+const char *snd_pcm_scope_get_name(snd_pcm_scope_t *scope);
+void *snd_pcm_scope_get_callback_private(snd_pcm_scope_t *scope);
+void snd_pcm_scope_set_callback_private(snd_pcm_scope_t *scope, void *val);
+int snd_pcm_scope_s16_open(snd_pcm_t *pcm, const char *name,
+			   snd_pcm_scope_t **scopep);
+int16_t *snd_pcm_scope_s16_get_channel_buffer(snd_pcm_scope_t *scope,
+					      unsigned int channel);
+
+/** \} */
+
+/**
+ * \defgroup PCM_Simple Simple setup functions
+ * \ingroup PCM
+ * See the \ref pcm page for more details.
+ * \{
+ */
+
+/** Simple PCM latency type */
+typedef enum _snd_spcm_latency {
+	/** standard latency - for standard playback or capture
+            (estimated latency in one direction 350ms) */
+	SND_SPCM_LATENCY_STANDARD = 0,
+	/** medium latency - software phones etc.
+	    (estimated latency in one direction maximally 25ms */
+	SND_SPCM_LATENCY_MEDIUM,
+	/** realtime latency - realtime applications (effect processors etc.)
+	    (estimated latency in one direction 5ms and better) */
+	SND_SPCM_LATENCY_REALTIME
+} snd_spcm_latency_t;
+
+/** Simple PCM xrun type */
+typedef enum _snd_spcm_xrun_type {
+	/** driver / library will ignore all xruns, the stream runs forever */
+	SND_SPCM_XRUN_IGNORE = 0,
+	/** driver / library stops the stream when an xrun occurs */
+	SND_SPCM_XRUN_STOP
+} snd_spcm_xrun_type_t;
+
+/** Simple PCM duplex type */
+typedef enum _snd_spcm_duplex_type {
+	/** liberal duplex - the buffer and period sizes might not match */
+	SND_SPCM_DUPLEX_LIBERAL = 0,
+	/** pedantic duplex - the buffer and period sizes MUST match */
+	SND_SPCM_DUPLEX_PEDANTIC
+} snd_spcm_duplex_type_t;
+
+int snd_spcm_init(snd_pcm_t *pcm,
+		  unsigned int rate,
+		  unsigned int channels,
+		  snd_pcm_format_t format,
+		  snd_pcm_subformat_t subformat,
+		  snd_spcm_latency_t latency,
+		  snd_pcm_access_t _access,
+		  snd_spcm_xrun_type_t xrun_type);
+
+int snd_spcm_init_duplex(snd_pcm_t *playback_pcm,
+			 snd_pcm_t *capture_pcm,
+			 unsigned int rate,
+			 unsigned int channels,
+			 snd_pcm_format_t format,
+			 snd_pcm_subformat_t subformat,
+			 snd_spcm_latency_t latency,
+			 snd_pcm_access_t _access,
+			 snd_spcm_xrun_type_t xrun_type,
+			 snd_spcm_duplex_type_t duplex_type);
+
+int snd_spcm_init_get_params(snd_pcm_t *pcm,
+			     unsigned int *rate,
+			     snd_pcm_uframes_t *buffer_size,
+			     snd_pcm_uframes_t *period_size);
+
+/** \} */
+
+/**
+ * \defgroup PCM_Deprecated Deprecated Functions
+ * \ingroup PCM
+ * See the \ref pcm page for more details.
+ * \{
+ */
+
+/* Deprecated functions, for compatibility */
+const char *snd_pcm_start_mode_name(snd_pcm_start_t mode) __attribute__((deprecated));
+const char *snd_pcm_xrun_mode_name(snd_pcm_xrun_t mode) __attribute__((deprecated));
+int snd_pcm_sw_params_set_start_mode(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_start_t val) __attribute__((deprecated));
+snd_pcm_start_t snd_pcm_sw_params_get_start_mode(const snd_pcm_sw_params_t *params) __attribute__((deprecated));
+int snd_pcm_sw_params_set_xrun_mode(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_xrun_t val) __attribute__((deprecated));
+snd_pcm_xrun_t snd_pcm_sw_params_get_xrun_mode(const snd_pcm_sw_params_t *params) __attribute__((deprecated));
+#if !defined(ALSA_LIBRARY_BUILD) && !defined(ALSA_PCM_OLD_SW_PARAMS_API)
+int snd_pcm_sw_params_set_xfer_align(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val) __attribute__((deprecated));
+int snd_pcm_sw_params_get_xfer_align(const snd_pcm_sw_params_t *params, snd_pcm_uframes_t *val) __attribute__((deprecated));
+int snd_pcm_sw_params_set_sleep_min(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, unsigned int val) __attribute__((deprecated));
+int snd_pcm_sw_params_get_sleep_min(const snd_pcm_sw_params_t *params, unsigned int *val) __attribute__((deprecated));
+#endif /* !ALSA_LIBRARY_BUILD && !ALSA_PCM_OLD_SW_PARAMS_API */
+#if !defined(ALSA_LIBRARY_BUILD) && !defined(ALSA_PCM_OLD_HW_PARAMS_API)
+int snd_pcm_hw_params_get_tick_time(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir) __attribute__((deprecated));
+int snd_pcm_hw_params_get_tick_time_min(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir) __attribute__((deprecated));
+int snd_pcm_hw_params_get_tick_time_max(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir) __attribute__((deprecated));
+int snd_pcm_hw_params_test_tick_time(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int dir) __attribute__((deprecated));
+int snd_pcm_hw_params_set_tick_time(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int dir) __attribute__((deprecated));
+int snd_pcm_hw_params_set_tick_time_min(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir) __attribute__((deprecated));
+int snd_pcm_hw_params_set_tick_time_max(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir) __attribute__((deprecated));
+int snd_pcm_hw_params_set_tick_time_minmax(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *min, int *mindir, unsigned int *max, int *maxdir) __attribute__((deprecated));
+int snd_pcm_hw_params_set_tick_time_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir) __attribute__((deprecated));
+int snd_pcm_hw_params_set_tick_time_first(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir) __attribute__((deprecated));
+int snd_pcm_hw_params_set_tick_time_last(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir) __attribute__((deprecated));
+#endif /* !ALSA_LIBRARY_BUILD && !ALSA_PCM_OLD_HW_PARAMS_API */
+
+/** \} */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __ALSA_PCM_H */
diff --git a/include/pcm_external.h b/include/pcm_external.h
new file mode 100644
index 0000000..5750418
--- /dev/null
+++ b/include/pcm_external.h
@@ -0,0 +1,70 @@
+/**
+ * \file include/pcm_external.h
+ * \brief External PCM plugin SDK
+ * \author Takashi Iwai <tiwai@suse.de>
+ * \date 2005
+ *
+ * Extern PCM plugin SDK.
+ */
+
+/*
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+#ifndef __ALSA_PCM_EXTERNAL_H
+#define __ALSA_PCM_EXTERNAL_H
+
+#include "pcm.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ *  \defgroup Plugin_SDK External PCM plugin SDK
+ *  \{
+ */
+
+/**
+ * Define the object entry for external PCM plugins
+ */
+#define SND_PCM_PLUGIN_ENTRY(name) _snd_pcm_##name##_open
+
+/**
+ * Define the symbols of the given plugin with versions
+ */
+#define SND_PCM_PLUGIN_SYMBOL(name) SND_DLSYM_BUILD_VERSION(SND_PCM_PLUGIN_ENTRY(name), SND_PCM_DLSYM_VERSION);
+
+/**
+ * Define the plugin
+ */
+#define SND_PCM_PLUGIN_DEFINE_FUNC(plugin) \
+int SND_PCM_PLUGIN_ENTRY(plugin) (snd_pcm_t **pcmp, const char *name,\
+				  snd_config_t *root, snd_config_t *conf, \
+				  snd_pcm_stream_t stream, int mode)
+
+#include "pcm_ioplug.h"
+#include "pcm_extplug.h"
+
+int snd_pcm_parse_control_id(snd_config_t *conf, snd_ctl_elem_id_t *ctl_id, int *cardp,
+			     int *cchannelsp, int *hwctlp);
+
+/** \} */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __ALSA_PCM_EXTERNAL_H */
diff --git a/include/pcm_extplug.h b/include/pcm_extplug.h
new file mode 100644
index 0000000..b14c5be
--- /dev/null
+++ b/include/pcm_extplug.h
@@ -0,0 +1,189 @@
+/**
+ * \file include/pcm_extplug.h
+ * \brief External Filter-Plugin SDK
+ * \author Takashi Iwai <tiwai@suse.de>
+ * \date 2005
+ *
+ * External Filter-Plugin SDK
+ */
+
+/*
+ * ALSA external PCM plugin SDK (draft version)
+ *
+ * Copyright (c) 2005 Takashi Iwai <tiwai@suse.de>
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#ifndef __ALSA_PCM_EXTPLUG_H
+#define __ALSA_PCM_EXTPLUG_H
+
+/**
+ * \defgroup PCM_ExtPlug External Filter plugin SDK
+ * \ingroup Plugin_SDK
+ * See the \ref pcm page for more details.
+ * \{
+ */
+
+/** hw constraints for extplug */
+enum {
+	SND_PCM_EXTPLUG_HW_FORMAT,	/**< format */
+	SND_PCM_EXTPLUG_HW_CHANNELS,	/**< channels */
+	SND_PCM_EXTPLUG_HW_PARAMS	/**< max number of hw constraints */
+};
+	
+/** Handle of external filter plugin */
+typedef struct snd_pcm_extplug snd_pcm_extplug_t;
+/** Callback table of extplug */
+typedef struct snd_pcm_extplug_callback snd_pcm_extplug_callback_t;
+
+/*
+ * Protocol version
+ */
+#define SND_PCM_EXTPLUG_VERSION_MAJOR	1	/**< Protocol major version */
+#define SND_PCM_EXTPLUG_VERSION_MINOR	0	/**< Protocol minor version */
+#define SND_PCM_EXTPLUG_VERSION_TINY	1	/**< Protocol tiny version */
+/**
+ * Filter-plugin protocol version
+ */
+#define SND_PCM_EXTPLUG_VERSION		((SND_PCM_EXTPLUG_VERSION_MAJOR<<16) |\
+					 (SND_PCM_EXTPLUG_VERSION_MINOR<<8) |\
+					 (SND_PCM_EXTPLUG_VERSION_TINY))
+
+/** Handle of extplug */
+struct snd_pcm_extplug {
+	/**
+	 * protocol version; #SND_PCM_EXTPLUG_VERSION must be filled here
+	 * before calling #snd_pcm_extplug_create()
+	 */
+	unsigned int version;
+	/**
+	 * name of this plugin; must be filled before calling #snd_pcm_extplug_create()
+	 */
+	const char *name;
+	/**
+	 * callbacks of this plugin; must be filled before calling #snd_pcm_extplug_create()
+	 */
+	const snd_pcm_extplug_callback_t *callback;
+	/**
+	 * private data, which can be used freely in the driver callbacks
+	 */
+	void *private_data;
+	/**
+	 * PCM handle filled by #snd_pcm_extplug_create()
+	 */
+	snd_pcm_t *pcm;
+	/**
+	 * stream direction; read-only status
+	 */
+	snd_pcm_stream_t stream;
+	/**
+	 * format hw parameter; filled after hw_params is caled
+	 */
+	snd_pcm_format_t format;
+	/**
+	 * subformat hw parameter; filled after hw_params is caled
+	 */
+	snd_pcm_subformat_t subformat;
+	/**
+	 * channels hw parameter; filled after hw_params is caled
+	 */
+	unsigned int channels;
+	/**
+	 * rate hw parameter; filled after hw_params is caled
+	 */
+	unsigned int rate;
+	/**
+	 * slave_format hw parameter; filled after hw_params is caled
+	 */
+	snd_pcm_format_t slave_format;
+	/**
+	 * slave_subformat hw parameter; filled after hw_params is caled
+	 */
+	snd_pcm_subformat_t slave_subformat;
+	/**
+	 * slave_channels hw parameter; filled after hw_params is caled
+	 */
+	unsigned int slave_channels;
+};
+
+/** Callback table of extplug */
+struct snd_pcm_extplug_callback {
+	/**
+	 * transfer between source and destination; this is a required callback
+	 */
+	snd_pcm_sframes_t (*transfer)(snd_pcm_extplug_t *ext,
+				      const snd_pcm_channel_area_t *dst_areas,
+				      snd_pcm_uframes_t dst_offset,
+				      const snd_pcm_channel_area_t *src_areas,
+				      snd_pcm_uframes_t src_offset,
+				      snd_pcm_uframes_t size);
+	/**
+	 * close the PCM; optional
+	 */
+	int (*close)(snd_pcm_extplug_t *ext);
+	/**
+	 * hw_params; optional
+	 */
+	int (*hw_params)(snd_pcm_extplug_t *ext, snd_pcm_hw_params_t *params);
+	/**
+	 * hw_free; optional
+	 */
+	int (*hw_free)(snd_pcm_extplug_t *ext);
+	/**
+	 * dump; optional
+	 */
+	void (*dump)(snd_pcm_extplug_t *ext, snd_output_t *out);
+	/**
+	 * init; optional initialization called at prepare or reset
+	 */
+	int (*init)(snd_pcm_extplug_t *ext);
+};
+
+
+int snd_pcm_extplug_create(snd_pcm_extplug_t *ext, const char *name,
+			   snd_config_t *root, snd_config_t *slave_conf,
+			   snd_pcm_stream_t stream, int mode);
+int snd_pcm_extplug_delete(snd_pcm_extplug_t *ext);
+
+/* clear hw_parameter setting */
+void snd_pcm_extplug_params_reset(snd_pcm_extplug_t *ext);
+
+/* hw_parameter setting */
+int snd_pcm_extplug_set_param_list(snd_pcm_extplug_t *extplug, int type, unsigned int num_list, const unsigned int *list);
+int snd_pcm_extplug_set_param_minmax(snd_pcm_extplug_t *extplug, int type, unsigned int min, unsigned int max);
+int snd_pcm_extplug_set_slave_param_list(snd_pcm_extplug_t *extplug, int type, unsigned int num_list, const unsigned int *list);
+int snd_pcm_extplug_set_slave_param_minmax(snd_pcm_extplug_t *extplug, int type, unsigned int min, unsigned int max);
+
+/**
+ * set the parameter constraint with a single value
+ */
+static inline int snd_pcm_extplug_set_param(snd_pcm_extplug_t *extplug, int type, unsigned int val)
+{
+	return snd_pcm_extplug_set_param_list(extplug, type, 1, &val);
+}
+
+/**
+ * set the parameter constraint for slave PCM with a single value
+ */
+static inline int snd_pcm_extplug_set_slave_param(snd_pcm_extplug_t *extplug, int type, unsigned int val)
+{
+	return snd_pcm_extplug_set_slave_param_list(extplug, type, 1, &val);
+}
+
+/** \} */
+
+#endif /* __ALSA_PCM_EXTPLUG_H */
diff --git a/include/pcm_ioplug.h b/include/pcm_ioplug.h
new file mode 100644
index 0000000..6331cf0
--- /dev/null
+++ b/include/pcm_ioplug.h
@@ -0,0 +1,217 @@
+/**
+ * \file include/pcm_ioplug.h
+ * \brief External I/O-Plugin SDK
+ * \author Takashi Iwai <tiwai@suse.de>
+ * \date 2005
+ *
+ * External I/O-Plugin SDK
+ */
+
+/*
+ * ALSA external PCM plugin SDK
+ *
+ * Copyright (c) 2005 Takashi Iwai <tiwai@suse.de>
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#ifndef __ALSA_PCM_IOPLUG_H
+#define __ALSA_PCM_IOPLUG_H
+
+/**
+ * \defgroup PCM_IOPlug External I/O plugin SDK
+ * \ingroup Plugin_SDK
+ * See the \ref pcm page for more details.
+ * \{
+ */
+
+/** hw constraints for ioplug */
+enum {
+	SND_PCM_IOPLUG_HW_ACCESS = 0,	/**< access type */
+	SND_PCM_IOPLUG_HW_FORMAT,	/**< format */
+	SND_PCM_IOPLUG_HW_CHANNELS,	/**< channels */
+	SND_PCM_IOPLUG_HW_RATE,		/**< rate */
+	SND_PCM_IOPLUG_HW_PERIOD_BYTES,	/**< period bytes */
+	SND_PCM_IOPLUG_HW_BUFFER_BYTES,	/**< buffer bytes */
+	SND_PCM_IOPLUG_HW_PERIODS,	/**< number of periods */
+	SND_PCM_IOPLUG_HW_PARAMS	/**< max number of hw constraints */
+};
+	
+/** I/O plugin handle */
+typedef struct snd_pcm_ioplug snd_pcm_ioplug_t;
+/** Callback table of ioplug */
+typedef struct snd_pcm_ioplug_callback snd_pcm_ioplug_callback_t;
+
+/*
+ * bit flags for additional conditions
+ */
+#define SND_PCM_IOPLUG_FLAG_LISTED	(1<<0)		/**< list up this PCM */
+#define SND_PCM_IOPLUG_FLAG_MONOTONIC	(1<<1)		/**< monotonic timestamps */
+
+/*
+ * Protocol version
+ */
+#define SND_PCM_IOPLUG_VERSION_MAJOR	1	/**< Protocol major version */
+#define SND_PCM_IOPLUG_VERSION_MINOR	0	/**< Protocol minor version */
+#define SND_PCM_IOPLUG_VERSION_TINY	1	/**< Protocol tiny version */
+/**
+ * IO-plugin protocol version
+ */
+#define SND_PCM_IOPLUG_VERSION		((SND_PCM_IOPLUG_VERSION_MAJOR<<16) |\
+					 (SND_PCM_IOPLUG_VERSION_MINOR<<8) |\
+					 (SND_PCM_IOPLUG_VERSION_TINY))
+
+/** Handle of ioplug */
+struct snd_pcm_ioplug {
+	/**
+	 * protocol version; #SND_PCM_IOPLUG_VERSION must be filled here
+	 * before calling #snd_pcm_ioplug_create()
+	 */
+	unsigned int version;
+	/**
+	 * name of this plugin; must be filled before calling #snd_pcm_ioplug_create()
+	 */
+	const char *name;
+	unsigned int flags;	/**< SND_PCM_IOPLUG_FLAG_XXX */
+	int poll_fd;		/**< poll file descriptor */
+	unsigned int poll_events;	/**< poll events */
+	unsigned int mmap_rw;		/**< pseudo mmap mode */
+	/**
+	 * callbacks of this plugin; must be filled before calling #snd_pcm_ioplug_create()
+	 */
+	const snd_pcm_ioplug_callback_t *callback;
+	/**
+	 * private data, which can be used freely in the driver callbacks
+	 */
+	void *private_data;
+	/**
+	 * PCM handle filled by #snd_pcm_extplug_create()
+	 */
+	snd_pcm_t *pcm;
+
+	snd_pcm_stream_t stream;	/**< stream direcion; read-only */	
+	snd_pcm_state_t state;		/**< current PCM state; read-only */
+	volatile snd_pcm_uframes_t appl_ptr;	/**< application pointer; read-only */
+	volatile snd_pcm_uframes_t hw_ptr;	/**< hw pointer; read-only */
+	int nonblock;			/**< non-block mode; read-only */
+
+	snd_pcm_access_t access;	/**< access type; filled after hw_params is called */
+	snd_pcm_format_t format;	/**< PCM format; filled after hw_params is called */
+	unsigned int channels;		/**< number of channels; filled after hw_params is called */
+	unsigned int rate;		/**< rate; filled after hw_params is called */
+	snd_pcm_uframes_t period_size;	/**< period size; filled after hw_params is called */
+	snd_pcm_uframes_t buffer_size;	/**< buffer size; filled after hw_params is called */
+};
+
+/** Callback table of ioplug */
+struct snd_pcm_ioplug_callback {
+	/**
+	 * start the PCM; required
+	 */
+	int (*start)(snd_pcm_ioplug_t *io);
+	/**
+	 * stop the PCM; required
+	 */
+	int (*stop)(snd_pcm_ioplug_t *io);
+	/**
+	 * get the current DMA position; required
+	 */
+	snd_pcm_sframes_t (*pointer)(snd_pcm_ioplug_t *io);
+	/**
+	 * transfer the data; optional
+	 */
+	snd_pcm_sframes_t (*transfer)(snd_pcm_ioplug_t *io,
+				      const snd_pcm_channel_area_t *areas,
+				      snd_pcm_uframes_t offset,
+				      snd_pcm_uframes_t size);
+	/**
+	 * close the PCM; optional
+	 */
+	int (*close)(snd_pcm_ioplug_t *io);
+	/**
+	 * hw_params; optional
+	 */
+	int (*hw_params)(snd_pcm_ioplug_t *io, snd_pcm_hw_params_t *params);
+	/**
+	 * hw_free; optional
+	 */
+	int (*hw_free)(snd_pcm_ioplug_t *io);
+	/**
+	 * sw_params; optional
+	 */
+	int (*sw_params)(snd_pcm_ioplug_t *io, snd_pcm_sw_params_t *params);
+	/**
+	 * prepare; optional
+	 */
+	int (*prepare)(snd_pcm_ioplug_t *io);
+	/**
+	 * drain; optional
+	 */
+	int (*drain)(snd_pcm_ioplug_t *io);
+	/**
+	 * toggle pause; optional
+	 */
+	int (*pause)(snd_pcm_ioplug_t *io, int enable);
+	/**
+	 * resume; optional
+	 */
+	int (*resume)(snd_pcm_ioplug_t *io);
+	/**
+	 * poll descriptors count; optional
+	 */
+	int (*poll_descriptors_count)(snd_pcm_ioplug_t *io);
+	/**
+	 * poll descriptors; optional
+	 */
+	int (*poll_descriptors)(snd_pcm_ioplug_t *io, struct pollfd *pfd, unsigned int space);
+	/**
+	 * mangle poll events; optional
+	 */
+	int (*poll_revents)(snd_pcm_ioplug_t *io, struct pollfd *pfd, unsigned int nfds, unsigned short *revents);
+	/**
+	 * dump; optional
+	 */
+	void (*dump)(snd_pcm_ioplug_t *io, snd_output_t *out);
+	/**
+	 * get the delay for the running PCM; optional
+	 */
+	int (*delay)(snd_pcm_ioplug_t *io, snd_pcm_sframes_t *delayp);
+};
+
+
+int snd_pcm_ioplug_create(snd_pcm_ioplug_t *io, const char *name,
+			  snd_pcm_stream_t stream, int mode);
+int snd_pcm_ioplug_delete(snd_pcm_ioplug_t *io);
+
+/* update poll_fd and mmap_rw */
+int snd_pcm_ioplug_reinit_status(snd_pcm_ioplug_t *ioplug);
+
+/* get a mmap area (for mmap_rw only) */
+const snd_pcm_channel_area_t *snd_pcm_ioplug_mmap_areas(snd_pcm_ioplug_t *ioplug);
+
+/* clear hw_parameter setting */
+void snd_pcm_ioplug_params_reset(snd_pcm_ioplug_t *io);
+
+/* hw_parameter setting */
+int snd_pcm_ioplug_set_param_minmax(snd_pcm_ioplug_t *io, int type, unsigned int min, unsigned int max);
+int snd_pcm_ioplug_set_param_list(snd_pcm_ioplug_t *io, int type, unsigned int num_list, const unsigned int *list);
+
+/* change PCM status */
+int snd_pcm_ioplug_set_state(snd_pcm_ioplug_t *ioplug, snd_pcm_state_t state);
+
+/** \} */
+
+#endif /* __ALSA_PCM_IOPLUG_H */
diff --git a/include/pcm_old.h b/include/pcm_old.h
new file mode 100644
index 0000000..f0de4c3
--- /dev/null
+++ b/include/pcm_old.h
@@ -0,0 +1,230 @@
+/*
+ * Old ALSA 0.9.x API
+ */
+
+#ifdef ALSA_PCM_OLD_HW_PARAMS_API
+
+asm(".symver snd_pcm_hw_params_get_access,snd_pcm_hw_params_get_access@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_set_access_first,snd_pcm_hw_params_set_access_first@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_set_access_last,snd_pcm_hw_params_set_access_last@ALSA_0.9");
+
+int snd_pcm_hw_params_get_access(const snd_pcm_hw_params_t *params);
+int snd_pcm_hw_params_test_access(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_access_t val);
+int snd_pcm_hw_params_set_access(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_access_t val);
+snd_pcm_access_t snd_pcm_hw_params_set_access_first(snd_pcm_t *pcm, snd_pcm_hw_params_t *params);
+snd_pcm_access_t snd_pcm_hw_params_set_access_last(snd_pcm_t *pcm, snd_pcm_hw_params_t *params);
+int snd_pcm_hw_params_set_access_mask(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_access_mask_t *mask);
+void snd_pcm_hw_params_get_access_mask(snd_pcm_hw_params_t *params, snd_pcm_access_mask_t *mask);
+
+asm(".symver snd_pcm_hw_params_get_format,snd_pcm_hw_params_get_format@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_set_format_first,snd_pcm_hw_params_set_format_first@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_set_format_last,snd_pcm_hw_params_set_format_last@ALSA_0.9");
+
+int snd_pcm_hw_params_get_format(const snd_pcm_hw_params_t *params);
+int snd_pcm_hw_params_test_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_format_t val);
+int snd_pcm_hw_params_set_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_format_t val);
+snd_pcm_format_t snd_pcm_hw_params_set_format_first(snd_pcm_t *pcm, snd_pcm_hw_params_t *params);
+snd_pcm_format_t snd_pcm_hw_params_set_format_last(snd_pcm_t *pcm, snd_pcm_hw_params_t *params);
+int snd_pcm_hw_params_set_format_mask(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_format_mask_t *mask);
+void snd_pcm_hw_params_get_format_mask(snd_pcm_hw_params_t *params, snd_pcm_format_mask_t *mask);
+
+asm(".symver snd_pcm_hw_params_get_subformat,snd_pcm_hw_params_get_subformat@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_set_subformat_first,snd_pcm_hw_params_set_subformat_first@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_set_subformat_last,snd_pcm_hw_params_set_subformat_last@ALSA_0.9");
+
+int snd_pcm_hw_params_test_subformat(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_subformat_t val);
+int snd_pcm_hw_params_get_subformat(const snd_pcm_hw_params_t *params);
+int snd_pcm_hw_params_set_subformat(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_subformat_t val);
+snd_pcm_subformat_t snd_pcm_hw_params_set_subformat_first(snd_pcm_t *pcm, snd_pcm_hw_params_t *params);
+snd_pcm_subformat_t snd_pcm_hw_params_set_subformat_last(snd_pcm_t *pcm, snd_pcm_hw_params_t *params);
+int snd_pcm_hw_params_set_subformat_mask(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_subformat_mask_t *mask);
+void snd_pcm_hw_params_get_subformat_mask(snd_pcm_hw_params_t *params, snd_pcm_subformat_mask_t *mask);
+
+asm(".symver snd_pcm_hw_params_get_channels,snd_pcm_hw_params_get_channels@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_get_channels_min,snd_pcm_hw_params_get_channels_min@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_get_channels_max,snd_pcm_hw_params_get_channels_max@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_set_channels_near,snd_pcm_hw_params_set_channels_near@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_set_channels_first,snd_pcm_hw_params_set_channels_first@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_set_channels_last,snd_pcm_hw_params_set_channels_last@ALSA_0.9");
+
+int snd_pcm_hw_params_get_channels(const snd_pcm_hw_params_t *params);
+unsigned int snd_pcm_hw_params_get_channels_min(const snd_pcm_hw_params_t *params);
+unsigned int snd_pcm_hw_params_get_channels_max(const snd_pcm_hw_params_t *params);
+int snd_pcm_hw_params_test_channels(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val);
+int snd_pcm_hw_params_set_channels(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val);
+int snd_pcm_hw_params_set_channels_min(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val);
+int snd_pcm_hw_params_set_channels_max(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val);
+int snd_pcm_hw_params_set_channels_minmax(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *min, unsigned int *max);
+unsigned int snd_pcm_hw_params_set_channels_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val);
+unsigned int snd_pcm_hw_params_set_channels_first(snd_pcm_t *pcm, snd_pcm_hw_params_t *params);
+unsigned int snd_pcm_hw_params_set_channels_last(snd_pcm_t *pcm, snd_pcm_hw_params_t *params);
+
+asm(".symver snd_pcm_hw_params_get_rate,snd_pcm_hw_params_get_rate@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_get_rate_min,snd_pcm_hw_params_get_rate_min@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_get_rate_max,snd_pcm_hw_params_get_rate_max@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_set_rate_near,snd_pcm_hw_params_set_rate_near@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_set_rate_first,snd_pcm_hw_params_set_rate_first@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_set_rate_last,snd_pcm_hw_params_set_rate_last@ALSA_0.9");
+
+int snd_pcm_hw_params_get_rate(const snd_pcm_hw_params_t *params, int *dir);
+unsigned int snd_pcm_hw_params_get_rate_min(const snd_pcm_hw_params_t *params, int *dir);
+unsigned int snd_pcm_hw_params_get_rate_max(const snd_pcm_hw_params_t *params, int *dir);
+int snd_pcm_hw_params_test_rate(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int dir);
+int snd_pcm_hw_params_set_rate(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int dir);
+int snd_pcm_hw_params_set_rate_min(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_set_rate_max(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_set_rate_minmax(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *min, int *mindir, unsigned int *max, int *maxdir);
+unsigned int snd_pcm_hw_params_set_rate_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int *dir);
+unsigned int snd_pcm_hw_params_set_rate_first(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, int *dir);
+unsigned int snd_pcm_hw_params_set_rate_last(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, int *dir);
+int snd_pcm_hw_params_set_rate_resample(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val);
+int snd_pcm_hw_params_get_rate_resample(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val);
+
+asm(".symver snd_pcm_hw_params_get_period_time,snd_pcm_hw_params_get_period_time@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_get_period_time_min,snd_pcm_hw_params_get_period_time_min@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_get_period_time_max,snd_pcm_hw_params_get_period_time_max@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_set_period_time_near,snd_pcm_hw_params_set_period_time_near@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_set_period_time_first,snd_pcm_hw_params_set_period_time_first@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_set_period_time_last,snd_pcm_hw_params_set_period_time_last@ALSA_0.9");
+
+int snd_pcm_hw_params_get_period_time(const snd_pcm_hw_params_t *params, int *dir);
+unsigned int snd_pcm_hw_params_get_period_time_min(const snd_pcm_hw_params_t *params, int *dir);
+unsigned int snd_pcm_hw_params_get_period_time_max(const snd_pcm_hw_params_t *params, int *dir);
+int snd_pcm_hw_params_test_period_time(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int dir);
+int snd_pcm_hw_params_set_period_time(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int dir);
+int snd_pcm_hw_params_set_period_time_min(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_set_period_time_max(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_set_period_time_minmax(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *min, int *mindir, unsigned int *max, int *maxdir);
+unsigned int snd_pcm_hw_params_set_period_time_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int *dir);
+unsigned int snd_pcm_hw_params_set_period_time_first(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, int *dir);
+unsigned int snd_pcm_hw_params_set_period_time_last(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, int *dir);
+
+asm(".symver snd_pcm_hw_params_get_period_size,snd_pcm_hw_params_get_period_size@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_get_period_size_min,snd_pcm_hw_params_get_period_size_min@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_get_period_size_max,snd_pcm_hw_params_get_period_size_max@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_set_period_size_near,snd_pcm_hw_params_set_period_size_near@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_set_period_size_first,snd_pcm_hw_params_set_period_size_first@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_set_period_size_last,snd_pcm_hw_params_set_period_size_last@ALSA_0.9");
+
+snd_pcm_sframes_t snd_pcm_hw_params_get_period_size(const snd_pcm_hw_params_t *params, int *dir);
+snd_pcm_uframes_t snd_pcm_hw_params_get_period_size_min(const snd_pcm_hw_params_t *params, int *dir);
+snd_pcm_uframes_t snd_pcm_hw_params_get_period_size_max(const snd_pcm_hw_params_t *params, int *dir);
+int snd_pcm_hw_params_test_period_size(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t val, int dir);
+int snd_pcm_hw_params_set_period_size(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t val, int dir);
+int snd_pcm_hw_params_set_period_size_min(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val, int *dir);
+int snd_pcm_hw_params_set_period_size_max(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val, int *dir);
+int snd_pcm_hw_params_set_period_size_minmax(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *min, int *mindir, snd_pcm_uframes_t *max, int *maxdir);
+snd_pcm_uframes_t snd_pcm_hw_params_set_period_size_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t val, int *dir);
+snd_pcm_uframes_t snd_pcm_hw_params_set_period_size_first(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, int *dir);
+snd_pcm_uframes_t snd_pcm_hw_params_set_period_size_last(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, int *dir);
+int snd_pcm_hw_params_set_period_size_integer(snd_pcm_t *pcm, snd_pcm_hw_params_t *params);
+
+asm(".symver snd_pcm_hw_params_get_periods,snd_pcm_hw_params_get_periods@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_get_periods_min,snd_pcm_hw_params_get_periods_min@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_get_periods_max,snd_pcm_hw_params_get_periods_max@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_set_periods_near,snd_pcm_hw_params_set_periods_near@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_set_periods_first,snd_pcm_hw_params_set_periods_first@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_set_periods_last,snd_pcm_hw_params_set_periods_last@ALSA_0.9");
+
+int snd_pcm_hw_params_get_periods(const snd_pcm_hw_params_t *params, int *dir);
+unsigned int snd_pcm_hw_params_get_periods_min(const snd_pcm_hw_params_t *params, int *dir);
+unsigned int snd_pcm_hw_params_get_periods_max(const snd_pcm_hw_params_t *params, int *dir);
+int snd_pcm_hw_params_test_periods(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int dir);
+int snd_pcm_hw_params_set_periods(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int dir);
+int snd_pcm_hw_params_set_periods_min(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_set_periods_max(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_set_periods_minmax(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *min, int *mindir, unsigned int *max, int *maxdir);
+unsigned int snd_pcm_hw_params_set_periods_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int *dir);
+unsigned int snd_pcm_hw_params_set_periods_first(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, int *dir);
+unsigned int snd_pcm_hw_params_set_periods_last(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, int *dir);
+int snd_pcm_hw_params_set_periods_integer(snd_pcm_t *pcm, snd_pcm_hw_params_t *params);
+
+asm(".symver snd_pcm_hw_params_get_buffer_time,snd_pcm_hw_params_get_buffer_time@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_get_buffer_time_min,snd_pcm_hw_params_get_buffer_time_min@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_get_buffer_time_max,snd_pcm_hw_params_get_buffer_time_max@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_set_buffer_time_near,snd_pcm_hw_params_set_buffer_time_near@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_set_buffer_time_first,snd_pcm_hw_params_set_buffer_time_first@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_set_buffer_time_last,snd_pcm_hw_params_set_buffer_time_last@ALSA_0.9");
+
+int snd_pcm_hw_params_get_buffer_time(const snd_pcm_hw_params_t *params, int *dir);
+unsigned int snd_pcm_hw_params_get_buffer_time_min(const snd_pcm_hw_params_t *params, int *dir);
+unsigned int snd_pcm_hw_params_get_buffer_time_max(const snd_pcm_hw_params_t *params, int *dir);
+int snd_pcm_hw_params_test_buffer_time(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int dir);
+int snd_pcm_hw_params_set_buffer_time(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int dir);
+int snd_pcm_hw_params_set_buffer_time_min(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_set_buffer_time_max(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_set_buffer_time_minmax(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *min, int *mindir, unsigned int *max, int *maxdir);
+unsigned int snd_pcm_hw_params_set_buffer_time_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int *dir);
+unsigned int snd_pcm_hw_params_set_buffer_time_first(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, int *dir);
+unsigned int snd_pcm_hw_params_set_buffer_time_last(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, int *dir);
+
+asm(".symver snd_pcm_hw_params_get_buffer_size,snd_pcm_hw_params_get_buffer_size@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_get_buffer_size_min,snd_pcm_hw_params_get_buffer_size_min@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_get_buffer_size_max,snd_pcm_hw_params_get_buffer_size_max@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_set_buffer_size_near,snd_pcm_hw_params_set_buffer_size_near@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_set_buffer_size_first,snd_pcm_hw_params_set_buffer_size_first@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_set_buffer_size_last,snd_pcm_hw_params_set_buffer_size_last@ALSA_0.9");
+
+snd_pcm_sframes_t snd_pcm_hw_params_get_buffer_size(const snd_pcm_hw_params_t *params);
+snd_pcm_uframes_t snd_pcm_hw_params_get_buffer_size_min(const snd_pcm_hw_params_t *params);
+snd_pcm_uframes_t snd_pcm_hw_params_get_buffer_size_max(const snd_pcm_hw_params_t *params);
+int snd_pcm_hw_params_test_buffer_size(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t val);
+int snd_pcm_hw_params_set_buffer_size(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t val);
+int snd_pcm_hw_params_set_buffer_size_min(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val);
+int snd_pcm_hw_params_set_buffer_size_max(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val);
+int snd_pcm_hw_params_set_buffer_size_minmax(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *min, snd_pcm_uframes_t *max);
+snd_pcm_uframes_t snd_pcm_hw_params_set_buffer_size_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t val);
+snd_pcm_uframes_t snd_pcm_hw_params_set_buffer_size_first(snd_pcm_t *pcm, snd_pcm_hw_params_t *params);
+snd_pcm_uframes_t snd_pcm_hw_params_set_buffer_size_last(snd_pcm_t *pcm, snd_pcm_hw_params_t *params);
+
+asm(".symver snd_pcm_hw_params_get_tick_time,snd_pcm_hw_params_get_tick_time@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_get_tick_time_min,snd_pcm_hw_params_get_tick_time_min@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_get_tick_time_max,snd_pcm_hw_params_get_tick_time_max@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_set_tick_time_near,snd_pcm_hw_params_set_tick_time_near@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_set_tick_time_first,snd_pcm_hw_params_set_tick_time_first@ALSA_0.9");
+asm(".symver snd_pcm_hw_params_set_tick_time_last,snd_pcm_hw_params_set_tick_time_last@ALSA_0.9");
+
+int snd_pcm_hw_params_get_tick_time(const snd_pcm_hw_params_t *params, int *dir);
+unsigned int snd_pcm_hw_params_get_tick_time_min(const snd_pcm_hw_params_t *params, int *dir);
+unsigned int snd_pcm_hw_params_get_tick_time_max(const snd_pcm_hw_params_t *params, int *dir);
+int snd_pcm_hw_params_test_tick_time(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int dir);
+int snd_pcm_hw_params_set_tick_time(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int dir);
+int snd_pcm_hw_params_set_tick_time_min(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_set_tick_time_max(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_set_tick_time_minmax(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *min, int *mindir, unsigned int *max, int *maxdir);
+unsigned int snd_pcm_hw_params_set_tick_time_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int *dir);
+unsigned int snd_pcm_hw_params_set_tick_time_first(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, int *dir);
+unsigned int snd_pcm_hw_params_set_tick_time_last(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, int *dir);
+
+#endif /* ALSA_PCM_OLD_HW_PARAMS_API */
+
+
+#ifdef ALSA_PCM_OLD_SW_PARAMS_API
+
+asm(".symver snd_pcm_sw_params_get_tstamp_mode,snd_pcm_sw_params_get_tstamp_mode@ALSA_0.9");
+asm(".symver snd_pcm_sw_params_get_sleep_min,snd_pcm_sw_params_get_sleep_min@ALSA_0.9");
+asm(".symver snd_pcm_sw_params_get_avail_min,snd_pcm_sw_params_get_avail_min@ALSA_0.9");
+asm(".symver snd_pcm_sw_params_get_xfer_align,snd_pcm_sw_params_get_xfer_align@ALSA_0.9");
+asm(".symver snd_pcm_sw_params_get_start_threshold,snd_pcm_sw_params_get_start_threshold@ALSA_0.9");
+asm(".symver snd_pcm_sw_params_get_stop_threshold,snd_pcm_sw_params_set_stop_threshold@ALSA_0.9");
+asm(".symver snd_pcm_sw_params_get_silence_threshold,snd_pcm_sw_params_get_silence_threshold@ALSA_0.9");
+asm(".symver snd_pcm_sw_params_get_silence_size,snd_pcm_sw_params_get_silence_size@ALSA_0.9");
+
+int snd_pcm_sw_params_set_tstamp_mode(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_tstamp_t val);
+snd_pcm_tstamp_t snd_pcm_sw_params_get_tstamp_mode(const snd_pcm_sw_params_t *params);
+int snd_pcm_sw_params_set_sleep_min(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, unsigned int val);
+unsigned int snd_pcm_sw_params_get_sleep_min(const snd_pcm_sw_params_t *params);
+int snd_pcm_sw_params_set_avail_min(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val);
+snd_pcm_uframes_t snd_pcm_sw_params_get_avail_min(const snd_pcm_sw_params_t *params);
+int snd_pcm_sw_params_set_xfer_align(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val);
+snd_pcm_uframes_t snd_pcm_sw_params_get_xfer_align(const snd_pcm_sw_params_t *params);
+int snd_pcm_sw_params_set_start_threshold(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val);
+snd_pcm_uframes_t snd_pcm_sw_params_get_start_threshold(const snd_pcm_sw_params_t *params);
+int snd_pcm_sw_params_set_stop_threshold(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val);
+snd_pcm_uframes_t snd_pcm_sw_params_get_stop_threshold(const snd_pcm_sw_params_t *params);
+int snd_pcm_sw_params_set_silence_threshold(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val);
+snd_pcm_uframes_t snd_pcm_sw_params_get_silence_threshold(const snd_pcm_sw_params_t *params);
+int snd_pcm_sw_params_set_silence_size(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val);
+snd_pcm_uframes_t snd_pcm_sw_params_get_silence_size(const snd_pcm_sw_params_t *params);
+
+#endif /* ALSA_PCM_OLD_SW_PARAMS_API */
diff --git a/include/pcm_plugin.h b/include/pcm_plugin.h
new file mode 100644
index 0000000..eea1d82
--- /dev/null
+++ b/include/pcm_plugin.h
@@ -0,0 +1,202 @@
+/**
+ * \file include/pcm_plugin.h
+ * \brief Common PCM plugin code
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \date 2000-2001
+ *
+ * Application interface library for the ALSA driver.
+ * See the \ref pcm_plugins page for more details.
+ *
+ * \warning Using of contents of this header file might be dangerous
+ *	    in the sense of compatibility reasons. The contents might be
+ *	    freely changed in future.
+ */
+/*
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#ifndef __ALSA_PCM_PLUGIN_H
+
+/**
+ * \defgroup PCM_Plugins PCM Plugins
+ * \ingroup PCM
+ * See the \ref pcm_plugins page for more details.
+ * \{
+ */
+  
+#define SND_PCM_PLUGIN_RATE_MIN 4000	/**< minimal rate for the rate plugin */
+#define SND_PCM_PLUGIN_RATE_MAX 192000	/**< maximal rate for the rate plugin */
+
+/* ROUTE_FLOAT should be set to 0 for machines without FP unit - like iPAQ */
+#ifdef HAVE_SOFT_FLOAT
+#define SND_PCM_PLUGIN_ROUTE_FLOAT 0	   /**< use integers for route plugin */
+#else
+#define SND_PCM_PLUGIN_ROUTE_FLOAT 1	   /**< use floats for route plugin */
+#endif
+
+#define SND_PCM_PLUGIN_ROUTE_RESOLUTION 16 /**< integer resolution for route plugin */
+
+#if SND_PCM_PLUGIN_ROUTE_FLOAT
+/** route ttable entry type */
+typedef float snd_pcm_route_ttable_entry_t;
+#define SND_PCM_PLUGIN_ROUTE_HALF 0.5	/**< half value */
+#define SND_PCM_PLUGIN_ROUTE_FULL 1.0	/**< full value */
+#else
+/** route ttable entry type */
+typedef int snd_pcm_route_ttable_entry_t;
+#define SND_PCM_PLUGIN_ROUTE_HALF (SND_PCM_PLUGIN_ROUTE_RESOLUTION / 2)	/**< half value */
+#define SND_PCM_PLUGIN_ROUTE_FULL SND_PCM_PLUGIN_ROUTE_RESOLUTION	/**< full value */
+#endif
+
+/*
+ *  Hardware plugin
+ */
+int snd_pcm_hw_open(snd_pcm_t **pcmp, const char *name,
+		    int card, int device, int subdevice,
+		    snd_pcm_stream_t stream, int mode,
+		    int mmap_emulation, int sync_ptr_ioctl);
+int _snd_pcm_hw_open(snd_pcm_t **pcmp, const char *name,
+		     snd_config_t *root ATTRIBUTE_UNUSED, snd_config_t *conf,
+		     snd_pcm_stream_t stream, int mode);
+
+/*
+ *  Copy plugin
+ */
+int snd_pcm_copy_open(snd_pcm_t **pcmp, const char *name,
+		      snd_pcm_t *slave, int close_slave);
+int _snd_pcm_copy_open(snd_pcm_t **pcmp, const char *name,
+		       snd_config_t *root, snd_config_t *conf,
+                       snd_pcm_stream_t stream, int mode);
+                                              
+/*
+ *  Linear conversion plugin
+ */
+int snd_pcm_linear_open(snd_pcm_t **pcmp, const char *name,
+			snd_pcm_format_t sformat, snd_pcm_t *slave,
+			int close_slave);
+int _snd_pcm_linear_open(snd_pcm_t **pcmp, const char *name,
+			 snd_config_t *root, snd_config_t *conf,
+			 snd_pcm_stream_t stream, int mode);
+
+/*
+ *  Linear<->Float conversion plugin
+ */
+int snd_pcm_lfloat_open(snd_pcm_t **pcmp, const char *name,
+			snd_pcm_format_t sformat, snd_pcm_t *slave,
+			int close_slave);
+int _snd_pcm_lfloat_open(snd_pcm_t **pcmp, const char *name,
+			 snd_config_t *root, snd_config_t *conf,
+			 snd_pcm_stream_t stream, int mode);
+
+/*
+ *  Linear<->mu-Law conversion plugin
+ */
+int snd_pcm_mulaw_open(snd_pcm_t **pcmp, const char *name,
+		       snd_pcm_format_t sformat, snd_pcm_t *slave,
+		       int close_slave);
+int _snd_pcm_mulaw_open(snd_pcm_t **pcmp, const char *name,
+			snd_config_t *root, snd_config_t *conf,
+                        snd_pcm_stream_t stream, int mode);
+
+/*
+ *  Linear<->a-Law conversion plugin
+ */
+int snd_pcm_alaw_open(snd_pcm_t **pcmp, const char *name,
+		      snd_pcm_format_t sformat, snd_pcm_t *slave,
+		      int close_slave);
+int _snd_pcm_alaw_open(snd_pcm_t **pcmp, const char *name,
+		       snd_config_t *root, snd_config_t *conf,
+		       snd_pcm_stream_t stream, int mode);
+
+/*
+ *  Linear<->Ima-ADPCM conversion plugin
+ */
+int snd_pcm_adpcm_open(snd_pcm_t **pcmp, const char *name,
+		       snd_pcm_format_t sformat, snd_pcm_t *slave,
+		       int close_slave);
+int _snd_pcm_adpcm_open(snd_pcm_t **pcmp, const char *name,
+			snd_config_t *root, snd_config_t *conf,
+			snd_pcm_stream_t stream, int mode);
+
+/*
+ *  Route plugin for linear formats
+ */
+int snd_pcm_route_load_ttable(snd_config_t *tt, snd_pcm_route_ttable_entry_t *ttable,
+			      unsigned int tt_csize, unsigned int tt_ssize,
+			      unsigned int *tt_cused, unsigned int *tt_sused,
+			      int schannels);
+int snd_pcm_route_determine_ttable(snd_config_t *tt,
+				   unsigned int *tt_csize,
+				   unsigned int *tt_ssize);
+int snd_pcm_route_open(snd_pcm_t **pcmp, const char *name,
+		       snd_pcm_format_t sformat, int schannels,
+		       snd_pcm_route_ttable_entry_t *ttable,
+		       unsigned int tt_ssize,
+		       unsigned int tt_cused, unsigned int tt_sused,
+		       snd_pcm_t *slave, int close_slave);
+int _snd_pcm_route_open(snd_pcm_t **pcmp, const char *name,
+			snd_config_t *root, snd_config_t *conf,
+			snd_pcm_stream_t stream, int mode);
+
+/*
+ *  Rate plugin for linear formats
+ */
+int snd_pcm_rate_open(snd_pcm_t **pcmp, const char *name,
+		      snd_pcm_format_t sformat, unsigned int srate,
+		      const snd_config_t *converter,
+		      snd_pcm_t *slave, int close_slave);
+int _snd_pcm_rate_open(snd_pcm_t **pcmp, const char *name,
+		       snd_config_t *root, snd_config_t *conf,
+		       snd_pcm_stream_t stream, int mode);
+
+/*
+ *  Hooks plugin
+ */
+int snd_pcm_hooks_open(snd_pcm_t **pcmp, const char *name,
+		       snd_pcm_t *slave, int close_slave);
+int _snd_pcm_hooks_open(snd_pcm_t **pcmp, const char *name,
+			snd_config_t *root, snd_config_t *conf,
+			snd_pcm_stream_t stream, int mode);
+
+/*
+ *  LADSPA plugin
+ */
+int snd_pcm_ladspa_open(snd_pcm_t **pcmp, const char *name,
+			const char *ladspa_path,
+			unsigned int channels,
+			snd_config_t *ladspa_pplugins,
+			snd_config_t *ladspa_cplugins,
+			snd_pcm_t *slave, int close_slave);
+int _snd_pcm_ladspa_open(snd_pcm_t **pcmp, const char *name,
+			 snd_config_t *root, snd_config_t *conf,
+			 snd_pcm_stream_t stream, int mode);
+
+/*
+ *  Jack plugin
+ */
+int snd_pcm_jack_open(snd_pcm_t **pcmp, const char *name,
+					snd_config_t *playback_conf,
+					snd_config_t *capture_conf,
+		      snd_pcm_stream_t stream, int mode);
+int _snd_pcm_jack_open(snd_pcm_t **pcmp, const char *name,
+                       snd_config_t *root, snd_config_t *conf,
+                       snd_pcm_stream_t stream, int mode);
+
+
+/** \} */
+
+#endif /* __ALSA_PCM_PLUGIN_H */
diff --git a/include/pcm_rate.h b/include/pcm_rate.h
new file mode 100644
index 0000000..4d70df2
--- /dev/null
+++ b/include/pcm_rate.h
@@ -0,0 +1,153 @@
+/**
+ * \file include/pcm_rate.h
+ * \brief External Rate-Converter-Plugin SDK
+ * \author Takashi Iwai <tiwai@suse.de>
+ * \date 2006
+ *
+ * External Rate-Converter-Plugin SDK
+ */
+
+/*
+ * ALSA external PCM rate-converter plugin SDK (draft version)
+ *
+ * Copyright (c) 2006 Takashi Iwai <tiwai@suse.de>
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#ifndef __ALSA_PCM_RATE_H
+#define __ALSA_PCM_RATE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Protocol version
+ */
+#define SND_PCM_RATE_PLUGIN_VERSION	0x010002
+
+/** hw_params information for a single side */
+typedef struct snd_pcm_rate_side_info {
+	snd_pcm_format_t format;
+	unsigned int rate;
+	snd_pcm_uframes_t buffer_size;
+	snd_pcm_uframes_t period_size;
+} snd_pcm_rate_side_info_t;
+
+/** hw_params information */
+typedef struct snd_pcm_rate_info {
+	struct snd_pcm_rate_side_info in;
+	struct snd_pcm_rate_side_info out;
+	unsigned int channels;
+} snd_pcm_rate_info_t;
+
+/** Callback table of rate-converter */
+typedef struct snd_pcm_rate_ops {
+	/**
+	 * close the converter; optional
+	 */
+	void (*close)(void *obj);
+	/**
+	 * initialize the converter, called at hw_params
+	 */
+	int (*init)(void *obj, snd_pcm_rate_info_t *info);
+	/**
+	 * free the converter; optional
+	 */
+	void (*free)(void *obj);
+	/**
+	 * reset the converter, called at prepare; optional
+	 */
+	void (*reset)(void *obj);
+	/**
+	 * adjust the pitch, called at sw_params; optional
+	 */
+	int (*adjust_pitch)(void *obj, snd_pcm_rate_info_t *info);
+	/**
+	 * convert the data
+	 */
+	void (*convert)(void *obj,
+			const snd_pcm_channel_area_t *dst_areas,
+			snd_pcm_uframes_t dst_offset, unsigned int dst_frames,
+			const snd_pcm_channel_area_t *src_areas,
+			snd_pcm_uframes_t src_offset, unsigned int src_frames);
+	/**
+	 * convert an s16 interleaved-data array; exclusive with convert
+	 */
+	void (*convert_s16)(void *obj, int16_t *dst, unsigned int dst_frames,
+			    const int16_t *src, unsigned int src_frames);
+	/**
+	 * compute the frame size for input
+	 */
+	snd_pcm_uframes_t (*input_frames)(void *obj, snd_pcm_uframes_t frames);
+	/**
+	 * compute the frame size for output
+	 */
+	snd_pcm_uframes_t (*output_frames)(void *obj, snd_pcm_uframes_t frames);
+	/**
+	 * the protocol version the plugin supports;
+	 * new field since version 0x010002
+	 */
+	unsigned int version;
+	/**
+	 * return the supported min / max sample rates;
+	 * new ops since version 0x010002
+	 */
+	int (*get_supported_rates)(void *obj, unsigned int *rate_min,
+				   unsigned int *rate_max);
+	/**
+	 * show some status messages for verbose mode;
+	 * new ops since version 0x010002
+	 */
+	void (*dump)(void *obj, snd_output_t *out);
+} snd_pcm_rate_ops_t;
+
+/** open function type */
+typedef int (*snd_pcm_rate_open_func_t)(unsigned int version, void **objp,
+					snd_pcm_rate_ops_t *opsp);
+
+/**
+ * Define the object entry for external PCM rate-converter plugins
+ */
+#define SND_PCM_RATE_PLUGIN_ENTRY(name) _snd_pcm_rate_##name##_open
+
+
+#ifndef DOC_HIDDEN
+/* old rate_ops for protocol version 0x010001 */
+typedef struct snd_pcm_rate_old_ops {
+	void (*close)(void *obj);
+	int (*init)(void *obj, snd_pcm_rate_info_t *info);
+	void (*free)(void *obj);
+	void (*reset)(void *obj);
+	int (*adjust_pitch)(void *obj, snd_pcm_rate_info_t *info);
+	void (*convert)(void *obj,
+			const snd_pcm_channel_area_t *dst_areas,
+			snd_pcm_uframes_t dst_offset, unsigned int dst_frames,
+			const snd_pcm_channel_area_t *src_areas,
+			snd_pcm_uframes_t src_offset, unsigned int src_frames);
+	void (*convert_s16)(void *obj, int16_t *dst, unsigned int dst_frames,
+			    const int16_t *src, unsigned int src_frames);
+	snd_pcm_uframes_t (*input_frames)(void *obj, snd_pcm_uframes_t frames);
+	snd_pcm_uframes_t (*output_frames)(void *obj, snd_pcm_uframes_t frames);
+} snd_pcm_rate_old_ops_t;
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __ALSA_PCM_RATE_H */
diff --git a/include/rawmidi.h b/include/rawmidi.h
new file mode 100644
index 0000000..1d8fd56
--- /dev/null
+++ b/include/rawmidi.h
@@ -0,0 +1,159 @@
+/**
+ * \file include/rawmidi.h
+ * \brief Application interface library for the ALSA driver
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \author Takashi Iwai <tiwai@suse.de>
+ * \date 1998-2001
+ *
+ * Application interface library for the ALSA driver
+ */
+/*
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#ifndef __ALSA_RAWMIDI_H
+#define __ALSA_RAWMIDI_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ *  \defgroup RawMidi RawMidi Interface
+ *  The RawMidi Interface. See \ref rawmidi page for more details.
+ *  \{
+ */
+
+/** dlsym version for interface entry callback */
+#define SND_RAWMIDI_DLSYM_VERSION	_dlsym_rawmidi_001
+
+/** RawMidi information container */
+typedef struct _snd_rawmidi_info snd_rawmidi_info_t;
+/** RawMidi settings container */
+typedef struct _snd_rawmidi_params snd_rawmidi_params_t;
+/** RawMidi status container */
+typedef struct _snd_rawmidi_status snd_rawmidi_status_t;
+
+/** RawMidi stream (direction) */
+typedef enum _snd_rawmidi_stream {
+	/** Output stream */
+	SND_RAWMIDI_STREAM_OUTPUT = 0,
+	/** Input stream */
+	SND_RAWMIDI_STREAM_INPUT,
+	SND_RAWMIDI_STREAM_LAST = SND_RAWMIDI_STREAM_INPUT
+} snd_rawmidi_stream_t;
+
+/** Append (flag to open mode) \hideinitializer */
+#define SND_RAWMIDI_APPEND	0x0001
+/** Non blocking mode (flag to open mode) \hideinitializer */
+#define SND_RAWMIDI_NONBLOCK	0x0002
+/** Write sync mode (Flag to open mode) \hideinitializer */
+#define SND_RAWMIDI_SYNC	0x0004
+
+/** RawMidi handle */
+typedef struct _snd_rawmidi snd_rawmidi_t;
+
+/** RawMidi type */
+typedef enum _snd_rawmidi_type {
+	/** Kernel level RawMidi */
+	SND_RAWMIDI_TYPE_HW,
+	/** Shared memory client RawMidi (not yet implemented) */
+	SND_RAWMIDI_TYPE_SHM,
+	/** INET client RawMidi (not yet implemented) */
+	SND_RAWMIDI_TYPE_INET,
+	/** Virtual (sequencer) RawMidi */
+	SND_RAWMIDI_TYPE_VIRTUAL
+} snd_rawmidi_type_t;
+
+int snd_rawmidi_open(snd_rawmidi_t **in_rmidi, snd_rawmidi_t **out_rmidi,
+		     const char *name, int mode);
+int snd_rawmidi_open_lconf(snd_rawmidi_t **in_rmidi, snd_rawmidi_t **out_rmidi,
+			   const char *name, int mode, snd_config_t *lconf);
+int snd_rawmidi_close(snd_rawmidi_t *rmidi);
+int snd_rawmidi_poll_descriptors_count(snd_rawmidi_t *rmidi);
+int snd_rawmidi_poll_descriptors(snd_rawmidi_t *rmidi, struct pollfd *pfds, unsigned int space);
+int snd_rawmidi_poll_descriptors_revents(snd_rawmidi_t *rawmidi, struct pollfd *pfds, unsigned int nfds, unsigned short *revent);
+int snd_rawmidi_nonblock(snd_rawmidi_t *rmidi, int nonblock);
+size_t snd_rawmidi_info_sizeof(void);
+/** \hideinitializer
+ * \brief allocate an invalid #snd_rawmidi_info_t using standard alloca
+ * \param ptr returned pointer
+ */
+#define snd_rawmidi_info_alloca(ptr) __snd_alloca(ptr, snd_rawmidi_info)
+int snd_rawmidi_info_malloc(snd_rawmidi_info_t **ptr);
+void snd_rawmidi_info_free(snd_rawmidi_info_t *obj);
+void snd_rawmidi_info_copy(snd_rawmidi_info_t *dst, const snd_rawmidi_info_t *src);
+unsigned int snd_rawmidi_info_get_device(const snd_rawmidi_info_t *obj);
+unsigned int snd_rawmidi_info_get_subdevice(const snd_rawmidi_info_t *obj);
+snd_rawmidi_stream_t snd_rawmidi_info_get_stream(const snd_rawmidi_info_t *obj);
+int snd_rawmidi_info_get_card(const snd_rawmidi_info_t *obj);
+unsigned int snd_rawmidi_info_get_flags(const snd_rawmidi_info_t *obj);
+const char *snd_rawmidi_info_get_id(const snd_rawmidi_info_t *obj);
+const char *snd_rawmidi_info_get_name(const snd_rawmidi_info_t *obj);
+const char *snd_rawmidi_info_get_subdevice_name(const snd_rawmidi_info_t *obj);
+unsigned int snd_rawmidi_info_get_subdevices_count(const snd_rawmidi_info_t *obj);
+unsigned int snd_rawmidi_info_get_subdevices_avail(const snd_rawmidi_info_t *obj);
+void snd_rawmidi_info_set_device(snd_rawmidi_info_t *obj, unsigned int val);
+void snd_rawmidi_info_set_subdevice(snd_rawmidi_info_t *obj, unsigned int val);
+void snd_rawmidi_info_set_stream(snd_rawmidi_info_t *obj, snd_rawmidi_stream_t val);
+int snd_rawmidi_info(snd_rawmidi_t *rmidi, snd_rawmidi_info_t * info);
+size_t snd_rawmidi_params_sizeof(void);
+/** \hideinitializer
+ * \brief allocate an invalid #snd_rawmidi_params_t using standard alloca
+ * \param ptr returned pointer
+ */
+#define snd_rawmidi_params_alloca(ptr) __snd_alloca(ptr, snd_rawmidi_params)
+int snd_rawmidi_params_malloc(snd_rawmidi_params_t **ptr);
+void snd_rawmidi_params_free(snd_rawmidi_params_t *obj);
+void snd_rawmidi_params_copy(snd_rawmidi_params_t *dst, const snd_rawmidi_params_t *src);
+int snd_rawmidi_params_set_buffer_size(snd_rawmidi_t *rmidi, snd_rawmidi_params_t *params, size_t val);
+size_t snd_rawmidi_params_get_buffer_size(const snd_rawmidi_params_t *params);
+int snd_rawmidi_params_set_avail_min(snd_rawmidi_t *rmidi, snd_rawmidi_params_t *params, size_t val);
+size_t snd_rawmidi_params_get_avail_min(const snd_rawmidi_params_t *params);
+int snd_rawmidi_params_set_no_active_sensing(snd_rawmidi_t *rmidi, snd_rawmidi_params_t *params, int val);
+int snd_rawmidi_params_get_no_active_sensing(const snd_rawmidi_params_t *params);
+int snd_rawmidi_params(snd_rawmidi_t *rmidi, snd_rawmidi_params_t * params);
+int snd_rawmidi_params_current(snd_rawmidi_t *rmidi, snd_rawmidi_params_t *params);
+size_t snd_rawmidi_status_sizeof(void);
+/** \hideinitializer
+ * \brief allocate an invalid #snd_rawmidi_status_t using standard alloca
+ * \param ptr returned pointer
+ */
+#define snd_rawmidi_status_alloca(ptr) __snd_alloca(ptr, snd_rawmidi_status)
+int snd_rawmidi_status_malloc(snd_rawmidi_status_t **ptr);
+void snd_rawmidi_status_free(snd_rawmidi_status_t *obj);
+void snd_rawmidi_status_copy(snd_rawmidi_status_t *dst, const snd_rawmidi_status_t *src);
+void snd_rawmidi_status_get_tstamp(const snd_rawmidi_status_t *obj, snd_htimestamp_t *ptr);
+size_t snd_rawmidi_status_get_avail(const snd_rawmidi_status_t *obj);
+size_t snd_rawmidi_status_get_xruns(const snd_rawmidi_status_t *obj);
+int snd_rawmidi_status(snd_rawmidi_t *rmidi, snd_rawmidi_status_t * status);
+int snd_rawmidi_drain(snd_rawmidi_t *rmidi);
+int snd_rawmidi_drop(snd_rawmidi_t *rmidi);
+ssize_t snd_rawmidi_write(snd_rawmidi_t *rmidi, const void *buffer, size_t size);
+ssize_t snd_rawmidi_read(snd_rawmidi_t *rmidi, void *buffer, size_t size);
+const char *snd_rawmidi_name(snd_rawmidi_t *rmidi);
+snd_rawmidi_type_t snd_rawmidi_type(snd_rawmidi_t *rmidi);
+snd_rawmidi_stream_t snd_rawmidi_stream(snd_rawmidi_t *rawmidi);
+
+/** \} */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __RAWMIDI_H */
+
diff --git a/include/search.h b/include/search.h
new file mode 100644
index 0000000..91e6210
--- /dev/null
+++ b/include/search.h
@@ -0,0 +1,177 @@
+/* Declarations for System V style searching functions.
+   Copyright (C) 1995-1999, 2000 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public License as
+   published by the Free Software Foundation; either version 2.1 of the
+   License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.  */
+
+#ifndef _SEARCH_H
+#define	_SEARCH_H 1
+
+#include <features.h>
+
+#define __need_size_t
+#include <stddef.h>
+
+__BEGIN_DECLS
+
+#if defined __USE_SVID || defined __USE_XOPEN_EXTENDED
+/* Prototype structure for a linked-list data structure.
+   This is the type used by the `insque' and `remque' functions.  */
+
+# ifdef __USE_GNU
+struct qelem
+  {
+    struct qelem *q_forw;
+    struct qelem *q_back;
+    char q_data[1];
+  };
+# endif
+
+
+/* Insert ELEM into a doubly-linked list, after PREV.  */
+extern void insque __P ((void *__elem, void *__prev));
+
+/* Unlink ELEM from the doubly-linked list that it is in.  */
+extern void remque __P ((void *__elem));
+#endif
+
+
+/* For use with hsearch(3).  */
+#ifndef __COMPAR_FN_T
+# define __COMPAR_FN_T
+typedef int (*__compar_fn_t) __PMT ((__const __ptr_t, __const __ptr_t));
+
+# ifdef	__USE_GNU
+typedef __compar_fn_t comparison_fn_t;
+# endif
+#endif
+
+/* Action which shall be performed in the call the hsearch.  */
+typedef enum
+  {
+    FIND,
+    ENTER
+  }
+ACTION;
+
+typedef struct entry
+  {
+    char *key;
+    void *data;
+  }
+ENTRY;
+
+/* Opaque type for internal use.  */
+struct _ENTRY;
+
+/* Family of hash table handling functions.  The functions also
+   have reentrant counterparts ending with _r.  The non-reentrant
+   functions all work on a single internal hashing table.  */
+
+/* Search for entry matching ITEM.key in internal hash table.  If
+   ACTION is `FIND' return found entry or signal error by returning
+   NULL.  If ACTION is `ENTER' replace existing data (if any) with
+   ITEM.data.  */
+extern ENTRY *hsearch __P ((ENTRY __item, ACTION __action));
+
+/* Create a new hashing table which will at most contain NEL elements.  */
+extern int hcreate __P ((size_t __nel));
+
+/* Destroy current internal hashing table.  */
+extern void hdestroy __P ((void));
+
+#ifdef __USE_GNU
+/* Data type for reentrant functions.  */
+struct hsearch_data
+  {
+    struct _ENTRY *table;
+    unsigned int size;
+    unsigned int filled;
+  };
+
+/* Reentrant versions which can handle multiple hashing tables at the
+   same time.  */
+extern int hsearch_r __P ((ENTRY __item, ACTION __action, ENTRY **__retval,
+			   struct hsearch_data *__htab));
+extern int hcreate_r __P ((size_t __nel, struct hsearch_data *__htab));
+extern void hdestroy_r __P ((struct hsearch_data *__htab));
+#endif
+
+
+/* The tsearch routines are very interesting. They make many
+   assumptions about the compiler.  It assumes that the first field
+   in node must be the "key" field, which points to the datum.
+   Everything depends on that.  */
+/* For tsearch */
+typedef enum
+{
+  preorder,
+  postorder,
+  endorder,
+  leaf
+}
+VISIT;
+
+/* Search for an entry matching the given KEY in the tree pointed to
+   by *ROOTP and insert a new element if not found.  */
+extern void *tsearch __PMT ((__const void *__key, void **__rootp,
+			     __compar_fn_t __compar));
+
+/* Search for an entry matching the given KEY in the tree pointed to
+   by *ROOTP.  If no matching entry is available return NULL.  */
+extern void *tfind __PMT ((__const void *__key, void *__const *__rootp,
+			   __compar_fn_t __compar));
+
+/* Remove the element matching KEY from the tree pointed to by *ROOTP.  */
+extern void *tdelete __PMT ((__const void *__key, void **__rootp,
+			     __compar_fn_t __compar));
+
+#ifndef __ACTION_FN_T
+# define __ACTION_FN_T
+typedef void (*__action_fn_t) __PMT ((__const void *__nodep,
+				      VISIT __value,
+				      int __level));
+#endif
+
+/* Walk through the whole tree and call the ACTION callback for every node
+   or leaf.  */
+extern void twalk __PMT ((__const void *__root, __action_fn_t __action));
+
+#ifdef __USE_GNU
+/* Callback type for function to free a tree node.  If the keys are atomic
+   data this function should do nothing.  */
+typedef void (*__free_fn_t) __PMT ((void *__nodep));
+
+/* Destroy the whole tree, call FREEFCT for each node or leaf.  */
+extern void tdestroy __PMT ((void *__root, __free_fn_t __freefct));
+#endif
+
+
+/* Perform linear search for KEY by comparing by COMPAR in an array
+   [BASE,BASE+NMEMB*SIZE).  */
+extern void *lfind __PMT ((__const void *__key, __const void *__base,
+			   size_t *__nmemb, size_t __size,
+			   __compar_fn_t __compar));
+
+/* Perform linear search for KEY by comparing by COMPAR function in
+   array [BASE,BASE+NMEMB*SIZE) and insert entry if not found.  */
+extern void *lsearch __PMT ((__const void *__key, void *__base,
+			     size_t *__nmemb, size_t __size,
+			     __compar_fn_t __compar));
+
+__END_DECLS
+
+#endif /* search.h */
diff --git a/include/seq.h b/include/seq.h
new file mode 100644
index 0000000..9576822
--- /dev/null
+++ b/include/seq.h
@@ -0,0 +1,737 @@
+/**
+ * \file include/seq.h
+ * \brief Application interface library for the ALSA driver
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \author Takashi Iwai <tiwai@suse.de>
+ * \date 1998-2001
+ */
+/*
+ * Application interface library for the ALSA driver
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#ifndef __ALSA_SEQ_H
+#define __ALSA_SEQ_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ *  \defgroup Sequencer MIDI Sequencer
+ *  MIDI Sequencer Interface.
+ *  See \ref seq page for more details.
+ *  \{
+ */
+
+/** dlsym version for interface entry callback */
+#define SND_SEQ_DLSYM_VERSION		_dlsym_seq_001
+
+/** Sequencer handle */
+typedef struct _snd_seq snd_seq_t;
+
+/**
+ * sequencer opening stream types
+ */
+#define SND_SEQ_OPEN_OUTPUT	1	/**< open for output (write) */
+#define SND_SEQ_OPEN_INPUT	2	/**< open for input (read) */
+#define SND_SEQ_OPEN_DUPLEX	(SND_SEQ_OPEN_OUTPUT|SND_SEQ_OPEN_INPUT)	/**< open for both input and output (read/write) */
+
+/**
+ * sequencer opening mode
+ */
+#define SND_SEQ_NONBLOCK	0x0001	/**< non-blocking mode (flag to open mode) */
+
+/** sequencer handle type */
+typedef enum _snd_seq_type {
+	SND_SEQ_TYPE_HW,		/**< hardware */
+	SND_SEQ_TYPE_SHM,		/**< shared memory (NYI) */
+	SND_SEQ_TYPE_INET		/**< network (NYI) */
+} snd_seq_type_t;
+
+/** special client (port) ids */
+#define SND_SEQ_ADDRESS_UNKNOWN		253	/**< unknown source */
+#define SND_SEQ_ADDRESS_SUBSCRIBERS	254	/**< send event to all subscribed ports */
+#define SND_SEQ_ADDRESS_BROADCAST	255	/**< send event to all queues/clients/ports/channels */
+
+/** known client numbers */
+#define SND_SEQ_CLIENT_SYSTEM		0	/**< system client */
+
+/*
+ */
+int snd_seq_open(snd_seq_t **handle, const char *name, int streams, int mode);
+int snd_seq_open_lconf(snd_seq_t **handle, const char *name, int streams, int mode, snd_config_t *lconf);
+const char *snd_seq_name(snd_seq_t *seq);
+snd_seq_type_t snd_seq_type(snd_seq_t *seq);
+int snd_seq_close(snd_seq_t *handle);
+int snd_seq_poll_descriptors_count(snd_seq_t *handle, short events);
+int snd_seq_poll_descriptors(snd_seq_t *handle, struct pollfd *pfds, unsigned int space, short events);
+int snd_seq_poll_descriptors_revents(snd_seq_t *seq, struct pollfd *pfds, unsigned int nfds, unsigned short *revents);
+int snd_seq_nonblock(snd_seq_t *handle, int nonblock);
+int snd_seq_client_id(snd_seq_t *handle);
+
+size_t snd_seq_get_output_buffer_size(snd_seq_t *handle);
+size_t snd_seq_get_input_buffer_size(snd_seq_t *handle);
+int snd_seq_set_output_buffer_size(snd_seq_t *handle, size_t size);
+int snd_seq_set_input_buffer_size(snd_seq_t *handle, size_t size);
+
+/** system information container */
+typedef struct _snd_seq_system_info snd_seq_system_info_t;
+
+size_t snd_seq_system_info_sizeof(void);
+/** allocate a #snd_seq_system_info_t container on stack */
+#define snd_seq_system_info_alloca(ptr) \
+	__snd_alloca(ptr, snd_seq_system_info)
+int snd_seq_system_info_malloc(snd_seq_system_info_t **ptr);
+void snd_seq_system_info_free(snd_seq_system_info_t *ptr);
+void snd_seq_system_info_copy(snd_seq_system_info_t *dst, const snd_seq_system_info_t *src);
+
+int snd_seq_system_info_get_queues(const snd_seq_system_info_t *info);
+int snd_seq_system_info_get_clients(const snd_seq_system_info_t *info);
+int snd_seq_system_info_get_ports(const snd_seq_system_info_t *info);
+int snd_seq_system_info_get_channels(const snd_seq_system_info_t *info);
+int snd_seq_system_info_get_cur_clients(const snd_seq_system_info_t *info);
+int snd_seq_system_info_get_cur_queues(const snd_seq_system_info_t *info);
+
+int snd_seq_system_info(snd_seq_t *handle, snd_seq_system_info_t *info);
+
+/** \} */
+
+
+/**
+ *  \defgroup SeqClient Sequencer Client Interface
+ *  Sequencer Client Interface
+ *  \ingroup Sequencer
+ *  \{
+ */
+
+/** client information container */
+typedef struct _snd_seq_client_info snd_seq_client_info_t;
+
+/** client types */
+typedef enum snd_seq_client_type {
+	SND_SEQ_USER_CLIENT     = 1,	/**< user client */
+	SND_SEQ_KERNEL_CLIENT   = 2	/**< kernel client */
+} snd_seq_client_type_t;
+                        
+size_t snd_seq_client_info_sizeof(void);
+/** allocate a #snd_seq_client_info_t container on stack */
+#define snd_seq_client_info_alloca(ptr) \
+	__snd_alloca(ptr, snd_seq_client_info)
+int snd_seq_client_info_malloc(snd_seq_client_info_t **ptr);
+void snd_seq_client_info_free(snd_seq_client_info_t *ptr);
+void snd_seq_client_info_copy(snd_seq_client_info_t *dst, const snd_seq_client_info_t *src);
+
+int snd_seq_client_info_get_client(const snd_seq_client_info_t *info);
+snd_seq_client_type_t snd_seq_client_info_get_type(const snd_seq_client_info_t *info);
+const char *snd_seq_client_info_get_name(snd_seq_client_info_t *info);
+int snd_seq_client_info_get_broadcast_filter(const snd_seq_client_info_t *info);
+int snd_seq_client_info_get_error_bounce(const snd_seq_client_info_t *info);
+const unsigned char *snd_seq_client_info_get_event_filter(const snd_seq_client_info_t *info);
+int snd_seq_client_info_get_num_ports(const snd_seq_client_info_t *info);
+int snd_seq_client_info_get_event_lost(const snd_seq_client_info_t *info);
+
+void snd_seq_client_info_set_client(snd_seq_client_info_t *info, int client);
+void snd_seq_client_info_set_name(snd_seq_client_info_t *info, const char *name);
+void snd_seq_client_info_set_broadcast_filter(snd_seq_client_info_t *info, int val);
+void snd_seq_client_info_set_error_bounce(snd_seq_client_info_t *info, int val);
+void snd_seq_client_info_set_event_filter(snd_seq_client_info_t *info, unsigned char *filter);
+
+void snd_seq_client_info_event_filter_clear(snd_seq_client_info_t *info);
+void snd_seq_client_info_event_filter_add(snd_seq_client_info_t *info, int event_type);
+void snd_seq_client_info_event_filter_del(snd_seq_client_info_t *info, int event_type);
+int snd_seq_client_info_event_filter_check(snd_seq_client_info_t *info, int event_type);
+
+int snd_seq_get_client_info(snd_seq_t *handle, snd_seq_client_info_t *info);
+int snd_seq_get_any_client_info(snd_seq_t *handle, int client, snd_seq_client_info_t *info);
+int snd_seq_set_client_info(snd_seq_t *handle, snd_seq_client_info_t *info);
+int snd_seq_query_next_client(snd_seq_t *handle, snd_seq_client_info_t *info);
+
+/*
+ */
+
+/** client pool information container */
+typedef struct _snd_seq_client_pool snd_seq_client_pool_t;
+
+size_t snd_seq_client_pool_sizeof(void);
+/** allocate a #snd_seq_client_pool_t container on stack */
+#define snd_seq_client_pool_alloca(ptr) \
+	__snd_alloca(ptr, snd_seq_client_pool)
+int snd_seq_client_pool_malloc(snd_seq_client_pool_t **ptr);
+void snd_seq_client_pool_free(snd_seq_client_pool_t *ptr);
+void snd_seq_client_pool_copy(snd_seq_client_pool_t *dst, const snd_seq_client_pool_t *src);
+
+int snd_seq_client_pool_get_client(const snd_seq_client_pool_t *info);
+size_t snd_seq_client_pool_get_output_pool(const snd_seq_client_pool_t *info);
+size_t snd_seq_client_pool_get_input_pool(const snd_seq_client_pool_t *info);
+size_t snd_seq_client_pool_get_output_room(const snd_seq_client_pool_t *info);
+size_t snd_seq_client_pool_get_output_free(const snd_seq_client_pool_t *info);
+size_t snd_seq_client_pool_get_input_free(const snd_seq_client_pool_t *info);
+void snd_seq_client_pool_set_output_pool(snd_seq_client_pool_t *info, size_t size);
+void snd_seq_client_pool_set_input_pool(snd_seq_client_pool_t *info, size_t size);
+void snd_seq_client_pool_set_output_room(snd_seq_client_pool_t *info, size_t size);
+
+int snd_seq_get_client_pool(snd_seq_t *handle, snd_seq_client_pool_t *info);
+int snd_seq_set_client_pool(snd_seq_t *handle, snd_seq_client_pool_t *info);
+
+
+/** \} */
+
+
+/**
+ *  \defgroup SeqPort Sequencer Port Interface
+ *  Sequencer Port Interface
+ *  \ingroup Sequencer
+ *  \{
+ */
+
+/** port information container */
+typedef struct _snd_seq_port_info snd_seq_port_info_t;
+
+/** known port numbers */
+#define SND_SEQ_PORT_SYSTEM_TIMER	0	/**< system timer port */
+#define SND_SEQ_PORT_SYSTEM_ANNOUNCE	1	/**< system announce port */
+
+/** port capabilities (32 bits) */
+#define SND_SEQ_PORT_CAP_READ		(1<<0)	/**< readable from this port */
+#define SND_SEQ_PORT_CAP_WRITE		(1<<1)	/**< writable to this port */
+
+#define SND_SEQ_PORT_CAP_SYNC_READ	(1<<2)	/**< allow read subscriptions */
+#define SND_SEQ_PORT_CAP_SYNC_WRITE	(1<<3)	/**< allow write subscriptions */
+
+#define SND_SEQ_PORT_CAP_DUPLEX		(1<<4)	/**< allow read/write duplex */
+
+#define SND_SEQ_PORT_CAP_SUBS_READ	(1<<5)	/**< allow read subscription */
+#define SND_SEQ_PORT_CAP_SUBS_WRITE	(1<<6)	/**< allow write subscription */
+#define SND_SEQ_PORT_CAP_NO_EXPORT	(1<<7)	/**< routing not allowed */
+
+/* port type */
+/** Messages sent from/to this port have device-specific semantics. */
+#define SND_SEQ_PORT_TYPE_SPECIFIC	(1<<0)
+/** This port understands MIDI messages. */
+#define SND_SEQ_PORT_TYPE_MIDI_GENERIC	(1<<1)
+/** This port is compatible with the General MIDI specification. */
+#define SND_SEQ_PORT_TYPE_MIDI_GM	(1<<2)
+/** This port is compatible with the Roland GS standard. */
+#define SND_SEQ_PORT_TYPE_MIDI_GS	(1<<3)
+/** This port is compatible with the Yamaha XG specification. */
+#define SND_SEQ_PORT_TYPE_MIDI_XG	(1<<4)
+/** This port is compatible with the Roland MT-32. */
+#define SND_SEQ_PORT_TYPE_MIDI_MT32	(1<<5)
+/** This port is compatible with the General MIDI 2 specification. */
+#define SND_SEQ_PORT_TYPE_MIDI_GM2	(1<<6)
+/** This port understands SND_SEQ_EVENT_SAMPLE_xxx messages
+    (these are not MIDI messages). */
+#define SND_SEQ_PORT_TYPE_SYNTH		(1<<10)
+/** Instruments can be downloaded to this port
+    (with SND_SEQ_EVENT_INSTR_xxx messages sent directly). */
+#define SND_SEQ_PORT_TYPE_DIRECT_SAMPLE (1<<11)
+/** Instruments can be downloaded to this port
+    (with SND_SEQ_EVENT_INSTR_xxx messages sent directly or through a queue). */
+#define SND_SEQ_PORT_TYPE_SAMPLE	(1<<12)
+/** This port is implemented in hardware. */
+#define SND_SEQ_PORT_TYPE_HARDWARE	(1<<16)
+/** This port is implemented in software. */
+#define SND_SEQ_PORT_TYPE_SOFTWARE	(1<<17)
+/** Messages sent to this port will generate sounds. */
+#define SND_SEQ_PORT_TYPE_SYNTHESIZER	(1<<18)
+/** This port may connect to other devices
+    (whose characteristics are not known). */
+#define SND_SEQ_PORT_TYPE_PORT		(1<<19)
+/** This port belongs to an application, such as a sequencer or editor. */
+#define SND_SEQ_PORT_TYPE_APPLICATION	(1<<20)
+
+
+size_t snd_seq_port_info_sizeof(void);
+/** allocate a #snd_seq_port_info_t container on stack */
+#define snd_seq_port_info_alloca(ptr) \
+	__snd_alloca(ptr, snd_seq_port_info)
+int snd_seq_port_info_malloc(snd_seq_port_info_t **ptr);
+void snd_seq_port_info_free(snd_seq_port_info_t *ptr);
+void snd_seq_port_info_copy(snd_seq_port_info_t *dst, const snd_seq_port_info_t *src);
+
+int snd_seq_port_info_get_client(const snd_seq_port_info_t *info);
+int snd_seq_port_info_get_port(const snd_seq_port_info_t *info);
+const snd_seq_addr_t *snd_seq_port_info_get_addr(const snd_seq_port_info_t *info);
+const char *snd_seq_port_info_get_name(const snd_seq_port_info_t *info);
+unsigned int snd_seq_port_info_get_capability(const snd_seq_port_info_t *info);
+unsigned int snd_seq_port_info_get_type(const snd_seq_port_info_t *info);
+int snd_seq_port_info_get_midi_channels(const snd_seq_port_info_t *info);
+int snd_seq_port_info_get_midi_voices(const snd_seq_port_info_t *info);
+int snd_seq_port_info_get_synth_voices(const snd_seq_port_info_t *info);
+int snd_seq_port_info_get_read_use(const snd_seq_port_info_t *info);
+int snd_seq_port_info_get_write_use(const snd_seq_port_info_t *info);
+int snd_seq_port_info_get_port_specified(const snd_seq_port_info_t *info);
+int snd_seq_port_info_get_timestamping(const snd_seq_port_info_t *info);
+int snd_seq_port_info_get_timestamp_real(const snd_seq_port_info_t *info);
+int snd_seq_port_info_get_timestamp_queue(const snd_seq_port_info_t *info);
+
+void snd_seq_port_info_set_client(snd_seq_port_info_t *info, int client);
+void snd_seq_port_info_set_port(snd_seq_port_info_t *info, int port);
+void snd_seq_port_info_set_addr(snd_seq_port_info_t *info, const snd_seq_addr_t *addr);
+void snd_seq_port_info_set_name(snd_seq_port_info_t *info, const char *name);
+void snd_seq_port_info_set_capability(snd_seq_port_info_t *info, unsigned int capability);
+void snd_seq_port_info_set_type(snd_seq_port_info_t *info, unsigned int type);
+void snd_seq_port_info_set_midi_channels(snd_seq_port_info_t *info, int channels);
+void snd_seq_port_info_set_midi_voices(snd_seq_port_info_t *info, int voices);
+void snd_seq_port_info_set_synth_voices(snd_seq_port_info_t *info, int voices);
+void snd_seq_port_info_set_port_specified(snd_seq_port_info_t *info, int val);
+void snd_seq_port_info_set_timestamping(snd_seq_port_info_t *info, int enable);
+void snd_seq_port_info_set_timestamp_real(snd_seq_port_info_t *info, int realtime);
+void snd_seq_port_info_set_timestamp_queue(snd_seq_port_info_t *info, int queue);
+
+int snd_seq_create_port(snd_seq_t *handle, snd_seq_port_info_t *info);
+int snd_seq_delete_port(snd_seq_t *handle, int port);
+int snd_seq_get_port_info(snd_seq_t *handle, int port, snd_seq_port_info_t *info);
+int snd_seq_get_any_port_info(snd_seq_t *handle, int client, int port, snd_seq_port_info_t *info);
+int snd_seq_set_port_info(snd_seq_t *handle, int port, snd_seq_port_info_t *info);
+int snd_seq_query_next_port(snd_seq_t *handle, snd_seq_port_info_t *info);
+
+/** \} */
+
+
+/**
+ *  \defgroup SeqSubscribe Sequencer Port Subscription
+ *  Sequencer Port Subscription
+ *  \ingroup Sequencer
+ *  \{
+ */
+
+/** port subscription container */
+typedef struct _snd_seq_port_subscribe snd_seq_port_subscribe_t;
+
+size_t snd_seq_port_subscribe_sizeof(void);
+/** allocate a #snd_seq_port_subscribe_t container on stack */
+#define snd_seq_port_subscribe_alloca(ptr) \
+	__snd_alloca(ptr, snd_seq_port_subscribe)
+int snd_seq_port_subscribe_malloc(snd_seq_port_subscribe_t **ptr);
+void snd_seq_port_subscribe_free(snd_seq_port_subscribe_t *ptr);
+void snd_seq_port_subscribe_copy(snd_seq_port_subscribe_t *dst, const snd_seq_port_subscribe_t *src);
+
+const snd_seq_addr_t *snd_seq_port_subscribe_get_sender(const snd_seq_port_subscribe_t *info);
+const snd_seq_addr_t *snd_seq_port_subscribe_get_dest(const snd_seq_port_subscribe_t *info);
+int snd_seq_port_subscribe_get_queue(const snd_seq_port_subscribe_t *info);
+int snd_seq_port_subscribe_get_exclusive(const snd_seq_port_subscribe_t *info);
+int snd_seq_port_subscribe_get_time_update(const snd_seq_port_subscribe_t *info);
+int snd_seq_port_subscribe_get_time_real(const snd_seq_port_subscribe_t *info);
+
+void snd_seq_port_subscribe_set_sender(snd_seq_port_subscribe_t *info, const snd_seq_addr_t *addr);
+void snd_seq_port_subscribe_set_dest(snd_seq_port_subscribe_t *info, const snd_seq_addr_t *addr);
+void snd_seq_port_subscribe_set_queue(snd_seq_port_subscribe_t *info, int q);
+void snd_seq_port_subscribe_set_exclusive(snd_seq_port_subscribe_t *info, int val);
+void snd_seq_port_subscribe_set_time_update(snd_seq_port_subscribe_t *info, int val);
+void snd_seq_port_subscribe_set_time_real(snd_seq_port_subscribe_t *info, int val);
+
+int snd_seq_get_port_subscription(snd_seq_t *handle, snd_seq_port_subscribe_t *sub);
+int snd_seq_subscribe_port(snd_seq_t *handle, snd_seq_port_subscribe_t *sub);
+int snd_seq_unsubscribe_port(snd_seq_t *handle, snd_seq_port_subscribe_t *sub);
+
+/*
+ */
+
+/** subscription query container */
+typedef struct _snd_seq_query_subscribe snd_seq_query_subscribe_t;
+
+/** type of query subscription */
+typedef enum {
+	SND_SEQ_QUERY_SUBS_READ,	/**< query read subscriptions */
+	SND_SEQ_QUERY_SUBS_WRITE	/**< query write subscriptions */
+} snd_seq_query_subs_type_t;
+
+size_t snd_seq_query_subscribe_sizeof(void);
+/** allocate a #snd_seq_query_subscribe_t container on stack */
+#define snd_seq_query_subscribe_alloca(ptr) \
+	__snd_alloca(ptr, snd_seq_query_subscribe)
+int snd_seq_query_subscribe_malloc(snd_seq_query_subscribe_t **ptr);
+void snd_seq_query_subscribe_free(snd_seq_query_subscribe_t *ptr);
+void snd_seq_query_subscribe_copy(snd_seq_query_subscribe_t *dst, const snd_seq_query_subscribe_t *src);
+
+int snd_seq_query_subscribe_get_client(const snd_seq_query_subscribe_t *info);
+int snd_seq_query_subscribe_get_port(const snd_seq_query_subscribe_t *info);
+const snd_seq_addr_t *snd_seq_query_subscribe_get_root(const snd_seq_query_subscribe_t *info);
+snd_seq_query_subs_type_t snd_seq_query_subscribe_get_type(const snd_seq_query_subscribe_t *info);
+int snd_seq_query_subscribe_get_index(const snd_seq_query_subscribe_t *info);
+int snd_seq_query_subscribe_get_num_subs(const snd_seq_query_subscribe_t *info);
+const snd_seq_addr_t *snd_seq_query_subscribe_get_addr(const snd_seq_query_subscribe_t *info);
+int snd_seq_query_subscribe_get_queue(const snd_seq_query_subscribe_t *info);
+int snd_seq_query_subscribe_get_exclusive(const snd_seq_query_subscribe_t *info);
+int snd_seq_query_subscribe_get_time_update(const snd_seq_query_subscribe_t *info);
+int snd_seq_query_subscribe_get_time_real(const snd_seq_query_subscribe_t *info);
+
+void snd_seq_query_subscribe_set_client(snd_seq_query_subscribe_t *info, int client);
+void snd_seq_query_subscribe_set_port(snd_seq_query_subscribe_t *info, int port);
+void snd_seq_query_subscribe_set_root(snd_seq_query_subscribe_t *info, const snd_seq_addr_t *addr);
+void snd_seq_query_subscribe_set_type(snd_seq_query_subscribe_t *info, snd_seq_query_subs_type_t type);
+void snd_seq_query_subscribe_set_index(snd_seq_query_subscribe_t *info, int _index);
+
+int snd_seq_query_port_subscribers(snd_seq_t *seq, snd_seq_query_subscribe_t * subs);
+
+/** \} */
+
+
+/**
+ *  \defgroup SeqQueue Sequencer Queue Interface
+ *  Sequencer Queue Interface
+ *  \ingroup Sequencer
+ *  \{
+ */
+
+/** queue information container */
+typedef struct _snd_seq_queue_info snd_seq_queue_info_t;
+/** queue status container */
+typedef struct _snd_seq_queue_status snd_seq_queue_status_t;
+/** queue tempo container */
+typedef struct _snd_seq_queue_tempo snd_seq_queue_tempo_t;
+/** queue timer information container */
+typedef struct _snd_seq_queue_timer snd_seq_queue_timer_t;
+
+/** special queue ids */
+#define SND_SEQ_QUEUE_DIRECT		253	/**< direct dispatch */
+
+size_t snd_seq_queue_info_sizeof(void);
+/** allocate a #snd_seq_queue_info_t container on stack */
+#define snd_seq_queue_info_alloca(ptr) \
+	__snd_alloca(ptr, snd_seq_queue_info)
+int snd_seq_queue_info_malloc(snd_seq_queue_info_t **ptr);
+void snd_seq_queue_info_free(snd_seq_queue_info_t *ptr);
+void snd_seq_queue_info_copy(snd_seq_queue_info_t *dst, const snd_seq_queue_info_t *src);
+
+int snd_seq_queue_info_get_queue(const snd_seq_queue_info_t *info);
+const char *snd_seq_queue_info_get_name(const snd_seq_queue_info_t *info);
+int snd_seq_queue_info_get_owner(const snd_seq_queue_info_t *info);
+int snd_seq_queue_info_get_locked(const snd_seq_queue_info_t *info);
+unsigned int snd_seq_queue_info_get_flags(const snd_seq_queue_info_t *info);
+
+void snd_seq_queue_info_set_name(snd_seq_queue_info_t *info, const char *name);
+void snd_seq_queue_info_set_owner(snd_seq_queue_info_t *info, int owner);
+void snd_seq_queue_info_set_locked(snd_seq_queue_info_t *info, int locked);
+void snd_seq_queue_info_set_flags(snd_seq_queue_info_t *info, unsigned int flags);
+
+int snd_seq_create_queue(snd_seq_t *seq, snd_seq_queue_info_t *info);
+int snd_seq_alloc_named_queue(snd_seq_t *seq, const char *name);
+int snd_seq_alloc_queue(snd_seq_t *handle);
+int snd_seq_free_queue(snd_seq_t *handle, int q);
+int snd_seq_get_queue_info(snd_seq_t *seq, int q, snd_seq_queue_info_t *info);
+int snd_seq_set_queue_info(snd_seq_t *seq, int q, snd_seq_queue_info_t *info);
+int snd_seq_query_named_queue(snd_seq_t *seq, const char *name);
+
+int snd_seq_get_queue_usage(snd_seq_t *handle, int q);
+int snd_seq_set_queue_usage(snd_seq_t *handle, int q, int used);
+
+/*
+ */
+size_t snd_seq_queue_status_sizeof(void);
+/** allocate a #snd_seq_queue_status_t container on stack */
+#define snd_seq_queue_status_alloca(ptr) \
+	__snd_alloca(ptr, snd_seq_queue_status)
+int snd_seq_queue_status_malloc(snd_seq_queue_status_t **ptr);
+void snd_seq_queue_status_free(snd_seq_queue_status_t *ptr);
+void snd_seq_queue_status_copy(snd_seq_queue_status_t *dst, const snd_seq_queue_status_t *src);
+
+int snd_seq_queue_status_get_queue(const snd_seq_queue_status_t *info);
+int snd_seq_queue_status_get_events(const snd_seq_queue_status_t *info);
+snd_seq_tick_time_t snd_seq_queue_status_get_tick_time(const snd_seq_queue_status_t *info);
+const snd_seq_real_time_t *snd_seq_queue_status_get_real_time(const snd_seq_queue_status_t *info);
+unsigned int snd_seq_queue_status_get_status(const snd_seq_queue_status_t *info);
+
+int snd_seq_get_queue_status(snd_seq_t *handle, int q, snd_seq_queue_status_t *status);
+
+/*
+ */
+size_t snd_seq_queue_tempo_sizeof(void);
+/** allocate a #snd_seq_queue_tempo_t container on stack */
+#define snd_seq_queue_tempo_alloca(ptr) \
+	__snd_alloca(ptr, snd_seq_queue_tempo)
+int snd_seq_queue_tempo_malloc(snd_seq_queue_tempo_t **ptr);
+void snd_seq_queue_tempo_free(snd_seq_queue_tempo_t *ptr);
+void snd_seq_queue_tempo_copy(snd_seq_queue_tempo_t *dst, const snd_seq_queue_tempo_t *src);
+
+int snd_seq_queue_tempo_get_queue(const snd_seq_queue_tempo_t *info);
+unsigned int snd_seq_queue_tempo_get_tempo(const snd_seq_queue_tempo_t *info);
+int snd_seq_queue_tempo_get_ppq(const snd_seq_queue_tempo_t *info);
+unsigned int snd_seq_queue_tempo_get_skew(const snd_seq_queue_tempo_t *info);
+unsigned int snd_seq_queue_tempo_get_skew_base(const snd_seq_queue_tempo_t *info);
+void snd_seq_queue_tempo_set_tempo(snd_seq_queue_tempo_t *info, unsigned int tempo);
+void snd_seq_queue_tempo_set_ppq(snd_seq_queue_tempo_t *info, int ppq);
+void snd_seq_queue_tempo_set_skew(snd_seq_queue_tempo_t *info, unsigned int skew);
+void snd_seq_queue_tempo_set_skew_base(snd_seq_queue_tempo_t *info, unsigned int base);
+
+int snd_seq_get_queue_tempo(snd_seq_t *handle, int q, snd_seq_queue_tempo_t *tempo);
+int snd_seq_set_queue_tempo(snd_seq_t *handle, int q, snd_seq_queue_tempo_t *tempo);
+
+/*
+ */
+
+/** sequencer timer sources */
+typedef enum {
+	SND_SEQ_TIMER_ALSA = 0,		/* ALSA timer */
+	SND_SEQ_TIMER_MIDI_CLOCK = 1,	/* Midi Clock (CLOCK event) */
+	SND_SEQ_TIMER_MIDI_TICK = 2	/* Midi Timer Tick (TICK event */
+} snd_seq_queue_timer_type_t;
+
+size_t snd_seq_queue_timer_sizeof(void);
+/** allocate a #snd_seq_queue_timer_t container on stack */
+#define snd_seq_queue_timer_alloca(ptr) \
+	__snd_alloca(ptr, snd_seq_queue_timer)
+int snd_seq_queue_timer_malloc(snd_seq_queue_timer_t **ptr);
+void snd_seq_queue_timer_free(snd_seq_queue_timer_t *ptr);
+void snd_seq_queue_timer_copy(snd_seq_queue_timer_t *dst, const snd_seq_queue_timer_t *src);
+
+int snd_seq_queue_timer_get_queue(const snd_seq_queue_timer_t *info);
+snd_seq_queue_timer_type_t snd_seq_queue_timer_get_type(const snd_seq_queue_timer_t *info);
+const snd_timer_id_t *snd_seq_queue_timer_get_id(const snd_seq_queue_timer_t *info);
+unsigned int snd_seq_queue_timer_get_resolution(const snd_seq_queue_timer_t *info);
+
+void snd_seq_queue_timer_set_type(snd_seq_queue_timer_t *info, snd_seq_queue_timer_type_t type);
+void snd_seq_queue_timer_set_id(snd_seq_queue_timer_t *info, const snd_timer_id_t *id);
+void snd_seq_queue_timer_set_resolution(snd_seq_queue_timer_t *info, unsigned int resolution);
+
+int snd_seq_get_queue_timer(snd_seq_t *handle, int q, snd_seq_queue_timer_t *timer);
+int snd_seq_set_queue_timer(snd_seq_t *handle, int q, snd_seq_queue_timer_t *timer);
+
+/** \} */
+
+/**
+ *  \defgroup SeqEvent Sequencer Event API
+ *  Sequencer Event API
+ *  \ingroup Sequencer
+ *  \{
+ */
+
+int snd_seq_free_event(snd_seq_event_t *ev);
+ssize_t snd_seq_event_length(snd_seq_event_t *ev);
+int snd_seq_event_output(snd_seq_t *handle, snd_seq_event_t *ev);
+int snd_seq_event_output_buffer(snd_seq_t *handle, snd_seq_event_t *ev);
+int snd_seq_event_output_direct(snd_seq_t *handle, snd_seq_event_t *ev);
+int snd_seq_event_input(snd_seq_t *handle, snd_seq_event_t **ev);
+int snd_seq_event_input_pending(snd_seq_t *seq, int fetch_sequencer);
+int snd_seq_drain_output(snd_seq_t *handle);
+int snd_seq_event_output_pending(snd_seq_t *seq);
+int snd_seq_extract_output(snd_seq_t *handle, snd_seq_event_t **ev);
+int snd_seq_drop_output(snd_seq_t *handle);
+int snd_seq_drop_output_buffer(snd_seq_t *handle);
+int snd_seq_drop_input(snd_seq_t *handle);
+int snd_seq_drop_input_buffer(snd_seq_t *handle);
+
+/** event removal conditionals */
+typedef struct _snd_seq_remove_events snd_seq_remove_events_t;
+
+/** Remove conditional flags */
+#define SND_SEQ_REMOVE_INPUT		(1<<0)	/**< Flush input queues */
+#define SND_SEQ_REMOVE_OUTPUT		(1<<1)	/**< Flush output queues */
+#define SND_SEQ_REMOVE_DEST		(1<<2)	/**< Restrict by destination q:client:port */
+#define SND_SEQ_REMOVE_DEST_CHANNEL	(1<<3)	/**< Restrict by channel */
+#define SND_SEQ_REMOVE_TIME_BEFORE	(1<<4)	/**< Restrict to before time */
+#define SND_SEQ_REMOVE_TIME_AFTER	(1<<5)	/**< Restrict to time or after */
+#define SND_SEQ_REMOVE_TIME_TICK	(1<<6)	/**< Time is in ticks */
+#define SND_SEQ_REMOVE_EVENT_TYPE	(1<<7)	/**< Restrict to event type */
+#define SND_SEQ_REMOVE_IGNORE_OFF 	(1<<8)	/**< Do not flush off events */
+#define SND_SEQ_REMOVE_TAG_MATCH 	(1<<9)	/**< Restrict to events with given tag */
+
+size_t snd_seq_remove_events_sizeof(void);
+/** allocate a #snd_seq_remove_events_t container on stack */
+#define snd_seq_remove_events_alloca(ptr) \
+	__snd_alloca(ptr, snd_seq_remove_events)
+int snd_seq_remove_events_malloc(snd_seq_remove_events_t **ptr);
+void snd_seq_remove_events_free(snd_seq_remove_events_t *ptr);
+void snd_seq_remove_events_copy(snd_seq_remove_events_t *dst, const snd_seq_remove_events_t *src);
+
+unsigned int snd_seq_remove_events_get_condition(const snd_seq_remove_events_t *info);
+int snd_seq_remove_events_get_queue(const snd_seq_remove_events_t *info);
+const snd_seq_timestamp_t *snd_seq_remove_events_get_time(const snd_seq_remove_events_t *info);
+const snd_seq_addr_t *snd_seq_remove_events_get_dest(const snd_seq_remove_events_t *info);
+int snd_seq_remove_events_get_channel(const snd_seq_remove_events_t *info);
+int snd_seq_remove_events_get_event_type(const snd_seq_remove_events_t *info);
+int snd_seq_remove_events_get_tag(const snd_seq_remove_events_t *info);
+
+void snd_seq_remove_events_set_condition(snd_seq_remove_events_t *info, unsigned int flags);
+void snd_seq_remove_events_set_queue(snd_seq_remove_events_t *info, int queue);
+void snd_seq_remove_events_set_time(snd_seq_remove_events_t *info, const snd_seq_timestamp_t *time);
+void snd_seq_remove_events_set_dest(snd_seq_remove_events_t *info, const snd_seq_addr_t *addr);
+void snd_seq_remove_events_set_channel(snd_seq_remove_events_t *info, int channel);
+void snd_seq_remove_events_set_event_type(snd_seq_remove_events_t *info, int type);
+void snd_seq_remove_events_set_tag(snd_seq_remove_events_t *info, int tag);
+
+int snd_seq_remove_events(snd_seq_t *handle, snd_seq_remove_events_t *info);
+
+/** \} */
+
+/**
+ *  \defgroup SeqMisc Sequencer Miscellaneous
+ *  Sequencer Miscellaneous
+ *  \ingroup Sequencer
+ *  \{
+ */
+
+void snd_seq_set_bit(int nr, void *array);
+void snd_seq_unset_bit(int nr, void *array);
+int snd_seq_change_bit(int nr, void *array);
+int snd_seq_get_bit(int nr, void *array);
+
+/** \} */
+
+
+/**
+ *  \defgroup SeqEvType Sequencer Event Type Checks
+ *  Sequencer Event Type Checks
+ *  \ingroup Sequencer
+ *  \{
+ */
+
+/* event type macros */
+enum {
+	SND_SEQ_EVFLG_RESULT,
+	SND_SEQ_EVFLG_NOTE,
+	SND_SEQ_EVFLG_CONTROL,
+	SND_SEQ_EVFLG_QUEUE,
+	SND_SEQ_EVFLG_SYSTEM,
+	SND_SEQ_EVFLG_MESSAGE,
+	SND_SEQ_EVFLG_CONNECTION,
+	SND_SEQ_EVFLG_SAMPLE,
+	SND_SEQ_EVFLG_USERS,
+	SND_SEQ_EVFLG_INSTR,
+	SND_SEQ_EVFLG_QUOTE,
+	SND_SEQ_EVFLG_NONE,
+	SND_SEQ_EVFLG_RAW,
+	SND_SEQ_EVFLG_FIXED,
+	SND_SEQ_EVFLG_VARIABLE,
+	SND_SEQ_EVFLG_VARUSR
+};
+
+enum {
+	SND_SEQ_EVFLG_NOTE_ONEARG,
+	SND_SEQ_EVFLG_NOTE_TWOARG
+};
+
+enum {
+	SND_SEQ_EVFLG_QUEUE_NOARG,
+	SND_SEQ_EVFLG_QUEUE_TICK,
+	SND_SEQ_EVFLG_QUEUE_TIME,
+	SND_SEQ_EVFLG_QUEUE_VALUE
+};
+
+/**
+ * Exported event type table
+ *
+ * This table is referred by snd_seq_ev_is_xxx.
+ */
+extern const unsigned int snd_seq_event_types[];
+
+#define _SND_SEQ_TYPE(x)	(1<<(x))	/**< master type - 24bit */
+#define _SND_SEQ_TYPE_OPT(x)	((x)<<24)	/**< optional type - 8bit */
+
+/** check the event type */
+#define snd_seq_type_check(ev,x) (snd_seq_event_types[(ev)->type] & _SND_SEQ_TYPE(x))
+
+/** event type check: result events */
+#define snd_seq_ev_is_result_type(ev) \
+	snd_seq_type_check(ev, SND_SEQ_EVFLG_RESULT)
+/** event type check: note events */
+#define snd_seq_ev_is_note_type(ev) \
+	snd_seq_type_check(ev, SND_SEQ_EVFLG_NOTE)
+/** event type check: control events */
+#define snd_seq_ev_is_control_type(ev) \
+	snd_seq_type_check(ev, SND_SEQ_EVFLG_CONTROL)
+/** event type check: channel specific events */
+#define snd_seq_ev_is_channel_type(ev) \
+	(snd_seq_event_types[(ev)->type] & (_SND_SEQ_TYPE(SND_SEQ_EVFLG_NOTE) | _SND_SEQ_TYPE(SND_SEQ_EVFLG_CONTROL)))
+
+/** event type check: queue control events */
+#define snd_seq_ev_is_queue_type(ev) \
+	snd_seq_type_check(ev, SND_SEQ_EVFLG_QUEUE)
+/** event type check: system status messages */
+#define snd_seq_ev_is_message_type(ev) \
+	snd_seq_type_check(ev, SND_SEQ_EVFLG_MESSAGE)
+/** event type check: system status messages */
+#define snd_seq_ev_is_subscribe_type(ev) \
+	snd_seq_type_check(ev, SND_SEQ_EVFLG_CONNECTION)
+/** event type check: sample messages */
+#define snd_seq_ev_is_sample_type(ev) \
+	snd_seq_type_check(ev, SND_SEQ_EVFLG_SAMPLE)
+/** event type check: user-defined messages */
+#define snd_seq_ev_is_user_type(ev) \
+	snd_seq_type_check(ev, SND_SEQ_EVFLG_USERS)
+/** event type check: instrument layer events */
+#define snd_seq_ev_is_instr_type(ev) \
+	snd_seq_type_check(ev, SND_SEQ_EVFLG_INSTR)
+/** event type check: fixed length events */
+#define snd_seq_ev_is_fixed_type(ev) \
+	snd_seq_type_check(ev, SND_SEQ_EVFLG_FIXED)
+/** event type check: variable length events */
+#define snd_seq_ev_is_variable_type(ev)	\
+	snd_seq_type_check(ev, SND_SEQ_EVFLG_VARIABLE)
+/** event type check: user pointer events */
+#define snd_seq_ev_is_varusr_type(ev) \
+	snd_seq_type_check(ev, SND_SEQ_EVFLG_VARUSR)
+/** event type check: reserved for kernel */
+#define snd_seq_ev_is_reserved(ev) \
+	(! snd_seq_event_types[(ev)->type])
+
+/**
+ * macros to check event flags
+ */
+/** prior events */
+#define snd_seq_ev_is_prior(ev)	\
+	(((ev)->flags & SND_SEQ_PRIORITY_MASK) == SND_SEQ_PRIORITY_HIGH)
+
+/** get the data length type */
+#define snd_seq_ev_length_type(ev) \
+	((ev)->flags & SND_SEQ_EVENT_LENGTH_MASK)
+/** fixed length events */
+#define snd_seq_ev_is_fixed(ev)	\
+	(snd_seq_ev_length_type(ev) == SND_SEQ_EVENT_LENGTH_FIXED)
+/** variable length events */
+#define snd_seq_ev_is_variable(ev) \
+	(snd_seq_ev_length_type(ev) == SND_SEQ_EVENT_LENGTH_VARIABLE)
+/** variable length on user-space */
+#define snd_seq_ev_is_varusr(ev) \
+	(snd_seq_ev_length_type(ev) == SND_SEQ_EVENT_LENGTH_VARUSR)
+
+/** time-stamp type */
+#define snd_seq_ev_timestamp_type(ev) \
+	((ev)->flags & SND_SEQ_TIME_STAMP_MASK)
+/** event is in tick time */
+#define snd_seq_ev_is_tick(ev) \
+	(snd_seq_ev_timestamp_type(ev) == SND_SEQ_TIME_STAMP_TICK)
+/** event is in real-time */
+#define snd_seq_ev_is_real(ev) \
+	(snd_seq_ev_timestamp_type(ev) == SND_SEQ_TIME_STAMP_REAL)
+
+/** time-mode type */
+#define snd_seq_ev_timemode_type(ev) \
+	((ev)->flags & SND_SEQ_TIME_MODE_MASK)
+/** scheduled in absolute time */
+#define snd_seq_ev_is_abstime(ev) \
+	(snd_seq_ev_timemode_type(ev) == SND_SEQ_TIME_MODE_ABS)
+/** scheduled in relative time */
+#define snd_seq_ev_is_reltime(ev) \
+	(snd_seq_ev_timemode_type(ev) == SND_SEQ_TIME_MODE_REL)
+
+/** direct dispatched events */
+#define snd_seq_ev_is_direct(ev) \
+	((ev)->queue == SND_SEQ_QUEUE_DIRECT)
+
+/** \} */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __ALSA_SEQ_H */
+
diff --git a/include/seq_event.h b/include/seq_event.h
new file mode 100644
index 0000000..583f1d0
--- /dev/null
+++ b/include/seq_event.h
@@ -0,0 +1,319 @@
+/**
+ * \file include/seq_event.h
+ * \brief Application interface library for the ALSA driver
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \author Takashi Iwai <tiwai@suse.de>
+ * \date 1998-2001
+ *
+ * Application interface library for the ALSA driver
+ */
+/*
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#ifndef __ALSA_SEQ_EVENT_H
+#define __ALSA_SEQ_EVENT_H
+
+/**
+ *  \defgroup SeqEvents Sequencer Event Definitions
+ *  Sequencer Event Definitions
+ *  \ingroup Sequencer
+ *  \{
+ */
+
+/**
+ * Sequencer event data type
+ */
+typedef unsigned char snd_seq_event_type_t;
+
+/** Sequencer event type */
+enum snd_seq_event_type {
+	/** system status; event data type = #snd_seq_result_t */
+	SND_SEQ_EVENT_SYSTEM = 0,
+	/** returned result status; event data type = #snd_seq_result_t */
+	SND_SEQ_EVENT_RESULT,
+
+	/** note on and off with duration; event data type = #snd_seq_ev_note_t */
+	SND_SEQ_EVENT_NOTE = 5,
+	/** note on; event data type = #snd_seq_ev_note_t */
+	SND_SEQ_EVENT_NOTEON,
+	/** note off; event data type = #snd_seq_ev_note_t */
+	SND_SEQ_EVENT_NOTEOFF,
+	/** key pressure change (aftertouch); event data type = #snd_seq_ev_note_t */
+	SND_SEQ_EVENT_KEYPRESS,
+	
+	/** controller; event data type = #snd_seq_ev_ctrl_t */
+	SND_SEQ_EVENT_CONTROLLER = 10,
+	/** program change; event data type = #snd_seq_ev_ctrl_t */
+	SND_SEQ_EVENT_PGMCHANGE,
+	/** channel pressure; event data type = #snd_seq_ev_ctrl_t */
+	SND_SEQ_EVENT_CHANPRESS,
+	/** pitchwheel; event data type = #snd_seq_ev_ctrl_t; data is from -8192 to 8191) */
+	SND_SEQ_EVENT_PITCHBEND,
+	/** 14 bit controller value; event data type = #snd_seq_ev_ctrl_t */
+	SND_SEQ_EVENT_CONTROL14,
+	/** 14 bit NRPN;  event data type = #snd_seq_ev_ctrl_t */
+	SND_SEQ_EVENT_NONREGPARAM,
+	/** 14 bit RPN; event data type = #snd_seq_ev_ctrl_t */
+	SND_SEQ_EVENT_REGPARAM,
+
+	/** SPP with LSB and MSB values; event data type = #snd_seq_ev_ctrl_t */
+	SND_SEQ_EVENT_SONGPOS = 20,
+	/** Song Select with song ID number; event data type = #snd_seq_ev_ctrl_t */
+	SND_SEQ_EVENT_SONGSEL,
+	/** midi time code quarter frame; event data type = #snd_seq_ev_ctrl_t */
+	SND_SEQ_EVENT_QFRAME,
+	/** SMF Time Signature event; event data type = #snd_seq_ev_ctrl_t */
+	SND_SEQ_EVENT_TIMESIGN,
+	/** SMF Key Signature event; event data type = #snd_seq_ev_ctrl_t */
+	SND_SEQ_EVENT_KEYSIGN,
+	        
+	/** MIDI Real Time Start message; event data type = #snd_seq_ev_queue_control_t */
+	SND_SEQ_EVENT_START = 30,
+	/** MIDI Real Time Continue message; event data type = #snd_seq_ev_queue_control_t */
+	SND_SEQ_EVENT_CONTINUE,
+	/** MIDI Real Time Stop message; event data type = #snd_seq_ev_queue_control_t */
+	SND_SEQ_EVENT_STOP,
+	/** Set tick queue position; event data type = #snd_seq_ev_queue_control_t */
+	SND_SEQ_EVENT_SETPOS_TICK,
+	/** Set real-time queue position; event data type = #snd_seq_ev_queue_control_t */
+	SND_SEQ_EVENT_SETPOS_TIME,
+	/** (SMF) Tempo event; event data type = #snd_seq_ev_queue_control_t */
+	SND_SEQ_EVENT_TEMPO,
+	/** MIDI Real Time Clock message; event data type = #snd_seq_ev_queue_control_t */
+	SND_SEQ_EVENT_CLOCK,
+	/** MIDI Real Time Tick message; event data type = #snd_seq_ev_queue_control_t */
+	SND_SEQ_EVENT_TICK,
+	/** Queue timer skew; event data type = #snd_seq_ev_queue_control_t */
+	SND_SEQ_EVENT_QUEUE_SKEW,
+	/** Sync position changed; event data type = #snd_seq_ev_queue_control_t */
+	SND_SEQ_EVENT_SYNC_POS,
+
+	/** Tune request; event data type = none */
+	SND_SEQ_EVENT_TUNE_REQUEST = 40,
+	/** Reset to power-on state; event data type = none */
+	SND_SEQ_EVENT_RESET,
+	/** Active sensing event; event data type = none */
+	SND_SEQ_EVENT_SENSING,
+
+	/** Echo-back event; event data type = any type */
+	SND_SEQ_EVENT_ECHO = 50,
+	/** OSS emulation raw event; event data type = any type */
+	SND_SEQ_EVENT_OSS,
+
+	/** New client has connected; event data type = #snd_seq_addr_t */
+	SND_SEQ_EVENT_CLIENT_START = 60,
+	/** Client has left the system; event data type = #snd_seq_addr_t */
+	SND_SEQ_EVENT_CLIENT_EXIT,
+	/** Client status/info has changed; event data type = #snd_seq_addr_t */
+	SND_SEQ_EVENT_CLIENT_CHANGE,
+	/** New port was created; event data type = #snd_seq_addr_t */
+	SND_SEQ_EVENT_PORT_START,
+	/** Port was deleted from system; event data type = #snd_seq_addr_t */
+	SND_SEQ_EVENT_PORT_EXIT,
+	/** Port status/info has changed; event data type = #snd_seq_addr_t */
+	SND_SEQ_EVENT_PORT_CHANGE,
+
+	/** Ports connected; event data type = #snd_seq_connect_t */
+	SND_SEQ_EVENT_PORT_SUBSCRIBED,
+	/** Ports disconnected; event data type = #snd_seq_connect_t */
+	SND_SEQ_EVENT_PORT_UNSUBSCRIBED,
+
+	/** user-defined event; event data type = any (fixed size) */
+	SND_SEQ_EVENT_USR0 = 90,
+	/** user-defined event; event data type = any (fixed size) */
+	SND_SEQ_EVENT_USR1,
+	/** user-defined event; event data type = any (fixed size) */
+	SND_SEQ_EVENT_USR2,
+	/** user-defined event; event data type = any (fixed size) */
+	SND_SEQ_EVENT_USR3,
+	/** user-defined event; event data type = any (fixed size) */
+	SND_SEQ_EVENT_USR4,
+	/** user-defined event; event data type = any (fixed size) */
+	SND_SEQ_EVENT_USR5,
+	/** user-defined event; event data type = any (fixed size) */
+	SND_SEQ_EVENT_USR6,
+	/** user-defined event; event data type = any (fixed size) */
+	SND_SEQ_EVENT_USR7,
+	/** user-defined event; event data type = any (fixed size) */
+	SND_SEQ_EVENT_USR8,
+	/** user-defined event; event data type = any (fixed size) */
+	SND_SEQ_EVENT_USR9,
+
+	/** system exclusive data (variable length);  event data type = #snd_seq_ev_ext_t */
+	SND_SEQ_EVENT_SYSEX = 130,
+	/** error event;  event data type = #snd_seq_ev_ext_t */
+	SND_SEQ_EVENT_BOUNCE,
+	/** reserved for user apps;  event data type = #snd_seq_ev_ext_t */
+	SND_SEQ_EVENT_USR_VAR0 = 135,
+	/** reserved for user apps; event data type = #snd_seq_ev_ext_t */
+	SND_SEQ_EVENT_USR_VAR1,
+	/** reserved for user apps; event data type = #snd_seq_ev_ext_t */
+	SND_SEQ_EVENT_USR_VAR2,
+	/** reserved for user apps; event data type = #snd_seq_ev_ext_t */
+	SND_SEQ_EVENT_USR_VAR3,
+	/** reserved for user apps; event data type = #snd_seq_ev_ext_t */
+	SND_SEQ_EVENT_USR_VAR4,
+
+	/** NOP; ignored in any case */
+	SND_SEQ_EVENT_NONE = 255
+};
+
+
+/** Sequencer event address */
+typedef struct snd_seq_addr {
+	unsigned char client;	/**< Client id */
+	unsigned char port;	/**< Port id */
+} snd_seq_addr_t;
+
+/** Connection (subscription) between ports */
+typedef struct snd_seq_connect {
+	snd_seq_addr_t sender;	/**< sender address */
+	snd_seq_addr_t dest;	/**< destination address */
+} snd_seq_connect_t;
+
+
+/** Real-time data record */
+typedef struct snd_seq_real_time {
+	unsigned int tv_sec;		/**< seconds */
+	unsigned int tv_nsec;		/**< nanoseconds */
+} snd_seq_real_time_t;
+
+/** (MIDI) Tick-time data record */
+typedef unsigned int snd_seq_tick_time_t;
+
+/** unioned time stamp */
+typedef union snd_seq_timestamp {
+	snd_seq_tick_time_t tick;	/**< tick-time */
+	struct snd_seq_real_time time;	/**< real-time */
+} snd_seq_timestamp_t;
+
+
+/**
+ * Event mode flags
+ *
+ * NOTE: only 8 bits available!
+ */
+#define SND_SEQ_TIME_STAMP_TICK		(0<<0)	/**< timestamp in clock ticks */
+#define SND_SEQ_TIME_STAMP_REAL		(1<<0)	/**< timestamp in real time */
+#define SND_SEQ_TIME_STAMP_MASK		(1<<0)	/**< mask for timestamp bits */
+
+#define SND_SEQ_TIME_MODE_ABS		(0<<1)	/**< absolute timestamp */
+#define SND_SEQ_TIME_MODE_REL		(1<<1)	/**< relative to current time */
+#define SND_SEQ_TIME_MODE_MASK		(1<<1)	/**< mask for time mode bits */
+
+#define SND_SEQ_EVENT_LENGTH_FIXED	(0<<2)	/**< fixed event size */
+#define SND_SEQ_EVENT_LENGTH_VARIABLE	(1<<2)	/**< variable event size */
+#define SND_SEQ_EVENT_LENGTH_VARUSR	(2<<2)	/**< variable event size - user memory space */
+#define SND_SEQ_EVENT_LENGTH_MASK	(3<<2)	/**< mask for event length bits */
+
+#define SND_SEQ_PRIORITY_NORMAL		(0<<4)	/**< normal priority */
+#define SND_SEQ_PRIORITY_HIGH		(1<<4)	/**< event should be processed before others */
+#define SND_SEQ_PRIORITY_MASK		(1<<4)	/**< mask for priority bits */
+
+
+/** Note event */
+typedef struct snd_seq_ev_note {
+	unsigned char channel;		/**< channel number */
+	unsigned char note;		/**< note */
+	unsigned char velocity;		/**< velocity */
+	unsigned char off_velocity;	/**< note-off velocity; only for #SND_SEQ_EVENT_NOTE */
+	unsigned int duration;		/**< duration until note-off; only for #SND_SEQ_EVENT_NOTE */
+} snd_seq_ev_note_t;
+
+/** Controller event */
+typedef struct snd_seq_ev_ctrl {
+	unsigned char channel;		/**< channel number */
+	unsigned char unused[3];	/**< reserved */
+	unsigned int param;		/**< control parameter */
+	signed int value;		/**< control value */
+} snd_seq_ev_ctrl_t;
+
+/** generic set of bytes (12x8 bit) */
+typedef struct snd_seq_ev_raw8 {
+	unsigned char d[12];		/**< 8 bit value */
+} snd_seq_ev_raw8_t;
+
+/** generic set of integers (3x32 bit) */
+typedef struct snd_seq_ev_raw32 {
+	unsigned int d[3];		/**< 32 bit value */
+} snd_seq_ev_raw32_t;
+
+/** external stored data */
+typedef struct snd_seq_ev_ext {
+	unsigned int len;		/**< length of data */
+	void *ptr;			/**< pointer to data (note: can be 64-bit) */
+} __attribute__((packed)) snd_seq_ev_ext_t;
+
+/** Result events */
+typedef struct snd_seq_result {
+	int event;		/**< processed event type */
+	int result;		/**< status */
+} snd_seq_result_t;
+
+/** Queue skew values */
+typedef struct snd_seq_queue_skew {
+	unsigned int value;	/**< skew value */
+	unsigned int base;	/**< skew base */
+} snd_seq_queue_skew_t;
+
+/** queue timer control */
+typedef struct snd_seq_ev_queue_control {
+	unsigned char queue;			/**< affected queue */
+	unsigned char unused[3];		/**< reserved */
+	union {
+		signed int value;		/**< affected value (e.g. tempo) */
+		snd_seq_timestamp_t time;	/**< time */
+		unsigned int position;		/**< sync position */
+		snd_seq_queue_skew_t skew;	/**< queue skew */
+		unsigned int d32[2];		/**< any data */
+		unsigned char d8[8];		/**< any data */
+	} param;				/**< data value union */
+} snd_seq_ev_queue_control_t;
+
+
+/** Sequencer event */
+typedef struct snd_seq_event {
+	snd_seq_event_type_t type;	/**< event type */
+	unsigned char flags;		/**< event flags */
+	unsigned char tag;		/**< tag */
+	
+	unsigned char queue;		/**< schedule queue */
+	snd_seq_timestamp_t time;	/**< schedule time */
+
+	snd_seq_addr_t source;		/**< source address */
+	snd_seq_addr_t dest;		/**< destination address */
+
+	union {
+		snd_seq_ev_note_t note;		/**< note information */
+		snd_seq_ev_ctrl_t control;	/**< MIDI control information */
+		snd_seq_ev_raw8_t raw8;		/**< raw8 data */
+		snd_seq_ev_raw32_t raw32;	/**< raw32 data */
+		snd_seq_ev_ext_t ext;		/**< external data */
+		snd_seq_ev_queue_control_t queue; /**< queue control */
+		snd_seq_timestamp_t time;	/**< timestamp */
+		snd_seq_addr_t addr;		/**< address */
+		snd_seq_connect_t connect;	/**< connect information */
+		snd_seq_result_t result;	/**< operation result code */
+	} data;				/**< event data... */
+} snd_seq_event_t;
+
+
+/** \} */
+
+#endif /* __ALSA_SEQ_EVENT_H */
+
diff --git a/include/seq_midi_event.h b/include/seq_midi_event.h
new file mode 100644
index 0000000..9b8ee59
--- /dev/null
+++ b/include/seq_midi_event.h
@@ -0,0 +1,65 @@
+/**
+ * \file include/seq_midi_event.h
+ * \brief Application interface library for the ALSA driver
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \author Takashi Iwai <tiwai@suse.de>
+ * \date 1998-2001
+ *
+ * Application interface library for the ALSA driver
+ */
+/*
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#ifndef __ALSA_SEQ_MIDI_EVENT_H
+#define __ALSA_SEQ_MIDI_EVENT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ *  \defgroup MIDI_Event Sequencer event <-> MIDI byte stream coder
+ *  \ingroup Sequencer
+ *  Sequencer event <-> MIDI byte stream coder
+ *  \{
+ */
+
+/** container for sequencer midi event parsers */
+typedef struct snd_midi_event snd_midi_event_t;
+
+int snd_midi_event_new(size_t bufsize, snd_midi_event_t **rdev);
+int snd_midi_event_resize_buffer(snd_midi_event_t *dev, size_t bufsize);
+void snd_midi_event_free(snd_midi_event_t *dev);
+void snd_midi_event_init(snd_midi_event_t *dev);
+void snd_midi_event_reset_encode(snd_midi_event_t *dev);
+void snd_midi_event_reset_decode(snd_midi_event_t *dev);
+void snd_midi_event_no_status(snd_midi_event_t *dev, int on);
+/* encode from byte stream - return number of written bytes if success */
+long snd_midi_event_encode(snd_midi_event_t *dev, const unsigned char *buf, long count, snd_seq_event_t *ev);
+int snd_midi_event_encode_byte(snd_midi_event_t *dev, int c, snd_seq_event_t *ev);
+/* decode from event to bytes - return number of written bytes if success */
+long snd_midi_event_decode(snd_midi_event_t *dev, unsigned char *buf, long count, const snd_seq_event_t *ev);
+
+/** \} */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __ALSA_SEQ_MIDI_EVENT_H */
+
diff --git a/include/seqmid.h b/include/seqmid.h
new file mode 100644
index 0000000..68069b2
--- /dev/null
+++ b/include/seqmid.h
@@ -0,0 +1,490 @@
+/**
+ * \file include/seqmid.h
+ * \brief Application interface library for the ALSA driver
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \author Takashi Iwai <tiwai@suse.de>
+ * \date 1998-2001
+ *
+ * Application interface library for the ALSA driver
+ */
+/*
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#ifndef __ALSA_SEQMID_H
+#define __ALSA_SEQMID_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ *  \defgroup SeqMiddle Sequencer Middle Level Interface
+ *  Sequencer Middle Level Interface
+ *  \ingroup Sequencer
+ *  \{
+ */
+
+/**
+ * \brief initialize event record
+ * \param ev event record pointer
+ * 
+ * This macro clears the given event record pointer to the default status.
+ */
+#define snd_seq_ev_clear(ev) \
+	memset(ev, 0, sizeof(snd_seq_event_t))
+
+/**
+ * \brief set the tag for given event
+ * \param ev event record
+ * \param t event tag
+ *
+ * This macro sets the tag to the given event record.
+ */
+#define snd_seq_ev_set_tag(ev,t) \
+	((ev)->tag = (t))
+
+/**
+ * \brief set the explicit destination
+ * \param ev event record
+ * \param c destination client id
+ * \param p destination port id
+ *
+ * This macro sets the client and port id numbers to the given event record.
+ *
+ * \sa snd_seq_ev_set_subs()
+ */
+#define snd_seq_ev_set_dest(ev,c,p) \
+	((ev)->dest.client = (c), (ev)->dest.port = (p))
+
+/**
+ * \brief set broadcasting to subscribers
+ * \param ev event record
+ *
+ * This macro sets the destination as the subscribers.
+ *
+ * \sa snd_seq_ev_set_dest()
+ */
+#define snd_seq_ev_set_subs(ev) \
+	((ev)->dest.client = SND_SEQ_ADDRESS_SUBSCRIBERS,\
+	 (ev)->dest.port = SND_SEQ_ADDRESS_UNKNOWN)
+
+/**
+ * \brief set broadcasting to all clients/ports
+ * \param ev event record
+ *
+ * This macro sets the destination as the broadcasting.
+ *
+ * \sa snd_seq_ev_set_dest()
+ */
+#define snd_seq_ev_set_broadcast(ev) \
+	((ev)->dest.client = SND_SEQ_ADDRESS_BROADCAST,\
+	 (ev)->dest.port = SND_SEQ_ADDRESS_BROADCAST)
+
+/**
+ * \brief set the source port
+ * \param ev event record
+ * \param p source port id
+ *
+ * This macro sets the source port id number.
+ */
+#define snd_seq_ev_set_source(ev,p) \
+	((ev)->source.port = (p))
+
+/**
+ * \brief set direct passing mode (without queued)
+ * \param ev event instance
+ *
+ * This macro sets the event to the direct passing mode
+ * to be delivered immediately without queueing.
+ * 
+ * \sa snd_seq_ev_schedule_tick(), snd_seq_ev_schedule_real()
+ */
+#define snd_seq_ev_set_direct(ev) \
+	((ev)->queue = SND_SEQ_QUEUE_DIRECT)
+
+/**
+ * \brief set tick-scheduling mode on queue
+ * \param ev event instance
+ * \param q queue id to schedule
+ * \param relative relative time-stamp if non-zero
+ * \param ttick tick time-stamp to be delivered
+ *
+ * This macro sets the scheduling of the event in the
+ * MIDI tick mode.
+ *
+ * \sa snd_seq_ev_schedule_real(), snd_seq_ev_set_direct()
+ */
+#define snd_seq_ev_schedule_tick(ev, q, relative, ttick) \
+	((ev)->flags &= ~(SND_SEQ_TIME_STAMP_MASK | SND_SEQ_TIME_MODE_MASK),\
+	 (ev)->flags |= SND_SEQ_TIME_STAMP_TICK,\
+	 (ev)->flags |= (relative) ? SND_SEQ_TIME_MODE_REL : SND_SEQ_TIME_MODE_ABS,\
+	 (ev)->time.tick = (ttick),\
+	 (ev)->queue = (q))
+
+/**
+ * \brief set real-time-scheduling mode on queue
+ * \param ev event instance
+ * \param q queue id to schedule
+ * \param relative relative time-stamp if non-zero
+ * \param rtime time-stamp to be delivered
+ *
+ * This macro sets the scheduling of the event in the
+ * realtime mode.
+ *
+ * \sa snd_seq_ev_schedule_tick(), snd_seq_ev_set_direct()
+ */
+#define snd_seq_ev_schedule_real(ev, q, relative, rtime) \
+	((ev)->flags &= ~(SND_SEQ_TIME_STAMP_MASK | SND_SEQ_TIME_MODE_MASK),\
+	 (ev)->flags |= SND_SEQ_TIME_STAMP_REAL,\
+	 (ev)->flags |= (relative) ? SND_SEQ_TIME_MODE_REL : SND_SEQ_TIME_MODE_ABS,\
+	 (ev)->time.time = *(rtime),\
+	 (ev)->queue = (q))
+
+/**
+ * \brief set event priority
+ * \param ev event instance
+ * \param high_prior 1 for high priority mode
+ */
+#define snd_seq_ev_set_priority(ev, high_prior) \
+	((ev)->flags &= ~SND_SEQ_PRIORITY_MASK,\
+	 (ev)->flags |= (high_prior) ? SND_SEQ_PRIORITY_HIGH : SND_SEQ_PRIORITY_NORMAL)
+
+/**
+ * \brief set fixed data
+ * \param ev event instance
+ *
+ * Sets the event length mode as fixed size.
+ *
+ * \sa snd_seq_ev_set_variable(), snd_seq_ev_set_varusr()
+ */
+#define snd_seq_ev_set_fixed(ev) \
+	((ev)->flags &= ~SND_SEQ_EVENT_LENGTH_MASK,\
+	 (ev)->flags |= SND_SEQ_EVENT_LENGTH_FIXED)
+
+/**
+ * \brief set variable data
+ * \param ev event instance
+ * \param datalen length of the external data
+ * \param dataptr pointer of the external data
+ *
+ * Sets the event length mode as variable length and stores the data.
+ *
+ * \sa snd_seq_ev_set_fixed(), snd_seq_ev_set_varusr()
+ */
+#define snd_seq_ev_set_variable(ev, datalen, dataptr) \
+	((ev)->flags &= ~SND_SEQ_EVENT_LENGTH_MASK,\
+	 (ev)->flags |= SND_SEQ_EVENT_LENGTH_VARIABLE,\
+	 (ev)->data.ext.len = (datalen),\
+	 (ev)->data.ext.ptr = (dataptr))
+
+/**
+ * \brief set varusr data
+ * \param ev event instance
+ * \param datalen length of the external data
+ * \param dataptr pointer of the external data
+ *
+ * Sets the event length mode as variable user-space data and stores the data.
+ *
+ * \sa snd_seq_ev_set_fixed(), snd_seq_ev_set_variable()
+ */
+#define snd_seq_ev_set_varusr(ev, datalen, dataptr) \
+	((ev)->flags &= ~SND_SEQ_EVENT_LENGTH_MASK,\
+	 (ev)->flags |= SND_SEQ_EVENT_LENGTH_VARUSR,\
+	 (ev)->data.ext.len = (datalen),\
+	 (ev)->data.ext.ptr = (dataptr))
+
+/**
+ * \brief set queue controls
+ * \param ev event record
+ * \param typ event type
+ * \param q queue id
+ * \param val control value
+ */
+#define snd_seq_ev_set_queue_control(ev, typ, q, val) \
+	((ev)->type = (typ),\
+	 snd_seq_ev_set_dest(ev, SND_SEQ_CLIENT_SYSTEM, SND_SEQ_PORT_SYSTEM_TIMER),\
+	 (ev)->data.queue.queue = (q),\
+	 (ev)->data.queue.param.value = (val))
+
+/**
+ * \brief set the start queue event
+ * \param ev event record
+ * \param q queue id to start
+ *
+ * \sa snd_seq_ev_set_queue_stop(), snd_seq_ev_set_queue_continue()
+ */
+#define snd_seq_ev_set_queue_start(ev, q) \
+	snd_seq_ev_set_queue_control(ev, SND_SEQ_EVENT_START, q, 0)
+
+/**
+ * \brief set the stop queue event
+ * \param ev event record
+ * \param q queue id to stop
+ *
+ * \sa snd_seq_ev_set_queue_start(), snd_seq_ev_set_queue_continue()
+ */
+#define snd_seq_ev_set_queue_stop(ev, q) \
+	snd_seq_ev_set_queue_control(ev, SND_SEQ_EVENT_STOP, q, 0)
+
+/**
+ * \brief set the stop queue event
+ * \param ev event record
+ * \param q queue id to continue
+ *
+ * \sa snd_seq_ev_set_queue_start(), snd_seq_ev_set_queue_stop()
+ */
+#define snd_seq_ev_set_queue_continue(ev, q) \
+	snd_seq_ev_set_queue_control(ev, SND_SEQ_EVENT_CONTINUE, q, 0)
+
+/**
+ * \brief set the stop queue event
+ * \param ev event record
+ * \param q queue id to change tempo
+ * \param val the new tempo value
+ */
+#define snd_seq_ev_set_queue_tempo(ev, q, val) \
+	snd_seq_ev_set_queue_control(ev, SND_SEQ_EVENT_TEMPO, q, val)
+
+/**
+ * \brief set the real-time position of a queue
+ * \param ev event record
+ * \param q queue id to change tempo
+ * \param rtime the new real-time pointer
+ */
+#define snd_seq_ev_set_queue_pos_real(ev, q, rtime) \
+	((ev)->type = SND_SEQ_EVENT_SETPOS_TIME,\
+	 snd_seq_ev_set_dest(ev, SND_SEQ_CLIENT_SYSTEM, SND_SEQ_PORT_SYSTEM_TIMER),\
+	 (ev)->data.queue.queue = (q),\
+	 (ev)->data.queue.param.time.time = *(rtime))
+
+/**
+ * \brief set the tick-time position of a queue
+ * \param ev event record
+ * \param q queue id to change tempo
+ * \param ttime the new tick-time
+ */
+#define snd_seq_ev_set_queue_pos_tick(ev, q, ttime) \
+	((ev)->type = SND_SEQ_EVENT_SETPOS_TICK,\
+	 snd_seq_ev_set_dest(ev, SND_SEQ_CLIENT_SYSTEM, SND_SEQ_PORT_SYSTEM_TIMER),\
+	 (ev)->data.queue.queue = (q),\
+	 (ev)->data.queue.param.time.tick = (ttime))
+
+/* set and send a queue control event */
+int snd_seq_control_queue(snd_seq_t *seq, int q, int type, int value, snd_seq_event_t *ev);
+
+/**
+ * \brief start the specified queue
+ * \param seq sequencer handle
+ * \param q queue id to start
+ * \param ev optional event record (see #snd_seq_control_queue)
+ */
+#define snd_seq_start_queue(seq, q, ev) \
+	snd_seq_control_queue(seq, q, SND_SEQ_EVENT_START, 0, ev)
+
+/**
+ * \brief stop the specified queue
+ * \param seq sequencer handle
+ * \param q queue id to stop
+ * \param ev optional event record (see #snd_seq_control_queue)
+ */
+#define snd_seq_stop_queue(seq, q, ev) \
+	snd_seq_control_queue(seq, q, SND_SEQ_EVENT_STOP, 0, ev)
+
+/**
+ * \brief continue the specified queue
+ * \param seq sequencer handle
+ * \param q queue id to continue
+ * \param ev optional event record (see #snd_seq_control_queue)
+ */
+#define snd_seq_continue_queue(seq, q, ev) \
+	snd_seq_control_queue(seq, q, SND_SEQ_EVENT_CONTINUE, 0, ev)
+
+/**
+ * \brief change the tempo of the specified queue
+ * \param seq sequencer handle
+ * \param q queue id
+ * \param tempo the new tempo value
+ * \param ev optional event record (see #snd_seq_control_queue)
+ */
+#define snd_seq_change_queue_tempo(seq, q, tempo, ev) \
+	snd_seq_control_queue(seq, q, SND_SEQ_EVENT_TEMPO, tempo, ev)
+
+/* create a port - simple version - return the port number */
+int snd_seq_create_simple_port(snd_seq_t *seq, const char *name,
+			       unsigned int caps, unsigned int type);
+/* delete the port */
+int snd_seq_delete_simple_port(snd_seq_t *seq, int port);
+
+/* simple subscription between this port and another port
+   (w/o exclusive & time conversion)
+   */
+int snd_seq_connect_from(snd_seq_t *seq, int my_port, int src_client, int src_port);
+int snd_seq_connect_to(snd_seq_t *seq, int my_port, int dest_client, int dest_port);
+int snd_seq_disconnect_from(snd_seq_t *seq, int my_port, int src_client, int src_port);
+int snd_seq_disconnect_to(snd_seq_t *seq, int my_port, int dest_client, int dest_port);
+
+/*
+ * set client information
+ */
+int snd_seq_set_client_name(snd_seq_t *seq, const char *name);
+int snd_seq_set_client_event_filter(snd_seq_t *seq, int event_type);
+int snd_seq_set_client_pool_output(snd_seq_t *seq, size_t size);
+int snd_seq_set_client_pool_output_room(snd_seq_t *seq, size_t size);
+int snd_seq_set_client_pool_input(snd_seq_t *seq, size_t size);
+/* sync output queue */
+int snd_seq_sync_output_queue(snd_seq_t *seq);
+
+/*
+ * parse the given string and get the sequencer address
+ */
+int snd_seq_parse_address(snd_seq_t *seq, snd_seq_addr_t *addr, const char *str);
+
+/*
+ * reset client input/output pool
+ */
+int snd_seq_reset_pool_output(snd_seq_t *seq);
+int snd_seq_reset_pool_input(snd_seq_t *seq);
+
+/**
+ * \brief set note event
+ * \param ev event record
+ * \param ch channel number
+ * \param key note key
+ * \param vel velocity
+ * \param dur duration (in tick or msec)
+ */
+#define snd_seq_ev_set_note(ev, ch, key, vel, dur) \
+	((ev)->type = SND_SEQ_EVENT_NOTE,\
+	 snd_seq_ev_set_fixed(ev),\
+	 (ev)->data.note.channel = (ch),\
+	 (ev)->data.note.note = (key),\
+	 (ev)->data.note.velocity = (vel),\
+	 (ev)->data.note.duration = (dur))
+
+/**
+ * \brief set note-on event
+ * \param ev event record
+ * \param ch channel number
+ * \param key note key
+ * \param vel velocity
+ */
+#define snd_seq_ev_set_noteon(ev, ch, key, vel) \
+	((ev)->type = SND_SEQ_EVENT_NOTEON,\
+	 snd_seq_ev_set_fixed(ev),\
+	 (ev)->data.note.channel = (ch),\
+	 (ev)->data.note.note = (key),\
+	 (ev)->data.note.velocity = (vel))
+
+/**
+ * \brief set note-off event
+ * \param ev event record
+ * \param ch channel number
+ * \param key note key
+ * \param vel velocity
+ */
+#define snd_seq_ev_set_noteoff(ev, ch, key, vel) \
+	((ev)->type = SND_SEQ_EVENT_NOTEOFF,\
+	 snd_seq_ev_set_fixed(ev),\
+	 (ev)->data.note.channel = (ch),\
+	 (ev)->data.note.note = (key),\
+	 (ev)->data.note.velocity = (vel))
+
+/**
+ * \brief set key-pressure event
+ * \param ev event record
+ * \param ch channel number
+ * \param key note key
+ * \param vel velocity
+ */
+#define snd_seq_ev_set_keypress(ev,ch,key,vel) \
+	((ev)->type = SND_SEQ_EVENT_KEYPRESS,\
+	 snd_seq_ev_set_fixed(ev),\
+	 (ev)->data.note.channel = (ch),\
+	 (ev)->data.note.note = (key),\
+	 (ev)->data.note.velocity = (vel))
+
+/**
+ * \brief set MIDI controller event
+ * \param ev event record
+ * \param ch channel number
+ * \param cc controller number
+ * \param val control value
+ */
+#define snd_seq_ev_set_controller(ev,ch,cc,val) \
+	((ev)->type = SND_SEQ_EVENT_CONTROLLER,\
+	 snd_seq_ev_set_fixed(ev),\
+	 (ev)->data.control.channel = (ch),\
+	 (ev)->data.control.param = (cc),\
+	 (ev)->data.control.value = (val))
+
+/**
+ * \brief set program change event
+ * \param ev event record
+ * \param ch channel number
+ * \param val program number
+ */
+#define snd_seq_ev_set_pgmchange(ev,ch,val) \
+	((ev)->type = SND_SEQ_EVENT_PGMCHANGE,\
+	 snd_seq_ev_set_fixed(ev),\
+	 (ev)->data.control.channel = (ch),\
+	 (ev)->data.control.value = (val))
+
+/**
+ * \brief set pitch-bend event
+ * \param ev event record
+ * \param ch channel number
+ * \param val pitch bend; zero centered from -8192 to 8191
+ */
+#define snd_seq_ev_set_pitchbend(ev,ch,val) \
+	((ev)->type = SND_SEQ_EVENT_PITCHBEND,\
+	 snd_seq_ev_set_fixed(ev),\
+	 (ev)->data.control.channel = (ch),\
+	 (ev)->data.control.value = (val))
+
+/**
+ * \brief set channel pressure event
+ * \param ev event record
+ * \param ch channel number
+ * \param val channel pressure value
+ */
+#define snd_seq_ev_set_chanpress(ev,ch,val) \
+	((ev)->type = SND_SEQ_EVENT_CHANPRESS,\
+	 snd_seq_ev_set_fixed(ev),\
+	 (ev)->data.control.channel = (ch),\
+	 (ev)->data.control.value = (val))
+
+/**
+ * \brief set sysex event
+ * \param ev event record
+ * \param datalen length of sysex data
+ * \param dataptr sysex data pointer
+ *
+ * the sysex data must contain the start byte 0xf0 and the end byte 0xf7.
+ */
+#define snd_seq_ev_set_sysex(ev,datalen,dataptr) \
+	((ev)->type = SND_SEQ_EVENT_SYSEX,\
+	 snd_seq_ev_set_variable(ev, datalen, dataptr))
+
+/** \} */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __ALSA_SEQMID_H */
+
diff --git a/include/sound/Makefile.am b/include/sound/Makefile.am
new file mode 100644
index 0000000..31aa2db
--- /dev/null
+++ b/include/sound/Makefile.am
@@ -0,0 +1,6 @@
+alsasoundincludedir = ${includedir}/alsa/sound
+
+alsasoundinclude_HEADERS = asound_fm.h hdsp.h hdspm.h sb16_csp.h \
+			   sscape_ioctl.h emu10k1.h type_compat.h
+
+noinst_HEADERS = asound.h asoundef.h asequencer.h
diff --git a/include/sound/asequencer.h b/include/sound/asequencer.h
new file mode 100644
index 0000000..6d14fb1
--- /dev/null
+++ b/include/sound/asequencer.h
@@ -0,0 +1,678 @@
+/*
+ *  Main header file for the ALSA sequencer
+ *  Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl>
+ *            (c) 1998-1999 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+#ifndef __SOUND_ASEQUENCER_H
+#define __SOUND_ASEQUENCER_H
+
+#ifdef __KERNEL__
+#include <linux/ioctl.h>
+#endif
+
+#include <sound/asound.h>
+
+/** version of the sequencer */
+#define SNDRV_SEQ_VERSION SNDRV_PROTOCOL_VERSION (1, 0, 1)
+
+#ifdef __KERNEL__
+/**
+ * definition of sequencer event types
+ */
+
+/** system messages
+ * event data type = #sndrv_seq_result_t
+ */
+#define SNDRV_SEQ_EVENT_SYSTEM		0
+#define SNDRV_SEQ_EVENT_RESULT		1
+
+/** note messages (channel specific)
+ * event data type = #sndrv_seq_ev_note
+ */
+#define SNDRV_SEQ_EVENT_NOTE		5
+#define SNDRV_SEQ_EVENT_NOTEON		6
+#define SNDRV_SEQ_EVENT_NOTEOFF		7
+#define SNDRV_SEQ_EVENT_KEYPRESS	8
+	
+/** control messages (channel specific)
+ * event data type = #sndrv_seq_ev_ctrl
+ */
+#define SNDRV_SEQ_EVENT_CONTROLLER	10
+#define SNDRV_SEQ_EVENT_PGMCHANGE	11
+#define SNDRV_SEQ_EVENT_CHANPRESS	12
+#define SNDRV_SEQ_EVENT_PITCHBEND	13	/**< from -8192 to 8191 */
+#define SNDRV_SEQ_EVENT_CONTROL14	14	/**< 14 bit controller value */
+#define SNDRV_SEQ_EVENT_NONREGPARAM	15	/**< 14 bit NRPN address + 14 bit unsigned value */
+#define SNDRV_SEQ_EVENT_REGPARAM	16	/**< 14 bit RPN address + 14 bit unsigned value */
+
+/** synchronisation messages
+ * event data type = #sndrv_seq_ev_ctrl
+ */
+#define SNDRV_SEQ_EVENT_SONGPOS		20	/* Song Position Pointer with LSB and MSB values */
+#define SNDRV_SEQ_EVENT_SONGSEL		21	/* Song Select with song ID number */
+#define SNDRV_SEQ_EVENT_QFRAME		22	/* midi time code quarter frame */
+#define SNDRV_SEQ_EVENT_TIMESIGN	23	/* SMF Time Signature event */
+#define SNDRV_SEQ_EVENT_KEYSIGN		24	/* SMF Key Signature event */
+	        
+/** timer messages
+ * event data type = sndrv_seq_ev_queue_control_t
+ */
+#define SNDRV_SEQ_EVENT_START		30	/* midi Real Time Start message */
+#define SNDRV_SEQ_EVENT_CONTINUE	31	/* midi Real Time Continue message */
+#define SNDRV_SEQ_EVENT_STOP		32	/* midi Real Time Stop message */	
+#define	SNDRV_SEQ_EVENT_SETPOS_TICK	33	/* set tick queue position */
+#define SNDRV_SEQ_EVENT_SETPOS_TIME	34	/* set realtime queue position */
+#define SNDRV_SEQ_EVENT_TEMPO		35	/* (SMF) Tempo event */
+#define SNDRV_SEQ_EVENT_CLOCK		36	/* midi Real Time Clock message */
+#define SNDRV_SEQ_EVENT_TICK		37	/* midi Real Time Tick message */
+#define SNDRV_SEQ_EVENT_QUEUE_SKEW	38	/* skew queue tempo */
+
+/** others
+ * event data type = none
+ */
+#define SNDRV_SEQ_EVENT_TUNE_REQUEST	40	/* tune request */
+#define SNDRV_SEQ_EVENT_RESET		41	/* reset to power-on state */
+#define SNDRV_SEQ_EVENT_SENSING		42	/* "active sensing" event */
+
+/** echo back, kernel private messages
+ * event data type = any type
+ */
+#define SNDRV_SEQ_EVENT_ECHO		50	/* echo event */
+#define SNDRV_SEQ_EVENT_OSS		51	/* OSS raw event */
+
+/** system status messages (broadcast for subscribers)
+ * event data type = sndrv_seq_addr_t
+ */
+#define SNDRV_SEQ_EVENT_CLIENT_START	60	/* new client has connected */
+#define SNDRV_SEQ_EVENT_CLIENT_EXIT	61	/* client has left the system */
+#define SNDRV_SEQ_EVENT_CLIENT_CHANGE	62	/* client status/info has changed */
+#define SNDRV_SEQ_EVENT_PORT_START	63	/* new port was created */
+#define SNDRV_SEQ_EVENT_PORT_EXIT	64	/* port was deleted from system */
+#define SNDRV_SEQ_EVENT_PORT_CHANGE	65	/* port status/info has changed */
+
+/** port connection changes
+ * event data type = sndrv_seq_connect_t
+ */
+#define SNDRV_SEQ_EVENT_PORT_SUBSCRIBED	66	/* ports connected */
+#define SNDRV_SEQ_EVENT_PORT_UNSUBSCRIBED 67	/* ports disconnected */
+
+/* 70-89:  synthesizer events - obsoleted */
+
+/** user-defined events with fixed length
+ * event data type = any
+ */
+#define SNDRV_SEQ_EVENT_USR0		90
+#define SNDRV_SEQ_EVENT_USR1		91
+#define SNDRV_SEQ_EVENT_USR2		92
+#define SNDRV_SEQ_EVENT_USR3		93
+#define SNDRV_SEQ_EVENT_USR4		94
+#define SNDRV_SEQ_EVENT_USR5		95
+#define SNDRV_SEQ_EVENT_USR6		96
+#define SNDRV_SEQ_EVENT_USR7		97
+#define SNDRV_SEQ_EVENT_USR8		98
+#define SNDRV_SEQ_EVENT_USR9		99
+
+/* 100-118: instrument layer - obsoleted */
+/* 119-129: reserved */
+
+/* 130-139: variable length events
+ * event data type = sndrv_seq_ev_ext
+ * (SNDRV_SEQ_EVENT_LENGTH_VARIABLE must be set)
+ */
+#define SNDRV_SEQ_EVENT_SYSEX		130	/* system exclusive data (variable length) */
+#define SNDRV_SEQ_EVENT_BOUNCE		131	/* error event */
+/* 132-134: reserved */
+#define SNDRV_SEQ_EVENT_USR_VAR0	135
+#define SNDRV_SEQ_EVENT_USR_VAR1	136
+#define SNDRV_SEQ_EVENT_USR_VAR2	137
+#define SNDRV_SEQ_EVENT_USR_VAR3	138
+#define SNDRV_SEQ_EVENT_USR_VAR4	139
+
+/* 150-151: kernel events with quote - DO NOT use in user clients */
+#define SNDRV_SEQ_EVENT_KERNEL_ERROR	150
+#define SNDRV_SEQ_EVENT_KERNEL_QUOTE	151	/* obsolete */
+
+/* 152-191: reserved */
+
+/* 192-254: hardware specific events */
+
+/* 255: special event */
+#define SNDRV_SEQ_EVENT_NONE		255
+
+
+typedef unsigned char sndrv_seq_event_type_t;
+
+/** event address */
+struct sndrv_seq_addr {
+	unsigned char client;	/**< Client number:         0..255, 255 = broadcast to all clients */
+	unsigned char port;	/**< Port within client:    0..255, 255 = broadcast to all ports */
+};
+
+/** port connection */
+struct sndrv_seq_connect {
+	struct sndrv_seq_addr sender;
+	struct sndrv_seq_addr dest;
+};
+
+
+#define SNDRV_SEQ_ADDRESS_UNKNOWN	253	/* unknown source */
+#define SNDRV_SEQ_ADDRESS_SUBSCRIBERS	254	/* send event to all subscribed ports */
+#define SNDRV_SEQ_ADDRESS_BROADCAST	255	/* send event to all queues/clients/ports/channels */
+#define SNDRV_SEQ_QUEUE_DIRECT		253	/* direct dispatch */
+
+	/* event mode flag - NOTE: only 8 bits available! */
+#define SNDRV_SEQ_TIME_STAMP_TICK	(0<<0) /* timestamp in clock ticks */
+#define SNDRV_SEQ_TIME_STAMP_REAL	(1<<0) /* timestamp in real time */
+#define SNDRV_SEQ_TIME_STAMP_MASK	(1<<0)
+
+#define SNDRV_SEQ_TIME_MODE_ABS		(0<<1)	/* absolute timestamp */
+#define SNDRV_SEQ_TIME_MODE_REL		(1<<1)	/* relative to current time */
+#define SNDRV_SEQ_TIME_MODE_MASK	(1<<1)
+
+#define SNDRV_SEQ_EVENT_LENGTH_FIXED	(0<<2)	/* fixed event size */
+#define SNDRV_SEQ_EVENT_LENGTH_VARIABLE	(1<<2)	/* variable event size */
+#define SNDRV_SEQ_EVENT_LENGTH_VARUSR	(2<<2)	/* variable event size - user memory space */
+#define SNDRV_SEQ_EVENT_LENGTH_MASK	(3<<2)
+
+#define SNDRV_SEQ_PRIORITY_NORMAL	(0<<4)	/* normal priority */
+#define SNDRV_SEQ_PRIORITY_HIGH		(1<<4)	/* event should be processed before others */
+#define SNDRV_SEQ_PRIORITY_MASK		(1<<4)
+
+
+	/* note event */
+struct sndrv_seq_ev_note {
+	unsigned char channel;
+	unsigned char note;
+	unsigned char velocity;
+	unsigned char off_velocity;	/* only for SNDRV_SEQ_EVENT_NOTE */
+	unsigned int duration;		/* only for SNDRV_SEQ_EVENT_NOTE */
+};
+
+	/* controller event */
+struct sndrv_seq_ev_ctrl {
+	unsigned char channel;
+	unsigned char unused1, unused2, unused3;	/* pad */
+	unsigned int param;
+	signed int value;
+};
+
+	/* generic set of bytes (12x8 bit) */
+struct sndrv_seq_ev_raw8 {
+	unsigned char d[12];	/* 8 bit value */
+};
+
+	/* generic set of integers (3x32 bit) */
+struct sndrv_seq_ev_raw32 {
+	unsigned int d[3];	/* 32 bit value */
+};
+
+	/* external stored data */
+struct sndrv_seq_ev_ext {
+	unsigned int len;	/* length of data */
+	void *ptr;		/* pointer to data (note: maybe 64-bit) */
+} __attribute__((packed));
+
+struct sndrv_seq_result {
+	int event;		/* processed event type */
+	int result;
+};
+
+
+struct sndrv_seq_real_time {
+	unsigned int tv_sec;	/* seconds */
+	unsigned int tv_nsec;	/* nanoseconds */
+};
+
+typedef unsigned int sndrv_seq_tick_time_t;	/* midi ticks */
+
+union sndrv_seq_timestamp {
+	sndrv_seq_tick_time_t tick;
+	struct sndrv_seq_real_time time;
+};
+
+struct sndrv_seq_queue_skew {
+	unsigned int value;
+	unsigned int base;
+};
+
+	/* queue timer control */
+struct sndrv_seq_ev_queue_control {
+	unsigned char queue;			/* affected queue */
+	unsigned char pad[3];			/* reserved */
+	union {
+		signed int value;		/* affected value (e.g. tempo) */
+		union sndrv_seq_timestamp time;	/* time */
+		unsigned int position;		/* sync position */
+		struct sndrv_seq_queue_skew skew;
+		unsigned int d32[2];
+		unsigned char d8[8];
+	} param;
+};
+
+	/* quoted event - inside the kernel only */
+struct sndrv_seq_ev_quote {
+	struct sndrv_seq_addr origin;		/* original sender */
+	unsigned short value;		/* optional data */
+	struct sndrv_seq_event *event;		/* quoted event */
+} __attribute__((packed));
+
+
+	/* sequencer event */
+struct sndrv_seq_event {
+	sndrv_seq_event_type_t type;	/* event type */
+	unsigned char flags;		/* event flags */
+	char tag;
+	
+	unsigned char queue;		/* schedule queue */
+	union sndrv_seq_timestamp time;	/* schedule time */
+
+
+	struct sndrv_seq_addr source;	/* source address */
+	struct sndrv_seq_addr dest;	/* destination address */
+
+	union {				/* event data... */
+		struct sndrv_seq_ev_note note;
+		struct sndrv_seq_ev_ctrl control;
+		struct sndrv_seq_ev_raw8 raw8;
+		struct sndrv_seq_ev_raw32 raw32;
+		struct sndrv_seq_ev_ext ext;
+		struct sndrv_seq_ev_queue_control queue;
+		union sndrv_seq_timestamp time;
+		struct sndrv_seq_addr addr;
+		struct sndrv_seq_connect connect;
+		struct sndrv_seq_result result;
+		struct sndrv_seq_ev_quote quote;
+	} data;
+};
+
+
+/*
+ * bounce event - stored as variable size data
+ */
+struct sndrv_seq_event_bounce {
+	int err;
+	struct sndrv_seq_event event;
+	/* external data follows here. */
+};
+
+#define sndrv_seq_event_bounce_ext_data(ev) ((void*)((char *)(ev)->data.ext.ptr + sizeof(sndrv_seq_event_bounce_t)))
+
+/*
+ * type check macros
+ */
+/* result events: 0-4 */
+#define sndrv_seq_ev_is_result_type(ev)	((ev)->type < 5)
+/* channel specific events: 5-19 */
+#define sndrv_seq_ev_is_channel_type(ev)	((ev)->type >= 5 && (ev)->type < 20)
+/* note events: 5-9 */
+#define sndrv_seq_ev_is_note_type(ev)	((ev)->type >= 5 && (ev)->type < 10)
+/* control events: 10-19 */
+#define sndrv_seq_ev_is_control_type(ev)	((ev)->type >= 10 && (ev)->type < 20)
+/* queue control events: 30-39 */
+#define sndrv_seq_ev_is_queue_type(ev)	((ev)->type >= 30 && (ev)->type < 40)
+/* system status messages */
+#define sndrv_seq_ev_is_message_type(ev)	((ev)->type >= 60 && (ev)->type < 69)
+/* sample messages */
+#define sndrv_seq_ev_is_sample_type(ev)	((ev)->type >= 70 && (ev)->type < 79)
+/* user-defined messages */
+#define sndrv_seq_ev_is_user_type(ev)	((ev)->type >= 90 && (ev)->type < 99)
+/* fixed length events: 0-99 */
+#define sndrv_seq_ev_is_fixed_type(ev)	((ev)->type < 100)
+/* variable length events: 130-139 */
+#define sndrv_seq_ev_is_variable_type(ev)	((ev)->type >= 130 && (ev)->type < 140)
+/* reserved for kernel */
+#define sndrv_seq_ev_is_reserved(ev)	((ev)->type >= 150)
+
+/* direct dispatched events */
+#define sndrv_seq_ev_is_direct(ev)	((ev)->queue == SNDRV_SEQ_QUEUE_DIRECT)
+
+/*
+ * macros to check event flags
+ */
+/* prior events */
+#define sndrv_seq_ev_is_prior(ev)		(((ev)->flags & SNDRV_SEQ_PRIORITY_MASK) == SNDRV_SEQ_PRIORITY_HIGH)
+
+/* event length type */
+#define sndrv_seq_ev_length_type(ev)	((ev)->flags & SNDRV_SEQ_EVENT_LENGTH_MASK)
+#define sndrv_seq_ev_is_fixed(ev)		(sndrv_seq_ev_length_type(ev) == SNDRV_SEQ_EVENT_LENGTH_FIXED)
+#define sndrv_seq_ev_is_variable(ev)	(sndrv_seq_ev_length_type(ev) == SNDRV_SEQ_EVENT_LENGTH_VARIABLE)
+#define sndrv_seq_ev_is_varusr(ev)	(sndrv_seq_ev_length_type(ev) == SNDRV_SEQ_EVENT_LENGTH_VARUSR)
+
+/* time-stamp type */
+#define sndrv_seq_ev_timestamp_type(ev)	((ev)->flags & SNDRV_SEQ_TIME_STAMP_MASK)
+#define sndrv_seq_ev_is_tick(ev)		(sndrv_seq_ev_timestamp_type(ev) == SNDRV_SEQ_TIME_STAMP_TICK)
+#define sndrv_seq_ev_is_real(ev)		(sndrv_seq_ev_timestamp_type(ev) == SNDRV_SEQ_TIME_STAMP_REAL)
+
+/* time-mode type */
+#define sndrv_seq_ev_timemode_type(ev)	((ev)->flags & SNDRV_SEQ_TIME_MODE_MASK)
+#define sndrv_seq_ev_is_abstime(ev)	(sndrv_seq_ev_timemode_type(ev) == SNDRV_SEQ_TIME_MODE_ABS)
+#define sndrv_seq_ev_is_reltime(ev)	(sndrv_seq_ev_timemode_type(ev) == SNDRV_SEQ_TIME_MODE_REL)
+
+/* queue sync port */
+#define sndrv_seq_queue_sync_port(q)	((q) + 16)
+
+#endif /* __KERNEL__ */
+
+	/* system information */
+struct sndrv_seq_system_info {
+	int queues;			/* maximum queues count */
+	int clients;			/* maximum clients count */
+	int ports;			/* maximum ports per client */
+	int channels;			/* maximum channels per port */
+	int cur_clients;		/* current clients */
+	int cur_queues;			/* current queues */
+	char reserved[24];
+};
+
+
+	/* system running information */
+struct sndrv_seq_running_info {
+	unsigned char client;		/* client id */
+	unsigned char big_endian;	/* 1 = big-endian */
+	unsigned char cpu_mode;		/* 4 = 32bit, 8 = 64bit */
+	unsigned char pad;		/* reserved */
+	unsigned char reserved[12];
+};
+
+
+	/* known client numbers */
+#define SNDRV_SEQ_CLIENT_SYSTEM		0
+	/* internal client numbers */
+#define SNDRV_SEQ_CLIENT_DUMMY		14	/* midi through */
+#define SNDRV_SEQ_CLIENT_OSS		15	/* oss sequencer emulator */
+
+
+	/* client types */
+enum sndrv_seq_client_type {
+	NO_CLIENT       = 0,
+	USER_CLIENT     = 1,
+	KERNEL_CLIENT   = 2
+};
+                        
+	/* event filter flags */
+#define SNDRV_SEQ_FILTER_BROADCAST	(1<<0)	/* accept broadcast messages */
+#define SNDRV_SEQ_FILTER_MULTICAST	(1<<1)	/* accept multicast messages */
+#define SNDRV_SEQ_FILTER_BOUNCE		(1<<2)	/* accept bounce event in error */
+#define SNDRV_SEQ_FILTER_USE_EVENT	(1<<31)	/* use event filter */
+
+struct sndrv_seq_client_info {
+	int client;			/* client number to inquire */
+	int type;			/* client type */
+	char name[64];			/* client name */
+	unsigned int filter;		/* filter flags */
+	unsigned char multicast_filter[8]; /* multicast filter bitmap */
+	unsigned char event_filter[32];	/* event filter bitmap */
+	int num_ports;			/* RO: number of ports */
+	int event_lost;			/* number of lost events */
+	char reserved[64];		/* for future use */
+};
+
+
+/* client pool size */
+struct sndrv_seq_client_pool {
+	int client;			/* client number to inquire */
+	int output_pool;		/* outgoing (write) pool size */
+	int input_pool;			/* incoming (read) pool size */
+	int output_room;		/* minimum free pool size for select/blocking mode */
+	int output_free;		/* unused size */
+	int input_free;			/* unused size */
+	char reserved[64];
+};
+
+
+/* Remove events by specified criteria */
+
+#define SNDRV_SEQ_REMOVE_INPUT		(1<<0)	/* Flush input queues */
+#define SNDRV_SEQ_REMOVE_OUTPUT		(1<<1)	/* Flush output queues */
+#define SNDRV_SEQ_REMOVE_DEST		(1<<2)	/* Restrict by destination q:client:port */
+#define SNDRV_SEQ_REMOVE_DEST_CHANNEL	(1<<3)	/* Restrict by channel */
+#define SNDRV_SEQ_REMOVE_TIME_BEFORE	(1<<4)	/* Restrict to before time */
+#define SNDRV_SEQ_REMOVE_TIME_AFTER	(1<<5)	/* Restrict to time or after */
+#define SNDRV_SEQ_REMOVE_TIME_TICK	(1<<6)	/* Time is in ticks */
+#define SNDRV_SEQ_REMOVE_EVENT_TYPE	(1<<7)	/* Restrict to event type */
+#define SNDRV_SEQ_REMOVE_IGNORE_OFF 	(1<<8)	/* Do not flush off events */
+#define SNDRV_SEQ_REMOVE_TAG_MATCH 	(1<<9)	/* Restrict to events with given tag */
+
+struct sndrv_seq_remove_events {
+	unsigned int  remove_mode;	/* Flags that determine what gets removed */
+
+	union sndrv_seq_timestamp time;
+
+	unsigned char queue;	/* Queue for REMOVE_DEST */
+	struct sndrv_seq_addr dest;	/* Address for REMOVE_DEST */
+	unsigned char channel;	/* Channel for REMOVE_DEST */
+
+	int  type;	/* For REMOVE_EVENT_TYPE */
+	char  tag;	/* Tag for REMOVE_TAG */
+
+	int  reserved[10];	/* To allow for future binary compatibility */
+
+};
+
+
+	/* known port numbers */
+#define SNDRV_SEQ_PORT_SYSTEM_TIMER	0
+#define SNDRV_SEQ_PORT_SYSTEM_ANNOUNCE	1
+
+	/* port capabilities (32 bits) */
+#define SNDRV_SEQ_PORT_CAP_READ		(1<<0)	/* readable from this port */
+#define SNDRV_SEQ_PORT_CAP_WRITE	(1<<1)	/* writable to this port */
+
+#define SNDRV_SEQ_PORT_CAP_SYNC_READ	(1<<2)
+#define SNDRV_SEQ_PORT_CAP_SYNC_WRITE	(1<<3)
+
+#define SNDRV_SEQ_PORT_CAP_DUPLEX	(1<<4)
+
+#define SNDRV_SEQ_PORT_CAP_SUBS_READ	(1<<5)	/* allow read subscription */
+#define SNDRV_SEQ_PORT_CAP_SUBS_WRITE	(1<<6)	/* allow write subscription */
+#define SNDRV_SEQ_PORT_CAP_NO_EXPORT	(1<<7)	/* routing not allowed */
+
+	/* port type */
+#define SNDRV_SEQ_PORT_TYPE_SPECIFIC	(1<<0)	/* hardware specific */
+#define SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC (1<<1)	/* generic MIDI device */
+#define SNDRV_SEQ_PORT_TYPE_MIDI_GM	(1<<2)	/* General MIDI compatible device */
+#define SNDRV_SEQ_PORT_TYPE_MIDI_GS	(1<<3)	/* GS compatible device */
+#define SNDRV_SEQ_PORT_TYPE_MIDI_XG	(1<<4)	/* XG compatible device */
+#define SNDRV_SEQ_PORT_TYPE_MIDI_MT32	(1<<5)	/* MT-32 compatible device */
+#define SNDRV_SEQ_PORT_TYPE_MIDI_GM2	(1<<6)	/* General MIDI 2 compatible device */
+
+/* other standards...*/
+#define SNDRV_SEQ_PORT_TYPE_SYNTH	(1<<10)	/* Synth device (no MIDI compatible - direct wavetable) */
+#define SNDRV_SEQ_PORT_TYPE_DIRECT_SAMPLE (1<<11)	/* Sampling device (support sample download) */
+#define SNDRV_SEQ_PORT_TYPE_SAMPLE	(1<<12)	/* Sampling device (sample can be downloaded at any time) */
+/*...*/
+#define SNDRV_SEQ_PORT_TYPE_HARDWARE	(1<<16)	/* driver for a hardware device */
+#define SNDRV_SEQ_PORT_TYPE_SOFTWARE	(1<<17)	/* implemented in software */
+#define SNDRV_SEQ_PORT_TYPE_SYNTHESIZER	(1<<18)	/* generates sound */
+#define SNDRV_SEQ_PORT_TYPE_PORT	(1<<19)	/* connects to other device(s) */
+#define SNDRV_SEQ_PORT_TYPE_APPLICATION	(1<<20)	/* application (sequencer/editor) */
+
+/* misc. conditioning flags */
+#define SNDRV_SEQ_PORT_FLG_GIVEN_PORT	(1<<0)
+#define SNDRV_SEQ_PORT_FLG_TIMESTAMP	(1<<1)
+#define SNDRV_SEQ_PORT_FLG_TIME_REAL	(1<<2)
+
+struct sndrv_seq_port_info {
+	struct sndrv_seq_addr addr;	/* client/port numbers */
+	char name[64];			/* port name */
+
+	unsigned int capability;	/* port capability bits */
+	unsigned int type;		/* port type bits */
+	int midi_channels;		/* channels per MIDI port */
+	int midi_voices;		/* voices per MIDI port */
+	int synth_voices;		/* voices per SYNTH port */
+
+	int read_use;			/* R/O: subscribers for output (from this port) */
+	int write_use;			/* R/O: subscribers for input (to this port) */
+
+	void *kernel;			/* reserved for kernel use (must be NULL) */
+	unsigned int flags;		/* misc. conditioning */
+	unsigned char time_queue;	/* queue # for timestamping */
+	char reserved[59];		/* for future use */
+};
+
+
+/* queue flags */
+#define SNDRV_SEQ_QUEUE_FLG_SYNC	(1<<0)	/* sync enabled */
+
+/* queue information */
+struct sndrv_seq_queue_info {
+	int queue;		/* queue id */
+
+	/*
+	 *  security settings, only owner of this queue can start/stop timer
+	 *  etc. if the queue is locked for other clients
+	 */
+	int owner;		/* client id for owner of the queue */
+	int locked:1;		/* timing queue locked for other queues */
+	char name[64];		/* name of this queue */
+	unsigned int flags;	/* flags */
+	char reserved[60];	/* for future use */
+
+};
+
+/* queue info/status */
+struct sndrv_seq_queue_status {
+	int queue;			/* queue id */
+	int events;			/* read-only - queue size */
+	sndrv_seq_tick_time_t tick;	/* current tick */
+	struct sndrv_seq_real_time time; /* current time */
+	int running;			/* running state of queue */
+	int flags;			/* various flags */
+	char reserved[64];		/* for the future */
+};
+
+
+/* queue tempo */
+struct sndrv_seq_queue_tempo {
+	int queue;			/* sequencer queue */
+	unsigned int tempo;		/* current tempo, us/tick */
+	int ppq;			/* time resolution, ticks/quarter */
+	unsigned int skew_value;	/* queue skew */
+	unsigned int skew_base;		/* queue skew base */
+	char reserved[24];		/* for the future */
+};
+
+
+/* sequencer timer sources */
+#define SNDRV_SEQ_TIMER_ALSA		0	/* ALSA timer */
+#define SNDRV_SEQ_TIMER_MIDI_CLOCK	1	/* Midi Clock (CLOCK event) */
+#define SNDRV_SEQ_TIMER_MIDI_TICK	2	/* Midi Timer Tick (TICK event) */
+
+/* queue timer info */
+struct sndrv_seq_queue_timer {
+	int queue;			/* sequencer queue */
+	int type;			/* source timer type */
+	union {
+		struct {
+			struct sndrv_timer_id id;	/* ALSA's timer ID */
+			unsigned int resolution;	/* resolution in Hz */
+		} alsa;
+	} u;
+	char reserved[64];		/* for the future use */
+};
+
+
+struct sndrv_seq_queue_client {
+	int queue;		/* sequencer queue */
+	int client;		/* sequencer client */
+	int used;		/* queue is used with this client
+				   (must be set for accepting events) */
+	/* per client watermarks */
+	char reserved[64];	/* for future use */
+};
+
+
+#define SNDRV_SEQ_PORT_SUBS_EXCLUSIVE	(1<<0)	/* exclusive connection */
+#define SNDRV_SEQ_PORT_SUBS_TIMESTAMP	(1<<1)
+#define SNDRV_SEQ_PORT_SUBS_TIME_REAL	(1<<2)
+
+struct sndrv_seq_port_subscribe {
+	struct sndrv_seq_addr sender;	/* sender address */
+	struct sndrv_seq_addr dest;	/* destination address */
+	unsigned int voices;		/* number of voices to be allocated (0 = don't care) */
+	unsigned int flags;		/* modes */
+	unsigned char queue;		/* input time-stamp queue (optional) */
+	unsigned char pad[3];		/* reserved */
+	char reserved[64];
+};
+
+/* type of query subscription */
+#define SNDRV_SEQ_QUERY_SUBS_READ	0
+#define SNDRV_SEQ_QUERY_SUBS_WRITE	1
+
+struct sndrv_seq_query_subs {
+	struct sndrv_seq_addr root;	/* client/port id to be searched */
+	int type;		/* READ or WRITE */
+	int index;		/* 0..N-1 */
+	int num_subs;		/* R/O: number of subscriptions on this port */
+	struct sndrv_seq_addr addr;	/* R/O: result */
+	unsigned char queue;	/* R/O: result */
+	unsigned int flags;	/* R/O: result */
+	char reserved[64];	/* for future use */
+};
+
+
+/*
+ *  IOCTL commands
+ */
+
+#define SNDRV_SEQ_IOCTL_PVERSION	_IOR ('S', 0x00, int)
+#define SNDRV_SEQ_IOCTL_CLIENT_ID	_IOR ('S', 0x01, int)
+#define SNDRV_SEQ_IOCTL_SYSTEM_INFO	_IOWR('S', 0x02, struct sndrv_seq_system_info)
+#define SNDRV_SEQ_IOCTL_RUNNING_MODE	_IOWR('S', 0x03, struct sndrv_seq_running_info)
+
+#define SNDRV_SEQ_IOCTL_GET_CLIENT_INFO	_IOWR('S', 0x10, struct sndrv_seq_client_info)
+#define SNDRV_SEQ_IOCTL_SET_CLIENT_INFO	_IOW ('S', 0x11, struct sndrv_seq_client_info)
+
+#define SNDRV_SEQ_IOCTL_CREATE_PORT	_IOWR('S', 0x20, struct sndrv_seq_port_info)
+#define SNDRV_SEQ_IOCTL_DELETE_PORT	_IOW ('S', 0x21, struct sndrv_seq_port_info)
+#define SNDRV_SEQ_IOCTL_GET_PORT_INFO	_IOWR('S', 0x22, struct sndrv_seq_port_info)
+#define SNDRV_SEQ_IOCTL_SET_PORT_INFO	_IOW ('S', 0x23, struct sndrv_seq_port_info)
+
+#define SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT	_IOW ('S', 0x30, struct sndrv_seq_port_subscribe)
+#define SNDRV_SEQ_IOCTL_UNSUBSCRIBE_PORT _IOW ('S', 0x31, struct sndrv_seq_port_subscribe)
+
+#define SNDRV_SEQ_IOCTL_CREATE_QUEUE	_IOWR('S', 0x32, struct sndrv_seq_queue_info)
+#define SNDRV_SEQ_IOCTL_DELETE_QUEUE	_IOW ('S', 0x33, struct sndrv_seq_queue_info)
+#define SNDRV_SEQ_IOCTL_GET_QUEUE_INFO	_IOWR('S', 0x34, struct sndrv_seq_queue_info)
+#define SNDRV_SEQ_IOCTL_SET_QUEUE_INFO	_IOWR('S', 0x35, struct sndrv_seq_queue_info)
+#define SNDRV_SEQ_IOCTL_GET_NAMED_QUEUE	_IOWR('S', 0x36, struct sndrv_seq_queue_info)
+#define SNDRV_SEQ_IOCTL_GET_QUEUE_STATUS _IOWR('S', 0x40, struct sndrv_seq_queue_status)
+#define SNDRV_SEQ_IOCTL_GET_QUEUE_TEMPO	_IOWR('S', 0x41, struct sndrv_seq_queue_tempo)
+#define SNDRV_SEQ_IOCTL_SET_QUEUE_TEMPO	_IOW ('S', 0x42, struct sndrv_seq_queue_tempo)
+#define SNDRV_SEQ_IOCTL_GET_QUEUE_OWNER	_IOWR('S', 0x43, struct sndrv_seq_queue_owner)
+#define SNDRV_SEQ_IOCTL_SET_QUEUE_OWNER	_IOW ('S', 0x44, struct sndrv_seq_queue_owner)
+#define SNDRV_SEQ_IOCTL_GET_QUEUE_TIMER	_IOWR('S', 0x45, struct sndrv_seq_queue_timer)
+#define SNDRV_SEQ_IOCTL_SET_QUEUE_TIMER	_IOW ('S', 0x46, struct sndrv_seq_queue_timer)
+/* XXX
+#define SNDRV_SEQ_IOCTL_GET_QUEUE_SYNC	_IOWR('S', 0x53, struct sndrv_seq_queue_sync)
+#define SNDRV_SEQ_IOCTL_SET_QUEUE_SYNC	_IOW ('S', 0x54, struct sndrv_seq_queue_sync)
+*/
+#define SNDRV_SEQ_IOCTL_GET_QUEUE_CLIENT	_IOWR('S', 0x49, struct sndrv_seq_queue_client)
+#define SNDRV_SEQ_IOCTL_SET_QUEUE_CLIENT	_IOW ('S', 0x4a, struct sndrv_seq_queue_client)
+#define SNDRV_SEQ_IOCTL_GET_CLIENT_POOL	_IOWR('S', 0x4b, struct sndrv_seq_client_pool)
+#define SNDRV_SEQ_IOCTL_SET_CLIENT_POOL	_IOW ('S', 0x4c, struct sndrv_seq_client_pool)
+#define SNDRV_SEQ_IOCTL_REMOVE_EVENTS	_IOW ('S', 0x4e, struct sndrv_seq_remove_events)
+#define SNDRV_SEQ_IOCTL_QUERY_SUBS	_IOWR('S', 0x4f, struct sndrv_seq_query_subs)
+#define SNDRV_SEQ_IOCTL_GET_SUBSCRIPTION	_IOWR('S', 0x50, struct sndrv_seq_port_subscribe)
+#define SNDRV_SEQ_IOCTL_QUERY_NEXT_CLIENT	_IOWR('S', 0x51, struct sndrv_seq_client_info)
+#define SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT	_IOWR('S', 0x52, struct sndrv_seq_port_info)
+
+#endif /* __SOUND_ASEQUENCER_H */
diff --git a/include/sound/asound.h b/include/sound/asound.h
new file mode 100644
index 0000000..07c03fa
--- /dev/null
+++ b/include/sound/asound.h
@@ -0,0 +1,952 @@
+/*
+ *  Advanced Linux Sound Architecture - ALSA - Driver
+ *  Copyright (c) 1994-2003 by Jaroslav Kysela <perex@perex.cz>,
+ *                             Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#ifndef __SOUND_ASOUND_H
+#define __SOUND_ASOUND_H
+
+#if defined(LINUX) || defined(__LINUX__) || defined(__linux__)
+
+#include <linux/ioctl.h>
+#include <linux/types.h>
+
+#ifdef __KERNEL__
+
+#include <linux/time.h>
+#include <asm/byteorder.h>
+
+#if  __LITTLE_ENDIAN == 1234
+#define SNDRV_LITTLE_ENDIAN
+#elif __BIG_ENDIAN == 4321
+#define SNDRV_BIG_ENDIAN
+#else
+#error "Unsupported endian..."
+#endif
+
+#else /* !__KERNEL__ */
+
+#include <endian.h>
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+#define SNDRV_LITTLE_ENDIAN
+#elif __BYTE_ORDER == __BIG_ENDIAN
+#define SNDRV_BIG_ENDIAN
+#else
+#error "Unsupported endian..."
+#endif
+
+#endif /* __KERNEL **/
+
+#endif /* LINUX */
+
+#ifndef __KERNEL__
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#endif
+
+/*
+ *  protocol version
+ */
+
+#define SNDRV_PROTOCOL_VERSION(major, minor, subminor) (((major)<<16)|((minor)<<8)|(subminor))
+#define SNDRV_PROTOCOL_MAJOR(version) (((version)>>16)&0xffff)
+#define SNDRV_PROTOCOL_MINOR(version) (((version)>>8)&0xff)
+#define SNDRV_PROTOCOL_MICRO(version) ((version)&0xff)
+#define SNDRV_PROTOCOL_INCOMPATIBLE(kversion, uversion) \
+	(SNDRV_PROTOCOL_MAJOR(kversion) != SNDRV_PROTOCOL_MAJOR(uversion) || \
+	 (SNDRV_PROTOCOL_MAJOR(kversion) == SNDRV_PROTOCOL_MAJOR(uversion) && \
+	   SNDRV_PROTOCOL_MINOR(kversion) != SNDRV_PROTOCOL_MINOR(uversion)))
+
+/****************************************************************************
+ *                                                                          *
+ *        Digital audio interface					    *
+ *                                                                          *
+ ****************************************************************************/
+
+struct sndrv_aes_iec958 {
+	unsigned char status[24];	/* AES/IEC958 channel status bits */
+	unsigned char subcode[147];	/* AES/IEC958 subcode bits */
+	unsigned char pad;		/* nothing */
+	unsigned char dig_subframe[4];	/* AES/IEC958 subframe bits */
+};
+
+/****************************************************************************
+ *                                                                          *
+ *      Section for driver hardware dependent interface - /dev/snd/hw?      *
+ *                                                                          *
+ ****************************************************************************/
+
+#define SNDRV_HWDEP_VERSION		SNDRV_PROTOCOL_VERSION(1, 0, 1)
+
+enum sndrv_hwdep_iface {
+	SNDRV_HWDEP_IFACE_OPL2 = 0,
+	SNDRV_HWDEP_IFACE_OPL3,
+	SNDRV_HWDEP_IFACE_OPL4,
+	SNDRV_HWDEP_IFACE_SB16CSP,	/* Creative Signal Processor */
+	SNDRV_HWDEP_IFACE_EMU10K1,	/* FX8010 processor in EMU10K1 chip */
+	SNDRV_HWDEP_IFACE_YSS225,	/* Yamaha FX processor */
+	SNDRV_HWDEP_IFACE_ICS2115,	/* Wavetable synth */
+	SNDRV_HWDEP_IFACE_SSCAPE,	/* Ensoniq SoundScape ISA card (MC68EC000) */
+	SNDRV_HWDEP_IFACE_VX,		/* Digigram VX cards */
+	SNDRV_HWDEP_IFACE_MIXART,	/* Digigram miXart cards */
+	SNDRV_HWDEP_IFACE_USX2Y,	/* Tascam US122, US224 & US428 usb */
+	SNDRV_HWDEP_IFACE_EMUX_WAVETABLE, /* EmuX wavetable */	
+	SNDRV_HWDEP_IFACE_BLUETOOTH,	/* Bluetooth audio */
+	SNDRV_HWDEP_IFACE_USX2Y_PCM,	/* Tascam US122, US224 & US428 rawusb pcm */
+	SNDRV_HWDEP_IFACE_PCXHR,	/* Digigram PCXHR */
+	SNDRV_HWDEP_IFACE_SB_RC,	/* SB Extigy/Audigy2NX remote control */
+	SNDRV_HWDEP_IFACE_HDA,		/* HD-audio */
+	SNDRV_HWDEP_IFACE_USB_STREAM,	/* direct access to usb stream */
+
+	/* Don't forget to change the following: */
+	SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_USB_STREAM
+};
+
+struct sndrv_hwdep_info {
+	unsigned int device;		/* WR: device number */
+	int card;			/* R: card number */
+	unsigned char id[64];		/* ID (user selectable) */
+	unsigned char name[80];		/* hwdep name */
+	int iface;			/* hwdep interface */
+	unsigned char reserved[64];	/* reserved for future */
+};
+
+/* generic DSP loader */
+struct sndrv_hwdep_dsp_status {
+	unsigned int version;		/* R: driver-specific version */
+	unsigned char id[32];		/* R: driver-specific ID string */
+	unsigned int num_dsps;		/* R: number of DSP images to transfer */
+	unsigned int dsp_loaded;	/* R: bit flags indicating the loaded DSPs */
+	unsigned int chip_ready;	/* R: 1 = initialization finished */
+	unsigned char reserved[16];	/* reserved for future use */
+};
+
+struct sndrv_hwdep_dsp_image {
+	unsigned int index;		/* W: DSP index */
+	unsigned char name[64];		/* W: ID (e.g. file name) */
+	unsigned char *image;		/* W: binary image */
+	size_t length;			/* W: size of image in bytes */
+	unsigned long driver_data;	/* W: driver-specific data */
+};
+
+enum {
+	SNDRV_HWDEP_IOCTL_PVERSION = _IOR ('H', 0x00, int),
+	SNDRV_HWDEP_IOCTL_INFO = _IOR ('H', 0x01, struct sndrv_hwdep_info),
+	SNDRV_HWDEP_IOCTL_DSP_STATUS = _IOR('H', 0x02, struct sndrv_hwdep_dsp_status),
+	SNDRV_HWDEP_IOCTL_DSP_LOAD   = _IOW('H', 0x03, struct sndrv_hwdep_dsp_image)
+};
+
+/*****************************************************************************
+ *                                                                           *
+ *             Digital Audio (PCM) interface - /dev/snd/pcm??                *
+ *                                                                           *
+ *****************************************************************************/
+
+#define SNDRV_PCM_VERSION		SNDRV_PROTOCOL_VERSION(2, 0, 10)
+
+typedef unsigned long sndrv_pcm_uframes_t;
+typedef long sndrv_pcm_sframes_t;
+
+enum sndrv_pcm_class {
+	SNDRV_PCM_CLASS_GENERIC = 0,	/* standard mono or stereo device */
+	SNDRV_PCM_CLASS_MULTI,		/* multichannel device */
+	SNDRV_PCM_CLASS_MODEM,		/* software modem class */
+	SNDRV_PCM_CLASS_DIGITIZER,	/* digitizer class */
+	/* Don't forget to change the following: */
+	SNDRV_PCM_CLASS_LAST = SNDRV_PCM_CLASS_DIGITIZER,
+};
+
+enum sndrv_pcm_subclass {
+	SNDRV_PCM_SUBCLASS_GENERIC_MIX = 0, /* mono or stereo subdevices are mixed together */
+	SNDRV_PCM_SUBCLASS_MULTI_MIX,	/* multichannel subdevices are mixed together */
+	/* Don't forget to change the following: */
+	SNDRV_PCM_SUBCLASS_LAST = SNDRV_PCM_SUBCLASS_MULTI_MIX,
+};
+
+enum sndrv_pcm_stream {
+	SNDRV_PCM_STREAM_PLAYBACK = 0,
+	SNDRV_PCM_STREAM_CAPTURE,
+	SNDRV_PCM_STREAM_LAST = SNDRV_PCM_STREAM_CAPTURE,
+};
+
+enum sndrv_pcm_access {
+	SNDRV_PCM_ACCESS_MMAP_INTERLEAVED = 0,	/* interleaved mmap */
+	SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED, 	/* noninterleaved mmap */
+	SNDRV_PCM_ACCESS_MMAP_COMPLEX,		/* complex mmap */
+	SNDRV_PCM_ACCESS_RW_INTERLEAVED,	/* readi/writei */
+	SNDRV_PCM_ACCESS_RW_NONINTERLEAVED,	/* readn/writen */
+	SNDRV_PCM_ACCESS_LAST = SNDRV_PCM_ACCESS_RW_NONINTERLEAVED,
+};
+
+enum sndrv_pcm_format {
+	SNDRV_PCM_FORMAT_S8 = 0,
+	SNDRV_PCM_FORMAT_U8,
+	SNDRV_PCM_FORMAT_S16_LE,
+	SNDRV_PCM_FORMAT_S16_BE,
+	SNDRV_PCM_FORMAT_U16_LE,
+	SNDRV_PCM_FORMAT_U16_BE,
+	SNDRV_PCM_FORMAT_S24_LE,	/* low three bytes */
+	SNDRV_PCM_FORMAT_S24_BE,	/* low three bytes */
+	SNDRV_PCM_FORMAT_U24_LE,	/* low three bytes */
+	SNDRV_PCM_FORMAT_U24_BE,	/* low three bytes */
+	SNDRV_PCM_FORMAT_S32_LE,
+	SNDRV_PCM_FORMAT_S32_BE,
+	SNDRV_PCM_FORMAT_U32_LE,
+	SNDRV_PCM_FORMAT_U32_BE,
+	SNDRV_PCM_FORMAT_FLOAT_LE,	/* 4-byte float, IEEE-754 32-bit, range -1.0 to 1.0 */
+	SNDRV_PCM_FORMAT_FLOAT_BE,	/* 4-byte float, IEEE-754 32-bit, range -1.0 to 1.0 */
+	SNDRV_PCM_FORMAT_FLOAT64_LE,	/* 8-byte float, IEEE-754 64-bit, range -1.0 to 1.0 */
+	SNDRV_PCM_FORMAT_FLOAT64_BE,	/* 8-byte float, IEEE-754 64-bit, range -1.0 to 1.0 */
+	SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE,	/* IEC-958 subframe, Little Endian */
+	SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE,	/* IEC-958 subframe, Big Endian */
+	SNDRV_PCM_FORMAT_MU_LAW,
+	SNDRV_PCM_FORMAT_A_LAW,
+	SNDRV_PCM_FORMAT_IMA_ADPCM,
+	SNDRV_PCM_FORMAT_MPEG,
+	SNDRV_PCM_FORMAT_GSM,
+	SNDRV_PCM_FORMAT_SPECIAL = 31,
+	SNDRV_PCM_FORMAT_S24_3LE = 32,	/* in three bytes */
+	SNDRV_PCM_FORMAT_S24_3BE,	/* in three bytes */
+	SNDRV_PCM_FORMAT_U24_3LE,	/* in three bytes */
+	SNDRV_PCM_FORMAT_U24_3BE,	/* in three bytes */
+	SNDRV_PCM_FORMAT_S20_3LE,	/* in three bytes */
+	SNDRV_PCM_FORMAT_S20_3BE,	/* in three bytes */
+	SNDRV_PCM_FORMAT_U20_3LE,	/* in three bytes */
+	SNDRV_PCM_FORMAT_U20_3BE,	/* in three bytes */
+	SNDRV_PCM_FORMAT_S18_3LE,	/* in three bytes */
+	SNDRV_PCM_FORMAT_S18_3BE,	/* in three bytes */
+	SNDRV_PCM_FORMAT_U18_3LE,	/* in three bytes */
+	SNDRV_PCM_FORMAT_U18_3BE,	/* in three bytes */
+	SNDRV_PCM_FORMAT_G723_24,	/* 8 samples in 3 bytes */
+	SNDRV_PCM_FORMAT_G723_24_1B,	/* 1 sample in 1 byte */
+	SNDRV_PCM_FORMAT_G723_40,	/* 8 Samples in 5 bytes */
+	SNDRV_PCM_FORMAT_G723_40_1B,	/* 1 sample in 1 byte */
+	SNDRV_PCM_FORMAT_LAST = SNDRV_PCM_FORMAT_G723_40_1B,
+
+#ifdef SNDRV_LITTLE_ENDIAN
+	SNDRV_PCM_FORMAT_S16 = SNDRV_PCM_FORMAT_S16_LE,
+	SNDRV_PCM_FORMAT_U16 = SNDRV_PCM_FORMAT_U16_LE,
+	SNDRV_PCM_FORMAT_S24 = SNDRV_PCM_FORMAT_S24_LE,
+	SNDRV_PCM_FORMAT_U24 = SNDRV_PCM_FORMAT_U24_LE,
+	SNDRV_PCM_FORMAT_S32 = SNDRV_PCM_FORMAT_S32_LE,
+	SNDRV_PCM_FORMAT_U32 = SNDRV_PCM_FORMAT_U32_LE,
+	SNDRV_PCM_FORMAT_FLOAT = SNDRV_PCM_FORMAT_FLOAT_LE,
+	SNDRV_PCM_FORMAT_FLOAT64 = SNDRV_PCM_FORMAT_FLOAT64_LE,
+	SNDRV_PCM_FORMAT_IEC958_SUBFRAME = SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE,
+#endif
+#ifdef SNDRV_BIG_ENDIAN
+	SNDRV_PCM_FORMAT_S16 = SNDRV_PCM_FORMAT_S16_BE,
+	SNDRV_PCM_FORMAT_U16 = SNDRV_PCM_FORMAT_U16_BE,
+	SNDRV_PCM_FORMAT_S24 = SNDRV_PCM_FORMAT_S24_BE,
+	SNDRV_PCM_FORMAT_U24 = SNDRV_PCM_FORMAT_U24_BE,
+	SNDRV_PCM_FORMAT_S32 = SNDRV_PCM_FORMAT_S32_BE,
+	SNDRV_PCM_FORMAT_U32 = SNDRV_PCM_FORMAT_U32_BE,
+	SNDRV_PCM_FORMAT_FLOAT = SNDRV_PCM_FORMAT_FLOAT_BE,
+	SNDRV_PCM_FORMAT_FLOAT64 = SNDRV_PCM_FORMAT_FLOAT64_BE,
+	SNDRV_PCM_FORMAT_IEC958_SUBFRAME = SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE,
+#endif
+};
+
+enum sndrv_pcm_subformat {
+	SNDRV_PCM_SUBFORMAT_STD = 0,
+	SNDRV_PCM_SUBFORMAT_LAST = SNDRV_PCM_SUBFORMAT_STD,
+};
+
+#define SNDRV_PCM_INFO_MMAP		0x00000001	/* hardware supports mmap */
+#define SNDRV_PCM_INFO_MMAP_VALID	0x00000002	/* period data are valid during transfer */
+#define SNDRV_PCM_INFO_DOUBLE		0x00000004	/* Double buffering needed for PCM start/stop */
+#define SNDRV_PCM_INFO_BATCH		0x00000010	/* double buffering */
+#define SNDRV_PCM_INFO_INTERLEAVED	0x00000100	/* channels are interleaved */
+#define SNDRV_PCM_INFO_NONINTERLEAVED	0x00000200	/* channels are not interleaved */
+#define SNDRV_PCM_INFO_COMPLEX		0x00000400	/* complex frame organization (mmap only) */
+#define SNDRV_PCM_INFO_BLOCK_TRANSFER	0x00010000	/* hardware transfer block of samples */
+#define SNDRV_PCM_INFO_OVERRANGE	0x00020000	/* hardware supports ADC (capture) overrange detection */
+#define SNDRV_PCM_INFO_RESUME		0x00040000	/* hardware supports stream resume after suspend */
+#define SNDRV_PCM_INFO_PAUSE		0x00080000	/* pause ioctl is supported */
+#define SNDRV_PCM_INFO_HALF_DUPLEX	0x00100000	/* only half duplex */
+#define SNDRV_PCM_INFO_JOINT_DUPLEX	0x00200000	/* playback and capture stream are somewhat correlated */
+#define SNDRV_PCM_INFO_SYNC_START	0x00400000	/* pcm support some kind of sync go */
+#define SNDRV_PCM_INFO_NO_PERIOD_WAKEUP	0x00800000	/* period wakeup can be disabled */
+
+enum sndrv_pcm_state {
+	SNDRV_PCM_STATE_OPEN = 0,	/* stream is open */
+	SNDRV_PCM_STATE_SETUP,		/* stream has a setup */
+	SNDRV_PCM_STATE_PREPARED,	/* stream is ready to start */
+	SNDRV_PCM_STATE_RUNNING,	/* stream is running */
+	SNDRV_PCM_STATE_XRUN,		/* stream reached an xrun */
+	SNDRV_PCM_STATE_DRAINING,	/* stream is draining */
+	SNDRV_PCM_STATE_PAUSED,		/* stream is paused */
+	SNDRV_PCM_STATE_SUSPENDED,	/* hardware is suspended */
+	SNDRV_PCM_STATE_DISCONNECTED,	/* hardware is disconnected */
+	SNDRV_PCM_STATE_LAST = SNDRV_PCM_STATE_DISCONNECTED,
+};
+
+enum {
+	SNDRV_PCM_MMAP_OFFSET_DATA = 0x00000000,
+	SNDRV_PCM_MMAP_OFFSET_STATUS = 0x80000000,
+	SNDRV_PCM_MMAP_OFFSET_CONTROL = 0x81000000,
+};
+
+union sndrv_pcm_sync_id {
+	unsigned char id[16];
+	unsigned short id16[8];
+	unsigned int id32[4];
+};
+
+struct sndrv_pcm_info {
+	unsigned int device;		/* RO/WR (control): device number */
+	unsigned int subdevice;		/* RO/WR (control): subdevice number */
+	int stream;			/* RO/WR (control): stream direction */
+	int card;			/* R: card number */
+	unsigned char id[64];		/* ID (user selectable) */
+	unsigned char name[80];		/* name of this device */
+	unsigned char subname[32];	/* subdevice name */
+	int dev_class;			/* SNDRV_PCM_CLASS_* */
+	int dev_subclass;		/* SNDRV_PCM_SUBCLASS_* */
+	unsigned int subdevices_count;
+	unsigned int subdevices_avail;
+	union sndrv_pcm_sync_id sync;	/* hardware synchronization ID */
+	unsigned char reserved[64];	/* reserved for future... */
+};
+
+enum sndrv_pcm_hw_param {
+	SNDRV_PCM_HW_PARAM_ACCESS = 0,	/* Access type */
+	SNDRV_PCM_HW_PARAM_FIRST_MASK = SNDRV_PCM_HW_PARAM_ACCESS,
+	SNDRV_PCM_HW_PARAM_FORMAT,	/* Format */
+	SNDRV_PCM_HW_PARAM_SUBFORMAT,	/* Subformat */
+	SNDRV_PCM_HW_PARAM_LAST_MASK = SNDRV_PCM_HW_PARAM_SUBFORMAT,
+
+	SNDRV_PCM_HW_PARAM_SAMPLE_BITS = 8, /* Bits per sample */
+	SNDRV_PCM_HW_PARAM_FIRST_INTERVAL = SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
+	SNDRV_PCM_HW_PARAM_FRAME_BITS,	/* Bits per frame */
+	SNDRV_PCM_HW_PARAM_CHANNELS,	/* Channels */
+	SNDRV_PCM_HW_PARAM_RATE,	/* Approx rate */
+	SNDRV_PCM_HW_PARAM_PERIOD_TIME,	/* Approx distance between interrupts
+					   in us */
+	SNDRV_PCM_HW_PARAM_PERIOD_SIZE,	/* Approx frames between interrupts */
+	SNDRV_PCM_HW_PARAM_PERIOD_BYTES, /* Approx bytes between interrupts */
+	SNDRV_PCM_HW_PARAM_PERIODS,	/* Approx interrupts per buffer */
+	SNDRV_PCM_HW_PARAM_BUFFER_TIME,	/* Approx duration of buffer in us */
+	SNDRV_PCM_HW_PARAM_BUFFER_SIZE,	/* Size of buffer in frames */
+	SNDRV_PCM_HW_PARAM_BUFFER_BYTES, /* Size of buffer in bytes */
+	SNDRV_PCM_HW_PARAM_TICK_TIME,	/* Approx tick duration in us */
+	SNDRV_PCM_HW_PARAM_LAST_INTERVAL = SNDRV_PCM_HW_PARAM_TICK_TIME
+};
+
+#define SNDRV_PCM_HW_PARAMS_NORESAMPLE	(1<<0)	/* avoid rate resampling */
+#define SNDRV_PCM_HW_PARAMS_EXPORT_BUFFER	(1<<1)	/* export buffer */
+#define SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP	(1<<2)	/* disable period wakeups */
+
+struct sndrv_interval {
+	unsigned int min, max;
+	unsigned int openmin:1,
+		     openmax:1,
+		     integer:1,
+		     empty:1;
+};
+
+#define SNDRV_MASK_MAX	256
+
+struct sndrv_mask {
+	u_int32_t bits[(SNDRV_MASK_MAX+31)/32];
+};
+
+struct sndrv_pcm_hw_params {
+	unsigned int flags;
+	struct sndrv_mask masks[SNDRV_PCM_HW_PARAM_LAST_MASK - 
+			       SNDRV_PCM_HW_PARAM_FIRST_MASK + 1];
+	struct sndrv_mask mres[5];	/* reserved masks */
+	struct sndrv_interval intervals[SNDRV_PCM_HW_PARAM_LAST_INTERVAL -
+				        SNDRV_PCM_HW_PARAM_FIRST_INTERVAL + 1];
+	struct sndrv_interval ires[9];	/* reserved intervals */
+	unsigned int rmask;		/* W: requested masks */
+	unsigned int cmask;		/* R: changed masks */
+	unsigned int info;		/* R: Info flags for returned setup */
+	unsigned int msbits;		/* R: used most significant bits */
+	unsigned int rate_num;		/* R: rate numerator */
+	unsigned int rate_den;		/* R: rate denominator */
+	sndrv_pcm_uframes_t fifo_size;	/* R: chip FIFO size in frames */
+	unsigned char reserved[64];	/* reserved for future */
+};
+
+enum sndrv_pcm_tstamp {
+	SNDRV_PCM_TSTAMP_NONE = 0,
+	SNDRV_PCM_TSTAMP_ENABLE,
+	SNDRV_PCM_TSTAMP_LAST = SNDRV_PCM_TSTAMP_ENABLE,
+};
+
+struct sndrv_pcm_sw_params {
+	int tstamp_mode;			/* timestamp mode */
+	unsigned int period_step;
+	unsigned int sleep_min;			/* min ticks to sleep */
+	sndrv_pcm_uframes_t avail_min;		/* min avail frames for wakeup */
+	sndrv_pcm_uframes_t xfer_align;		/* xfer size need to be a multiple */
+	sndrv_pcm_uframes_t start_threshold;	/* min hw_avail frames for automatic start */
+	sndrv_pcm_uframes_t stop_threshold;	/* min avail frames for automatic stop */
+	sndrv_pcm_uframes_t silence_threshold;	/* min distance from noise for silence filling */
+	sndrv_pcm_uframes_t silence_size;	/* silence block size */
+	sndrv_pcm_uframes_t boundary;		/* pointers wrap point */
+	unsigned char reserved[60];		/* reserved for future */
+	unsigned int period_event;		/* for alsa-lib implementation */
+};
+
+struct sndrv_pcm_channel_info {
+	unsigned int channel;
+	long offset;			/* mmap offset */
+	unsigned int first;		/* offset to first sample in bits */
+	unsigned int step;		/* samples distance in bits */
+};
+
+struct sndrv_pcm_status {
+	int state;			/* stream state */
+	struct timespec trigger_tstamp;	/* time when stream was started/stopped/paused */
+	struct timespec tstamp;		/* reference timestamp */
+	sndrv_pcm_uframes_t appl_ptr;	/* appl ptr */
+	sndrv_pcm_uframes_t hw_ptr;	/* hw ptr */
+	sndrv_pcm_sframes_t delay;	/* current delay in frames */
+	sndrv_pcm_uframes_t avail;	/* number of frames available */
+	sndrv_pcm_uframes_t avail_max;	/* max frames available on hw since last status */
+	sndrv_pcm_uframes_t overrange;	/* count of ADC (capture) overrange detections from last status */
+	int suspended_state;		/* suspended stream state */
+	unsigned char reserved[60];	/* must be filled with zero */
+};
+
+struct sndrv_pcm_mmap_status {
+	int state;			/* RO: state - SNDRV_PCM_STATE_XXXX */
+	int pad1;			/* Needed for 64 bit alignment */
+	sndrv_pcm_uframes_t hw_ptr;	/* RO: hw ptr (0...boundary-1) */
+	struct timespec tstamp;		/* Timestamp */
+	int suspended_state;		/* RO: suspended stream state */
+};
+
+struct sndrv_pcm_mmap_control {
+	sndrv_pcm_uframes_t appl_ptr;	/* RW: appl ptr (0...boundary-1) */
+	sndrv_pcm_uframes_t avail_min;	/* RW: min available frames for wakeup */
+};
+
+#define SNDRV_PCM_SYNC_PTR_HWSYNC	(1<<0)	/* execute hwsync */
+#define SNDRV_PCM_SYNC_PTR_APPL		(1<<1)	/* get appl_ptr from driver (r/w op) */
+#define SNDRV_PCM_SYNC_PTR_AVAIL_MIN	(1<<2)	/* get avail_min from driver */
+
+struct sndrv_pcm_sync_ptr {
+	unsigned int flags;
+	union {
+		struct sndrv_pcm_mmap_status status;
+		unsigned char reserved[64];
+	} s;
+	union {
+		struct sndrv_pcm_mmap_control control;
+		unsigned char reserved[64];
+	} c;
+};
+
+struct sndrv_xferi {
+	sndrv_pcm_sframes_t result;
+	void *buf;
+	sndrv_pcm_uframes_t frames;
+};
+
+struct sndrv_xfern {
+	sndrv_pcm_sframes_t result;
+	void **bufs;
+	sndrv_pcm_uframes_t frames;
+};
+
+
+enum {
+	SNDRV_PCM_TSTAMP_TYPE_GETTIMEOFDAY = 0,	/* gettimeofday equivalent */
+	SNDRV_PCM_TSTAMP_TYPE_MONOTONIC,	/* posix_clock_monotonic equivalent */
+	SNDRV_PCM_TSTAMP_TYPE_LAST = SNDRV_PCM_TSTAMP_TYPE_MONOTONIC,
+};
+
+enum {
+	SNDRV_PCM_IOCTL_PVERSION = _IOR('A', 0x00, int),
+	SNDRV_PCM_IOCTL_INFO = _IOR('A', 0x01, struct sndrv_pcm_info),
+	SNDRV_PCM_IOCTL_TSTAMP = _IOW('A', 0x02, int),
+	SNDRV_PCM_IOCTL_TTSTAMP = _IOW('A', 0x03, int),
+	SNDRV_PCM_IOCTL_HW_REFINE = _IOWR('A', 0x10, struct sndrv_pcm_hw_params),
+	SNDRV_PCM_IOCTL_HW_PARAMS = _IOWR('A', 0x11, struct sndrv_pcm_hw_params),
+	SNDRV_PCM_IOCTL_HW_FREE = _IO('A', 0x12),
+	SNDRV_PCM_IOCTL_SW_PARAMS = _IOWR('A', 0x13, struct sndrv_pcm_sw_params),
+	SNDRV_PCM_IOCTL_STATUS = _IOR('A', 0x20, struct sndrv_pcm_status),
+	SNDRV_PCM_IOCTL_DELAY = _IOR('A', 0x21, sndrv_pcm_sframes_t),
+	SNDRV_PCM_IOCTL_HWSYNC = _IO('A', 0x22),
+	SNDRV_PCM_IOCTL_SYNC_PTR = _IOWR('A', 0x23, struct sndrv_pcm_sync_ptr),
+	SNDRV_PCM_IOCTL_CHANNEL_INFO = _IOR('A', 0x32, struct sndrv_pcm_channel_info),
+	SNDRV_PCM_IOCTL_PREPARE = _IO('A', 0x40),
+	SNDRV_PCM_IOCTL_RESET = _IO('A', 0x41),
+	SNDRV_PCM_IOCTL_START = _IO('A', 0x42),
+	SNDRV_PCM_IOCTL_DROP = _IO('A', 0x43),
+	SNDRV_PCM_IOCTL_DRAIN = _IO('A', 0x44),
+	SNDRV_PCM_IOCTL_PAUSE = _IOW('A', 0x45, int),
+	SNDRV_PCM_IOCTL_REWIND = _IOW('A', 0x46, sndrv_pcm_uframes_t),
+	SNDRV_PCM_IOCTL_RESUME = _IO('A', 0x47),
+	SNDRV_PCM_IOCTL_XRUN = _IO('A', 0x48),
+	SNDRV_PCM_IOCTL_FORWARD = _IOW('A', 0x49, sndrv_pcm_uframes_t),
+	SNDRV_PCM_IOCTL_WRITEI_FRAMES = _IOW('A', 0x50, struct sndrv_xferi),
+	SNDRV_PCM_IOCTL_READI_FRAMES = _IOR('A', 0x51, struct sndrv_xferi),
+	SNDRV_PCM_IOCTL_WRITEN_FRAMES = _IOW('A', 0x52, struct sndrv_xfern),
+	SNDRV_PCM_IOCTL_READN_FRAMES = _IOR('A', 0x53, struct sndrv_xfern),
+	SNDRV_PCM_IOCTL_LINK = _IOW('A', 0x60, int),
+	SNDRV_PCM_IOCTL_UNLINK = _IO('A', 0x61),
+};
+
+/* Trick to make alsa-lib/acinclude.m4 happy */
+#define SNDRV_PCM_IOCTL_REWIND SNDRV_PCM_IOCTL_REWIND
+
+/*****************************************************************************
+ *                                                                           *
+ *                            MIDI v1.0 interface                            *
+ *                                                                           *
+ *****************************************************************************/
+
+/*
+ *  Raw MIDI section - /dev/snd/midi??
+ */
+
+#define SNDRV_RAWMIDI_VERSION		SNDRV_PROTOCOL_VERSION(2, 0, 0)
+
+enum sndrv_rawmidi_stream {
+	SNDRV_RAWMIDI_STREAM_OUTPUT = 0,
+	SNDRV_RAWMIDI_STREAM_INPUT,
+	SNDRV_RAWMIDI_STREAM_LAST = SNDRV_RAWMIDI_STREAM_INPUT,
+};
+
+#define SNDRV_RAWMIDI_INFO_OUTPUT		0x00000001
+#define SNDRV_RAWMIDI_INFO_INPUT		0x00000002
+#define SNDRV_RAWMIDI_INFO_DUPLEX		0x00000004
+
+struct sndrv_rawmidi_info {
+	unsigned int device;		/* RO/WR (control): device number */
+	unsigned int subdevice;		/* RO/WR (control): subdevice number */
+	int stream;			/* WR: stream */
+	int card;			/* R: card number */
+	unsigned int flags;		/* SNDRV_RAWMIDI_INFO_XXXX */
+	unsigned char id[64];		/* ID (user selectable) */
+	unsigned char name[80];		/* name of device */
+	unsigned char subname[32];	/* name of active or selected subdevice */
+	unsigned int subdevices_count;
+	unsigned int subdevices_avail;
+	unsigned char reserved[64];	/* reserved for future use */
+};
+
+struct sndrv_rawmidi_params {
+	int stream;
+	size_t buffer_size;		/* queue size in bytes */
+	size_t avail_min;		/* minimum avail bytes for wakeup */
+	unsigned int no_active_sensing: 1; /* do not send active sensing byte in close() */
+	unsigned char reserved[16];	/* reserved for future use */
+};
+
+struct sndrv_rawmidi_status {
+	int stream;
+	struct timespec tstamp;		/* Timestamp */
+	size_t avail;			/* available bytes */
+	size_t xruns;			/* count of overruns since last status (in bytes) */
+	unsigned char reserved[16];	/* reserved for future use */
+};
+
+enum {
+	SNDRV_RAWMIDI_IOCTL_PVERSION = _IOR('W', 0x00, int),
+	SNDRV_RAWMIDI_IOCTL_INFO = _IOR('W', 0x01, struct sndrv_rawmidi_info),
+	SNDRV_RAWMIDI_IOCTL_PARAMS = _IOWR('W', 0x10, struct sndrv_rawmidi_params),
+	SNDRV_RAWMIDI_IOCTL_STATUS = _IOWR('W', 0x20, struct sndrv_rawmidi_status),
+	SNDRV_RAWMIDI_IOCTL_DROP = _IOW('W', 0x30, int),
+	SNDRV_RAWMIDI_IOCTL_DRAIN = _IOW('W', 0x31, int),
+};
+
+/*
+ *  Timer section - /dev/snd/timer
+ */
+
+#define SNDRV_TIMER_VERSION		SNDRV_PROTOCOL_VERSION(2, 0, 6)
+
+enum sndrv_timer_class {
+	SNDRV_TIMER_CLASS_NONE = -1,
+	SNDRV_TIMER_CLASS_SLAVE = 0,
+	SNDRV_TIMER_CLASS_GLOBAL,
+	SNDRV_TIMER_CLASS_CARD,
+	SNDRV_TIMER_CLASS_PCM,
+	SNDRV_TIMER_CLASS_LAST = SNDRV_TIMER_CLASS_PCM,
+};
+
+/* slave timer classes */
+enum sndrv_timer_slave_class {
+	SNDRV_TIMER_SCLASS_NONE = 0,
+	SNDRV_TIMER_SCLASS_APPLICATION,
+	SNDRV_TIMER_SCLASS_SEQUENCER,		/* alias */
+	SNDRV_TIMER_SCLASS_OSS_SEQUENCER,	/* alias */
+	SNDRV_TIMER_SCLASS_LAST = SNDRV_TIMER_SCLASS_OSS_SEQUENCER,
+};
+
+/* global timers (device member) */
+#define SNDRV_TIMER_GLOBAL_SYSTEM	0
+#define SNDRV_TIMER_GLOBAL_RTC		1
+#define SNDRV_TIMER_GLOBAL_HPET		2
+#define SNDRV_TIMER_GLOBAL_HRTIMER	3
+
+/* info flags */
+#define SNDRV_TIMER_FLG_SLAVE		(1<<0)	/* cannot be controlled */
+
+struct sndrv_timer_id {
+	int dev_class;
+	int dev_sclass;
+	int card;
+	int device;
+	int subdevice;
+};
+
+struct sndrv_timer_ginfo {
+	struct sndrv_timer_id tid;	/* requested timer ID */
+	unsigned int flags;		/* timer flags - SNDRV_TIMER_FLG_* */
+	int card;			/* card number */
+	unsigned char id[64];		/* timer identification */
+	unsigned char name[80];		/* timer name */
+	unsigned long reserved0;	/* reserved for future use */
+	unsigned long resolution;	/* average period resolution in ns */
+	unsigned long resolution_min;	/* minimal period resolution in ns */
+	unsigned long resolution_max;	/* maximal period resolution in ns */
+	unsigned int clients;		/* active timer clients */
+	unsigned char reserved[32];
+};
+
+struct sndrv_timer_gparams {
+	struct sndrv_timer_id tid;	/* requested timer ID */
+	unsigned long period_num;	/* requested precise period duration (in seconds) - numerator */
+	unsigned long period_den;	/* requested precise period duration (in seconds) - denominator */
+	unsigned char reserved[32];
+};
+
+struct sndrv_timer_gstatus {
+	struct sndrv_timer_id tid;	/* requested timer ID */
+	unsigned long resolution;	/* current period resolution in ns */
+	unsigned long resolution_num;	/* precise current period resolution (in seconds) - numerator */
+	unsigned long resolution_den;	/* precise current period resolution (in seconds) - denominator */
+	unsigned char reserved[32];
+};
+
+struct sndrv_timer_select {
+	struct sndrv_timer_id id;	/* bind to timer ID */
+	unsigned char reserved[32];	/* reserved */
+};
+
+struct sndrv_timer_info {
+	unsigned int flags;		/* timer flags - SNDRV_TIMER_FLG_* */
+	int card;			/* card number */
+	unsigned char id[64];		/* timer identificator */
+	unsigned char name[80];		/* timer name */
+	unsigned long reserved0;	/* reserved for future use */
+	unsigned long resolution;	/* average period resolution in ns */
+	unsigned char reserved[64];	/* reserved */
+};
+
+#define SNDRV_TIMER_PSFLG_AUTO		(1<<0)	/* auto start, otherwise one-shot */
+#define SNDRV_TIMER_PSFLG_EXCLUSIVE	(1<<1)	/* exclusive use, precise start/stop/pause/continue */
+#define SNDRV_TIMER_PSFLG_EARLY_EVENT	(1<<2)	/* write early event to the poll queue */
+
+struct sndrv_timer_params {
+	unsigned int flags;		/* flags - SNDRV_MIXER_PSFLG_* */
+	unsigned int ticks;		/* requested resolution in ticks */
+	unsigned int queue_size;	/* total size of queue (32-1024) */
+	unsigned int reserved0;		/* reserved, was: failure locations */
+	unsigned int filter;		/* event filter (bitmask of SNDRV_TIMER_EVENT_*) */
+	unsigned char reserved[60];	/* reserved */
+};
+
+struct sndrv_timer_status {
+	struct timespec tstamp;		/* Timestamp - last update */
+	unsigned int resolution;	/* current period resolution in ns */
+	unsigned int lost;		/* counter of master tick lost */
+	unsigned int overrun;		/* count of read queue overruns */
+	unsigned int queue;		/* used queue size */
+	unsigned char reserved[64];	/* reserved */
+};
+
+enum {
+	SNDRV_TIMER_IOCTL_PVERSION = _IOR('T', 0x00, int),
+	SNDRV_TIMER_IOCTL_NEXT_DEVICE = _IOWR('T', 0x01, struct sndrv_timer_id),
+	SNDRV_TIMER_IOCTL_TREAD = _IOW('T', 0x02, int),
+	SNDRV_TIMER_IOCTL_GINFO = _IOWR('T', 0x03, struct sndrv_timer_ginfo),
+	SNDRV_TIMER_IOCTL_GPARAMS = _IOW('T', 0x04, struct sndrv_timer_gparams),
+	SNDRV_TIMER_IOCTL_GSTATUS = _IOWR('T', 0x05, struct sndrv_timer_gstatus),
+	SNDRV_TIMER_IOCTL_SELECT = _IOW('T', 0x10, struct sndrv_timer_select),
+	SNDRV_TIMER_IOCTL_INFO = _IOR('T', 0x11, struct sndrv_timer_info),
+	SNDRV_TIMER_IOCTL_PARAMS = _IOW('T', 0x12, struct sndrv_timer_params),
+	SNDRV_TIMER_IOCTL_STATUS = _IOR('T', 0x14, struct sndrv_timer_status),
+	/* The following four ioctls are changed since 1.0.9 due to confliction */
+	SNDRV_TIMER_IOCTL_START = _IO('T', 0xa0),
+	SNDRV_TIMER_IOCTL_STOP = _IO('T', 0xa1),
+	SNDRV_TIMER_IOCTL_CONTINUE = _IO('T', 0xa2),
+	SNDRV_TIMER_IOCTL_PAUSE = _IO('T', 0xa3),
+};
+
+struct sndrv_timer_read {
+	unsigned int resolution;
+	unsigned int ticks;
+};
+
+enum sndrv_timer_event {
+	SNDRV_TIMER_EVENT_RESOLUTION = 0,	/* val = resolution in ns */
+	SNDRV_TIMER_EVENT_TICK,			/* val = ticks */
+	SNDRV_TIMER_EVENT_START,		/* val = resolution in ns */
+	SNDRV_TIMER_EVENT_STOP,			/* val = 0 */
+	SNDRV_TIMER_EVENT_CONTINUE,		/* val = resolution in ns */
+	SNDRV_TIMER_EVENT_PAUSE,		/* val = 0 */
+	SNDRV_TIMER_EVENT_EARLY,		/* val = 0, early event */
+	SNDRV_TIMER_EVENT_SUSPEND,		/* val = 0 */
+	SNDRV_TIMER_EVENT_RESUME,		/* val = resolution in ns */
+	/* master timer events for slave timer instances */
+	SNDRV_TIMER_EVENT_MSTART = SNDRV_TIMER_EVENT_START + 10,
+	SNDRV_TIMER_EVENT_MSTOP = SNDRV_TIMER_EVENT_STOP + 10,
+	SNDRV_TIMER_EVENT_MCONTINUE = SNDRV_TIMER_EVENT_CONTINUE + 10,
+	SNDRV_TIMER_EVENT_MPAUSE = SNDRV_TIMER_EVENT_PAUSE + 10,
+	SNDRV_TIMER_EVENT_MSUSPEND = SNDRV_TIMER_EVENT_SUSPEND + 10,
+	SNDRV_TIMER_EVENT_MRESUME = SNDRV_TIMER_EVENT_RESUME + 10,
+};
+
+struct sndrv_timer_tread {
+	int event;
+	struct timespec tstamp;
+	unsigned int val;
+};
+
+/****************************************************************************
+ *                                                                          *
+ *        Section for driver control interface - /dev/snd/control?          *
+ *                                                                          *
+ ****************************************************************************/
+
+#define SNDRV_CTL_VERSION		SNDRV_PROTOCOL_VERSION(2, 0, 7)
+
+struct sndrv_ctl_card_info {
+	int card;			/* card number */
+	int pad;			/* reserved for future (was type) */
+	unsigned char id[16];		/* ID of card (user selectable) */
+	unsigned char driver[16];	/* Driver name */
+	unsigned char name[32];		/* Short name of soundcard */
+	unsigned char longname[80];	/* name + info text about soundcard */
+	unsigned char reserved_[16];	/* reserved for future (was ID of mixer) */
+	unsigned char mixername[80];	/* visual mixer identification */
+	unsigned char components[128];	/* card components / fine identification, delimited with one space (AC97 etc..) */
+};
+
+enum sndrv_ctl_elem_type {
+	SNDRV_CTL_ELEM_TYPE_NONE = 0,		/* invalid */
+	SNDRV_CTL_ELEM_TYPE_BOOLEAN,		/* boolean type */
+	SNDRV_CTL_ELEM_TYPE_INTEGER,		/* integer type */
+	SNDRV_CTL_ELEM_TYPE_ENUMERATED,		/* enumerated type */
+	SNDRV_CTL_ELEM_TYPE_BYTES,		/* byte array */
+	SNDRV_CTL_ELEM_TYPE_IEC958,		/* IEC958 (S/PDIF) setup */
+	SNDRV_CTL_ELEM_TYPE_INTEGER64,		/* 64-bit integer type */
+	SNDRV_CTL_ELEM_TYPE_LAST = SNDRV_CTL_ELEM_TYPE_INTEGER64,
+};
+
+enum sndrv_ctl_elem_iface {
+	SNDRV_CTL_ELEM_IFACE_CARD = 0,		/* global control */
+	SNDRV_CTL_ELEM_IFACE_HWDEP,		/* hardware dependent device */
+	SNDRV_CTL_ELEM_IFACE_MIXER,		/* virtual mixer device */
+	SNDRV_CTL_ELEM_IFACE_PCM,		/* PCM device */
+	SNDRV_CTL_ELEM_IFACE_RAWMIDI,		/* RawMidi device */
+	SNDRV_CTL_ELEM_IFACE_TIMER,		/* timer device */
+	SNDRV_CTL_ELEM_IFACE_SEQUENCER,		/* sequencer client */
+	SNDRV_CTL_ELEM_IFACE_LAST = SNDRV_CTL_ELEM_IFACE_SEQUENCER,
+};
+
+#define SNDRV_CTL_ELEM_ACCESS_READ		(1<<0)
+#define SNDRV_CTL_ELEM_ACCESS_WRITE		(1<<1)
+#define SNDRV_CTL_ELEM_ACCESS_READWRITE		(SNDRV_CTL_ELEM_ACCESS_READ|SNDRV_CTL_ELEM_ACCESS_WRITE)
+#define SNDRV_CTL_ELEM_ACCESS_VOLATILE		(1<<2)	/* control value may be changed without a notification */
+#define SNDRV_CTL_ELEM_ACCESS_TIMESTAMP		(1<<3)	/* when was control changed */
+#define SNDRV_CTL_ELEM_ACCESS_TLV_READ		(1<<4)	/* TLV read is possible */
+#define SNDRV_CTL_ELEM_ACCESS_TLV_WRITE		(1<<5)	/* TLV write is possible */
+#define SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE	(SNDRV_CTL_ELEM_ACCESS_TLV_READ|SNDRV_CTL_ELEM_ACCESS_TLV_WRITE)
+#define SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND	(1<<6)	/* TLV command is possible */
+#define SNDRV_CTL_ELEM_ACCESS_INACTIVE		(1<<8)	/* control does actually nothing, but may be updated */
+#define SNDRV_CTL_ELEM_ACCESS_LOCK		(1<<9)	/* write lock */
+#define SNDRV_CTL_ELEM_ACCESS_OWNER		(1<<10)	/* write lock owner */
+#define SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK	(1<<28)	/* kernel use a TLV callback */ 
+#define SNDRV_CTL_ELEM_ACCESS_USER		(1<<29) /* user space element */
+/* bits 30 and 31 are obsoleted (for indirect access) */
+
+/* for further details see the ACPI and PCI power management specification */
+#define SNDRV_CTL_POWER_D0		0x0000	/* full On */
+#define SNDRV_CTL_POWER_D1		0x0100	/* partial On */
+#define SNDRV_CTL_POWER_D2		0x0200	/* partial On */
+#define SNDRV_CTL_POWER_D3		0x0300	/* Off */
+#define SNDRV_CTL_POWER_D3hot		(SNDRV_CTL_POWER_D3|0x0000)	/* Off, with power */
+#define SNDRV_CTL_POWER_D3cold		(SNDRV_CTL_POWER_D3|0x0001)	/* Off, without power */
+
+struct sndrv_ctl_elem_id {
+	unsigned int numid;		/* numeric identifier, zero = invalid */
+	int iface;			/* interface identifier */
+	unsigned int device;		/* device/client number */
+	unsigned int subdevice;		/* subdevice (substream) number */
+	unsigned char name[44];		/* ASCII name of item */
+	unsigned int index;		/* index of item */
+};
+
+struct sndrv_ctl_elem_list {
+	unsigned int offset;		/* W: first element ID to get */
+	unsigned int space;		/* W: count of element IDs to get */
+	unsigned int used;		/* R: count of element IDs set */
+	unsigned int count;		/* R: count of all elements */
+	struct sndrv_ctl_elem_id *pids; /* R: IDs */
+	unsigned char reserved[50];
+};
+
+struct sndrv_ctl_elem_info {
+	struct sndrv_ctl_elem_id id;	/* W: element ID */
+	int type;			/* R: value type - SNDRV_CTL_ELEM_TYPE_* */
+	unsigned int access;		/* R: value access (bitmask) - SNDRV_CTL_ELEM_ACCESS_* */
+	unsigned int count;		/* count of values */
+	pid_t owner;			/* owner's PID of this control */
+	union {
+		struct {
+			long min;		/* R: minimum value */
+			long max;		/* R: maximum value */
+			long step;		/* R: step (0 variable) */
+		} integer;
+		struct {
+			long long min;		/* R: minimum value */
+			long long max;		/* R: maximum value */
+			long long step;		/* R: step (0 variable) */
+		} integer64;
+		struct {
+			unsigned int items;	/* R: number of items */
+			unsigned int item;	/* W: item number */
+			char name[64];		/* R: value name */
+			__u64 names_ptr;	/* W: names list (ELEM_ADD only) */
+			unsigned int names_length;
+		} enumerated;
+		unsigned char reserved[128];
+	} value;
+	union {
+		unsigned short d[4];		/* dimensions */
+		unsigned short *d_ptr;		/* indirect - obsoleted */
+	} dimen;
+	unsigned char reserved[64-4*sizeof(unsigned short)];
+};
+
+struct sndrv_ctl_elem_value {
+	struct sndrv_ctl_elem_id id;	/* W: element ID */
+	unsigned int indirect: 1;	/* W: indirect access - obsoleted */
+	union {
+		union {
+			long value[128];
+			long *value_ptr;	/* obsoleted */
+		} integer;
+		union {
+			long long value[64];
+			long long *value_ptr;	/* obsoleted */
+		} integer64;
+		union {
+			unsigned int item[128];
+			unsigned int *item_ptr;	/* obsoleted */
+		} enumerated;
+		union {
+			unsigned char data[512];
+			unsigned char *data_ptr;	/* obsoleted */
+		} bytes;
+		struct sndrv_aes_iec958 iec958;
+	} value;		/* RO */
+	struct timespec tstamp;
+	unsigned char reserved[128-sizeof(struct timespec)];
+};
+
+struct sndrv_ctl_tlv {
+	unsigned int numid;	/* control element numeric identification */
+	unsigned int length;	/* in bytes aligned to 4 */
+	unsigned int tlv[0];	/* first TLV */
+};
+
+enum {
+	SNDRV_CTL_IOCTL_PVERSION = _IOR('U', 0x00, int),
+	SNDRV_CTL_IOCTL_CARD_INFO = _IOR('U', 0x01, struct sndrv_ctl_card_info),
+	SNDRV_CTL_IOCTL_ELEM_LIST = _IOWR('U', 0x10, struct sndrv_ctl_elem_list),
+	SNDRV_CTL_IOCTL_ELEM_INFO = _IOWR('U', 0x11, struct sndrv_ctl_elem_info),
+	SNDRV_CTL_IOCTL_ELEM_READ = _IOWR('U', 0x12, struct sndrv_ctl_elem_value),
+	SNDRV_CTL_IOCTL_ELEM_WRITE = _IOWR('U', 0x13, struct sndrv_ctl_elem_value),
+	SNDRV_CTL_IOCTL_ELEM_LOCK = _IOW('U', 0x14, struct sndrv_ctl_elem_id),
+	SNDRV_CTL_IOCTL_ELEM_UNLOCK = _IOW('U', 0x15, struct sndrv_ctl_elem_id),
+	SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS = _IOWR('U', 0x16, int),
+	SNDRV_CTL_IOCTL_ELEM_ADD = _IOWR('U', 0x17, struct sndrv_ctl_elem_info),
+	SNDRV_CTL_IOCTL_ELEM_REPLACE = _IOWR('U', 0x18, struct sndrv_ctl_elem_info),
+	SNDRV_CTL_IOCTL_ELEM_REMOVE = _IOWR('U', 0x19, struct sndrv_ctl_elem_id),
+	SNDRV_CTL_IOCTL_TLV_READ = _IOWR('U', 0x1a, struct sndrv_ctl_tlv),
+	SNDRV_CTL_IOCTL_TLV_WRITE = _IOWR('U', 0x1b, struct sndrv_ctl_tlv),
+	SNDRV_CTL_IOCTL_TLV_COMMAND = _IOWR('U', 0x1c, struct sndrv_ctl_tlv),
+	SNDRV_CTL_IOCTL_HWDEP_NEXT_DEVICE = _IOWR('U', 0x20, int),
+	SNDRV_CTL_IOCTL_HWDEP_INFO = _IOR('U', 0x21, struct sndrv_hwdep_info),
+	SNDRV_CTL_IOCTL_PCM_NEXT_DEVICE = _IOR('U', 0x30, int),
+	SNDRV_CTL_IOCTL_PCM_INFO = _IOWR('U', 0x31, struct sndrv_pcm_info),
+	SNDRV_CTL_IOCTL_PCM_PREFER_SUBDEVICE = _IOW('U', 0x32, int),
+	SNDRV_CTL_IOCTL_RAWMIDI_NEXT_DEVICE = _IOWR('U', 0x40, int),
+	SNDRV_CTL_IOCTL_RAWMIDI_INFO = _IOWR('U', 0x41, struct sndrv_rawmidi_info),
+	SNDRV_CTL_IOCTL_RAWMIDI_PREFER_SUBDEVICE = _IOW('U', 0x42, int),
+	SNDRV_CTL_IOCTL_POWER = _IOWR('U', 0xd0, int),
+	SNDRV_CTL_IOCTL_POWER_STATE = _IOR('U', 0xd1, int),
+};
+
+/*
+ *  Read interface.
+ */
+
+enum sndrv_ctl_event_type {
+	SNDRV_CTL_EVENT_ELEM = 0,
+	SNDRV_CTL_EVENT_LAST = SNDRV_CTL_EVENT_ELEM,
+};
+
+#define SNDRV_CTL_EVENT_MASK_VALUE	(1<<0)	/* element value was changed */
+#define SNDRV_CTL_EVENT_MASK_INFO	(1<<1)	/* element info was changed */
+#define SNDRV_CTL_EVENT_MASK_ADD	(1<<2)	/* element was added */
+#define SNDRV_CTL_EVENT_MASK_TLV	(1<<3)	/* element TLV tree was changed */
+#define SNDRV_CTL_EVENT_MASK_REMOVE	(~0U)	/* element was removed */
+
+struct sndrv_ctl_event {
+	int type;	/* event type - SNDRV_CTL_EVENT_* */
+	union {
+		struct {
+			unsigned int mask;
+			struct sndrv_ctl_elem_id id;
+		} elem;
+		unsigned char data8[60];
+	} data;
+};
+
+/*
+ *  Control names
+ */
+
+#define SNDRV_CTL_NAME_NONE				""
+#define SNDRV_CTL_NAME_PLAYBACK				"Playback "
+#define SNDRV_CTL_NAME_CAPTURE				"Capture "
+
+#define SNDRV_CTL_NAME_IEC958_NONE			""
+#define SNDRV_CTL_NAME_IEC958_SWITCH			"Switch"
+#define SNDRV_CTL_NAME_IEC958_VOLUME			"Volume"
+#define SNDRV_CTL_NAME_IEC958_DEFAULT			"Default"
+#define SNDRV_CTL_NAME_IEC958_MASK			"Mask"
+#define SNDRV_CTL_NAME_IEC958_CON_MASK			"Con Mask"
+#define SNDRV_CTL_NAME_IEC958_PRO_MASK			"Pro Mask"
+#define SNDRV_CTL_NAME_IEC958_PCM_STREAM		"PCM Stream"
+#define SNDRV_CTL_NAME_IEC958(expl,direction,what)	"IEC958 " expl SNDRV_CTL_NAME_##direction SNDRV_CTL_NAME_IEC958_##what
+
+#endif /* __SOUND_ASOUND_H */
diff --git a/include/sound/asound_fm.h b/include/sound/asound_fm.h
new file mode 100644
index 0000000..c2a4b96
--- /dev/null
+++ b/include/sound/asound_fm.h
@@ -0,0 +1,134 @@
+#ifndef __SOUND_ASOUND_FM_H
+#define __SOUND_ASOUND_FM_H
+
+/*
+ *  Advanced Linux Sound Architecture - ALSA
+ *
+ *  Interface file between ALSA driver & user space
+ *  Copyright (c) 1994-98 by Jaroslav Kysela <perex@perex.cz>,
+ *                           4Front Technologies
+ *
+ *  Direct FM control
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#define SNDRV_DM_FM_MODE_OPL2	0x00
+#define SNDRV_DM_FM_MODE_OPL3	0x01
+
+struct snd_dm_fm_info {
+	unsigned char fm_mode;		/* OPL mode, see SNDRV_DM_FM_MODE_XXX */
+	unsigned char rhythm;		/* percussion mode flag */
+};
+
+/*
+ *  Data structure composing an FM "note" or sound event.
+ */
+
+struct snd_dm_fm_voice {
+	unsigned char op;		/* operator cell (0 or 1) */
+	unsigned char voice;		/* FM voice (0 to 17) */
+
+	unsigned char am;		/* amplitude modulation */
+	unsigned char vibrato;		/* vibrato effect */
+	unsigned char do_sustain;	/* sustain phase */
+	unsigned char kbd_scale;	/* keyboard scaling */
+	unsigned char harmonic;		/* 4 bits: harmonic and multiplier */
+	unsigned char scale_level;	/* 2 bits: decrease output freq rises */
+	unsigned char volume;		/* 6 bits: volume */
+
+	unsigned char attack;		/* 4 bits: attack rate */
+	unsigned char decay;		/* 4 bits: decay rate */
+	unsigned char sustain;		/* 4 bits: sustain level */
+	unsigned char release;		/* 4 bits: release rate */
+
+	unsigned char feedback;		/* 3 bits: feedback for op0 */
+	unsigned char connection;	/* 0 for serial, 1 for parallel */
+	unsigned char left;		/* stereo left */
+	unsigned char right;		/* stereo right */
+	unsigned char waveform;		/* 3 bits: waveform shape */
+};
+
+/*
+ *  This describes an FM note by its voice, octave, frequency number (10bit)
+ *  and key on/off.
+ */
+
+struct snd_dm_fm_note {
+	unsigned char voice;	/* 0-17 voice channel */
+	unsigned char octave;	/* 3 bits: what octave to play */
+	unsigned int fnum;	/* 10 bits: frequency number */
+	unsigned char key_on;	/* set for active, clear for silent */
+};
+
+/*
+ *  FM parameters that apply globally to all voices, and thus are not "notes"
+ */
+
+struct snd_dm_fm_params {
+	unsigned char am_depth;		/* amplitude modulation depth (1=hi) */
+	unsigned char vib_depth;	/* vibrato depth (1=hi) */
+	unsigned char kbd_split;	/* keyboard split */
+	unsigned char rhythm;		/* percussion mode select */
+
+	/* This block is the percussion instrument data */
+	unsigned char bass;
+	unsigned char snare;
+	unsigned char tomtom;
+	unsigned char cymbal;
+	unsigned char hihat;
+};
+
+/*
+ *  FM mode ioctl settings
+ */
+
+#define SNDRV_DM_FM_IOCTL_INFO		_IOR('H', 0x20, struct snd_dm_fm_info)
+#define SNDRV_DM_FM_IOCTL_RESET		_IO ('H', 0x21)
+#define SNDRV_DM_FM_IOCTL_PLAY_NOTE	_IOW('H', 0x22, struct snd_dm_fm_note)
+#define SNDRV_DM_FM_IOCTL_SET_VOICE	_IOW('H', 0x23, struct snd_dm_fm_voice)
+#define SNDRV_DM_FM_IOCTL_SET_PARAMS	_IOW('H', 0x24, struct snd_dm_fm_params)
+#define SNDRV_DM_FM_IOCTL_SET_MODE	_IOW('H', 0x25, int)
+/* for OPL3 only */
+#define SNDRV_DM_FM_IOCTL_SET_CONNECTION	_IOW('H', 0x26, int)
+/* SBI patch management */
+#define SNDRV_DM_FM_IOCTL_CLEAR_PATCHES	_IO ('H', 0x40)
+
+#define SNDRV_DM_FM_OSS_IOCTL_RESET		0x20
+#define SNDRV_DM_FM_OSS_IOCTL_PLAY_NOTE		0x21
+#define SNDRV_DM_FM_OSS_IOCTL_SET_VOICE		0x22
+#define SNDRV_DM_FM_OSS_IOCTL_SET_PARAMS	0x23
+#define SNDRV_DM_FM_OSS_IOCTL_SET_MODE		0x24
+#define SNDRV_DM_FM_OSS_IOCTL_SET_OPL		0x25
+
+/*
+ * Patch Record - fixed size for write
+ */
+
+#define FM_KEY_SBI	"SBI\032"
+#define FM_KEY_2OP	"2OP\032"
+#define FM_KEY_4OP	"4OP\032"
+
+struct sbi_patch {
+	unsigned char prog;
+	unsigned char bank;
+	char key[4];
+	char name[25];
+	char extension[7];
+	unsigned char data[32];
+};
+
+#endif /* __SOUND_ASOUND_FM_H */
diff --git a/include/sound/asoundef.h b/include/sound/asoundef.h
new file mode 100644
index 0000000..f962c5a
--- /dev/null
+++ b/include/sound/asoundef.h
@@ -0,0 +1,227 @@
+#ifndef __SOUND_ASOUNDEF_H
+#define __SOUND_ASOUNDEF_H
+
+/*
+ *  Advanced Linux Sound Architecture - ALSA - Driver
+ *  Copyright (c) 1994-2000 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+/****************************************************************************
+ *                                                                          *
+ *        Digital audio interface					    *
+ *                                                                          *
+ ****************************************************************************/
+
+/* AES/IEC958 channel status bits */
+#define IEC958_AES0_PROFESSIONAL	(1<<0)	/* 0 = consumer, 1 = professional */
+#define IEC958_AES0_NONAUDIO		(1<<1)	/* 0 = audio, 1 = non-audio */
+#define IEC958_AES0_PRO_EMPHASIS	(7<<2)	/* mask - emphasis */
+#define IEC958_AES0_PRO_EMPHASIS_NOTID	(0<<2)	/* emphasis not indicated */
+#define IEC958_AES0_PRO_EMPHASIS_NONE	(1<<2)	/* none emphasis */
+#define IEC958_AES0_PRO_EMPHASIS_5015	(3<<2)	/* 50/15us emphasis */
+#define IEC958_AES0_PRO_EMPHASIS_CCITT	(7<<2)	/* CCITT J.17 emphasis */
+#define IEC958_AES0_PRO_FREQ_UNLOCKED	(1<<5)	/* source sample frequency: 0 = locked, 1 = unlocked */
+#define IEC958_AES0_PRO_FS		(3<<6)	/* mask - sample frequency */
+#define IEC958_AES0_PRO_FS_NOTID	(0<<6)	/* fs not indicated */
+#define IEC958_AES0_PRO_FS_44100	(1<<6)	/* 44.1kHz */
+#define IEC958_AES0_PRO_FS_48000	(2<<6)	/* 48kHz */
+#define IEC958_AES0_PRO_FS_32000	(3<<6)	/* 32kHz */
+#define IEC958_AES0_CON_NOT_COPYRIGHT	(1<<2)	/* 0 = copyright, 1 = not copyright */
+#define IEC958_AES0_CON_EMPHASIS	(7<<3)	/* mask - emphasis */
+#define IEC958_AES0_CON_EMPHASIS_NONE	(0<<3)	/* none emphasis */
+#define IEC958_AES0_CON_EMPHASIS_5015	(1<<3)	/* 50/15us emphasis */
+#define IEC958_AES0_CON_MODE		(3<<6)	/* mask - mode */
+#define IEC958_AES1_PRO_MODE		(15<<0)	/* mask - channel mode */
+#define IEC958_AES1_PRO_MODE_NOTID	(0<<0)	/* not indicated */
+#define IEC958_AES1_PRO_MODE_STEREOPHONIC (2<<0) /* stereophonic - ch A is left */
+#define IEC958_AES1_PRO_MODE_SINGLE	(4<<0)	/* single channel */
+#define IEC958_AES1_PRO_MODE_TWO	(8<<0)	/* two channels */
+#define IEC958_AES1_PRO_MODE_PRIMARY	(12<<0)	/* primary/secondary */
+#define IEC958_AES1_PRO_MODE_BYTE3	(15<<0)	/* vector to byte 3 */
+#define IEC958_AES1_PRO_USERBITS	(15<<4)	/* mask - user bits */
+#define IEC958_AES1_PRO_USERBITS_NOTID	(0<<4)	/* not indicated */
+#define IEC958_AES1_PRO_USERBITS_192	(8<<4)	/* 192-bit structure */
+#define IEC958_AES1_PRO_USERBITS_UDEF	(12<<4)	/* user defined application */
+#define IEC958_AES1_CON_CATEGORY	0x7f
+#define IEC958_AES1_CON_GENERAL		0x00
+#define IEC958_AES1_CON_EXPERIMENTAL	0x40
+#define IEC958_AES1_CON_SOLIDMEM_MASK	0x0f
+#define IEC958_AES1_CON_SOLIDMEM_ID	0x08
+#define IEC958_AES1_CON_BROADCAST1_MASK 0x07
+#define IEC958_AES1_CON_BROADCAST1_ID	0x04
+#define IEC958_AES1_CON_DIGDIGCONV_MASK 0x07
+#define IEC958_AES1_CON_DIGDIGCONV_ID	0x02
+#define IEC958_AES1_CON_ADC_COPYRIGHT_MASK 0x1f
+#define IEC958_AES1_CON_ADC_COPYRIGHT_ID 0x06
+#define IEC958_AES1_CON_ADC_MASK	0x1f
+#define IEC958_AES1_CON_ADC_ID		0x16
+#define IEC958_AES1_CON_BROADCAST2_MASK 0x0f
+#define IEC958_AES1_CON_BROADCAST2_ID	0x0e
+#define IEC958_AES1_CON_LASEROPT_MASK	0x07
+#define IEC958_AES1_CON_LASEROPT_ID	0x01
+#define IEC958_AES1_CON_MUSICAL_MASK	0x07
+#define IEC958_AES1_CON_MUSICAL_ID	0x05
+#define IEC958_AES1_CON_MAGNETIC_MASK	0x07
+#define IEC958_AES1_CON_MAGNETIC_ID	0x03
+#define IEC958_AES1_CON_IEC908_CD	(IEC958_AES1_CON_LASEROPT_ID|0x00)
+#define IEC958_AES1_CON_NON_IEC908_CD	(IEC958_AES1_CON_LASEROPT_ID|0x08)
+#define IEC958_AES1_CON_PCM_CODER	(IEC958_AES1_CON_DIGDIGCONV_ID|0x00)
+#define IEC958_AES1_CON_SAMPLER		(IEC958_AES1_CON_DIGDIGCONV_ID|0x20)
+#define IEC958_AES1_CON_MIXER		(IEC958_AES1_CON_DIGDIGCONV_ID|0x10)
+#define IEC958_AES1_CON_RATE_CONVERTER	(IEC958_AES1_CON_DIGDIGCONV_ID|0x18)
+#define IEC958_AES1_CON_SYNTHESIZER	(IEC958_AES1_CON_MUSICAL_ID|0x00)
+#define IEC958_AES1_CON_MICROPHONE	(IEC958_AES1_CON_MUSICAL_ID|0x08)
+#define IEC958_AES1_CON_DAT		(IEC958_AES1_CON_MAGNETIC_ID|0x00)
+#define IEC958_AES1_CON_VCR		(IEC958_AES1_CON_MAGNETIC_ID|0x08)
+#define IEC958_AES1_CON_ORIGINAL	(1<<7)	/* this bits depends on the category code */
+#define IEC958_AES2_PRO_SBITS		(7<<0)	/* mask - sample bits */
+#define IEC958_AES2_PRO_SBITS_20	(2<<0)	/* 20-bit - coordination */
+#define IEC958_AES2_PRO_SBITS_24	(4<<0)	/* 24-bit - main audio */
+#define IEC958_AES2_PRO_SBITS_UDEF	(6<<0)	/* user defined application */
+#define IEC958_AES2_PRO_WORDLEN		(7<<3)	/* mask - source word length */
+#define IEC958_AES2_PRO_WORDLEN_NOTID	(0<<3)	/* not indicated */
+#define IEC958_AES2_PRO_WORDLEN_22_18	(2<<3)	/* 22-bit or 18-bit */
+#define IEC958_AES2_PRO_WORDLEN_23_19	(4<<3)	/* 23-bit or 19-bit */
+#define IEC958_AES2_PRO_WORDLEN_24_20	(5<<3)	/* 24-bit or 20-bit */
+#define IEC958_AES2_PRO_WORDLEN_20_16	(6<<3)	/* 20-bit or 16-bit */
+#define IEC958_AES2_CON_SOURCE		(15<<0)	/* mask - source number */
+#define IEC958_AES2_CON_SOURCE_UNSPEC	(0<<0)	/* unspecified */
+#define IEC958_AES2_CON_CHANNEL		(15<<4)	/* mask - channel number */
+#define IEC958_AES2_CON_CHANNEL_UNSPEC	(0<<4)	/* unspecified */
+#define IEC958_AES3_CON_FS		(15<<0)	/* mask - sample frequency */
+#define IEC958_AES3_CON_FS_44100	(0<<0)	/* 44.1kHz */
+#define IEC958_AES3_CON_FS_48000	(2<<0)	/* 48kHz */
+#define IEC958_AES3_CON_FS_32000	(3<<0)	/* 32kHz */
+#define IEC958_AES3_CON_CLOCK		(3<<4)	/* mask - clock accuracy */
+#define IEC958_AES3_CON_CLOCK_1000PPM	(0<<4)	/* 1000 ppm */
+#define IEC958_AES3_CON_CLOCK_50PPM	(1<<4)	/* 50 ppm */
+#define IEC958_AES3_CON_CLOCK_VARIABLE	(2<<4)	/* variable pitch */
+
+/*****************************************************************************
+ *                                                                           *
+ *                            MIDI v1.0 interface                            *
+ *                                                                           *
+ *****************************************************************************/
+
+#define MIDI_CHANNELS			16
+#define MIDI_GM_DRUM_CHANNEL		(10-1)
+
+/*
+ *  MIDI commands
+ */
+
+#define MIDI_CMD_NOTE_OFF		0x80
+#define MIDI_CMD_NOTE_ON		0x90
+#define MIDI_CMD_NOTE_PRESSURE		0xa0
+#define MIDI_CMD_CONTROL		0xb0
+#define MIDI_CMD_PGM_CHANGE		0xc0
+#define MIDI_CMD_CHANNEL_PRESSURE	0xd0
+#define MIDI_CMD_BENDER			0xe0
+
+#define MIDI_CMD_COMMON_SYSEX		0xf0
+#define MIDI_CMD_COMMON_MTC_QUARTER	0xf1
+#define MIDI_CMD_COMMON_SONG_POS	0xf2
+#define MIDI_CMD_COMMON_SONG_SELECT	0xf3
+#define MIDI_CMD_COMMON_TUNE_REQUEST	0xf6
+#define MIDI_CMD_COMMON_SYSEX_END	0xf7
+#define MIDI_CMD_COMMON_CLOCK		0xf8
+#define MIDI_CMD_COMMON_START		0xfa
+#define MIDI_CMD_COMMON_CONTINUE	0xfb
+#define MIDI_CMD_COMMON_STOP		0xfc
+#define MIDI_CMD_COMMON_SENSING		0xfe
+#define MIDI_CMD_COMMON_RESET		0xff
+
+/*
+ *  MIDI controllers
+ */
+
+#define MIDI_CTL_MSB_BANK		0x00
+#define MIDI_CTL_MSB_MODWHEEL         	0x01
+#define MIDI_CTL_MSB_BREATH           	0x02
+#define MIDI_CTL_MSB_FOOT             	0x04
+#define MIDI_CTL_MSB_PORTAMENTO_TIME 	0x05
+#define MIDI_CTL_MSB_DATA_ENTRY		0x06
+#define MIDI_CTL_MSB_MAIN_VOLUME      	0x07
+#define MIDI_CTL_MSB_BALANCE          	0x08
+#define MIDI_CTL_MSB_PAN              	0x0a
+#define MIDI_CTL_MSB_EXPRESSION       	0x0b
+#define MIDI_CTL_MSB_EFFECT1		0x0c
+#define MIDI_CTL_MSB_EFFECT2		0x0d
+#define MIDI_CTL_MSB_GENERAL_PURPOSE1 	0x10
+#define MIDI_CTL_MSB_GENERAL_PURPOSE2 	0x11
+#define MIDI_CTL_MSB_GENERAL_PURPOSE3 	0x12
+#define MIDI_CTL_MSB_GENERAL_PURPOSE4 	0x13
+#define MIDI_CTL_LSB_BANK		0x20
+#define MIDI_CTL_LSB_MODWHEEL        	0x21
+#define MIDI_CTL_LSB_BREATH           	0x22
+#define MIDI_CTL_LSB_FOOT             	0x24
+#define MIDI_CTL_LSB_PORTAMENTO_TIME 	0x25
+#define MIDI_CTL_LSB_DATA_ENTRY		0x26
+#define MIDI_CTL_LSB_MAIN_VOLUME      	0x27
+#define MIDI_CTL_LSB_BALANCE          	0x28
+#define MIDI_CTL_LSB_PAN              	0x2a
+#define MIDI_CTL_LSB_EXPRESSION       	0x2b
+#define MIDI_CTL_LSB_EFFECT1		0x2c
+#define MIDI_CTL_LSB_EFFECT2		0x2d
+#define MIDI_CTL_LSB_GENERAL_PURPOSE1 	0x30
+#define MIDI_CTL_LSB_GENERAL_PURPOSE2 	0x31
+#define MIDI_CTL_LSB_GENERAL_PURPOSE3 	0x32
+#define MIDI_CTL_LSB_GENERAL_PURPOSE4 	0x33
+#define MIDI_CTL_SUSTAIN              	0x40
+#define MIDI_CTL_PORTAMENTO           	0x41
+#define MIDI_CTL_SUSTENUTO            	0x42
+#define MIDI_CTL_SOFT_PEDAL           	0x43
+#define MIDI_CTL_LEGATO_FOOTSWITCH	0x44
+#define MIDI_CTL_HOLD2                	0x45
+#define MIDI_CTL_SC1_SOUND_VARIATION	0x46
+#define MIDI_CTL_SC2_TIMBRE		0x47
+#define MIDI_CTL_SC3_RELEASE_TIME	0x48
+#define MIDI_CTL_SC4_ATTACK_TIME	0x49
+#define MIDI_CTL_SC5_BRIGHTNESS		0x4a
+#define MIDI_CTL_SC6			0x4b
+#define MIDI_CTL_SC7			0x4c
+#define MIDI_CTL_SC8			0x4d
+#define MIDI_CTL_SC9			0x4e
+#define MIDI_CTL_SC10			0x4f
+#define MIDI_CTL_GENERAL_PURPOSE5     	0x50
+#define MIDI_CTL_GENERAL_PURPOSE6     	0x51
+#define MIDI_CTL_GENERAL_PURPOSE7     	0x52
+#define MIDI_CTL_GENERAL_PURPOSE8     	0x53
+#define MIDI_CTL_PORTAMENTO_CONTROL	0x54
+#define MIDI_CTL_E1_REVERB_DEPTH	0x5b
+#define MIDI_CTL_E2_TREMOLO_DEPTH	0x5c
+#define MIDI_CTL_E3_CHORUS_DEPTH	0x5d
+#define MIDI_CTL_E4_DETUNE_DEPTH	0x5e
+#define MIDI_CTL_E5_PHASER_DEPTH	0x5f
+#define MIDI_CTL_DATA_INCREMENT       	0x60
+#define MIDI_CTL_DATA_DECREMENT       	0x61
+#define MIDI_CTL_NONREG_PARM_NUM_LSB  	0x62
+#define MIDI_CTL_NONREG_PARM_NUM_MSB  	0x63
+#define MIDI_CTL_REGIST_PARM_NUM_LSB  	0x64
+#define MIDI_CTL_REGIST_PARM_NUM_MSB	0x65
+#define MIDI_CTL_ALL_SOUNDS_OFF		0x78
+#define MIDI_CTL_RESET_CONTROLLERS	0x79
+#define MIDI_CTL_LOCAL_CONTROL_SWITCH	0x7a
+#define MIDI_CTL_ALL_NOTES_OFF		0x7b
+#define MIDI_CTL_OMNI_OFF		0x7c
+#define MIDI_CTL_OMNI_ON		0x7d
+#define MIDI_CTL_MONO1			0x7e
+#define MIDI_CTL_MONO2			0x7f
+
+#endif /* __SOUND_ASOUNDEF_H */
diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h
new file mode 100644
index 0000000..94018b7
--- /dev/null
+++ b/include/sound/emu10k1.h
@@ -0,0 +1,349 @@
+#ifndef __SOUND_EMU10K1_H
+#define __SOUND_EMU10K1_H
+
+/*
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>,
+ *		     Creative Labs, Inc.
+ *  Definitions for EMU10K1 (SB Live!) chips
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <stdint.h>
+
+/*
+ * ---- FX8010 ----
+ */
+
+#define EMU10K1_CARD_CREATIVE			0x00000000
+#define EMU10K1_CARD_EMUAPS			0x00000001
+
+#define EMU10K1_FX8010_PCM_COUNT		8
+
+/* instruction set */
+#define iMAC0	 0x00	/* R = A + (X * Y >> 31)   ; saturation */
+#define iMAC1	 0x01	/* R = A + (-X * Y >> 31)  ; saturation */
+#define iMAC2	 0x02	/* R = A + (X * Y >> 31)   ; wraparound */
+#define iMAC3	 0x03	/* R = A + (-X * Y >> 31)  ; wraparound */
+#define iMACINT0 0x04	/* R = A + X * Y	   ; saturation */
+#define iMACINT1 0x05	/* R = A + X * Y	   ; wraparound (31-bit) */
+#define iACC3	 0x06	/* R = A + X + Y	   ; saturation */
+#define iMACMV   0x07	/* R = A, acc += X * Y >> 31 */
+#define iANDXOR  0x08	/* R = (A & X) ^ Y */
+#define iTSTNEG  0x09	/* R = (A >= Y) ? X : ~X */
+#define iLIMITGE 0x0a	/* R = (A >= Y) ? X : Y */
+#define iLIMITLT 0x0b	/* R = (A < Y) ? X : Y */
+#define iLOG	 0x0c	/* R = linear_data, A (log_data), X (max_exp), Y (format_word) */
+#define iEXP	 0x0d	/* R = log_data, A (linear_data), X (max_exp), Y (format_word) */
+#define iINTERP  0x0e	/* R = A + (X * (Y - A) >> 31)  ; saturation */
+#define iSKIP    0x0f	/* R = A (cc_reg), X (count), Y (cc_test) */
+
+/* GPRs */
+#define FXBUS(x)	(0x00 + (x))	/* x = 0x00 - 0x0f */
+#define EXTIN(x)	(0x10 + (x))	/* x = 0x00 - 0x0f */
+#define EXTOUT(x)	(0x20 + (x))	/* x = 0x00 - 0x0f */
+#define C_00000000	0x40
+#define C_00000001	0x41
+#define C_00000002	0x42
+#define C_00000003	0x43
+#define C_00000004	0x44
+#define C_00000008	0x45
+#define C_00000010	0x46
+#define C_00000020	0x47
+#define C_00000100	0x48
+#define C_00010000	0x49
+#define C_00080000	0x4a
+#define C_10000000	0x4b
+#define C_20000000	0x4c
+#define C_40000000	0x4d
+#define C_80000000	0x4e
+#define C_7fffffff	0x4f
+#define C_ffffffff	0x50
+#define C_fffffffe	0x51
+#define C_c0000000	0x52
+#define C_4f1bbcdc	0x53
+#define C_5a7ef9db	0x54
+#define C_00100000	0x55		/* ?? */
+#define GPR_ACCU	0x56		/* ACCUM, accumulator */
+#define GPR_COND	0x57		/* CCR, condition register */
+#define GPR_NOISE0	0x58		/* noise source */
+#define GPR_NOISE1	0x59		/* noise source */
+#define GPR_IRQ		0x5a		/* IRQ register */
+#define GPR_DBAC	0x5b		/* TRAM Delay Base Address Counter */
+#define GPR(x)		(FXGPREGBASE + (x)) /* free GPRs: x = 0x00 - 0xff */
+#define ITRAM_DATA(x)	(TANKMEMDATAREGBASE + 0x00 + (x)) /* x = 0x00 - 0x7f */
+#define ETRAM_DATA(x)	(TANKMEMDATAREGBASE + 0x80 + (x)) /* x = 0x00 - 0x1f */
+#define ITRAM_ADDR(x)	(TANKMEMADDRREGBASE + 0x00 + (x)) /* x = 0x00 - 0x7f */
+#define ETRAM_ADDR(x)	(TANKMEMADDRREGBASE + 0x80 + (x)) /* x = 0x00 - 0x1f */
+
+#define A_FXBUS(x)	(0x00 + (x))	/* x = 0x00 - 0x3f? */
+#define A_EXTIN(x)	(0x40 + (x))	/* x = 0x00 - 0x1f? */
+#define A_EXTOUT(x)	(0x60 + (x))	/* x = 0x00 - 0x1f? */
+#define A_GPR(x)	(A_FXGPREGBASE + (x))
+
+/* cc_reg constants */
+#define CC_REG_NORMALIZED C_00000001
+#define CC_REG_BORROW	C_00000002
+#define CC_REG_MINUS	C_00000004
+#define CC_REG_ZERO	C_00000008
+#define CC_REG_SATURATE	C_00000010
+#define CC_REG_NONZERO	C_00000100
+
+/* FX buses */
+#define FXBUS_PCM_LEFT		0x00
+#define FXBUS_PCM_RIGHT		0x01
+#define FXBUS_PCM_LEFT_REAR	0x02
+#define FXBUS_PCM_RIGHT_REAR	0x03
+#define FXBUS_MIDI_LEFT		0x04
+#define FXBUS_MIDI_RIGHT	0x05
+#define FXBUS_PCM_CENTER	0x06
+#define FXBUS_PCM_LFE		0x07
+#define FXBUS_PCM_LEFT_FRONT	0x08
+#define FXBUS_PCM_RIGHT_FRONT	0x09
+#define FXBUS_MIDI_REVERB	0x0c
+#define FXBUS_MIDI_CHORUS	0x0d
+#define FXBUS_PCM_LEFT_SIDE	0x0e
+#define FXBUS_PCM_RIGHT_SIDE	0x0f
+#define FXBUS_PT_LEFT		0x14
+#define FXBUS_PT_RIGHT		0x15
+
+/* Inputs */
+#define EXTIN_AC97_L	   0x00	/* AC'97 capture channel - left */
+#define EXTIN_AC97_R	   0x01	/* AC'97 capture channel - right */
+#define EXTIN_SPDIF_CD_L   0x02	/* internal S/PDIF CD - onboard - left */
+#define EXTIN_SPDIF_CD_R   0x03	/* internal S/PDIF CD - onboard - right */
+#define EXTIN_ZOOM_L	   0x04	/* Zoom Video I2S - left */
+#define EXTIN_ZOOM_R	   0x05	/* Zoom Video I2S - right */
+#define EXTIN_TOSLINK_L	   0x06	/* LiveDrive - TOSLink Optical - left */
+#define EXTIN_TOSLINK_R    0x07	/* LiveDrive - TOSLink Optical - right */
+#define EXTIN_LINE1_L	   0x08	/* LiveDrive - Line/Mic 1 - left */
+#define EXTIN_LINE1_R	   0x09	/* LiveDrive - Line/Mic 1 - right */
+#define EXTIN_COAX_SPDIF_L 0x0a	/* LiveDrive - Coaxial S/PDIF - left */
+#define EXTIN_COAX_SPDIF_R 0x0b /* LiveDrive - Coaxial S/PDIF - right */
+#define EXTIN_LINE2_L	   0x0c	/* LiveDrive - Line/Mic 2 - left */
+#define EXTIN_LINE2_R	   0x0d	/* LiveDrive - Line/Mic 2 - right */
+
+/* Outputs */
+#define EXTOUT_AC97_L	   0x00	/* AC'97 playback channel - left */
+#define EXTOUT_AC97_R	   0x01	/* AC'97 playback channel - right */
+#define EXTOUT_TOSLINK_L   0x02	/* LiveDrive - TOSLink Optical - left */
+#define EXTOUT_TOSLINK_R   0x03	/* LiveDrive - TOSLink Optical - right */
+#define EXTOUT_AC97_CENTER 0x04	/* SB Live 5.1 - center */
+#define EXTOUT_AC97_LFE	   0x05 /* SB Live 5.1 - LFE */
+#define EXTOUT_HEADPHONE_L 0x06	/* LiveDrive - Headphone - left */
+#define EXTOUT_HEADPHONE_R 0x07	/* LiveDrive - Headphone - right */
+#define EXTOUT_REAR_L	   0x08	/* Rear channel - left */
+#define EXTOUT_REAR_R	   0x09	/* Rear channel - right */
+#define EXTOUT_ADC_CAP_L   0x0a	/* ADC Capture buffer - left */
+#define EXTOUT_ADC_CAP_R   0x0b	/* ADC Capture buffer - right */
+#define EXTOUT_MIC_CAP	   0x0c	/* MIC Capture buffer */
+#define EXTOUT_AC97_REAR_L 0x0d	/* SB Live 5.1 (c) 2003 - Rear Left */
+#define EXTOUT_AC97_REAR_R 0x0e	/* SB Live 5.1 (c) 2003 - Rear Right */
+#define EXTOUT_ACENTER	   0x11 /* Analog Center */
+#define EXTOUT_ALFE	   0x12 /* Analog LFE */
+
+/* Audigy Inputs */
+#define A_EXTIN_AC97_L		0x00	/* AC'97 capture channel - left */
+#define A_EXTIN_AC97_R		0x01	/* AC'97 capture channel - right */
+#define A_EXTIN_SPDIF_CD_L	0x02	/* digital CD left */
+#define A_EXTIN_SPDIF_CD_R	0x03	/* digital CD left */
+#define A_EXTIN_OPT_SPDIF_L     0x04    /* audigy drive Optical SPDIF - left */
+#define A_EXTIN_OPT_SPDIF_R     0x05    /*                              right */ 
+#define A_EXTIN_LINE2_L		0x08	/* audigy drive line2/mic2 - left */
+#define A_EXTIN_LINE2_R		0x09	/*                           right */
+#define A_EXTIN_ADC_L		0x0a    /* Philips ADC - left */
+#define A_EXTIN_ADC_R		0x0b    /*               right */
+#define A_EXTIN_AUX2_L		0x0c	/* audigy drive aux2 - left */
+#define A_EXTIN_AUX2_R		0x0d	/*                   - right */
+
+/* Audigiy Outputs */
+#define A_EXTOUT_FRONT_L	0x00	/* digital front left */
+#define A_EXTOUT_FRONT_R	0x01	/*               right */
+#define A_EXTOUT_CENTER		0x02	/* digital front center */
+#define A_EXTOUT_LFE		0x03	/* digital front lfe */
+#define A_EXTOUT_HEADPHONE_L	0x04	/* headphone audigy drive left */
+#define A_EXTOUT_HEADPHONE_R	0x05	/*                        right */
+#define A_EXTOUT_REAR_L		0x06	/* digital rear left */
+#define A_EXTOUT_REAR_R		0x07	/*              right */
+#define A_EXTOUT_AFRONT_L	0x08	/* analog front left */
+#define A_EXTOUT_AFRONT_R	0x09	/*              right */
+#define A_EXTOUT_ACENTER	0x0a	/* analog center */
+#define A_EXTOUT_ALFE		0x0b	/* analog LFE */
+#define A_EXTOUT_ASIDE_L	0x0c	/* analog side left  - Audigy 2 ZS */
+#define A_EXTOUT_ASIDE_R	0x0d	/*             right - Audigy 2 ZS */
+#define A_EXTOUT_AREAR_L	0x0e	/* analog rear left */
+#define A_EXTOUT_AREAR_R	0x0f	/*             right */
+#define A_EXTOUT_AC97_L		0x10	/* AC97 left (front) */
+#define A_EXTOUT_AC97_R		0x11	/*      right */
+#define A_EXTOUT_ADC_CAP_L	0x16	/* ADC capture buffer left */
+#define A_EXTOUT_ADC_CAP_R	0x17	/*                    right */
+#define A_EXTOUT_MIC_CAP	0x18	/* Mic capture buffer */
+
+/* Audigy constants */
+#define A_C_00000000	0xc0
+#define A_C_00000001	0xc1
+#define A_C_00000002	0xc2
+#define A_C_00000003	0xc3
+#define A_C_00000004	0xc4
+#define A_C_00000008	0xc5
+#define A_C_00000010	0xc6
+#define A_C_00000020	0xc7
+#define A_C_00000100	0xc8
+#define A_C_00010000	0xc9
+#define A_C_00000800	0xca
+#define A_C_10000000	0xcb
+#define A_C_20000000	0xcc
+#define A_C_40000000	0xcd
+#define A_C_80000000	0xce
+#define A_C_7fffffff	0xcf
+#define A_C_ffffffff	0xd0
+#define A_C_fffffffe	0xd1
+#define A_C_c0000000	0xd2
+#define A_C_4f1bbcdc	0xd3
+#define A_C_5a7ef9db	0xd4
+#define A_C_00100000	0xd5
+#define A_GPR_ACCU	0xd6		/* ACCUM, accumulator */
+#define A_GPR_COND	0xd7		/* CCR, condition register */
+#define A_GPR_NOISE0	0xd8		/* noise source */
+#define A_GPR_NOISE1	0xd9		/* noise source */
+#define A_GPR_IRQ	0xda		/* IRQ register */
+#define A_GPR_DBAC	0xdb		/* TRAM Delay Base Address Counter - internal */
+#define A_GPR_DBACE	0xde		/* TRAM Delay Base Address Counter - external */
+
+/* definitions for debug register */
+#define EMU10K1_DBG_ZC			0x80000000	/* zero tram counter */
+#define EMU10K1_DBG_SATURATION_OCCURED	0x02000000	/* saturation control */
+#define EMU10K1_DBG_SATURATION_ADDR	0x01ff0000	/* saturation address */
+#define EMU10K1_DBG_SINGLE_STEP		0x00008000	/* single step mode */
+#define EMU10K1_DBG_STEP		0x00004000	/* start single step */
+#define EMU10K1_DBG_CONDITION_CODE	0x00003e00	/* condition code */
+#define EMU10K1_DBG_SINGLE_STEP_ADDR	0x000001ff	/* single step address */
+
+/* tank memory address line */
+#ifndef __KERNEL__
+#define TANKMEMADDRREG_ADDR_MASK 0x000fffff	/* 20 bit tank address field			*/
+#define TANKMEMADDRREG_CLEAR	 0x00800000	/* Clear tank memory				*/
+#define TANKMEMADDRREG_ALIGN	 0x00400000	/* Align read or write relative to tank access	*/
+#define TANKMEMADDRREG_WRITE	 0x00200000	/* Write to tank memory				*/
+#define TANKMEMADDRREG_READ	 0x00100000	/* Read from tank memory			*/
+#endif
+
+typedef struct {
+	unsigned int internal_tram_size;	/* in samples */
+	unsigned int external_tram_size;	/* in samples */
+	char fxbus_names[16][32];		/* names of FXBUSes */
+	char extin_names[16][32];		/* names of external inputs */
+	char extout_names[32][32];		/* names of external outputs */
+	unsigned int gpr_controls;		/* count of GPR controls */
+} emu10k1_fx8010_info_t;
+
+#define EMU10K1_GPR_TRANSLATION_NONE		0
+#define EMU10K1_GPR_TRANSLATION_TABLE100	1
+#define EMU10K1_GPR_TRANSLATION_BASS		2
+#define EMU10K1_GPR_TRANSLATION_TREBLE		3
+#define EMU10K1_GPR_TRANSLATION_ONOFF		4
+
+enum emu10k1_ctl_elem_iface {
+	EMU10K1_CTL_ELEM_IFACE_MIXER = 2,	/* virtual mixer device */
+	EMU10K1_CTL_ELEM_IFACE_PCM = 3,		/* PCM device */
+};
+
+typedef struct {
+	unsigned int pad;		/* don't use */
+	int iface;			/* interface identifier */
+	unsigned int device;		/* device/client number */
+	unsigned int subdevice;		/* subdevice (substream) number */
+	unsigned char name[44];		/* ASCII name of item */ 
+	unsigned int index;		/* index of item */
+} emu10k1_ctl_elem_id_t;
+
+typedef struct {
+	emu10k1_ctl_elem_id_t id;	/* full control ID definition */
+	unsigned int vcount;		/* visible count */
+	unsigned int count;		/* count of GPR (1..16) */
+	unsigned short gpr[32];		/* GPR number(s) */
+	unsigned int value[32];		/* initial values */
+	unsigned int min;		/* minimum range */
+	unsigned int max;		/* maximum range */
+	unsigned int translation;	/* translation type (EMU10K1_GPR_TRANSLATION*) */
+	unsigned int *tlv;
+} emu10k1_fx8010_control_gpr_t;
+
+typedef struct {
+	char name[128];
+
+	unsigned long gpr_valid[0x200/(sizeof(unsigned long)*8)]; /* bitmask of valid initializers */
+	uint32_t *gpr_map;		  /* initializers */
+
+	unsigned int gpr_add_control_count; /* count of GPR controls to add/replace */
+	emu10k1_fx8010_control_gpr_t *gpr_add_controls; /* GPR controls to add/replace */
+
+	unsigned int gpr_del_control_count; /* count of GPR controls to remove */
+	emu10k1_ctl_elem_id_t *gpr_del_controls; /* IDs of GPR controls to remove */
+
+	unsigned int gpr_list_control_count; /* count of GPR controls to list */
+	unsigned int gpr_list_control_total; /* total count of GPR controls */
+	emu10k1_fx8010_control_gpr_t *gpr_list_controls; /* listed GPR controls */
+
+	unsigned long tram_valid[0x100/(sizeof(unsigned long)*8)]; /* bitmask of valid initializers */
+	uint32_t *tram_data_map;	/* data initializers */
+	uint32_t *tram_addr_map;	/* map initializers */
+
+	unsigned long code_valid[1024/(sizeof(unsigned long)*8)];  /* bitmask of valid instructions */
+	uint32_t *code;			/* one instruction - 64 bits */
+} emu10k1_fx8010_code_t;
+
+typedef struct {
+	unsigned int address;		/* 31.bit == 1 -> external TRAM */
+	unsigned int size;		/* size in samples (4 bytes) */
+	unsigned int *samples;		/* pointer to samples (20-bit) */
+					/* NULL->clear memory */
+} emu10k1_fx8010_tram_t;
+
+typedef struct {
+	unsigned int substream;		/* substream number */
+	unsigned int res1;		/* reserved */
+	unsigned int channels;		/* 16-bit channels count, zero = remove this substream */
+	unsigned int tram_start;	/* ring buffer position in TRAM (in samples) */
+	unsigned int buffer_size;	/* count of buffered samples */
+	unsigned short gpr_size;		/* GPR containing size of ringbuffer in samples (host) */
+	unsigned short gpr_ptr;		/* GPR containing current pointer in the ring buffer (host = reset, FX8010) */
+	unsigned short gpr_count;	/* GPR containing count of samples between two interrupts (host) */
+	unsigned short gpr_tmpcount;	/* GPR containing current count of samples to interrupt (host = set, FX8010) */
+	unsigned short gpr_trigger;	/* GPR containing trigger (activate) information (host) */
+	unsigned short gpr_running;	/* GPR containing info if PCM is running (FX8010) */
+	unsigned char pad;		/* reserved */
+	unsigned char etram[32];	/* external TRAM address & data (one per channel) */
+	unsigned int res2;		/* reserved */
+} emu10k1_fx8010_pcm_t;
+
+#define SNDRV_EMU10K1_IOCTL_INFO	_IOR ('H', 0x10, emu10k1_fx8010_info_t)
+#define SNDRV_EMU10K1_IOCTL_CODE_POKE	_IOW ('H', 0x11, emu10k1_fx8010_code_t)
+#define SNDRV_EMU10K1_IOCTL_CODE_PEEK	_IOWR('H', 0x12, emu10k1_fx8010_code_t)
+#define SNDRV_EMU10K1_IOCTL_TRAM_SETUP	_IOW ('H', 0x20, int)
+#define SNDRV_EMU10K1_IOCTL_TRAM_POKE	_IOW ('H', 0x21, emu10k1_fx8010_tram_t)
+#define SNDRV_EMU10K1_IOCTL_TRAM_PEEK	_IOWR('H', 0x22, emu10k1_fx8010_tram_t)
+#define SNDRV_EMU10K1_IOCTL_PCM_POKE	_IOW ('H', 0x30, emu10k1_fx8010_pcm_t)
+#define SNDRV_EMU10K1_IOCTL_PCM_PEEK	_IOWR('H', 0x31, emu10k1_fx8010_pcm_t)
+#define SNDRV_EMU10K1_IOCTL_PVERSION	_IOR ('H', 0x40, int)
+#define SNDRV_EMU10K1_IOCTL_STOP	_IO  ('H', 0x80)
+#define SNDRV_EMU10K1_IOCTL_CONTINUE	_IO  ('H', 0x81)
+#define SNDRV_EMU10K1_IOCTL_ZERO_TRAM_COUNTER _IO ('H', 0x82)
+#define SNDRV_EMU10K1_IOCTL_SINGLE_STEP	_IOW ('H', 0x83, int)
+#define SNDRV_EMU10K1_IOCTL_DBG_READ	_IOR ('H', 0x84, int)
+
+#endif	/* __SOUND_EMU10K1_H */
diff --git a/include/sound/hdsp.h b/include/sound/hdsp.h
new file mode 100644
index 0000000..5adaf7b
--- /dev/null
+++ b/include/sound/hdsp.h
@@ -0,0 +1,113 @@
+#ifndef __SOUND_HDSP_H
+#define __SOUND_HDSP_H
+
+/*
+ *   Copyright (C) 2003 Thomas Charbonnel (thomas@undata.org)
+ *    
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <stdint.h>
+
+#define HDSP_MATRIX_MIXER_SIZE 2048
+
+typedef enum {
+	Digiface,
+	Multiface,
+	H9652,
+	H9632,
+	RPM,
+	Undefined,
+} HDSP_IO_Type;
+
+typedef struct _snd_hdsp_peak_rms hdsp_peak_rms_t;
+
+struct _snd_hdsp_peak_rms {
+	uint32_t input_peaks[26];
+	uint32_t playback_peaks[26];
+	uint32_t output_peaks[28];
+	uint64_t input_rms[26];
+	uint64_t playback_rms[26];
+	/* These are only used for H96xx cards */
+	uint64_t output_rms[26];
+};
+
+#define SNDRV_HDSP_IOCTL_GET_PEAK_RMS _IOR('H', 0x40, hdsp_peak_rms_t)
+
+typedef struct _snd_hdsp_config_info hdsp_config_info_t;
+
+struct _snd_hdsp_config_info {
+	unsigned char pref_sync_ref;
+	unsigned char wordclock_sync_check;
+	unsigned char spdif_sync_check;
+	unsigned char adatsync_sync_check;
+	unsigned char adat_sync_check[3];
+	unsigned char spdif_in;
+	unsigned char spdif_out;
+	unsigned char spdif_professional;
+	unsigned char spdif_emphasis;
+	unsigned char spdif_nonaudio;
+	unsigned int spdif_sample_rate;
+	unsigned int system_sample_rate;
+	unsigned int autosync_sample_rate;
+	unsigned char system_clock_mode;
+	unsigned char clock_source;
+	unsigned char autosync_ref;
+	unsigned char line_out;
+	unsigned char passthru; 
+	unsigned char da_gain;
+	unsigned char ad_gain;
+	unsigned char phone_gain;
+	unsigned char xlr_breakout_cable;
+	unsigned char analog_extension_board;
+};
+
+#define SNDRV_HDSP_IOCTL_GET_CONFIG_INFO _IOR('H', 0x41, hdsp_config_info_t)
+
+typedef struct _snd_hdsp_firmware hdsp_firmware_t;
+
+struct _snd_hdsp_firmware {
+	void *firmware_data;	/* 24413 x 4 bytes */
+};
+
+#define SNDRV_HDSP_IOCTL_UPLOAD_FIRMWARE _IOW('H', 0x42, hdsp_firmware_t)
+
+typedef struct _snd_hdsp_version hdsp_version_t;
+
+struct _snd_hdsp_version {
+	HDSP_IO_Type io_type;
+	unsigned short firmware_rev;
+};
+
+#define SNDRV_HDSP_IOCTL_GET_VERSION _IOR('H', 0x43, hdsp_version_t)
+
+typedef struct _snd_hdsp_mixer hdsp_mixer_t;
+
+struct _snd_hdsp_mixer {
+	unsigned short matrix[HDSP_MATRIX_MIXER_SIZE];
+};
+
+#define SNDRV_HDSP_IOCTL_GET_MIXER _IOR('H', 0x44, hdsp_mixer_t)
+
+typedef struct _snd_hdsp_9632_aeb hdsp_9632_aeb_t;
+
+struct _snd_hdsp_9632_aeb {
+	int aebi;
+	int aebo;
+};
+
+#define SNDRV_HDSP_IOCTL_GET_9632_AEB _IOR('H', 0x45, hdsp_9632_aeb_t)
+
+#endif /* __SOUND_HDSP_H */
diff --git a/include/sound/hdspm.h b/include/sound/hdspm.h
new file mode 100644
index 0000000..1774ff5
--- /dev/null
+++ b/include/sound/hdspm.h
@@ -0,0 +1,229 @@
+#ifndef __SOUND_HDSPM_H
+#define __SOUND_HDSPM_H
+/*
+ *   Copyright (C) 2003 Winfried Ritsch (IEM)
+ *   based on hdsp.h from Thomas Charbonnel (thomas@undata.org)
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/* Maximum channels is 64 even on 56Mode you have 64playbacks to matrix */
+#define HDSPM_MAX_CHANNELS      64
+
+enum hdspm_io_type {
+	MADI,
+	MADIface,
+	AIO,
+	AES32,
+	RayDAT
+};
+
+enum hdspm_speed {
+	ss,
+	ds,
+	qs
+};
+
+/* -------------------- IOCTL Peak/RMS Meters -------------------- */
+
+struct hdspm_peak_rms {
+	uint32_t input_peaks[64];
+	uint32_t playback_peaks[64];
+	uint32_t output_peaks[64];
+
+	uint64_t input_rms[64];
+	uint64_t playback_rms[64];
+	uint64_t output_rms[64];
+
+	uint8_t speed; /* enum {ss, ds, qs} */
+	int status2;
+};
+
+#define SNDRV_HDSPM_IOCTL_GET_PEAK_RMS \
+	_IOR('H', 0x42, struct hdspm_peak_rms)
+
+/* ------------ CONFIG block IOCTL ---------------------- */
+
+struct hdspm_config {
+	unsigned char pref_sync_ref;
+	unsigned char wordclock_sync_check;
+	unsigned char madi_sync_check;
+	unsigned int system_sample_rate;
+	unsigned int autosync_sample_rate;
+	unsigned char system_clock_mode;
+	unsigned char clock_source;
+	unsigned char autosync_ref;
+	unsigned char line_out;
+	unsigned int passthru;
+	unsigned int analog_out;
+};
+
+#define SNDRV_HDSPM_IOCTL_GET_CONFIG \
+	_IOR('H', 0x41, struct hdspm_config)
+
+/**
+ * If there's a TCO (TimeCode Option) board installed,
+ * there are further options and status data available.
+ * The hdspm_ltc structure contains the current SMPTE
+ * timecode and some status information and can be
+ * obtained via SNDRV_HDSPM_IOCTL_GET_LTC or in the
+ * hdspm_status struct.
+ **/
+
+enum hdspm_ltc_format {
+	format_invalid,
+	fps_24,
+	fps_25,
+	fps_2997,
+	fps_30
+};
+
+enum hdspm_ltc_frame {
+	frame_invalid,
+	drop_frame,
+	full_frame
+};
+
+enum hdspm_ltc_input_format {
+	ntsc,
+	pal,
+	no_video
+};
+
+struct hdspm_ltc {
+	unsigned int ltc;
+
+	enum hdspm_ltc_format format;
+	enum hdspm_ltc_frame frame;
+	enum hdspm_ltc_input_format input_format;
+};
+
+#define SNDRV_HDSPM_IOCTL_GET_LTC _IOR('H', 0x46, struct hdspm_mixer_ioctl)
+
+/**
+ * The status data reflects the device's current state
+ * as determined by the card's configuration and
+ * connection status.
+ **/
+
+enum hdspm_sync {
+	hdspm_sync_no_lock = 0,
+	hdspm_sync_lock = 1,
+	hdspm_sync_sync = 2
+};
+
+enum hdspm_madi_input {
+	hdspm_input_optical = 0,
+	hdspm_input_coax = 1
+};
+
+enum hdspm_madi_channel_format {
+	hdspm_format_ch_64 = 0,
+	hdspm_format_ch_56 = 1
+};
+
+enum hdspm_madi_frame_format {
+	hdspm_frame_48 = 0,
+	hdspm_frame_96 = 1
+};
+
+enum hdspm_syncsource {
+	syncsource_wc = 0,
+	syncsource_madi = 1,
+	syncsource_tco = 2,
+	syncsource_sync = 3,
+	syncsource_none = 4
+};
+
+struct hdspm_status {
+	uint8_t card_type; /* enum hdspm_io_type */
+	enum hdspm_syncsource autosync_source;
+
+	uint64_t card_clock;
+	uint32_t master_period;
+
+	union {
+		struct {
+			uint8_t sync_wc; /* enum hdspm_sync */
+			uint8_t sync_madi; /* enum hdspm_sync */
+			uint8_t sync_tco; /* enum hdspm_sync */
+			uint8_t sync_in; /* enum hdspm_sync */
+			uint8_t madi_input; /* enum hdspm_madi_input */
+			uint8_t channel_format; /* enum hdspm_madi_channel_format */
+			uint8_t frame_format; /* enum hdspm_madi_frame_format */
+		} madi;
+	} card_specific;
+};
+
+#define SNDRV_HDSPM_IOCTL_GET_STATUS \
+	_IOR('H', 0x47, struct hdspm_status)
+
+/**
+ * Get information about the card and its add-ons.
+ **/
+
+#define HDSPM_ADDON_TCO 1
+
+struct hdspm_version {
+	uint8_t card_type; /* enum hdspm_io_type */
+	char cardname[20];
+	unsigned int serial;
+	unsigned short firmware_rev;
+	int addons;
+};
+
+#define SNDRV_HDSPM_IOCTL_GET_VERSION _IOR('H', 0x48, struct hdspm_version)
+
+/* ------------- get Matrix Mixer IOCTL --------------- */
+
+/* MADI mixer: 64inputs+64playback in 64outputs = 8192 => *4Byte =
+ * 32768 Bytes
+ */
+
+/* organisation is 64 channelfader in a continous memory block */
+/* equivalent to hardware definition, maybe for future feature of mmap of
+ * them
+ */
+/* each of 64 outputs has 64 infader and 64 outfader:
+   Ins to Outs mixer[out].in[in], Outstreams to Outs mixer[out].pb[pb] */
+
+#define HDSPM_MIXER_CHANNELS HDSPM_MAX_CHANNELS
+
+struct hdspm_channelfader {
+	unsigned int in[HDSPM_MIXER_CHANNELS];
+	unsigned int pb[HDSPM_MIXER_CHANNELS];
+};
+
+struct hdspm_mixer {
+	struct hdspm_channelfader ch[HDSPM_MIXER_CHANNELS];
+};
+
+struct hdspm_mixer_ioctl {
+	struct hdspm_mixer *mixer;
+};
+
+/* use indirect access due to the limit of ioctl bit size */
+#define SNDRV_HDSPM_IOCTL_GET_MIXER _IOR('H', 0x44, struct hdspm_mixer_ioctl)
+
+/* typedefs for compatibility to user-space */
+typedef struct hdspm_peak_rms hdspm_peak_rms_t;
+typedef struct hdspm_config_info hdspm_config_info_t;
+typedef struct hdspm_version hdspm_version_t;
+typedef struct hdspm_channelfader snd_hdspm_channelfader_t;
+typedef struct hdspm_mixer hdspm_mixer_t;
+
+
+#endif
diff --git a/include/sound/sb16_csp.h b/include/sound/sb16_csp.h
new file mode 100644
index 0000000..78817b4
--- /dev/null
+++ b/include/sound/sb16_csp.h
@@ -0,0 +1,115 @@
+#ifndef __SOUND_SB16_CSP_H
+#define __SOUND_SB16_CSP_H
+
+/*
+ *  Copyright (c) 1999 by Uros Bizjak <uros@kss-loka.si>
+ *                        Takashi Iwai <tiwai@suse.de>
+ *
+ *  SB16ASP/AWE32 CSP control
+ *
+ *   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
+ *
+ */
+
+/* CSP modes */
+#define SNDRV_SB_CSP_MODE_NONE		0x00
+#define SNDRV_SB_CSP_MODE_DSP_READ	0x01	/* Record from DSP */
+#define SNDRV_SB_CSP_MODE_DSP_WRITE	0x02	/* Play to DSP */
+#define SNDRV_SB_CSP_MODE_QSOUND		0x04	/* QSound */
+
+/* CSP load flags */
+#define SNDRV_SB_CSP_LOAD_FROMUSER	0x01
+#define SNDRV_SB_CSP_LOAD_INITBLOCK	0x02
+
+/* CSP sample width */
+#define SNDRV_SB_CSP_SAMPLE_8BIT		0x01
+#define SNDRV_SB_CSP_SAMPLE_16BIT		0x02
+
+/* CSP channels */
+#define SNDRV_SB_CSP_MONO			0x01
+#define SNDRV_SB_CSP_STEREO		0x02
+
+/* CSP rates */
+#define SNDRV_SB_CSP_RATE_8000		0x01
+#define SNDRV_SB_CSP_RATE_11025		0x02
+#define SNDRV_SB_CSP_RATE_22050		0x04
+#define SNDRV_SB_CSP_RATE_44100		0x08
+#define SNDRV_SB_CSP_RATE_ALL		0x0f
+
+/* CSP running state */
+#define SNDRV_SB_CSP_ST_IDLE		0x00
+#define SNDRV_SB_CSP_ST_LOADED		0x01
+#define SNDRV_SB_CSP_ST_RUNNING		0x02
+#define SNDRV_SB_CSP_ST_PAUSED		0x04
+#define SNDRV_SB_CSP_ST_AUTO		0x08
+#define SNDRV_SB_CSP_ST_QSOUND		0x10
+
+/* maximum QSound value (180 degrees right) */
+#define SNDRV_SB_CSP_QSOUND_MAX_RIGHT	0x20
+
+/* maximum microcode RIFF file size */
+#define SNDRV_SB_CSP_MAX_MICROCODE_FILE_SIZE	0x3000
+
+/* microcode header */
+typedef struct snd_sb_csp_mc_header {
+	char codec_name[16];		/* id name of codec */
+	unsigned short func_req;	/* requested function */
+} snd_sb_csp_mc_header_t;
+
+/* microcode to be loaded */
+typedef struct snd_sb_csp_microcode {
+	snd_sb_csp_mc_header_t info;
+	unsigned char data[SNDRV_SB_CSP_MAX_MICROCODE_FILE_SIZE];
+} snd_sb_csp_microcode_t;
+
+/* start CSP with sample_width in mono/stereo */
+typedef struct snd_sb_csp_start {
+	int sample_width;	/* sample width, look above */
+	int channels;		/* channels, look above */
+} snd_sb_csp_start_t;
+
+/* CSP information */
+typedef struct snd_sb_csp_info {
+	char codec_name[16];		/* id name of codec */
+	unsigned short func_nr;		/* function number */
+	unsigned int acc_format;	/* accepted PCM formats */
+	unsigned short acc_channels;	/* accepted channels */
+	unsigned short acc_width;	/* accepted sample width */
+	unsigned short acc_rates;	/* accepted sample rates */
+	unsigned short csp_mode;	/* CSP mode, see above */
+	unsigned short run_channels;	/* current channels  */
+	unsigned short run_width;	/* current sample width */
+	unsigned short version;		/* version id: 0x10 - 0x1f */
+	unsigned short state;		/* state bits */
+} snd_sb_csp_info_t;
+
+/* HWDEP controls */
+/* get CSP information */
+#define SNDRV_SB_CSP_IOCTL_INFO		_IOR('H', 0x10, snd_sb_csp_info_t)
+/* load microcode to CSP */
+#define SNDRV_SB_CSP_IOCTL_LOAD_CODE	_IOW('H', 0x11, snd_sb_csp_microcode_t)
+/* unload microcode from CSP */
+#define SNDRV_SB_CSP_IOCTL_UNLOAD_CODE	_IO('H', 0x12)
+/* start CSP */
+#define SNDRV_SB_CSP_IOCTL_START	_IOW('H', 0x13, snd_sb_csp_start_t)
+/* stop CSP */
+#define SNDRV_SB_CSP_IOCTL_STOP		_IO('H', 0x14)
+/* pause CSP and DMA transfer */
+#define SNDRV_SB_CSP_IOCTL_PAUSE	_IO('H', 0x15)
+/* restart CSP and DMA transfer */
+#define SNDRV_SB_CSP_IOCTL_RESTART	_IO('H', 0x16)
+
+
+#endif /* __SOUND_SB16_CSP */
diff --git a/include/sound/sscape_ioctl.h b/include/sound/sscape_ioctl.h
new file mode 100644
index 0000000..c6653eb
--- /dev/null
+++ b/include/sound/sscape_ioctl.h
@@ -0,0 +1,21 @@
+#ifndef SSCAPE_IOCTL_H
+#define SSCAPE_IOCTL_H
+
+
+struct sscape_bootblock
+{
+  unsigned char code[256];
+  unsigned version;
+};
+
+#define SSCAPE_MICROCODE_SIZE  65536
+
+struct sscape_microcode
+{
+  unsigned char *code;
+};
+
+#define SND_SSCAPE_LOAD_BOOTB  _IOWR('P', 100, struct sscape_bootblock)
+#define SND_SSCAPE_LOAD_MCODE  _IOW ('P', 101, struct sscape_microcode)
+
+#endif
diff --git a/include/sound/type_compat.h b/include/sound/type_compat.h
new file mode 100644
index 0000000..eec86e4
--- /dev/null
+++ b/include/sound/type_compat.h
@@ -0,0 +1,40 @@
+#ifndef __TYPE_COMPAT_H
+#define __TYPE_COMPAT_H
+
+#ifndef DOC_HIDDEN
+#include <stdint.h>
+typedef uint8_t __u8;
+typedef uint16_t __u16;
+typedef uint32_t __u32;
+typedef int8_t __s8;
+typedef int16_t __s16;
+typedef int32_t __s32;
+
+#include <endian.h>
+#include <byteswap.h>
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+#define __cpu_to_le32(x) (x)
+#define __cpu_to_be32(x) bswap_32(x)
+#define __cpu_to_le16(x) (x)
+#define __cpu_to_be16(x) bswap_16(x)
+#else
+#define __cpu_to_le32(x) bswap_32(x)
+#define __cpu_to_be32(x) (x)
+#define __cpu_to_le16(x) bswap_16(x)
+#define __cpu_to_be16(x) (x)
+#endif
+
+#define __le32_to_cpu __cpu_to_le32
+#define __be32_to_cpu __cpu_to_be32
+#define __le16_to_cpu __cpu_to_le16
+#define __be16_to_cpu __cpu_to_be16
+
+#define __le64 __u64
+#define __le32 __u32
+#define __le16 __u16
+#define __be64 __u64
+#define __be32 __u32
+#define __be16 __u16
+#endif /* DOC_HIDDEN */
+
+#endif /* __TYPE_COMPAT_H */
diff --git a/include/sys.h b/include/sys.h
new file mode 100644
index 0000000..47f587c
--- /dev/null
+++ b/include/sys.h
@@ -0,0 +1,2 @@
+#warning This header is deprecated, use <alsa/asoundlib.h> instead.
+#include <alsa/asoundlib.h>
diff --git a/include/timer.h b/include/timer.h
new file mode 100644
index 0000000..2803f53
--- /dev/null
+++ b/include/timer.h
@@ -0,0 +1,259 @@
+/**
+ * \file include/timer.h
+ * \brief Application interface library for the ALSA driver
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \author Takashi Iwai <tiwai@suse.de>
+ * \date 1998-2001
+ *
+ * Application interface library for the ALSA driver
+ */
+/*
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#ifndef __ALSA_TIMER_H
+#define __ALSA_TIMER_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ *  \defgroup Timer Timer Interface
+ *  Timer Interface. See \ref timer page for more details.
+ *  \{
+ */
+
+/** dlsym version for interface entry callback */
+#define SND_TIMER_DLSYM_VERSION		_dlsym_timer_001
+/** dlsym version for interface entry callback */
+#define SND_TIMER_QUERY_DLSYM_VERSION	_dlsym_timer_query_001
+
+/** timer identification structure */
+typedef struct _snd_timer_id snd_timer_id_t;
+/** timer global info structure */
+typedef struct _snd_timer_ginfo snd_timer_ginfo_t;
+/** timer global params structure */
+typedef struct _snd_timer_gparams snd_timer_gparams_t;
+/** timer global status structure */
+typedef struct _snd_timer_gstatus snd_timer_gstatus_t;
+/** timer info structure */
+typedef struct _snd_timer_info snd_timer_info_t;
+/** timer params structure */
+typedef struct _snd_timer_params snd_timer_params_t;
+/** timer status structure */
+typedef struct _snd_timer_status snd_timer_status_t;
+/** timer master class */
+typedef enum _snd_timer_class {
+	SND_TIMER_CLASS_NONE = -1,	/**< invalid */
+	SND_TIMER_CLASS_SLAVE = 0,	/**< slave timer */
+	SND_TIMER_CLASS_GLOBAL,		/**< global timer */
+	SND_TIMER_CLASS_CARD,		/**< card timer */
+	SND_TIMER_CLASS_PCM,		/**< PCM timer */
+	SND_TIMER_CLASS_LAST = SND_TIMER_CLASS_PCM	/**< last timer */
+} snd_timer_class_t;
+
+/** timer slave class */
+typedef enum _snd_timer_slave_class {
+	SND_TIMER_SCLASS_NONE = 0,		/**< none */
+	SND_TIMER_SCLASS_APPLICATION,		/**< for internal use */
+	SND_TIMER_SCLASS_SEQUENCER,		/**< sequencer timer */
+	SND_TIMER_SCLASS_OSS_SEQUENCER,		/**< OSS sequencer timer */
+	SND_TIMER_SCLASS_LAST = SND_TIMER_SCLASS_OSS_SEQUENCER	/**< last slave timer */
+} snd_timer_slave_class_t;
+
+/** timer read event identification */
+typedef enum _snd_timer_event {
+	SND_TIMER_EVENT_RESOLUTION = 0,	/* val = resolution in ns */
+	SND_TIMER_EVENT_TICK,		/* val = ticks */
+	SND_TIMER_EVENT_START,		/* val = resolution in ns */
+	SND_TIMER_EVENT_STOP,		/* val = 0 */
+	SND_TIMER_EVENT_CONTINUE,	/* val = resolution in ns */
+	SND_TIMER_EVENT_PAUSE,		/* val = 0 */
+	SND_TIMER_EVENT_EARLY,		/* val = 0 */
+	SND_TIMER_EVENT_SUSPEND,	/* val = 0 */
+	SND_TIMER_EVENT_RESUME,		/* val = resolution in ns */
+	/* master timer events for slave timer instances */
+	SND_TIMER_EVENT_MSTART = SND_TIMER_EVENT_START + 10,
+	SND_TIMER_EVENT_MSTOP = SND_TIMER_EVENT_STOP + 10,
+	SND_TIMER_EVENT_MCONTINUE = SND_TIMER_EVENT_CONTINUE + 10,
+	SND_TIMER_EVENT_MPAUSE = SND_TIMER_EVENT_PAUSE + 10,
+	SND_TIMER_EVENT_MSUSPEND = SND_TIMER_EVENT_SUSPEND + 10,
+	SND_TIMER_EVENT_MRESUME = SND_TIMER_EVENT_RESUME + 10	
+} snd_timer_event_t;
+
+/** timer read structure */
+typedef struct _snd_timer_read {
+	unsigned int resolution;	/**< tick resolution in nanoseconds */
+        unsigned int ticks;		/**< count of happened ticks */
+} snd_timer_read_t;
+
+/** timer tstamp + event read structure */
+typedef struct _snd_timer_tread {
+	snd_timer_event_t event;	/**< Timer event */
+	snd_htimestamp_t tstamp;	/**< Time stamp of each event */
+	unsigned int val;		/**< Event value */
+} snd_timer_tread_t;
+
+/** global timer - system */
+#define SND_TIMER_GLOBAL_SYSTEM 0
+/** global timer - RTC */
+#define SND_TIMER_GLOBAL_RTC 	1
+/** global timer - HPET */
+#define SND_TIMER_GLOBAL_HPET	2
+/** global timer - HRTIMER */
+#define SND_TIMER_GLOBAL_HRTIMER 3
+
+/** timer open mode flag - non-blocking behaviour */
+#define SND_TIMER_OPEN_NONBLOCK		(1<<0)
+/** use timestamps and event notification - enhanced read */
+#define SND_TIMER_OPEN_TREAD		(1<<1)
+
+/** timer handle type */
+typedef enum _snd_timer_type {
+	/** Kernel level HwDep */
+	SND_TIMER_TYPE_HW = 0,
+	/** Shared memory client timer (not yet implemented) */
+	SND_TIMER_TYPE_SHM,
+	/** INET client timer (not yet implemented) */
+	SND_TIMER_TYPE_INET
+} snd_timer_type_t;
+
+/** timer query handle */
+typedef struct _snd_timer_query snd_timer_query_t;
+/** timer handle */
+typedef struct _snd_timer snd_timer_t;
+
+
+int snd_timer_query_open(snd_timer_query_t **handle, const char *name, int mode);
+int snd_timer_query_open_lconf(snd_timer_query_t **handle, const char *name, int mode, snd_config_t *lconf);
+int snd_timer_query_close(snd_timer_query_t *handle);
+int snd_timer_query_next_device(snd_timer_query_t *handle, snd_timer_id_t *tid);
+int snd_timer_query_info(snd_timer_query_t *handle, snd_timer_ginfo_t *info);
+int snd_timer_query_params(snd_timer_query_t *handle, snd_timer_gparams_t *params);
+int snd_timer_query_status(snd_timer_query_t *handle, snd_timer_gstatus_t *status);
+
+int snd_timer_open(snd_timer_t **handle, const char *name, int mode);
+int snd_timer_open_lconf(snd_timer_t **handle, const char *name, int mode, snd_config_t *lconf);
+int snd_timer_close(snd_timer_t *handle);
+int snd_async_add_timer_handler(snd_async_handler_t **handler, snd_timer_t *timer,
+				snd_async_callback_t callback, void *private_data);
+snd_timer_t *snd_async_handler_get_timer(snd_async_handler_t *handler);
+int snd_timer_poll_descriptors_count(snd_timer_t *handle);
+int snd_timer_poll_descriptors(snd_timer_t *handle, struct pollfd *pfds, unsigned int space);
+int snd_timer_poll_descriptors_revents(snd_timer_t *timer, struct pollfd *pfds, unsigned int nfds, unsigned short *revents);
+int snd_timer_info(snd_timer_t *handle, snd_timer_info_t *timer);
+int snd_timer_params(snd_timer_t *handle, snd_timer_params_t *params);
+int snd_timer_status(snd_timer_t *handle, snd_timer_status_t *status);
+int snd_timer_start(snd_timer_t *handle);
+int snd_timer_stop(snd_timer_t *handle);
+int snd_timer_continue(snd_timer_t *handle);
+ssize_t snd_timer_read(snd_timer_t *handle, void *buffer, size_t size);
+
+size_t snd_timer_id_sizeof(void);
+/** allocate #snd_timer_id_t container on stack */
+#define snd_timer_id_alloca(ptr) __snd_alloca(ptr, snd_timer_id)
+int snd_timer_id_malloc(snd_timer_id_t **ptr);
+void snd_timer_id_free(snd_timer_id_t *obj);
+void snd_timer_id_copy(snd_timer_id_t *dst, const snd_timer_id_t *src);
+
+void snd_timer_id_set_class(snd_timer_id_t *id, int dev_class);
+int snd_timer_id_get_class(snd_timer_id_t *id);
+void snd_timer_id_set_sclass(snd_timer_id_t *id, int dev_sclass);
+int snd_timer_id_get_sclass(snd_timer_id_t *id);
+void snd_timer_id_set_card(snd_timer_id_t *id, int card);
+int snd_timer_id_get_card(snd_timer_id_t *id);
+void snd_timer_id_set_device(snd_timer_id_t *id, int device);
+int snd_timer_id_get_device(snd_timer_id_t *id);
+void snd_timer_id_set_subdevice(snd_timer_id_t *id, int subdevice);
+int snd_timer_id_get_subdevice(snd_timer_id_t *id);
+
+size_t snd_timer_ginfo_sizeof(void);
+/** allocate #snd_timer_ginfo_t container on stack */
+#define snd_timer_ginfo_alloca(ptr) __snd_alloca(ptr, snd_timer_ginfo)
+int snd_timer_ginfo_malloc(snd_timer_ginfo_t **ptr);
+void snd_timer_ginfo_free(snd_timer_ginfo_t *obj);
+void snd_timer_ginfo_copy(snd_timer_ginfo_t *dst, const snd_timer_ginfo_t *src);
+
+int snd_timer_ginfo_set_tid(snd_timer_ginfo_t *obj, snd_timer_id_t *tid);
+snd_timer_id_t *snd_timer_ginfo_get_tid(snd_timer_ginfo_t *obj);
+unsigned int snd_timer_ginfo_get_flags(snd_timer_ginfo_t *obj);
+int snd_timer_ginfo_get_card(snd_timer_ginfo_t *obj);
+char *snd_timer_ginfo_get_id(snd_timer_ginfo_t *obj);
+char *snd_timer_ginfo_get_name(snd_timer_ginfo_t *obj);
+unsigned long snd_timer_ginfo_get_resolution(snd_timer_ginfo_t *obj);
+unsigned long snd_timer_ginfo_get_resolution_min(snd_timer_ginfo_t *obj);
+unsigned long snd_timer_ginfo_get_resolution_max(snd_timer_ginfo_t *obj);
+unsigned int snd_timer_ginfo_get_clients(snd_timer_ginfo_t *obj);
+
+size_t snd_timer_info_sizeof(void);
+/** allocate #snd_timer_info_t container on stack */
+#define snd_timer_info_alloca(ptr) __snd_alloca(ptr, snd_timer_info)
+int snd_timer_info_malloc(snd_timer_info_t **ptr);
+void snd_timer_info_free(snd_timer_info_t *obj);
+void snd_timer_info_copy(snd_timer_info_t *dst, const snd_timer_info_t *src);
+
+int snd_timer_info_is_slave(snd_timer_info_t * info);
+int snd_timer_info_get_card(snd_timer_info_t * info);
+const char *snd_timer_info_get_id(snd_timer_info_t * info);
+const char *snd_timer_info_get_name(snd_timer_info_t * info);
+long snd_timer_info_get_resolution(snd_timer_info_t * info);
+
+size_t snd_timer_params_sizeof(void);
+/** allocate #snd_timer_params_t container on stack */
+#define snd_timer_params_alloca(ptr) __snd_alloca(ptr, snd_timer_params)
+int snd_timer_params_malloc(snd_timer_params_t **ptr);
+void snd_timer_params_free(snd_timer_params_t *obj);
+void snd_timer_params_copy(snd_timer_params_t *dst, const snd_timer_params_t *src);
+
+int snd_timer_params_set_auto_start(snd_timer_params_t * params, int auto_start);
+int snd_timer_params_get_auto_start(snd_timer_params_t * params);
+int snd_timer_params_set_exclusive(snd_timer_params_t * params, int exclusive);
+int snd_timer_params_get_exclusive(snd_timer_params_t * params);
+int snd_timer_params_set_early_event(snd_timer_params_t * params, int early_event);
+int snd_timer_params_get_early_event(snd_timer_params_t * params);
+void snd_timer_params_set_ticks(snd_timer_params_t * params, long ticks);
+long snd_timer_params_get_ticks(snd_timer_params_t * params);
+void snd_timer_params_set_queue_size(snd_timer_params_t * params, long queue_size);
+long snd_timer_params_get_queue_size(snd_timer_params_t * params);
+void snd_timer_params_set_filter(snd_timer_params_t * params, unsigned int filter);
+unsigned int snd_timer_params_get_filter(snd_timer_params_t * params);
+
+size_t snd_timer_status_sizeof(void);
+/** allocate #snd_timer_status_t container on stack */
+#define snd_timer_status_alloca(ptr) __snd_alloca(ptr, snd_timer_status)
+int snd_timer_status_malloc(snd_timer_status_t **ptr);
+void snd_timer_status_free(snd_timer_status_t *obj);
+void snd_timer_status_copy(snd_timer_status_t *dst, const snd_timer_status_t *src);
+
+snd_htimestamp_t snd_timer_status_get_timestamp(snd_timer_status_t * status);
+long snd_timer_status_get_resolution(snd_timer_status_t * status);
+long snd_timer_status_get_lost(snd_timer_status_t * status);
+long snd_timer_status_get_overrun(snd_timer_status_t * status);
+long snd_timer_status_get_queue(snd_timer_status_t * status);
+
+/* deprecated functions, for compatibility */
+long snd_timer_info_get_ticks(snd_timer_info_t * info);
+
+/** \} */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /** __ALSA_TIMER_H */
+
diff --git a/include/use-case.h b/include/use-case.h
new file mode 100644
index 0000000..c37c842
--- /dev/null
+++ b/include/use-case.h
@@ -0,0 +1,381 @@
+/**
+ * \file include/use-case.h
+ * \brief use case interface for the ALSA driver
+ * \author Liam Girdwood <lrg@slimlogic.co.uk>
+ * \author Stefan Schmidt <stefan@slimlogic.co.uk>
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \author Justin Xu <justinx@slimlogic.co.uk>
+ * \date 2008-2010
+ */
+/*
+ *
+ *  This library is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as
+ *  published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ *  Copyright (C) 2008-2010 SlimLogic Ltd
+ *  Copyright (C) 2010 Wolfson Microelectronics PLC
+ *  Copyright (C) 2010 Texas Instruments Inc.
+ *
+ *  Support for the verb/device/modifier core logic and API,
+ *  command line tool and file parser was kindly sponsored by
+ *  Texas Instruments Inc.
+ *  Support for multiple active modifiers and devices,
+ *  transition sequences, multiple client access and user defined use
+ *  cases was kindly sponsored by Wolfson Microelectronics PLC.
+ */
+
+#ifndef __ALSA_USE_CASE_H
+#define __ALSA_USE_CASE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ *  \defgroup Use Case Interface
+ *  The ALSA Use Case manager interface.
+ *  See \ref Usecase page for more details.
+ *  \{
+ */
+
+/**
+ * ALSA Use Case Interface
+ *
+ * The use case manager works by configuring the sound card ALSA kcontrols to
+ * change the hardware digital and analog audio routing to match the requested
+ * device use case. The use case manager kcontrol configurations are stored in
+ * easy to modify text files.
+ *
+ * An audio use case can be defined by a verb and device parameter. The verb
+ * describes the use case action i.e. a phone call, listening to music, recording
+ * a conversation etc. The device describes the physical audio capture and playback
+ * hardware i.e. headphones, phone handset, bluetooth headset, etc.
+ *
+ * It's intended clients will mostly only need to set the use case verb and
+ * device for each system use case change (as the verb and device parameters
+ * cover most audio use cases).
+ *
+ * However there are times when a use case has to be modified at runtime. e.g.
+ *
+ *  o Incoming phone call when the device is playing music
+ *  o Recording sections of a phone call
+ *  o Playing tones during a call.
+ *
+ * In order to allow asynchronous runtime use case adaptations, we have a third
+ * optional modifier parameter that can be used to further configure
+ * the use case during live audio runtime.
+ *
+ * This interface allows clients to :-
+ *
+ *  o Query the supported use case verbs, devices and modifiers for the machine.
+ *  o Set and Get use case verbs, devices and modifiers for the machine.
+ *  o Get the ALSA PCM playback and capture device PCMs for use case verb,
+ *     use case device and modifier.
+ *  o Get the TQ parameter for each use case verb, use case device and
+ *     modifier.
+ *  o Get the ALSA master playback and capture volume/switch kcontrols
+ *     for each use case.
+ */
+
+
+/*
+ * Use Case Verb.
+ *
+ * The use case verb is the main device audio action. e.g. the "HiFi" use
+ * case verb will configure the audio hardware for HiFi Music playback
+ * and capture.
+ */
+#define SND_USE_CASE_VERB_INACTIVE		"Inactive"
+#define SND_USE_CASE_VERB_HIFI			"HiFi"
+#define SND_USE_CASE_VERB_HIFI_LOW_POWER	"HiFi Low Power"
+#define SND_USE_CASE_VERB_VOICE			"Voice"
+#define SND_USE_CASE_VERB_VOICE_LOW_POWER	"Voice Low Power"
+#define SND_USE_CASE_VERB_VOICECALL		"Voice Call"
+#define SND_USE_CASE_VERB_IP_VOICECALL		"Voice Call IP"
+#define SND_USE_CASE_VERB_ANALOG_RADIO		"FM Analog Radio"
+#define SND_USE_CASE_VERB_DIGITAL_RADIO		"FM Digital Radio"
+/* add new verbs to end of list */
+
+
+/*
+ * Use Case Device.
+ *
+ * Physical system devices the render and capture audio. Devices can be OR'ed
+ * together to support audio on simultaneous devices.
+ */
+#define SND_USE_CASE_DEV_NONE		"None"
+#define SND_USE_CASE_DEV_SPEAKER	"Speaker"
+#define SND_USE_CASE_DEV_LINE		"Line"
+#define SND_USE_CASE_DEV_HEADPHONES	"Headphones"
+#define SND_USE_CASE_DEV_HEADSET	"Headset"
+#define SND_USE_CASE_DEV_HANDSET	"Handset"
+#define SND_USE_CASE_DEV_BLUETOOTH	"Bluetooth"
+#define SND_USE_CASE_DEV_EARPIECE	"Earpiece"
+#define SND_USE_CASE_DEV_SPDIF		"SPDIF"
+#define SND_USE_CASE_DEV_HDMI		"HDMI"
+/* add new devices to end of list */
+
+
+/*
+ * Use Case Modifiers.
+ *
+ * The use case modifier allows runtime configuration changes to deal with
+ * asynchronous events.
+ *
+ * e.g. to record a voice call :-
+ *  1. Set verb to SND_USE_CASE_VERB_VOICECALL (for voice call)
+ *  2. Set modifier SND_USE_CASE_MOD_CAPTURE_VOICE when capture required.
+ *  3. Call snd_use_case_get("CapturePCM") to get ALSA source PCM name
+ *     with captured voice pcm data.
+ *
+ * e.g. to play a ring tone when listenin to MP3 Music :-
+ *  1. Set verb to SND_USE_CASE_VERB_HIFI (for MP3 playback)
+ *  2. Set modifier to SND_USE_CASE_MOD_PLAY_TONE when incoming call happens.
+ *  3. Call snd_use_case_get("PlaybackPCM") to get ALSA PCM sink name for
+ *     ringtone pcm data.
+ */
+#define SND_USE_CASE_MOD_CAPTURE_VOICE		"Capture Voice"
+#define SND_USE_CASE_MOD_CAPTURE_MUSIC		"Capture Music"
+#define SND_USE_CASE_MOD_PLAY_MUSIC		"Play Music"
+#define SND_USE_CASE_MOD_PLAY_VOICE		"Play Voice"
+#define SND_USE_CASE_MOD_PLAY_TONE		"Play Tone"
+#define SND_USE_CASE_MOD_ECHO_REF		"Echo Reference"
+/* add new modifiers to end of list */
+
+
+/**
+ * TQ - Tone Quality
+ *
+ * The interface allows clients to determine the audio TQ required for each
+ * use case verb and modifier. It's intended as an optional hint to the
+ * audio driver in order to lower power consumption.
+ *
+ */
+#define SND_USE_CASE_TQ_MUSIC		"Music"
+#define SND_USE_CASE_TQ_VOICE		"Voice"
+#define SND_USE_CASE_TQ_TONES		"Tones"
+
+/** use case container */
+typedef struct snd_use_case_mgr snd_use_case_mgr_t;
+
+/**
+ * \brief Create an identifier
+ * \param fmt Format (sprintf like)
+ * \param ... Optional arguments for sprintf like format
+ * \return Allocated string identifier or NULL on error
+ */
+char *snd_use_case_identifier(const char *fmt, ...);
+
+/**
+ * \brief Free a string list
+ * \param list The string list to free
+ * \param items Count of strings
+ * \return Zero if success, otherwise a negative error code
+ */
+int snd_use_case_free_list(const char *list[], int items);
+
+/**
+ * \brief Obtain a list of entries
+ * \param uc_mgr Use case manager (may be NULL - card list)
+ * \param identifier (may be NULL - card list)
+ * \param list Returned allocated list
+ * \return Number of list entries if success, otherwise a negative error code
+ *
+ * Defined identifiers:
+ *   NULL 		- get card list
+ *			  (in pair cardname+comment)
+ *   _verbs		- get verb list
+ *			  (in pair verb+comment)
+ *   _devices[/<verb>]	- get list of supported devices
+ *			  (in pair device+comment)
+ *   _modifiers[/<verb>]- get list of supported modifiers
+ *			  (in pair modifier+comment)
+ *   TQ[/<verb>]	- get list of TQ identifiers
+ *   _enadevs		- get list of enabled devices
+ *   _enamods		- get list of enabled modifiers
+ *
+ *   _supporteddevs/<modifier>|<device>[/<verb>]   - list of supported devices
+ *   _conflictingdevs/<modifier>|<device>[/<verb>] - list of conflicting devices
+ *   Note that at most one of the supported/conflicting devs lists has
+ *   any entries, and when neither is present, all devices are supported.
+ *
+ */
+int snd_use_case_get_list(snd_use_case_mgr_t *uc_mgr,
+                          const char *identifier,
+                          const char **list[]);
+
+
+/**
+ * \brief Get current - string
+ * \param uc_mgr Use case manager
+ * \param identifier 
+ * \param value Value pointer
+ * \return Zero if success, otherwise a negative error code
+ *
+ * Note: String is dynamically allocated, use free() to
+ * deallocate this string.
+ *
+ * Known identifiers:
+ *   NULL 		- return current card
+ *   _verb		- return current verb
+ *
+ *   [=]<NAME>[/[<modifier>|</device>][/<verb>]]
+ *                      - value identifier <NAME>
+ *                      - Search starts at given modifier or device if any,
+ *                          else at a verb
+ *                      - Search starts at given verb if any,
+ *                          else current verb
+ *                      - Searches modifier/device, then verb, then defaults
+ *                      - Specify a leading "=" to search only the exact
+ *                        device/modifier/verb specified, and not search
+ *                        through each object in turn.
+ *                      - Examples:
+ *                          "PlaybackPCM/Play Music"
+ *                          "CapturePCM/SPDIF"
+ *                        From ValueDefaults only:
+ *                          "=Variable"
+ *                        From current active verb:
+ *                          "=Variable//"
+ *                        From verb "Verb":
+ *                          "=Variable//Verb"
+ *                        From "Modifier" in current active verb:
+ *                          "=Variable/Modifier/"
+ *                        From "Modifier" in "Verb":
+ *                          "=Variable/Modifier/Verb"
+ *
+ * Recommended names for values:
+ *   TQ			- Tone Quality
+ *   PlaybackPCM	- full PCM playback device name
+ *   CapturePCM		- full PCM capture device name
+ *   PlaybackCTL	- playback control device name
+ *   PlaybackVolume	- playback control volume ID string
+ *   PlaybackSwitch	- playback control switch ID string
+ *   CaptureCTL		- capture control device name
+ *   CaptureVolume	- capture control volume ID string
+ *   CaptureSwitch	- capture control switch ID string
+ *   PlaybackMixer	- name of playback mixer
+ *   PlaybackMixerID	- mixer playback ID
+ *   CaptureMixer	- name of capture mixer
+ *   CaptureMixerID	- mixer capture ID
+ */
+int snd_use_case_get(snd_use_case_mgr_t *uc_mgr,
+                     const char *identifier,
+                     const char **value);
+
+/**
+ * \brief Get current - integer
+ * \param uc_mgr Use case manager
+ * \param identifier 
+ * \param value result 
+ * \return Zero if success, otherwise a negative error code
+ *
+ * Known identifiers:
+ *   _devstatus/<device>	- return status for given device
+ *   _modstatus/<modifier>	- return status for given modifier
+ */
+int snd_use_case_geti(snd_use_case_mgr_t *uc_mgr,
+		      const char *identifier,
+		      long *value);
+
+/**
+ * \brief Set new
+ * \param uc_mgr Use case manager
+ * \param identifier
+ * \param value Value
+ * \return Zero if success, otherwise a negative error code
+ *
+ * Known identifiers:
+ *   _verb 		- set current verb = value
+ *   _enadev		- enable given device = value
+ *   _disdev		- disable given device = value
+ *   _swdev/<old_device> - new_device = value
+ *			- disable old_device and then enable new_device
+ *			- if old_device is not enabled just return
+ *			- check transmit sequence firstly
+ *   _enamod		- enable given modifier = value
+ *   _dismod		- disable given modifier = value
+ *   _swmod/<old_modifier> - new_modifier = value
+ *			- disable old_modifier and then enable new_modifier
+ *			- if old_modifier is not enabled just return
+ *			- check transmit sequence firstly
+ */
+int snd_use_case_set(snd_use_case_mgr_t *uc_mgr,
+                     const char *identifier,
+                     const char *value);
+
+/**
+ * \brief Open and initialise use case core for sound card
+ * \param uc_mgr Returned use case manager pointer
+ * \param card_name Sound card name.
+ * \return zero if success, otherwise a negative error code
+ */
+int snd_use_case_mgr_open(snd_use_case_mgr_t **uc_mgr, const char *card_name);
+
+
+/**
+ * \brief Reload and re-parse use case configuration files for sound card.
+ * \param uc_mgr Use case manager
+ * \return zero if success, otherwise a negative error code
+ */
+int snd_use_case_mgr_reload(snd_use_case_mgr_t *uc_mgr);
+
+/**
+ * \brief Close use case manager
+ * \param uc_mgr Use case manager
+ * \return zero if success, otherwise a negative error code
+ */
+int snd_use_case_mgr_close(snd_use_case_mgr_t *uc_mgr);
+
+/**
+ * \brief Reset use case manager verb, device, modifier to deafult settings.
+ * \param uc_mgr Use case manager
+ * \return zero if success, otherwise a negative error code
+ */
+int snd_use_case_mgr_reset(snd_use_case_mgr_t *uc_mgr);
+
+/*
+ * helper functions
+ */
+
+/**
+ * \brief Obtain a list of cards
+ * \param list Returned allocated list
+ * \return Number of list entries if success, otherwise a negative error code
+ */
+static inline int snd_use_case_card_list(const char **list[])
+{
+	return snd_use_case_get_list(NULL, NULL, list);
+}
+
+/**
+ * \brief Obtain a list of verbs
+ * \param uc_mgr Use case manager
+ * \param list Returned list of verbs
+ * \return Number of list entries if success, otherwise a negative error code
+ */
+static inline int snd_use_case_verb_list(snd_use_case_mgr_t *uc_mgr,
+					 const char **list[])
+{
+	return snd_use_case_get_list(uc_mgr, "_verbs", list);
+}
+
+/**
+ *  \}
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __ALSA_USE_CASE_H */
diff --git a/m4/attributes.m4 b/m4/attributes.m4
new file mode 100644
index 0000000..e86456a
--- /dev/null
+++ b/m4/attributes.m4
@@ -0,0 +1,311 @@
+dnl Macros to check the presence of generic (non-typed) symbols.
+dnl Copyright (c) 2006-2007 Diego Pettenò <flameeyes@gmail.com>
+dnl Copyright (c) 2006-2007 xine project
+dnl
+dnl This program is free software; you can redistribute it and/or modify
+dnl it under the terms of the GNU General Public License as published by
+dnl the Free Software Foundation; either version 2, or (at your option)
+dnl any later version.
+dnl
+dnl This program is distributed in the hope that it will be useful,
+dnl but WITHOUT ANY WARRANTY; without even the implied warranty of
+dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+dnl GNU General Public License for more details.
+dnl
+dnl You should have received a copy of the GNU General Public License
+dnl along with this program; if not, write to the Free Software
+dnl Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+dnl 02110-1301, USA.
+dnl
+dnl As a special exception, the copyright owners of the
+dnl macro gives unlimited permission to copy, distribute and modify the
+dnl configure scripts that are the output of Autoconf when processing the
+dnl Macro. You need not follow the terms of the GNU General Public
+dnl License when using or distributing such scripts, even though portions
+dnl of the text of the Macro appear in them. The GNU General Public
+dnl License (GPL) does govern all other use of the material that
+dnl constitutes the Autoconf Macro.
+dnl 
+dnl This special exception to the GPL applies to versions of the
+dnl Autoconf Macro released by this project. When you make and
+dnl distribute a modified version of the Autoconf Macro, you may extend
+dnl this special exception to the GPL to apply to your modified version as
+dnl well.
+
+dnl Check if the flag is supported by compiler
+dnl CC_CHECK_CFLAGS_SILENT([FLAG], [ACTION-IF-FOUND],[ACTION-IF-NOT-FOUND])
+
+AC_DEFUN([CC_CHECK_CFLAGS_SILENT], [
+  AC_CACHE_VAL(AS_TR_SH([cc_cv_cflags_$1]),
+    [ac_save_CFLAGS="$CFLAGS"
+     CFLAGS="$CFLAGS $1"
+     AC_COMPILE_IFELSE([int a;],
+       [eval "AS_TR_SH([cc_cv_cflags_$1])='yes'"],
+       [eval "AS_TR_SH([cc_cv_cflags_$1])='no'"])
+     CFLAGS="$ac_save_CFLAGS"
+    ])
+
+  AS_IF([eval test x$]AS_TR_SH([cc_cv_cflags_$1])[ = xyes],
+    [$2], [$3])
+])
+
+dnl Check if the flag is supported by compiler (cacheable)
+dnl CC_CHECK_CFLAGS([FLAG], [ACTION-IF-FOUND],[ACTION-IF-NOT-FOUND])
+
+AC_DEFUN([CC_CHECK_CFLAGS], [
+  AC_CACHE_CHECK([if $CC supports $1 flag],
+    AS_TR_SH([cc_cv_cflags_$1]),
+    CC_CHECK_CFLAGS_SILENT([$1]) dnl Don't execute actions here!
+  )
+
+  AS_IF([eval test x$]AS_TR_SH([cc_cv_cflags_$1])[ = xyes],
+    [$2], [$3])
+])
+
+dnl CC_CHECK_CFLAG_APPEND(FLAG, [action-if-found], [action-if-not-found])
+dnl Check for CFLAG and appends them to CFLAGS if supported
+AC_DEFUN([CC_CHECK_CFLAG_APPEND], [
+  AC_CACHE_CHECK([if $CC supports $1 flag],
+    AS_TR_SH([cc_cv_cflags_$1]),
+    CC_CHECK_CFLAGS_SILENT([$1]) dnl Don't execute actions here!
+  )
+
+  AS_IF([eval test x$]AS_TR_SH([cc_cv_cflags_$1])[ = xyes],
+    [CFLAGS="$CFLAGS $1"; $2], [$3])
+])
+
+dnl CC_CHECK_CFLAGS_APPEND([FLAG1 FLAG2], [action-if-found], [action-if-not])
+AC_DEFUN([CC_CHECK_CFLAGS_APPEND], [
+  for flag in $1; do
+    CC_CHECK_CFLAG_APPEND($flag, [$2], [$3])
+  done
+])
+
+dnl Check if the flag is supported by linker (cacheable)
+dnl CC_CHECK_LDFLAGS([FLAG], [ACTION-IF-FOUND],[ACTION-IF-NOT-FOUND])
+
+AC_DEFUN([CC_CHECK_LDFLAGS], [
+  AC_CACHE_CHECK([if $CC supports $1 flag],
+    AS_TR_SH([cc_cv_ldflags_$1]),
+    [ac_save_LDFLAGS="$LDFLAGS"
+     LDFLAGS="$LDFLAGS $1"
+     AC_LINK_IFELSE([int main() { return 1; }],
+       [eval "AS_TR_SH([cc_cv_ldflags_$1])='yes'"],
+       [eval "AS_TR_SH([cc_cv_ldflags_$1])="])
+     LDFLAGS="$ac_save_LDFLAGS"
+    ])
+
+  AS_IF([eval test x$]AS_TR_SH([cc_cv_ldflags_$1])[ = xyes],
+    [$2], [$3])
+])
+
+dnl define the LDFLAGS_NOUNDEFINED variable with the correct value for
+dnl the current linker to avoid undefined references in a shared object.
+AC_DEFUN([CC_NOUNDEFINED], [
+  dnl We check $host for which systems to enable this for.
+  AC_REQUIRE([AC_CANONICAL_HOST])
+
+  case $host in
+     dnl FreeBSD (et al.) does not complete linking for shared objects when pthreads
+     dnl are requested, as different implementations are present; to avoid problems
+     dnl use -Wl,-z,defs only for those platform not behaving this way.
+     *-freebsd*) ;;
+     *)
+        dnl First of all check for the --no-undefined variant of GNU ld. This allows
+        dnl for a much more readable commandline, so that people can understand what
+        dnl it does without going to look for what the heck -z defs does.
+   	for possible_flags in "-Wl,--no-undefined" "-Wl,-z,defs"; do
+          CC_CHECK_LDFLAGS([$possible_flags], [LDFLAGS_NOUNDEFINED="$possible_flags"])
+	  break
+        done
+	;;
+  esac
+
+  AC_SUBST([LDFLAGS_NOUNDEFINED])
+])
+
+dnl Check for a -Werror flag or equivalent. -Werror is the GCC
+dnl and ICC flag that tells the compiler to treat all the warnings
+dnl as fatal. We usually need this option to make sure that some
+dnl constructs (like attributes) are not simply ignored.
+dnl
+dnl Other compilers don't support -Werror per se, but they support
+dnl an equivalent flag:
+dnl  - Sun Studio compiler supports -errwarn=%all
+AC_DEFUN([CC_CHECK_WERROR], [
+  AC_CACHE_CHECK(
+    [for $CC way to treat warnings as errors],
+    [cc_cv_werror],
+    [CC_CHECK_CFLAGS_SILENT([-Werror], [cc_cv_werror=-Werror],
+      [CC_CHECK_CFLAGS_SILENT([-errwarn=%all], [cc_cv_werror=-errwarn=%all])])
+    ])
+])
+
+AC_DEFUN([CC_CHECK_ATTRIBUTE], [
+  AC_REQUIRE([CC_CHECK_WERROR])
+  AC_CACHE_CHECK([if $CC supports __attribute__(( ifelse([$2], , [$1], [$2]) ))],
+    AS_TR_SH([cc_cv_attribute_$1]),
+    [ac_save_CFLAGS="$CFLAGS"
+     CFLAGS="$CFLAGS $cc_cv_werror"
+     AC_COMPILE_IFELSE([$3],
+       [eval "AS_TR_SH([cc_cv_attribute_$1])='yes'"],
+       [eval "AS_TR_SH([cc_cv_attribute_$1])='no'"])
+     CFLAGS="$ac_save_CFLAGS"
+    ])
+
+  AS_IF([eval test x$]AS_TR_SH([cc_cv_attribute_$1])[ = xyes],
+    [AC_DEFINE(
+       AS_TR_CPP([SUPPORT_ATTRIBUTE_$1]), 1,
+         [Define this if the compiler supports __attribute__(( ifelse([$2], , [$1], [$2]) ))]
+         )
+     $4],
+    [$5])
+])
+
+AC_DEFUN([CC_ATTRIBUTE_CONSTRUCTOR], [
+  CC_CHECK_ATTRIBUTE(
+    [constructor],,
+    [void __attribute__((constructor)) ctor() { int a; }],
+    [$1], [$2])
+])
+
+AC_DEFUN([CC_ATTRIBUTE_FORMAT], [
+  CC_CHECK_ATTRIBUTE(
+    [format], [format(printf, n, n)],
+    [void __attribute__((format(printf, 1, 2))) printflike(const char *fmt, ...) { fmt = (void *)0; }],
+    [$1], [$2])
+])
+
+AC_DEFUN([CC_ATTRIBUTE_FORMAT_ARG], [
+  CC_CHECK_ATTRIBUTE(
+    [format_arg], [format_arg(printf)],
+    [char *__attribute__((format_arg(1))) gettextlike(const char *fmt) { fmt = (void *)0; }],
+    [$1], [$2])
+])
+
+AC_DEFUN([CC_ATTRIBUTE_VISIBILITY], [
+  CC_CHECK_ATTRIBUTE(
+    [visibility_$1], [visibility("$1")],
+    [void __attribute__((visibility("$1"))) $1_function() { }],
+    [$2], [$3])
+])
+
+AC_DEFUN([CC_ATTRIBUTE_NONNULL], [
+  CC_CHECK_ATTRIBUTE(
+    [nonnull], [nonnull()],
+    [void __attribute__((nonnull())) some_function(void *foo, void *bar) { foo = (void*)0; bar = (void*)0; }],
+    [$1], [$2])
+])
+
+AC_DEFUN([CC_ATTRIBUTE_UNUSED], [
+  CC_CHECK_ATTRIBUTE(
+    [unused], ,
+    [void some_function(void *foo, __attribute__((unused)) void *bar);],
+    [$1], [$2])
+])
+
+AC_DEFUN([CC_ATTRIBUTE_SENTINEL], [
+  CC_CHECK_ATTRIBUTE(
+    [sentinel], ,
+    [void some_function(void *foo, ...) __attribute__((sentinel));],
+    [$1], [$2])
+])
+
+AC_DEFUN([CC_ATTRIBUTE_DEPRECATED], [
+  CC_CHECK_ATTRIBUTE(
+    [deprecated], ,
+    [void some_function(void *foo, ...) __attribute__((deprecated));],
+    [$1], [$2])
+])
+
+AC_DEFUN([CC_ATTRIBUTE_ALIAS], [
+  CC_CHECK_ATTRIBUTE(
+    [alias], [weak, alias],
+    [void other_function(void *foo) { }
+     void some_function(void *foo) __attribute__((weak, alias("other_function")));],
+    [$1], [$2])
+])
+
+AC_DEFUN([CC_ATTRIBUTE_MALLOC], [
+  CC_CHECK_ATTRIBUTE(
+    [malloc], ,
+    [void * __attribute__((malloc)) my_alloc(int n);],
+    [$1], [$2])
+])
+
+AC_DEFUN([CC_ATTRIBUTE_PACKED], [
+  CC_CHECK_ATTRIBUTE(
+    [packed], ,
+    [struct astructure { char a; int b; long c; void *d; } __attribute__((packed));],
+    [$1], [$2])
+])
+
+AC_DEFUN([CC_ATTRIBUTE_CONST], [
+  CC_CHECK_ATTRIBUTE(
+    [const], ,
+    [int __attribute__((const)) twopow(int n) { return 1 << n; } ],
+    [$1], [$2])
+])
+
+AC_DEFUN([CC_FLAG_VISIBILITY], [
+  AC_REQUIRE([CC_CHECK_WERROR])
+  AC_CACHE_CHECK([if $CC supports -fvisibility=hidden],
+    [cc_cv_flag_visibility],
+    [cc_flag_visibility_save_CFLAGS="$CFLAGS"
+     CFLAGS="$CFLAGS $cc_cv_werror"
+     CC_CHECK_CFLAGS_SILENT([-fvisibility=hidden],
+	cc_cv_flag_visibility='yes',
+	cc_cv_flag_visibility='no')
+     CFLAGS="$cc_flag_visibility_save_CFLAGS"])
+  
+  AS_IF([test "x$cc_cv_flag_visibility" = "xyes"],
+    [AC_DEFINE([SUPPORT_FLAG_VISIBILITY], 1,
+       [Define this if the compiler supports the -fvisibility flag])
+     $1],
+    [$2])
+])
+
+AC_DEFUN([CC_FUNC_EXPECT], [
+  AC_REQUIRE([CC_CHECK_WERROR])
+  AC_CACHE_CHECK([if compiler has __builtin_expect function],
+    [cc_cv_func_expect],
+    [ac_save_CFLAGS="$CFLAGS"
+     CFLAGS="$CFLAGS $cc_cv_werror"
+     AC_COMPILE_IFELSE(
+       [int some_function() {
+        int a = 3;
+        return (int)__builtin_expect(a, 3);
+	}],
+       [cc_cv_func_expect=yes],
+       [cc_cv_func_expect=no])
+     CFLAGS="$ac_save_CFLAGS"
+    ])
+
+  AS_IF([test "x$cc_cv_func_expect" = "xyes"],
+    [AC_DEFINE([SUPPORT__BUILTIN_EXPECT], 1,
+     [Define this if the compiler supports __builtin_expect() function])
+     $1],
+    [$2])
+])
+
+AC_DEFUN([CC_ATTRIBUTE_ALIGNED], [
+  AC_REQUIRE([CC_CHECK_WERROR])
+  AC_CACHE_CHECK([highest __attribute__ ((aligned ())) supported],
+    [cc_cv_attribute_aligned],
+    [ac_save_CFLAGS="$CFLAGS"
+     CFLAGS="$CFLAGS $cc_cv_werror"
+     for cc_attribute_align_try in 64 32 16 8 4 2; do
+        AC_COMPILE_IFELSE([
+          int main() {
+            static char c __attribute__ ((aligned($cc_attribute_align_try))) = 0;
+            return c;
+          }], [cc_cv_attribute_aligned=$cc_attribute_align_try; break])
+     done
+     CFLAGS="$ac_save_CFLAGS"
+  ])
+
+  if test "x$cc_cv_attribute_aligned" != "x"; then
+     AC_DEFINE_UNQUOTED([ATTRIBUTE_ALIGNED_MAX], [$cc_cv_attribute_aligned],
+       [Define the highest alignment supported])
+  fi
+])
diff --git a/modules/Makefile.am b/modules/Makefile.am
new file mode 100644
index 0000000..bf9543e
--- /dev/null
+++ b/modules/Makefile.am
@@ -0,0 +1,3 @@
+if BUILD_MIXER
+SUBDIRS=mixer
+endif
diff --git a/modules/mixer/Makefile.am b/modules/mixer/Makefile.am
new file mode 100644
index 0000000..9f5917f
--- /dev/null
+++ b/modules/mixer/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS=simple
diff --git a/modules/mixer/simple/Makefile.am b/modules/mixer/simple/Makefile.am
new file mode 100644
index 0000000..bad0944
--- /dev/null
+++ b/modules/mixer/simple/Makefile.am
@@ -0,0 +1,35 @@
+alsaplugindir = @ALSA_PLUGIN_DIR@
+pkglibdir = $(alsaplugindir)/smixer
+
+AM_CFLAGS = -g -O2 -W -Wall
+
+INCLUDES=-I$(top_srcdir)/include
+
+pkglib_LTLIBRARIES = smixer-sbase.la \
+		     smixer-ac97.la \
+		     smixer-hda.la
+
+if BUILD_PYTHON
+pkglib_LTLIBRARIES += smixer-python.la
+endif
+
+noinst_HEADERS = sbase.h
+
+smixer_sbase_la_SOURCES = sbase.c
+smixer_sbase_la_LDFLAGS = -module -avoid-version $(LDFLAGS_NOUNDEFINED)
+smixer_sbase_la_LIBADD = ../../../src/libasound.la
+
+smixer_ac97_la_SOURCES = ac97.c sbasedl.c
+smixer_ac97_la_LDFLAGS = -module -avoid-version $(LDFLAGS_NOUNDEFINED)
+smixer_ac97_la_LIBADD = ../../../src/libasound.la -ldl
+
+smixer_hda_la_SOURCES = hda.c sbasedl.c
+smixer_hda_la_LDFLAGS = -module -avoid-version $(LDFLAGS_NOUNDEFINED)
+smixer_hda_la_LIBADD = ../../../src/libasound.la -ldl
+
+if BUILD_PYTHON
+smixer_python_la_SOURCES = python.c
+smixer_python_la_LDFLAGS = -module -avoid-version $(LDFLAGS_NOUNDEFINED)
+smixer_python_la_CFLAGS = $(PYTHON_INCLUDES)
+smixer_python_la_LIBADD = ../../../src/libasound.la $(PYTHON_LIBS)
+endif
diff --git a/modules/mixer/simple/ac97.c b/modules/mixer/simple/ac97.c
new file mode 100644
index 0000000..ddb6143
--- /dev/null
+++ b/modules/mixer/simple/ac97.c
@@ -0,0 +1,89 @@
+/*
+ *  Mixer Interface - AC97 simple abstact module
+ *  Copyright (c) 2005 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <math.h>
+#include "asoundlib.h"
+#include "mixer_abst.h"
+#include "sbase.h"
+
+static struct sm_elem_ops simple_ac97_ops;
+
+struct melem_sids sids[] = {
+	{
+		.sid	= SID_MASTER,
+		.sname	= "Master",
+		.sindex	= 0,
+		.weight = 1,
+		.chanmap = { 3, 0 },
+		.sops = &simple_ac97_ops,
+	}
+};
+
+#define SELECTORS (sizeof(selectors)/sizeof(selectors[0]))
+
+struct helem_selector selectors[] = {
+	{
+		.iface =	SND_CTL_ELEM_IFACE_MIXER,
+		.name =		"Master Playback Volume",
+		.index = 	0,
+		.sid =		SID_MASTER,
+		.purpose =	PURPOSE_VOLUME,
+		.caps = 	SM_CAP_PVOLUME,
+	},
+	{
+		.iface =	SND_CTL_ELEM_IFACE_MIXER,
+		.name =		"Master Playback Switch",
+		.index = 	0,
+		.sid =		SID_MASTER,
+		.purpose =	PURPOSE_SWITCH,
+		.caps = 	SM_CAP_PSWITCH,
+	}
+};
+
+int alsa_mixer_simple_event(snd_mixer_class_t *class, unsigned int mask,
+			    snd_hctl_elem_t *helem, snd_mixer_elem_t *melem)
+{
+	struct bclass_private *priv = snd_mixer_sbasic_get_private(class);
+	return priv->ops.event(class, mask, helem, melem);
+}
+
+int alsa_mixer_simple_init(snd_mixer_class_t *class)
+{
+	struct bclass_base_ops *ops;
+	int err;
+	
+	err = mixer_simple_basic_dlopen(class, &ops);
+	if (err < 0)
+		return 0;
+	err = ops->selreg(class, selectors, SELECTORS);
+	if (err < 0)
+		return err;
+	err = ops->sidreg(class, sids, SELECTORS);
+	if (err < 0)
+		return err;
+	return 0;
+}
diff --git a/modules/mixer/simple/hda.c b/modules/mixer/simple/hda.c
new file mode 100644
index 0000000..13c931a
--- /dev/null
+++ b/modules/mixer/simple/hda.c
@@ -0,0 +1,90 @@
+/*
+ *  Mixer Interface - HDA simple abstact module
+ *  Copyright (c) 2005 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <math.h>
+#include "asoundlib.h"
+#include "mixer_abst.h"
+#include "sbase.h"
+
+static struct sm_elem_ops simple_hda_ops;
+
+struct melem_sids sids[] = {
+	{
+		.sid	= SID_FRONT,
+		.sname	= "Front",
+		.sindex	= 0,
+		.weight = 1,
+		.chanmap = { 3, 0 },
+		.sops = &simple_hda_ops,
+	}
+};
+
+#define SELECTORS (sizeof(selectors)/sizeof(selectors[0]))
+
+struct helem_selector selectors[] = {
+	{
+		.iface =	SND_CTL_ELEM_IFACE_MIXER,
+		.name =		"Front Playback Volume",
+		.index = 	0,
+		.sid =		SID_FRONT,
+		.purpose =	PURPOSE_VOLUME,
+		.caps = 	SM_CAP_PVOLUME,
+	},
+	{
+		.iface =	SND_CTL_ELEM_IFACE_MIXER,
+		.name =		"Front Playback Switch",
+		.index = 	0,
+		.sid =		SID_FRONT,
+		.purpose =	PURPOSE_SWITCH,
+		.caps = 	SM_CAP_PSWITCH,
+	}
+};
+
+int alsa_mixer_simple_event(snd_mixer_class_t *class, unsigned int mask,
+			    snd_hctl_elem_t *helem, snd_mixer_elem_t *melem)
+{
+	struct bclass_private *priv = snd_mixer_sbasic_get_private(class);
+	return priv->ops.event(class, mask, helem, melem);
+}
+
+int alsa_mixer_simple_init(snd_mixer_class_t *class)
+{
+	struct bclass_base_ops *ops;
+	int err;
+	
+	err = mixer_simple_basic_dlopen(class, &ops);
+	if (err < 0)
+		return 0;
+	err = ops->selreg(class, selectors, SELECTORS);
+	if (err < 0)
+		return err;
+	err = ops->sidreg(class, sids, SELECTORS);
+	if (err < 0)
+		return err;
+	return 0;
+}
diff --git a/modules/mixer/simple/python.c b/modules/mixer/simple/python.c
new file mode 100644
index 0000000..c822c52
--- /dev/null
+++ b/modules/mixer/simple/python.c
@@ -0,0 +1,1046 @@
+/*
+ *  Mixer Interface - python binding simple abstact module
+ *  Copyright (c) 2007 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include "Python.h"
+#include <stddef.h>
+#include "config.h"
+#include "asoundlib.h"
+#include "mixer_abst.h"
+
+struct python_priv {
+	int py_initialized;
+	PyObject *py_event_func;
+	PyObject *py_mdict;
+	PyObject *py_mixer;
+};
+
+#define SCRIPT ALSA_PLUGIN_DIR "/smixer/python/main.py"
+
+struct pymelem {
+	PyObject_HEAD
+	sm_selem_t selem;
+	PyObject *py_mixer;
+	snd_mixer_elem_t *melem;
+};
+
+struct pymixer {
+	PyObject_HEAD
+	snd_mixer_class_t *class;
+	snd_mixer_t *mixer;
+	PyObject *mdict;
+	int hctl_count;
+	void **hctl;
+	int helem_count;
+	void **helem;
+	int melem_count;
+	void **melem;
+};
+
+static PyInterpreterState *main_interpreter;
+
+static void *get_C_ptr(PyObject *obj, const char *attr)
+{
+	PyObject *o;
+
+	o = PyObject_GetAttr(obj, PyString_InternFromString(attr));
+	if (!o) {
+		PyErr_Format(PyExc_TypeError, "missing '%s' attribute", attr);
+		return NULL;
+	}
+	if (!PyInt_Check(o)) {
+		PyErr_Format(PyExc_TypeError, "'%s' attribute is not integer", attr);
+		return NULL;
+	}
+	return (void *)PyInt_AsLong(o);
+}
+
+static struct pymelem *melem_to_pymelem(snd_mixer_elem_t *elem)
+{
+	return (struct pymelem *)((char *)snd_mixer_elem_get_private(elem) - offsetof(struct pymelem, selem));
+}
+
+static int pcall(struct pymelem *pymelem, const char *attr, PyObject *args, PyObject **_res)
+{
+	PyObject *obj = (PyObject *)pymelem, *res;
+	int xres = 0;
+
+	if (_res)
+		*_res = NULL;
+	obj = PyObject_GetAttr(obj, PyString_InternFromString(attr));
+	if (!obj) {
+		PyErr_Format(PyExc_TypeError, "missing '%s' attribute", attr);
+		PyErr_Print();
+		PyErr_Clear();
+		Py_DECREF(args);
+		return -EIO;
+	}
+	res = PyObject_CallObject(obj, args);
+	Py_XDECREF(args);
+	if (res == NULL) {
+		PyErr_Print();
+		PyErr_Clear();
+		return -EIO;
+	}
+	if (_res && PyTuple_Check(res)) {
+		*_res = res;
+		res = PyTuple_GetItem(res, 0);
+	}
+	if (PyInt_Check(res)) {
+		xres = PyInt_AsLong(res);
+	} else if (res == Py_None) {
+		xres = 0;
+	} else if (PyBool_Check(res)) {
+		xres = res == Py_True;
+	} else {
+		PyErr_Format(PyExc_TypeError, "wrong result from '%s'!", attr);
+		PyErr_Print();
+		PyErr_Clear();
+		Py_DECREF(res);
+		if (_res)
+			*_res = NULL;
+		return -EIO;
+	}
+	if (_res && *_res)
+		return xres;
+	Py_DECREF(res);
+	return xres;
+}
+
+static int is_ops(snd_mixer_elem_t *elem, int dir, int cmd, int val)
+{
+	PyObject *obj1;
+	struct pymelem *pymelem = melem_to_pymelem(elem);
+	char *s, fcn[32] = "opsIs";
+	int res, xdir = 1, xval = 0;
+
+	switch (cmd) {
+	case SM_OPS_IS_ACTIVE: 	s = "Active"; xdir = 0; break;
+	case SM_OPS_IS_MONO:	s = "Mono"; break;
+	case SM_OPS_IS_CHANNEL:	s = "Channel"; xval = 1; break;
+	case SM_OPS_IS_ENUMERATED: s = "Enumerated"; xdir = val == 1; break;
+	case SM_OPS_IS_ENUMCNT:	s = "EnumCnt"; break;
+	default:
+		return 1;
+	}
+	strcat(fcn, s);
+
+	obj1 = PyTuple_New(xdir + xval);
+	if (xdir) {
+		PyTuple_SET_ITEM(obj1, 0, PyInt_FromLong(dir));
+		if (xval)
+			PyTuple_SET_ITEM(obj1, 1, PyInt_FromLong(val));
+	}
+	res = pcall(pymelem, fcn, obj1, NULL);
+	return res < 0 ? 0 : res;
+}
+
+static int get_x_range_ops(snd_mixer_elem_t *elem, int dir,
+                           long *min, long *max, const char *attr)
+{
+	PyObject *obj1, *res;
+	struct pymelem *pymelem = melem_to_pymelem(elem);
+	int err;
+	
+	obj1 = PyTuple_New(1);
+	PyTuple_SET_ITEM(obj1, 0, PyInt_FromLong(dir));
+	err = pcall(pymelem, attr, obj1, &res);
+	if (err >= 0) {
+		err = !PyInt_Check(PyTuple_GetItem(res, 1)) || !PyInt_Check(PyTuple_GetItem(res, 2));
+		if (err) {
+			err = !PyLong_Check(PyTuple_GetItem(res, 1)) || !PyLong_Check(PyTuple_GetItem(res, 2));
+			if (err) {
+				PyErr_Format(PyExc_TypeError, "wrong result (invalid tuple)");
+				PyErr_Print();
+				PyErr_Clear();
+				err = -EIO;
+			} else {
+				*min = PyLong_AsLong(PyTuple_GetItem(res, 1));
+				*max = PyLong_AsLong(PyTuple_GetItem(res, 2));
+			}
+		} else {
+			*min = PyInt_AsLong(PyTuple_GetItem(res, 1));
+			*max = PyInt_AsLong(PyTuple_GetItem(res, 2));
+		}
+	}
+	Py_XDECREF(res);
+	return err;
+}
+
+static int get_range_ops(snd_mixer_elem_t *elem, int dir,
+                         long *min, long *max)
+{
+	return get_x_range_ops(elem, dir, min, max, "opsGetRange");
+}
+
+static int set_range_ops(snd_mixer_elem_t *elem, int dir,
+                         long min, long max)
+{
+	PyObject *obj1;
+	struct pymelem *pymelem = melem_to_pymelem(elem);
+
+	obj1 = PyTuple_New(3);
+	PyTuple_SET_ITEM(obj1, 0, PyInt_FromLong(dir));
+	PyTuple_SET_ITEM(obj1, 1, PyInt_FromLong(min));
+	PyTuple_SET_ITEM(obj1, 2, PyInt_FromLong(max));
+	return pcall(pymelem, "opsGetRange", obj1, NULL);
+}
+
+static int get_x_ops(snd_mixer_elem_t *elem, int dir,
+                     long channel, long *value,
+                     const char *attr)
+{
+	PyObject *obj1, *res;
+	struct pymelem *pymelem = melem_to_pymelem(elem);
+	int err;
+	
+	obj1 = PyTuple_New(2);
+	PyTuple_SET_ITEM(obj1, 0, PyInt_FromLong(dir));
+	PyTuple_SET_ITEM(obj1, 1, PyInt_FromLong(channel));
+	err = pcall(pymelem, attr, obj1, &res);
+	if (err >= 0) {
+		err = !PyInt_Check(PyTuple_GetItem(res, 1));
+		if (err) {
+			err = !PyLong_Check(PyTuple_GetItem(res, 1));
+			if (err) {
+				PyErr_Format(PyExc_TypeError, "wrong result (invalid tuple)");
+				PyErr_Print();
+				PyErr_Clear();
+				err = -EIO;
+			} else {
+				*value = PyLong_AsLong(PyTuple_GetItem(res, 1));
+			}
+		} else {
+			*value = PyInt_AsLong(PyTuple_GetItem(res, 1));
+		}
+	}
+	Py_XDECREF(res);
+	return err;
+}
+
+static int get_volume_ops(snd_mixer_elem_t *elem, int dir,
+			  snd_mixer_selem_channel_id_t channel, long *value)
+{
+	return get_x_ops(elem, dir, channel, value, "opsGetVolume");
+}
+
+static int get_switch_ops(snd_mixer_elem_t *elem, int dir,
+                          snd_mixer_selem_channel_id_t channel, int *value)
+{
+	long value1;
+	int res;
+	res = get_x_ops(elem, dir, channel, &value1, "opsGetSwitch");
+	*value = value1;
+	return res;
+}
+
+static int ask_vol_dB_ops(snd_mixer_elem_t *elem,
+			  int dir,
+			  long value,
+			  long *dbValue)
+{
+	return get_x_ops(elem, dir, value, dbValue, "opsGetVolDB");
+}
+
+static int ask_dB_vol_ops(snd_mixer_elem_t *elem,
+			  int dir,
+			  long value,
+			  long *dbValue,
+			  int xdir)
+{
+	PyObject *obj1, *res;
+	struct pymelem *pymelem = melem_to_pymelem(elem);
+	int err;
+	
+	obj1 = PyTuple_New(3);
+	PyTuple_SET_ITEM(obj1, 0, PyInt_FromLong(dir));
+	PyTuple_SET_ITEM(obj1, 1, PyInt_FromLong(value));
+	PyTuple_SET_ITEM(obj1, 2, PyInt_FromLong(xdir));
+	err = pcall(pymelem, "opsGetDBVol", obj1, &res);
+	if (err >= 0) {
+		err = !PyInt_Check(PyTuple_GetItem(res, 1));
+		if (err) {
+			err = !PyLong_Check(PyTuple_GetItem(res, 1));
+			if (err) {
+				PyErr_Format(PyExc_TypeError, "wrong result (invalid tuple)");
+				PyErr_Print();
+				PyErr_Clear();
+				err = -EIO;
+			} else {
+				*dbValue = PyLong_AsLong(PyTuple_GetItem(res, 1));
+			}
+		} else {
+			*dbValue = PyInt_AsLong(PyTuple_GetItem(res, 1));
+		}
+	}
+	Py_XDECREF(res);
+	return err;
+}
+
+static int get_dB_ops(snd_mixer_elem_t *elem,
+                      int dir,
+                      snd_mixer_selem_channel_id_t channel,
+                      long *value)
+{
+	return get_x_ops(elem, dir, channel, value, "opsGetDB");
+}
+
+static int get_dB_range_ops(snd_mixer_elem_t *elem, int dir,
+                            long *min, long *max)
+{
+	return get_x_range_ops(elem, dir, min, max, "opsGetDBRange");
+}
+
+static int set_volume_ops(snd_mixer_elem_t *elem, int dir,
+                          snd_mixer_selem_channel_id_t channel, long value)
+{
+	PyObject *obj1;
+	struct pymelem *pymelem = melem_to_pymelem(elem);
+
+	obj1 = PyTuple_New(3);
+	PyTuple_SET_ITEM(obj1, 0, PyInt_FromLong(dir));
+	PyTuple_SET_ITEM(obj1, 1, PyInt_FromLong(channel));
+	PyTuple_SET_ITEM(obj1, 2, PyInt_FromLong(value));
+	return pcall(pymelem, "opsSetVolume", obj1, NULL);
+}
+
+static int set_switch_ops(snd_mixer_elem_t *elem, int dir,
+                          snd_mixer_selem_channel_id_t channel, int value)
+{
+	PyObject *obj1;
+	struct pymelem *pymelem = melem_to_pymelem(elem);
+
+	obj1 = PyTuple_New(3);
+	PyTuple_SET_ITEM(obj1, 0, PyInt_FromLong(dir));
+	PyTuple_SET_ITEM(obj1, 1, PyInt_FromLong(channel));
+	PyTuple_SET_ITEM(obj1, 2, PyInt_FromLong(value));
+	return pcall(pymelem, "opsSetSwitch", obj1, NULL);
+}
+
+static int set_dB_ops(snd_mixer_elem_t *elem, int dir,
+                      snd_mixer_selem_channel_id_t channel,
+                      long db_gain, int xdir)
+{
+	PyObject *obj1;
+	struct pymelem *pymelem = melem_to_pymelem(elem);
+
+	obj1 = PyTuple_New(4);
+	PyTuple_SET_ITEM(obj1, 0, PyInt_FromLong(dir));
+	PyTuple_SET_ITEM(obj1, 1, PyInt_FromLong(channel));
+	PyTuple_SET_ITEM(obj1, 2, PyInt_FromLong(db_gain));
+	PyTuple_SET_ITEM(obj1, 3, PyInt_FromLong(xdir));
+	return pcall(pymelem, "opsSetDB", obj1, NULL);
+}
+
+static int enum_item_name_ops(snd_mixer_elem_t *elem,
+                              unsigned int item,
+                              size_t maxlen, char *buf)
+{
+	PyObject *obj1, *res;
+	struct pymelem *pymelem = melem_to_pymelem(elem);
+	int err;
+	unsigned int len;
+	char *s;
+	
+	obj1 = PyTuple_New(1);
+	PyTuple_SET_ITEM(obj1, 0, PyInt_FromLong(item));
+	err = pcall(pymelem, "opsGetEnumItemName", obj1, &res);
+	if (err >= 0) {
+		err = !PyString_Check(PyTuple_GetItem(res, 1));
+		if (err) {
+			PyErr_Format(PyExc_TypeError, "wrong result (invalid tuple)");
+			PyErr_Print();
+			PyErr_Clear();
+			err = -EIO;
+		} else {
+			s = PyString_AsString(PyTuple_GetItem(res, 1));
+			len = strlen(s);
+			if (maxlen - 1 > len)
+				len = maxlen - 1;
+			memcpy(buf, s, len);
+			buf[len] = '\0';
+		}
+	}
+	Py_XDECREF(res);
+	return err;
+}
+
+static int get_enum_item_ops(snd_mixer_elem_t *elem,
+                             snd_mixer_selem_channel_id_t channel,
+                             unsigned int *itemp)
+{
+	PyObject *obj1, *res;
+	struct pymelem *pymelem = melem_to_pymelem(elem);
+	int err;
+	
+	obj1 = PyTuple_New(1);
+	PyTuple_SET_ITEM(obj1, 0, PyInt_FromLong(channel));
+	err = pcall(pymelem, "opsGetEnumItem", obj1, &res);
+	if (err >= 0) {
+		err = !PyInt_Check(PyTuple_GetItem(res, 1));
+		if (err) {
+			PyErr_Format(PyExc_TypeError, "wrong result (invalid tuple)");
+			PyErr_Print();
+			PyErr_Clear();
+			err = -EIO;
+		} else {
+			*itemp = PyInt_AsLong(PyTuple_GetItem(res, 1));
+		}
+	}
+	Py_XDECREF(res);
+	return err;
+}
+
+static int set_enum_item_ops(snd_mixer_elem_t *elem,
+                             snd_mixer_selem_channel_id_t channel,
+                             unsigned int item)
+{
+	PyObject *obj1;
+	struct pymelem *pymelem = melem_to_pymelem(elem);
+
+	obj1 = PyTuple_New(2);
+	PyTuple_SET_ITEM(obj1, 0, PyInt_FromLong(channel));
+	PyTuple_SET_ITEM(obj1, 1, PyInt_FromLong(item));
+	return pcall(pymelem, "opsSetEnumItem", obj1, NULL);
+}
+
+static struct sm_elem_ops simple_python_ops = {
+        .is             = is_ops,
+        .get_range      = get_range_ops,
+        .get_dB_range   = get_dB_range_ops,
+        .set_range      = set_range_ops,
+        .ask_vol_dB	= ask_vol_dB_ops,
+        .ask_dB_vol	= ask_dB_vol_ops,
+        .get_volume     = get_volume_ops,
+        .get_dB         = get_dB_ops,
+        .set_volume     = set_volume_ops,
+        .set_dB         = set_dB_ops,
+        .get_switch     = get_switch_ops,
+        .set_switch     = set_switch_ops,
+        .enum_item_name = enum_item_name_ops,
+        .get_enum_item  = get_enum_item_ops,
+        .set_enum_item  = set_enum_item_ops
+};
+
+static void selem_free(snd_mixer_elem_t *elem)
+{
+	sm_selem_t *simple = snd_mixer_elem_get_private(elem);
+
+	if (simple->id) {
+		snd_mixer_selem_id_free(simple->id);
+		simple->id = NULL;
+	}
+}
+
+static PyObject *
+pymelem_cap(struct pymelem *pymelem ATTRIBUTE_UNUSED, void *priv)
+{
+	return PyInt_FromLong((long)priv);
+}
+
+static PyObject *
+pymelem_get_caps(struct pymelem *pymelem, void *priv ATTRIBUTE_UNUSED)
+{
+	return PyInt_FromLong(pymelem->selem.caps);
+}
+
+static PyObject *
+pymelem_get_name(struct pymelem *pymelem, void *priv ATTRIBUTE_UNUSED)
+{
+	return PyString_FromString(snd_mixer_selem_id_get_name(pymelem->selem.id));
+}
+
+static PyObject *
+pymelem_get_index(struct pymelem *pymelem, void *priv ATTRIBUTE_UNUSED)
+{
+	return PyInt_FromLong(snd_mixer_selem_id_get_index(pymelem->selem.id));
+}
+
+static int
+pymelem_set_caps(struct pymelem *pymelem, PyObject *val, void *priv ATTRIBUTE_UNUSED)
+{
+	if (!PyInt_Check(val)) {
+		PyErr_SetString(PyExc_TypeError, "The last attribute value must be an integer");
+		return -1;
+	}
+	pymelem->selem.caps = PyInt_AsLong(val);
+	return 0;
+}
+
+static PyObject *
+pymelem_ignore(struct pymelem *pymelem ATTRIBUTE_UNUSED, PyObject *args ATTRIBUTE_UNUSED)
+{
+	Py_RETURN_NONE;
+}
+
+static PyObject *
+pymelem_ignore1(struct pymelem *pymelem ATTRIBUTE_UNUSED, PyObject *args ATTRIBUTE_UNUSED)
+{
+	Py_RETURN_TRUE;
+}
+
+static PyObject *
+pymelem_error(struct pymelem *pymelem ATTRIBUTE_UNUSED, PyObject *args ATTRIBUTE_UNUSED)
+{
+	return PyInt_FromLong(-EIO);
+}
+
+static PyObject *
+pymelem_attach(struct pymelem *pymelem, PyObject *args)
+{
+	PyObject *obj;
+	snd_hctl_elem_t *helem;
+	int err;
+	
+	if (!PyArg_ParseTuple(args, "O", &obj))
+		return NULL;
+	helem = (snd_hctl_elem_t *)get_C_ptr(obj, "get_C_helem");
+	if (helem == NULL)
+		return NULL;
+	err = snd_mixer_elem_attach(pymelem->melem, helem);
+	if (err < 0) {
+		PyErr_Format(PyExc_RuntimeError, "Cannot attach hcontrol element to mixer element: %s", snd_strerror(err));
+		return NULL;		
+	}
+	Py_RETURN_NONE;
+}
+
+static PyObject *
+pymelem_detach(struct pymelem *pymelem, PyObject *args)
+{
+	PyObject *obj;
+	snd_hctl_elem_t *helem;
+	int err;
+	
+	if (!PyArg_ParseTuple(args, "O", &obj))
+		return NULL;
+	helem = (snd_hctl_elem_t *)get_C_ptr(obj, "get_C_helem");
+	if (helem == NULL)
+		return NULL;
+	err = snd_mixer_elem_detach(pymelem->melem, helem);
+	if (err < 0) {
+		PyErr_Format(PyExc_RuntimeError, "Cannot detach hcontrol element to mixer element: %s", snd_strerror(err));
+		return NULL;		
+	}
+	Py_RETURN_NONE;
+}
+
+static PyObject *
+pymelem_event_info(struct pymelem *pymelem, PyObject *args)
+{
+	if (!PyArg_ParseTuple(args, ""))
+		return NULL;
+	return PyInt_FromLong(snd_mixer_elem_info(pymelem->melem));
+}
+
+static PyObject *
+pymelem_event_value(struct pymelem *pymelem, PyObject *args)
+{
+	if (!PyArg_ParseTuple(args, ""))
+		return NULL;
+	return PyInt_FromLong(snd_mixer_elem_value(pymelem->melem));
+}
+
+static int
+pymelem_init(struct pymelem *pymelem, PyObject *args, PyObject *kwds ATTRIBUTE_UNUSED)
+{
+	char *name;
+	long index, weight;
+	snd_mixer_selem_id_t *id;
+	int err;
+
+	if (!PyArg_ParseTuple(args, "Osii", &pymelem->py_mixer, &name, &index, &weight))
+		return -1;
+	memset(&pymelem->selem, 0, sizeof(pymelem->selem));
+	if (snd_mixer_selem_id_malloc(&id))
+		return -1;
+	snd_mixer_selem_id_set_name(id, name);
+	snd_mixer_selem_id_set_index(id, index);
+	pymelem->selem.id = id;
+	pymelem->selem.ops = &simple_python_ops;
+	err = snd_mixer_elem_new(&pymelem->melem, SND_MIXER_ELEM_SIMPLE,
+				 weight, &pymelem->selem, selem_free);
+	if (err < 0) {
+		snd_mixer_selem_id_free(id);
+		return -1;
+	}
+	return 0;
+}
+
+static void
+pymelem_dealloc(struct pymelem *self)
+{
+	selem_free(self->melem);
+        self->ob_type->tp_free(self);
+}
+
+static PyGetSetDef pymelem_getseters[] = {
+	{"CAP_GVOLUME", (getter)pymelem_cap, NULL, NULL, (void *)SM_CAP_GVOLUME},
+	{"CAP_GSWITCH", (getter)pymelem_cap, NULL, NULL, (void *)SM_CAP_GSWITCH},
+	{"CAP_PVOLUME", (getter)pymelem_cap, NULL, NULL, (void *)SM_CAP_PVOLUME},
+	{"CAP_PVOLUME_JOIN", (getter)pymelem_cap, NULL, NULL, (void *)SM_CAP_PVOLUME_JOIN},
+	{"CAP_PSWITCH", (getter)pymelem_cap, NULL, NULL, (void *)SM_CAP_PSWITCH},
+	{"CAP_PSWITCH_JOIN", (getter)pymelem_cap, NULL, NULL, (void *)SM_CAP_PSWITCH_JOIN},
+	{"CAP_CVOLUME", (getter)pymelem_cap, NULL, NULL, (void *)SM_CAP_CVOLUME},
+	{"CAP_CVOLUME_JOIN", (getter)pymelem_cap, NULL, NULL, (void *)SM_CAP_CVOLUME_JOIN},
+	{"CAP_CSWITCH", (getter)pymelem_cap, NULL, NULL, (void *)SM_CAP_CSWITCH},
+	{"CAP_CSWITCH_JOIN", (getter)pymelem_cap, NULL, NULL, (void *)SM_CAP_CSWITCH_JOIN},
+	{"CAP_CSWITCH_EXCL", (getter)pymelem_cap, NULL, NULL, (void *)SM_CAP_CSWITCH_EXCL},
+	{"CAP_PENUM", (getter)pymelem_cap, NULL, NULL, (void *)SM_CAP_PENUM},
+	{"CAP_CENUM", (getter)pymelem_cap, NULL, NULL, (void *)SM_CAP_CENUM},
+
+	{"caps", (getter)pymelem_get_caps, (setter)pymelem_set_caps, NULL, NULL},
+
+	{"name", (getter)pymelem_get_name, NULL, NULL, NULL},
+	{"index", (getter)pymelem_get_index, NULL, NULL, NULL},
+	        
+	{NULL,NULL,NULL,NULL,NULL}
+};
+
+static PyMethodDef pymelem_methods[] = {
+	{"attach", (PyCFunction)pymelem_attach, METH_VARARGS, NULL},
+	{"detach", (PyCFunction)pymelem_detach, METH_VARARGS, NULL},
+	
+	/* "default" functions - no functionality */
+	{"opsIsActive", (PyCFunction)pymelem_ignore1, METH_VARARGS, NULL},
+	{"opsIsMono", (PyCFunction)pymelem_ignore, METH_VARARGS, NULL},
+	{"opsIsChannel", (PyCFunction)pymelem_ignore, METH_VARARGS, NULL},
+	{"opsIsEnumerated", (PyCFunction)pymelem_ignore, METH_VARARGS, NULL},
+	{"opsIsEnumCnt", (PyCFunction)pymelem_ignore, METH_VARARGS, NULL},
+
+	{"opsGetDB", (PyCFunction)pymelem_error, METH_VARARGS, NULL},
+	
+	{"eventInfo", (PyCFunction)pymelem_event_info, METH_VARARGS, NULL},
+	{"eventValue", (PyCFunction)pymelem_event_value, METH_VARARGS, NULL},
+
+	{NULL,NULL,0,NULL}
+};
+
+static PyTypeObject pymelem_type = {
+        PyObject_HEAD_INIT(0)
+        tp_name:        "smixer_python.InternalMElement",
+        tp_basicsize:   sizeof(struct pymelem),
+        tp_dealloc:     (destructor)pymelem_dealloc,
+        tp_flags:       Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+        tp_doc:         NULL /* mixerinit__doc__ */,
+        tp_getset:      pymelem_getseters,
+        tp_init:        (initproc)pymelem_init,
+        tp_alloc:       PyType_GenericAlloc,
+        tp_new:         PyType_GenericNew,
+        tp_free:        PyObject_Del,
+        tp_methods:     pymelem_methods,
+};
+
+static PyObject *
+pymixer_attach_hctl(struct pymixer *pymixer, PyObject *args)
+{
+	PyObject *obj;
+	snd_hctl_t *hctl;
+	void **hctls;
+	int err;
+	
+	if (!PyArg_ParseTuple(args, "O", &obj))
+		return NULL;
+	hctl = (snd_hctl_t *)get_C_ptr(obj, "get_C_hctl");
+	if (hctl == NULL)
+		return NULL;
+	err = snd_mixer_attach_hctl(pymixer->mixer, hctl);
+	if (err < 0) {
+		PyErr_Format(PyExc_RuntimeError, "Cannot attach hctl: %s", snd_strerror(err));
+		return NULL;
+	}
+	hctls = realloc(pymixer->hctl, sizeof(void *) * (pymixer->hctl_count+1) * 2);
+	if (hctls == NULL) {
+		PyErr_SetString(PyExc_RuntimeError, "No enough memory");
+		return NULL;
+	}
+	pymixer->hctl = hctls;
+	pymixer->hctl[pymixer->hctl_count*2] = (void *)hctl;
+	pymixer->hctl[pymixer->hctl_count*2+1] = (void *)obj;
+	pymixer->hctl_count++;
+	Py_INCREF(obj);
+	Py_RETURN_NONE;
+}
+
+static PyObject *
+pymixer_register(struct pymixer *pymixer, PyObject *args)
+{
+	int err;
+	
+	if (!PyArg_ParseTuple(args, ""))
+		return NULL;
+	err = snd_mixer_class_register(pymixer->class, pymixer->mixer);
+	if (err < 0) {
+		PyErr_Format(PyExc_RuntimeError, "Cannot register mixer: %s", snd_strerror(err));
+		return NULL;
+	}
+	Py_RETURN_NONE;
+}
+
+static PyObject *
+pymixer_melement_new(struct pymixer *pymixer, PyObject *args)
+{
+	PyObject *obj, *obj1, *obj2;
+	char *class, *name;
+	long index, weight;
+	
+	if (!PyArg_ParseTuple(args, "ssii", &class, &name, &index, &weight))
+		return NULL;
+	obj = PyDict_GetItemString(pymixer->mdict, class);
+	if (obj) {
+		obj1 = PyTuple_New(4);
+		if (PyTuple_SET_ITEM(obj1, 0, (PyObject *)pymixer))
+			Py_INCREF((PyObject *)pymixer);
+		PyTuple_SET_ITEM(obj1, 1, PyString_FromString(name));
+		PyTuple_SET_ITEM(obj1, 2, PyInt_FromLong(index));
+		PyTuple_SET_ITEM(obj1, 3, PyInt_FromLong(weight));
+		obj2 = PyObject_CallObject(obj, obj1);
+		Py_XDECREF(obj1);
+		if (obj2) {
+			struct pymelem *pymelem = (struct pymelem *)obj2;
+			void **melems = realloc(pymixer->melem, sizeof(void *) * (pymixer->melem_count + 1) * 2);
+			if (melems == NULL) {
+				Py_DECREF(obj2);
+				return NULL;
+			}
+			melems[pymixer->melem_count*2] = pymelem->melem;
+			melems[pymixer->melem_count*2+1] = obj2;
+			Py_INCREF(obj2);
+			pymixer->melem = melems;
+			pymixer->melem_count++;
+		}
+	} else {
+		PyErr_Format(PyExc_RuntimeError, "Cannot find class '%s'", class);
+		return NULL;
+	}
+	return obj2;
+}
+
+static PyObject *
+pymixer_melement_add(struct pymixer *pymixer, PyObject *args)
+{
+	PyObject *obj;
+	struct pymelem *pymelem;
+	int err;
+	
+	if (!PyArg_ParseTuple(args, "O", &obj))
+		return NULL;
+	pymelem = (struct pymelem *)obj;
+	err = snd_mixer_elem_add(pymelem->melem, pymixer->class);
+	if (err < 0) {
+		PyErr_Format(PyExc_RuntimeError, "Cannot add mixer element: %s", snd_strerror(err));
+		return NULL;		
+	}
+	Py_RETURN_NONE;
+}
+
+static int
+pymixer_init(struct pymixer *pymixer, PyObject *args, PyObject *kwds ATTRIBUTE_UNUSED)
+{
+	long class, mixer;
+
+	if (!PyArg_ParseTuple(args, "iiO", &class, &mixer, &pymixer->mdict))
+		return -1;
+	pymixer->class = (snd_mixer_class_t *)class;
+	pymixer->mixer = (snd_mixer_t *)mixer;
+	pymixer->hctl_count = 0;
+	pymixer->hctl = NULL;
+	pymixer->helem_count = 0;
+	pymixer->helem = NULL;
+	pymixer->melem_count = 0;
+	pymixer->melem = NULL;
+	return 0;
+}
+
+static void
+pymixer_free(struct pymixer *self)
+{
+	int idx;
+	
+	for (idx = 0; idx < self->hctl_count; idx++) {
+		snd_mixer_detach_hctl(self->mixer, self->hctl[idx*2]);
+		Py_DECREF((PyObject *)self->hctl[idx*2+1]);
+	}
+	if (self->hctl)
+		free(self->hctl);
+	self->hctl_count = 0;
+	self->hctl = NULL;
+	for (idx = 0; idx < self->helem_count; idx++)
+		Py_DECREF((PyObject *)self->helem[idx*2+1]);
+	if (self->helem)
+		free(self->helem);
+	self->helem_count = 0;
+	self->helem = NULL;
+	for (idx = 0; idx < self->melem_count; idx++)
+		Py_DECREF((PyObject *)self->melem[idx*2+1]);
+	if (self->melem)
+		free(self->melem);
+	self->melem_count = 0;
+	self->melem = NULL;
+}
+
+static void
+pymixer_dealloc(struct pymixer *self)
+{
+	pymixer_free(self);
+        self->ob_type->tp_free(self);
+}
+
+static PyGetSetDef pymixer_getseters[] = {
+	{NULL,NULL,NULL,NULL,NULL}
+};
+
+static PyMethodDef pymixer_methods[] = {
+	{"attachHCtl", (PyCFunction)pymixer_attach_hctl, METH_VARARGS, NULL},
+	{"register", (PyCFunction)pymixer_register, METH_VARARGS, NULL},
+	{"newMElement", (PyCFunction)pymixer_melement_new, METH_VARARGS, NULL},
+	{"addMElement", (PyCFunction)pymixer_melement_add, METH_VARARGS, NULL},
+	{NULL,NULL,0,NULL}
+};
+
+static PyTypeObject pymixer_type = {
+        PyObject_HEAD_INIT(0)
+        tp_name:        "smixer_python.InternalMixer",
+        tp_basicsize:   sizeof(struct pymixer),
+        tp_dealloc:     (destructor)pymixer_dealloc,
+        tp_flags:       Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+        tp_doc:         NULL /* mixerinit__doc__ */,
+        tp_getset:      pymixer_getseters,
+        tp_init:        (initproc)pymixer_init,
+        tp_alloc:       PyType_GenericAlloc,
+        tp_new:         PyType_GenericNew,
+        tp_free:        PyObject_Del,
+        tp_methods:     pymixer_methods,
+};
+
+static PyMethodDef python_methods[] = {
+	{NULL, NULL, 0, NULL}
+};
+
+static PyObject *new_helem(struct python_priv *priv, snd_hctl_elem_t *helem)
+{
+	PyObject *obj, *py_hctl = NULL, *obj1, *obj2;
+	snd_hctl_t *hctl = snd_hctl_elem_get_hctl(helem);
+	struct pymixer *pymixer = (struct pymixer *)priv->py_mixer;
+	int idx;
+
+	for (idx = 0; idx < pymixer->hctl_count; idx++) {
+		if (pymixer->hctl[idx] == hctl) {
+			py_hctl = pymixer->hctl[idx*2+1];
+			break;
+		}
+	}
+	if (py_hctl == NULL)
+		return NULL;
+	obj = PyDict_GetItemString(priv->py_mdict, "HElement");
+	if (obj) {
+		obj1 = PyTuple_New(3);
+		if (PyTuple_SET_ITEM(obj1, 0, py_hctl))
+			Py_INCREF(py_hctl);
+		PyTuple_SET_ITEM(obj1, 1, PyFloat_FromDouble(1));
+		PyTuple_SET_ITEM(obj1, 2, PyInt_FromLong((long)helem));
+		obj2 = PyObject_CallObject(obj, obj1);
+		if (obj2 == NULL) {
+			PyErr_Print();
+			PyErr_Clear();
+		}
+		Py_XDECREF(obj1);
+	} else {
+		SNDERR("Unable to create InternalMixer object");
+		return NULL;
+	}
+	if (obj2) {
+		struct pymixer *pymixer = (struct pymixer *)priv->py_mixer;
+		void **helems = realloc(pymixer->helem, sizeof(void *) * (pymixer->helem_count + 1) * 2);
+		if (helems == NULL) {
+			Py_DECREF(obj2);
+			return NULL;
+		}
+		helems[pymixer->helem_count*2] = helem;
+		helems[pymixer->helem_count*2+1] = obj2;
+		Py_INCREF(obj2);
+		pymixer->helem = helems;
+		pymixer->helem_count++;
+	}
+	return obj2;
+}
+
+static PyObject *find_helem(struct python_priv *priv, snd_hctl_elem_t *helem)
+{
+	struct pymixer *pymixer = (struct pymixer *)priv->py_mixer;
+	int idx;
+
+	for (idx = 0; idx < pymixer->helem_count; idx++) {
+		if (pymixer->helem[idx*2] == helem)
+			return (PyObject *)pymixer->helem[idx*2+1];
+	}
+	return NULL;
+}
+
+static PyObject *find_melem(struct python_priv *priv, snd_mixer_elem_t *melem)
+{
+	struct pymixer *pymixer = (struct pymixer *)priv->py_mixer;
+	int idx;
+
+	for (idx = 0; idx < pymixer->melem_count; idx++) {
+		if (pymixer->melem[idx*2] == melem)
+			return (PyObject *)pymixer->melem[idx*2+1];
+	}
+	return NULL;
+}
+
+int alsa_mixer_simple_event(snd_mixer_class_t *class, unsigned int mask,
+			    snd_hctl_elem_t *helem, snd_mixer_elem_t *melem)
+{
+	struct python_priv *priv = snd_mixer_sbasic_get_private(class);
+	PyThreadState *tstate, *origstate;
+	PyObject *t, *o, *r;
+	int res = -ENOMEM;
+
+	tstate = PyThreadState_New(main_interpreter);
+        origstate = PyThreadState_Swap(tstate);
+        
+        t = PyTuple_New(3);
+        if (t) {
+        	PyTuple_SET_ITEM(t, 0, (PyObject *)PyInt_FromLong(mask));
+        	o = find_helem(priv, helem);
+	        if (mask & SND_CTL_EVENT_MASK_ADD) {
+	        	if (o == NULL)
+        			o = new_helem(priv, helem);
+		}
+        	if (o == NULL)
+        		return 0;
+        	if (PyTuple_SET_ITEM(t, 1, o))
+        		Py_INCREF(o);
+        	o = melem ? find_melem(priv, melem) : Py_None;
+        	if (PyTuple_SET_ITEM(t, 2, o))
+        		Py_INCREF(o);
+		r = PyObject_CallObject(priv->py_event_func, t);
+		Py_DECREF(t);
+		if (r) {
+			if (PyInt_Check(r)) {
+				res = PyInt_AsLong(r);
+			} else if (r == Py_None) {
+				res = 0;
+			}
+			Py_DECREF(r);
+		} else {
+			PyErr_Print();
+			PyErr_Clear();
+			res = -EIO;
+		}
+	}
+	
+	return res;
+}
+
+static void alsa_mixer_simple_free(snd_mixer_class_t *class)
+{
+	struct python_priv *priv = snd_mixer_sbasic_get_private(class);
+
+	if (priv->py_mixer) {
+		pymixer_free((struct pymixer *)priv->py_mixer);
+		Py_DECREF(priv->py_mixer);
+	}
+	if (priv->py_initialized) {
+		Py_XDECREF(priv->py_event_func);
+		Py_Finalize();
+	}
+	free(priv);
+}
+
+int alsa_mixer_simple_finit(snd_mixer_class_t *class,
+			    snd_mixer_t *mixer,
+			    const char *device)
+{
+	struct python_priv *priv;
+	FILE *fp;
+	const char *file;
+	PyObject *obj, *obj1, *obj2, *py_mod, *mdict;
+
+	priv = calloc(1, sizeof(*priv));
+	if (priv == NULL)
+		return -ENOMEM;
+
+	snd_mixer_sbasic_set_private(class, priv);
+	snd_mixer_sbasic_set_private_free(class, alsa_mixer_simple_free);
+
+	file = getenv("ALSA_MIXER_SIMPLE_MPYTHON");
+	if (file == NULL)
+		file = SCRIPT;
+
+	fp = fopen(file, "r");
+	if (fp == NULL) {
+		SNDERR("Unable to find python module '%s'", file);
+		return -ENODEV;
+	}
+	
+	Py_Initialize();
+	if (PyType_Ready(&pymelem_type) < 0)
+		return -EIO;
+	if (PyType_Ready(&pymixer_type) < 0)
+		return -EIO;
+	Py_InitModule("smixer_python", python_methods);
+	priv->py_initialized = 1;
+	main_interpreter = PyThreadState_Get()->interp;
+	obj = PyImport_GetModuleDict();
+	py_mod = PyDict_GetItemString(obj, "__main__");
+	if (py_mod) {
+		mdict = priv->py_mdict = PyModule_GetDict(py_mod);
+		obj = PyString_FromString(file);
+		if (obj)
+			PyDict_SetItemString(mdict, "__file__", obj);
+		Py_XDECREF(obj);
+		obj = PyString_FromString(device);
+		if (obj)
+			PyDict_SetItemString(mdict, "device", obj);
+		Py_XDECREF(obj);
+		Py_INCREF(&pymixer_type);
+		PyModule_AddObject(py_mod, "InternalMElement", (PyObject *)&pymelem_type);
+		PyModule_AddObject(py_mod, "InternalMixer", (PyObject *)&pymixer_type);
+		obj = PyDict_GetItemString(mdict, "InternalMixer");
+		if (obj) {
+			obj1 = PyTuple_New(3);
+			PyTuple_SET_ITEM(obj1, 0, PyInt_FromLong((long)class));
+			PyTuple_SET_ITEM(obj1, 1, PyInt_FromLong((long)mixer));
+			if (PyTuple_SET_ITEM(obj1, 2, mdict))
+				Py_INCREF(mdict);
+			obj2 = PyObject_CallObject(obj, obj1);
+			Py_XDECREF(obj1);
+			PyDict_SetItemString(mdict, "mixer", obj2);
+			priv->py_mixer = obj2;
+		} else {
+			SNDERR("Unable to create InternalMixer object");
+			return -EIO;
+		}
+
+
+		obj = PyRun_FileEx(fp, file, Py_file_input, mdict, mdict, 1);
+		if (obj == NULL)
+			PyErr_Print();
+		Py_XDECREF(obj);
+		priv->py_event_func = PyDict_GetItemString(mdict, "event");
+		if (priv->py_event_func == NULL) {
+			SNDERR("Unable to find python function 'event'");
+			return -EIO;
+		}
+	}
+	return 0;
+}
diff --git a/modules/mixer/simple/python/common.py b/modules/mixer/simple/python/common.py
new file mode 100644
index 0000000..2c6075d
--- /dev/null
+++ b/modules/mixer/simple/python/common.py
@@ -0,0 +1,222 @@
+#!/usr/bin/python
+#  -*- coding: utf-8 -*-
+#  -*- Python -*-
+
+from pyalsa.alsahcontrol import HControl, Element as HElement, \
+                                Info as HInfo, Value as HValue, InterfaceId, \
+                                EventMask, EventMaskRemove
+
+MIXER = InterfaceId['Mixer']
+MIXERS = str(MIXER)
+
+class BaseElement(InternalMElement):
+
+  def __init__(self, mixer, name, index, weight):
+    InternalMElement.__init__(self, mixer, name, index, weight)
+    self.channels = 0
+    self.min = [0, 0]
+    self.max = [0, 0]
+
+  def opsIsChannel(self, dir, chn):
+    return chn >= 0 and chn < self.channels
+
+  def opsGetRange(self, dir):
+    return (0, self.min[dir], self.max[dir])
+
+  def opsSetRange(self, dir, min, max):
+    self.min[dir] = min
+    self.max[dir] = max
+
+  def volumeToUser(self, info, dir, value):
+    min = info.min
+    max = info.max
+    if min == max:
+      return self.min[dir]
+    n = (value - min) * (self.max[dir] - self.min[dir])
+    return self.min[dir] + (n + (max - min) / 2) / (max - min)
+
+  def volumeFromUser(self, info, dir, value):
+    min = info.min
+    max = info.max
+    if self.max[dir] == self.min[dir]:
+      return min
+    n = (value - self.min[dir]) * (max - min)
+    return min + (n + (self.max[dir] - self.min[dir]) / 2) / (self.max[dir] - self.min[dir])
+
+class StandardElement(BaseElement):
+
+  def __init__(self, mixer, name, index, weight):
+    BaseElement.__init__(self, mixer, name, index, weight)
+    self.channels = 1
+    self.volume = [None, None]
+    self.volumeinfo = [None, None]
+    self.volumearray = [None, None]
+    self.switch = [None, None]
+    self.switchinfo = [None, None]
+    self.switcharray = [None, None]
+
+  def decideChannels(self):
+    max = 0
+    for i in [0, 1]:
+      if self.volume[i]:
+        count = self.volumeinfo[i].count
+        if count > max:
+          max = count
+      if self.switch[i]:
+        count = self.switchinfo[i].count
+        if count > max:
+          max = count
+    self.channels = max
+
+  def attachVolume(self, helem, dir):
+    self.volume[dir] = helem
+    self.volumeinfo[dir] = HInfo(helem)
+    self.min[dir] = self.volumeinfo[dir].min
+    self.max[dir] = self.volumeinfo[dir].max
+    self.volumearray[dir] = HValue(helem).getArray(self.volumeinfo[dir].type, self.volumeinfo[dir].count)
+
+  def attachSwitch(self, helem, dir):
+    self.switch[dir] = helem
+    self.switchinfo[dir] = HInfo(helem)
+    self.switcharray[dir] = HValue(helem).getArray(self.switchinfo[dir].type, self.switchinfo[dir].count)
+
+  def attach(self, helem):
+    BaseElement.attach(self, helem)
+    if helem.name.endswith('Playback Volume'):
+      self.attachVolume(helem, 0)
+      self.caps |= self.CAP_PVOLUME
+    elif helem.name.endswith('Capture Volume'):
+      self.attachVolume(helem, 1)
+      self.caps |= self.CAP_CVOLUME
+    elif helem.name.endswith('Playback Switch'):
+      self.attachSwitch(helem, 0)
+      self.caps |= self.CAP_PSWITCH
+    elif helem.name.endswith('Capture Switch'):
+      self.attachSwitch(helem, 1)
+      self.caps |= self.CAP_CSWITCH
+    self.decideChannels()
+    self.eventInfo()
+
+  def opsGetVolume(self, dir, chn):
+    return (0, self.volumeToUser(self.volumeinfo[dir], dir, self.volumearray[dir][chn]))
+
+  def opsSetVolume(self, dir, chn, value):
+    val = self.volumeFromUser(self.volumeinfo[dir], dir, value)
+    if self.volumearray[dir][chn] == val:
+      return
+    self.volumearray[dir][chn] = val
+    hv = HValue(self.volume[dir])
+    hv.setArray(self.volumeinfo[dir].type, self.volumearray[dir])
+    hv.write()
+
+  def opsGetSwitch(self, dir, chn):
+    return (0, self.switcharray[dir][chn])
+
+  def opsSetSwitch(self, dir, chn, value):
+    if self.switcharray[dir][chn] and value:
+      return
+    if not self.switcharray[dir][chn] and not value:
+      return
+    self.switcharray[dir] = int(value)
+    hv = HValue(self.switch[dir])
+    hv.setArray(self.switchinfo[dir].type, self.switcharray[dir])
+    hv.write()
+
+  def update(self, helem):
+    for i in [0, 1]:
+      if helem == self.volume[i]:
+        self.volumearray[i] = HValue(helem).getArray(self.volumeinfo[i].type, self.volumeinfo[i].count)
+      elif helem == self.switch[i]:
+        self.switcharray[i] = HValue(helem).getArray(self.switchinfo[i].type, self.switchinfo[i].count)
+    self.eventValue()
+
+class EnumElement(BaseElement):
+
+  def __init__(self, mixer, name, index, weight):
+    BaseElement.__init__(self, mixer, name, index, weight)
+    self.mycaps = 0
+  
+  def attach(self, helem):
+    BaseElement.attach(self, helem)
+    self.enum = helem
+    self.enuminfo = HInfo(helem)
+    self.enumarray = HValue(helem).getArray(self.enuminfo.type, self.enuminfo.count)
+    self.channels = self.enuminfo.count
+    self.texts = self.enuminfo.itemNames
+    self.caps |= self.mycaps
+
+  def opsIsEnumerated(self, dir=-1):
+    if dir < 0:
+      return 1
+    if dir == 0 and self.mycaps & self.CAP_PENUM:
+      return 1
+    if dir == 1 and self.mycaps & self.CAP_CENUM:
+      return 1
+
+  def opsIsEnumCnt(self, dir):
+    return self.enuminfo.items
+
+  def opsGetEnumItemName(self, item):
+    return (0, self.texts[item])
+
+  def opsGetEnumItem(self, chn):
+    if chn >= self.channels:
+      return -1
+    return (0, self.enumarray[chn])
+    
+  def opsSetEnumItem(self, chn, value):
+    if chn >= self.channels:
+      return -1
+    if self.enumarray[chn] == value:
+      return
+    self.enumarray[chn] = int(value)
+    hv = HValue(self.enum)
+    hv.setArray(self.enuminfo.type, self.enumarray)
+    hv.write()
+
+  def update(self, helem):
+    self.enumarray = HValue(helem).getArray(self.enuminfo.type, self.enuminfo.count)
+    self.eventValue()
+
+class EnumElementPlayback(EnumElement):
+
+  def __init__(self, mixer, name, index, weight):
+    EnumElement.__init__(self, mixer, name, index, weight)
+    self.mycaps = self.CAP_PENUM
+  
+class EnumElementCapture(EnumElement):
+
+  def __init__(self, mixer, name, index, weight):
+    EnumElement.__init__(self, mixer, name, index, weight)
+    self.mycaps = self.CAP_CENUM
+  
+ELEMS = []
+
+def element_add(helem):
+  key = helem.name+'//'+str(helem.index)+'//'+str(helem.interface)
+  if not CONTROLS.has_key(key):
+    return
+  val = CONTROLS[key]
+  felem = None
+  for elem in ELEMS:
+    if elem.name == val[0] and elem.index == val[1]:
+      felem = elem
+      break
+  if not felem:
+    felem = mixer.newMElement(val[3], val[0], val[1], val[2])
+    mixer.addMElement(felem)
+    ELEMS.append(felem)
+  felem.attach(helem)
+
+def eventHandler(evmask, helem, melem):
+  if evmask == EventMaskRemove:
+    return
+  if evmask & EventMask['Add']:
+    element_add(helem)
+  if evmask & EventMask['Value']:
+    melem.update(helem)
+
+def init():
+  hctl = HControl(device, load=False)
+  mixer.attachHCtl(hctl)
+  mixer.register()
diff --git a/modules/mixer/simple/python/hda.py b/modules/mixer/simple/python/hda.py
new file mode 100644
index 0000000..b11c076
--- /dev/null
+++ b/modules/mixer/simple/python/hda.py
@@ -0,0 +1,42 @@
+#!/usr/bin/python
+#  -*- coding: utf-8 -*-
+#  -*- Python -*-
+
+alsacode('common')
+
+CONTROLS = {
+'Headphone Playback Switch//0//'+MIXERS:["Headphone", 0, 1, "StandardElement"],
+'IEC958 Playback Switch//0//'+MIXERS:["IEC958", 0, 2, "StandardElement"],
+'Front Playback Volume//0//'+MIXERS:["Front", 0, 3, "StandardElement"],
+'Front Playback Switch//0//'+MIXERS:["Front", 0, 3, "StandardElement"],
+'Surround Playback Volume//0//'+MIXERS:["Surround", 0, 4, "StandardElement"],
+'Surround Playback Switch//0//'+MIXERS:["Surround", 0, 4, "StandardElement"],
+'Center Playback Volume//0//'+MIXERS:["Center", 0, 5, "StandardElement"],
+'Center Playback Switch//0//'+MIXERS:["Center", 0, 5, "StandardElement"],
+'LFE Playback Volume//0//'+MIXERS:["LFE", 0, 6, "StandardElement"],
+'LFE Playback Switch//0//'+MIXERS:["LFE", 0, 6, "StandardElement"],
+'Line Playback Volume//0//'+MIXERS:["Line", 0, 7, "StandardElement"],
+'Line Playback Switch//0//'+MIXERS:["Line", 0, 7, "StandardElement"],
+'CD Playback Volume//0//'+MIXERS:["CD", 0, 8, "StandardElement"],
+'CD Playback Switch//0//'+MIXERS:["CD", 0, 8, "StandardElement"],
+'Mic Playback Volume//0//'+MIXERS:["Mic", 0, 9, "StandardElement"],
+'Mic Playback Switch//0//'+MIXERS:["Mic", 0, 9, "StandardElement"],
+'PC Speaker Playback Volume//0//'+MIXERS:["PC Speaker", 0, 10, "StandardElement"],
+'PC Speaker Playback Switch//0//'+MIXERS:["PC Speaker", 0, 10, "StandardElement"],
+'Front Mic Playback Volume//0//'+MIXERS:["Front Mic", 0, 11, "StandardElement"],
+'Front Mic Playback Switch//0//'+MIXERS:["Front Mic", 0, 11, "StandardElement"],
+'Capture Switch//0//'+MIXERS:["Capture", 0, 12, "StandardElement"],
+'Capture Volume//0//'+MIXERS:["Capture", 0, 12, "StandardElement"],
+'Capture Switch//1//'+MIXERS:["Capture", 1, 13, "StandardElement"],
+'Capture Volume//1//'+MIXERS:["Capture", 1, 13, "StandardElement"],
+'Capture Switch//2//'+MIXERS:["Capture", 2, 14, "StandardElement"],
+'Capture Volume//2//'+MIXERS:["Capture", 2, 14, "StandardElement"],
+'Input Source//0//'+MIXERS:["Input Source", 0, 15, "EnumElementCapture"],
+'Input Source//1//'+MIXERS:["Input Source", 1, 16, "EnumElementCapture"],
+'Input Source//2//'+MIXERS:["Input Source", 2, 17, "EnumElementCapture"],
+}
+
+def event(evmask, helem, melem):
+  return eventHandler(evmask, helem, melem)
+
+init()
diff --git a/modules/mixer/simple/python/main.py b/modules/mixer/simple/python/main.py
new file mode 100644
index 0000000..d3e69fb
--- /dev/null
+++ b/modules/mixer/simple/python/main.py
@@ -0,0 +1,24 @@
+#!/usr/bin/python
+#  -*- coding: utf-8 -*-
+#  -*- Python -*-
+
+from os.path import dirname
+from pyalsa.alsacontrol import Control
+from sys import path
+path.insert(0, dirname(__file__))
+
+def alsacode(module):
+  execfile(dirname(__file__)+'/'+module+'.py', globals())
+  
+ctl = Control(device)
+info = ctl.cardInfo()
+#mixername = info['mixername']
+components = info['components']
+del ctl
+
+if components.find('HDA:') >= 0:
+  module = 'hda'
+else:
+  raise ValueError, "Mixer for this hardware is not implemented in python"
+
+alsacode(module)
diff --git a/modules/mixer/simple/sbase.c b/modules/mixer/simple/sbase.c
new file mode 100644
index 0000000..97feee8
--- /dev/null
+++ b/modules/mixer/simple/sbase.c
@@ -0,0 +1,587 @@
+/*
+ *  Mixer Interface - simple abstact module - base library
+ *  Copyright (c) 2005 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <math.h>
+#include "asoundlib.h"
+#include "mixer_abst.h"
+#include "sbase.h"
+
+/*
+ * Prototypes
+ */
+
+static int selem_read(snd_mixer_elem_t *elem);
+
+/*
+ * Helpers
+ */
+
+static unsigned int chanmap_to_channels(unsigned int chanmap)
+{
+	unsigned int i, res;
+	
+	for (i = 0, res = 0; i < MAX_CHANNEL; i++)
+		if (chanmap & (1 << i))
+			res++;
+	return res;
+}
+
+#if 0
+static long to_user(struct selem_base *s, int dir, struct helem_base *c, long value)
+{
+	int64_t n;
+	if (c->max == c->min)
+		return s->dir[dir].min;
+	n = (int64_t) (value - c->min) * (s->dir[dir].max - s->dir[dir].min);
+	return s->dir[dir].min + (n + (c->max - c->min) / 2) / (c->max - c->min);
+}
+
+static long from_user(struct selem_base *s, int dir, struct helem_base *c, long value)
+{ 
+        int64_t n;
+	if (s->dir[dir].max == s->dir[dir].min)
+		return c->min;
+        n = (int64_t) (value - s->dir[dir].min) * (c->max - c->min);
+	return c->min + (n + (s->dir[dir].max - s->dir[dir].min) / 2) / (s->dir[dir].max - s->dir[dir].min);
+}
+#endif
+
+static void update_ranges(struct selem_base *s)
+{
+	static unsigned int mask[2] = { SM_CAP_PVOLUME, SM_CAP_CVOLUME };
+	static unsigned int gmask[2] = { SM_CAP_GVOLUME, SM_CAP_GVOLUME };
+	unsigned int dir, ok_flag;
+	struct list_head *pos;
+	struct helem_base *helem;
+	
+	for (dir = 0; dir < 2; dir++) {
+		s->dir[dir].min = 0;
+		s->dir[dir].max = 0;
+		ok_flag = 0;
+		list_for_each(pos, &s->helems) {
+			helem = list_entry(pos, struct helem_base, list);
+			printf("min = %li, max = %li\n", helem->min, helem->max);
+			if (helem->caps & mask[dir]) {
+				s->dir[dir].min = helem->min;
+				s->dir[dir].max = helem->max;
+				ok_flag = 1;
+				break;
+			}
+		}
+		if (ok_flag)
+			continue;
+		list_for_each(pos, &s->helems) {
+			helem = list_entry(pos, struct helem_base, list);
+			if (helem->caps & gmask[dir]) {
+				s->dir[dir].min = helem->min;
+				s->dir[dir].max = helem->max;
+				break;
+			}
+		}
+	}
+}
+
+/*
+ * Simple Mixer Operations
+ */
+
+static int is_ops(snd_mixer_elem_t *elem, int dir, int cmd, int val)
+{
+	struct selem_base *s = snd_mixer_elem_get_private(elem);
+
+	switch (cmd) {
+
+	case SM_OPS_IS_ACTIVE: {
+		struct list_head *pos;
+		struct helem_base *helem;
+		list_for_each(pos, &s->helems) {
+			helem = list_entry(pos, struct helem_base, list);
+			if (helem->inactive)
+				return 0;
+		}
+		return 1;
+	}
+
+	case SM_OPS_IS_MONO:
+		return chanmap_to_channels(s->dir[dir].chanmap) == 1;
+
+	case SM_OPS_IS_CHANNEL:
+		if (val > MAX_CHANNEL)
+			return 0;
+		return !!((1 << val) & s->dir[dir].chanmap);
+
+	case SM_OPS_IS_ENUMERATED: {
+		struct helem_base *helem;
+		helem = list_entry(s->helems.next, struct helem_base, list);
+		return !!(helem->purpose == PURPOSE_ENUMLIST);
+	}
+	
+	case SM_OPS_IS_ENUMCNT: {
+		struct helem_base *helem;
+		helem = list_entry(s->helems.next, struct helem_base, list);
+		return helem->max;
+	}
+
+	}
+	
+	return 1;
+}
+
+static int get_range_ops(snd_mixer_elem_t *elem, int dir,
+			 long *min, long *max)
+{
+	struct selem_base *s = snd_mixer_elem_get_private(elem);
+	
+	*min = s->dir[dir].min;
+	*max = s->dir[dir].max;
+
+	return 0;
+}
+
+static int get_dB_range_ops(snd_mixer_elem_t *elem ATTRIBUTE_UNUSED,
+			    int dir ATTRIBUTE_UNUSED,
+			    long *min ATTRIBUTE_UNUSED,
+			    long *max ATTRIBUTE_UNUSED)
+{
+	return -ENXIO;
+}
+
+static int set_range_ops(snd_mixer_elem_t *elem, int dir,
+			 long min, long max)
+{
+	struct selem_base *s = snd_mixer_elem_get_private(elem);
+	int err;
+
+	s->dir[dir].forced_range = 1;
+	s->dir[dir].min = min;
+	s->dir[dir].max = max;
+	
+	if ((err = selem_read(elem)) < 0)
+		return err;
+	return 0;
+}
+
+static int get_volume_ops(snd_mixer_elem_t *elem, int dir,
+			  snd_mixer_selem_channel_id_t channel, long *value)
+{
+	struct selem_base *s = snd_mixer_elem_get_private(elem);
+	
+	*value = s->dir[dir].vol[channel];
+	return 0;
+}
+
+static int get_dB_ops(snd_mixer_elem_t *elem ATTRIBUTE_UNUSED,
+		      int dir ATTRIBUTE_UNUSED,
+		      snd_mixer_selem_channel_id_t channel ATTRIBUTE_UNUSED,
+		      long *value ATTRIBUTE_UNUSED)
+{
+	return -ENXIO;
+}
+
+static int get_switch_ops(snd_mixer_elem_t *elem ATTRIBUTE_UNUSED,
+			  int dir ATTRIBUTE_UNUSED,
+			  snd_mixer_selem_channel_id_t channel ATTRIBUTE_UNUSED,
+			  int *value)
+{
+	/* struct selem_base *s = snd_mixer_elem_get_private(elem); */
+	*value = 0;
+	return 0;
+}
+
+static int set_volume_ops(snd_mixer_elem_t *elem ATTRIBUTE_UNUSED,
+			  int dir ATTRIBUTE_UNUSED,
+			  snd_mixer_selem_channel_id_t channel ATTRIBUTE_UNUSED,
+			  long value ATTRIBUTE_UNUSED)
+{
+	/* struct selem_base *s = snd_mixer_elem_get_private(elem); */
+	return 0;
+}
+
+static int set_dB_ops(snd_mixer_elem_t *elem ATTRIBUTE_UNUSED,
+		      int dir ATTRIBUTE_UNUSED,
+		      snd_mixer_selem_channel_id_t channel ATTRIBUTE_UNUSED,
+		      long value ATTRIBUTE_UNUSED,
+		      int xdir ATTRIBUTE_UNUSED)
+{
+	return -ENXIO;
+}
+
+static int set_switch_ops(snd_mixer_elem_t *elem ATTRIBUTE_UNUSED,
+			  int dir ATTRIBUTE_UNUSED,
+			  snd_mixer_selem_channel_id_t channel ATTRIBUTE_UNUSED,
+			  int value ATTRIBUTE_UNUSED)
+{
+	/* struct selem_base *s = snd_mixer_elem_get_private(elem); */
+	/* int changed; */
+	return 0;
+}
+
+static int enum_item_name_ops(snd_mixer_elem_t *elem ATTRIBUTE_UNUSED,
+			      unsigned int item ATTRIBUTE_UNUSED,
+			      size_t maxlen ATTRIBUTE_UNUSED,
+			      char *buf ATTRIBUTE_UNUSED)
+{
+	/* struct selem_base *s = snd_mixer_elem_get_private(elem);*/
+	return 0;
+}
+
+static int get_enum_item_ops(snd_mixer_elem_t *elem ATTRIBUTE_UNUSED,
+			     snd_mixer_selem_channel_id_t channel ATTRIBUTE_UNUSED,
+			     unsigned int *itemp ATTRIBUTE_UNUSED)
+{
+	/* struct selem_base *s = snd_mixer_elem_get_private(elem); */
+	return 0;
+}
+
+static int set_enum_item_ops(snd_mixer_elem_t *elem ATTRIBUTE_UNUSED,
+			     snd_mixer_selem_channel_id_t channel ATTRIBUTE_UNUSED,
+			     unsigned int item ATTRIBUTE_UNUSED)
+{
+	/* struct selem_base *s = snd_mixer_elem_get_private(elem); */
+	return 0;
+}
+
+static struct sm_elem_ops simple_ac97_ops = {
+	.is		= is_ops,
+	.get_range	= get_range_ops,
+	.get_dB_range	= get_dB_range_ops,
+	.set_range	= set_range_ops,
+	.get_volume	= get_volume_ops,
+	.get_dB		= get_dB_ops,
+	.set_volume	= set_volume_ops,
+	.set_dB		= set_dB_ops,
+	.get_switch	= get_switch_ops,
+	.set_switch	= set_switch_ops,
+	.enum_item_name	= enum_item_name_ops,
+	.get_enum_item	= get_enum_item_ops,
+	.set_enum_item	= set_enum_item_ops
+};
+
+/*
+ * event handling
+ */
+
+static int selem_read(snd_mixer_elem_t *elem)
+{
+	printf("elem read: %p\n", elem);
+	return 0;
+}
+
+static int simple_event_remove(snd_hctl_elem_t *helem,
+			       snd_mixer_elem_t *melem ATTRIBUTE_UNUSED)
+{
+	printf("event remove: %p\n", helem);
+	return 0;
+}
+
+static void selem_free(snd_mixer_elem_t *elem)
+{
+	struct selem_base *simple = snd_mixer_elem_get_private(elem);
+	struct helem_base *hsimple;
+	struct list_head *pos, *npos;
+
+	if (simple->selem.id)
+		snd_mixer_selem_id_free(simple->selem.id);
+	list_for_each_safe(pos, npos, &simple->helems) {
+		hsimple = list_entry(pos, struct helem_base, list);
+		free(hsimple);
+	}
+	free(simple);
+}
+
+static int simple_event_add1(snd_mixer_class_t *class,
+			     snd_hctl_elem_t *helem,
+			     struct helem_selector *sel)
+{
+	struct bclass_private *priv = snd_mixer_sbasic_get_private(class);
+	snd_mixer_elem_t *melem;
+	snd_mixer_selem_id_t *id;
+	snd_ctl_elem_info_t *info;
+	struct selem_base *simple;
+	struct helem_base *hsimple;
+	snd_ctl_elem_type_t ctype;
+	unsigned long values;
+	long min, max;
+	int err, new = 0;
+	struct list_head *pos;
+	struct bclass_sid *bsid;
+	struct melem_sids *sid;
+	unsigned int ui;
+	
+	list_for_each(pos, &priv->sids) {
+		bsid = list_entry(pos, struct bclass_sid, list);
+		for (ui = 0; ui < bsid->count; ui++) {
+			if (bsid->sids[ui].sid == sel->sid) {
+				sid = &bsid->sids[ui];
+				goto __sid_ok;
+			}
+		}
+	}
+	return 0;
+
+      __sid_ok:
+	snd_ctl_elem_info_alloca(&info);
+	err = snd_hctl_elem_info(helem, info);
+	if (err < 0)
+		return err;
+	ctype = snd_ctl_elem_info_get_type(info);
+	values = snd_ctl_elem_info_get_count(info);
+	switch (ctype) {
+	case SND_CTL_ELEM_TYPE_ENUMERATED:
+		min = 0;
+		max = snd_ctl_elem_info_get_items(info);
+		break;
+	case SND_CTL_ELEM_TYPE_INTEGER:
+		min = snd_ctl_elem_info_get_min(info);
+		max = snd_ctl_elem_info_get_max(info);
+		break;
+	default:
+		min = max = 0;
+		break;
+	}
+	
+	printf("event add: %p, %p (%s)\n", helem, sel, snd_hctl_elem_get_name(helem));
+	if (snd_mixer_selem_id_malloc(&id))
+		return -ENOMEM;
+	hsimple = calloc(1, sizeof(*hsimple));
+	if (hsimple == NULL) {
+		snd_mixer_selem_id_free(id);
+		return -ENOMEM;
+	}
+	switch (sel->purpose) {
+	case PURPOSE_SWITCH:
+		if (ctype != SND_CTL_ELEM_TYPE_BOOLEAN) {
+		      __invalid_type:
+		      	snd_mixer_selem_id_free(id);
+			return -EINVAL;
+		}
+		break;
+	case PURPOSE_VOLUME:
+		if (ctype != SND_CTL_ELEM_TYPE_INTEGER)
+			goto __invalid_type;
+		break;
+	}
+	hsimple->purpose = sel->purpose;
+	hsimple->caps = sel->caps;
+	hsimple->min = min;
+	hsimple->max = max;
+	snd_mixer_selem_id_set_name(id, sid->sname);
+	snd_mixer_selem_id_set_index(id, sid->sindex);
+	melem = snd_mixer_find_selem(snd_mixer_class_get_mixer(class), id);
+	if (!melem) {
+		simple = calloc(1, sizeof(*simple));
+		if (!simple) {
+			snd_mixer_selem_id_free(id);
+			free(hsimple);
+			return -ENOMEM;
+		}
+		simple->selem.id = id;
+		simple->selem.ops = &simple_ac97_ops;
+		INIT_LIST_HEAD(&simple->helems);
+		simple->sid = sel->sid;
+		err = snd_mixer_elem_new(&melem, SND_MIXER_ELEM_SIMPLE,
+					 sid->weight,
+					 simple, selem_free);
+		if (err < 0) {
+			snd_mixer_selem_id_free(id);
+			free(hsimple);
+			free(simple);
+			return err;
+		}
+		new = 1;
+	} else {
+		simple = snd_mixer_elem_get_private(melem);
+		snd_mixer_selem_id_free(id);
+	}
+	list_add_tail(&hsimple->list, &simple->helems);
+	hsimple->inactive = snd_ctl_elem_info_is_inactive(info);
+	err = snd_mixer_elem_attach(melem, helem);
+	if (err < 0)
+		goto __error;
+	simple->dir[0].chanmap |= sid->chanmap[0];
+	simple->dir[1].chanmap |= sid->chanmap[1];
+	simple->selem.caps |= hsimple->caps;
+	update_ranges(simple);
+#if 0
+	err = simple_update(melem);
+	if (err < 0) {
+		if (new)
+			goto __error;
+		return err;
+	}
+#endif
+	if (new)
+		err = snd_mixer_elem_add(melem, class);
+	else
+		err = snd_mixer_elem_info(melem);
+	if (err < 0)
+		return err;
+	err = selem_read(melem);
+	if (err < 0)
+		return err;
+	if (err)
+		err = snd_mixer_elem_value(melem);
+	return err;
+      __error:
+      	if (new)
+      		snd_mixer_elem_free(melem);
+      	return -EINVAL;
+}
+
+static int simple_event_add(snd_mixer_class_t *class, snd_hctl_elem_t *helem)
+{
+	struct bclass_private *priv = snd_mixer_sbasic_get_private(class);
+	struct bclass_selector *sel;
+	struct helem_selector *hsel;
+	struct list_head *pos;
+	snd_ctl_elem_iface_t iface = snd_hctl_elem_get_interface(helem);
+	const char *name = snd_hctl_elem_get_name(helem);
+	unsigned int index = snd_hctl_elem_get_index(helem);
+	unsigned int ui;
+	int err;
+
+	list_for_each(pos, &priv->selectors) {
+		sel = list_entry(pos, struct bclass_selector, list);
+		for (ui = 0; ui < sel->count; ui++) {
+			hsel = &sel->selectors[ui];
+			if (hsel->iface == iface && !strcmp(hsel->name, name) && hsel->index == index) {
+				err = simple_event_add1(class, helem, hsel);
+				if (err < 0)
+					return err;	/* early exit? */
+			}
+		}
+	}
+	return 0;
+}
+
+int alsa_mixer_sbasic_event(snd_mixer_class_t *class, unsigned int mask,
+			    snd_hctl_elem_t *helem, snd_mixer_elem_t *melem)
+{
+	int err;
+	if (mask == SND_CTL_EVENT_MASK_REMOVE)
+		return simple_event_remove(helem, melem);
+	if (mask & SND_CTL_EVENT_MASK_ADD) {
+		err = simple_event_add(class, helem);
+		if (err < 0)
+			return err;
+	}
+	if (mask & SND_CTL_EVENT_MASK_INFO) {
+		err = simple_event_remove(helem, melem);
+		if (err < 0)
+			return err;
+		err = simple_event_add(class, helem);
+		if (err < 0)
+			return err;
+		return 0;
+	}
+	if (mask & SND_CTL_EVENT_MASK_VALUE) {
+		err = selem_read(melem);
+		if (err < 0)
+			return err;
+		if (err) {
+			err = snd_mixer_elem_value(melem);
+			if (err < 0)
+				return err;
+		}
+	}
+	return 0;
+}
+
+static void sbasic_cpriv_free(snd_mixer_class_t *class)
+{
+	struct bclass_private *priv = snd_mixer_sbasic_get_private(class);
+	struct bclass_selector *sel;
+	struct bclass_sid *sid;
+	struct list_head *pos, *pos1;
+
+	list_for_each_safe(pos, pos1, &priv->selectors) {
+		sel = list_entry(pos, struct bclass_selector, list);
+		free(sel);
+	}
+	list_for_each_safe(pos, pos1, &priv->sids) {
+		sid = list_entry(pos, struct bclass_sid, list);
+		free(sid);
+	}
+	free(priv);
+}
+
+void alsa_mixer_sbasic_initpriv(snd_mixer_class_t *class,
+				struct bclass_private *priv)
+{
+	INIT_LIST_HEAD(&priv->selectors);
+	INIT_LIST_HEAD(&priv->sids);
+	snd_mixer_sbasic_set_private(class, priv);
+	snd_mixer_sbasic_set_private_free(class, sbasic_cpriv_free);
+}
+
+int alsa_mixer_sbasic_selreg(snd_mixer_class_t *class,
+			     struct helem_selector *selectors,
+			     unsigned int count)
+{
+	struct bclass_private *priv = snd_mixer_sbasic_get_private(class);
+	struct bclass_selector *sel = calloc(1, sizeof(*sel));
+
+	if (sel == NULL)
+		return -ENOMEM;
+	if (priv == NULL) {
+		priv = calloc(1, sizeof(*priv));
+		if (priv == NULL) {
+			free(sel);
+			return -ENOMEM;
+		}
+	}
+	sel->selectors = selectors;
+	sel->count = count;
+	list_add_tail(&sel->list, &priv->selectors);
+	return 0;
+}
+
+int alsa_mixer_sbasic_sidreg(snd_mixer_class_t *class,
+			     struct melem_sids *sids,
+			     unsigned int count)
+{
+	struct bclass_private *priv = snd_mixer_sbasic_get_private(class);
+	struct bclass_sid *sid = calloc(1, sizeof(*sid));
+
+	if (sid == NULL)
+		return -ENOMEM;
+	if (priv == NULL) {
+		priv = calloc(1, sizeof(*priv));
+		if (priv == NULL) {
+			free(sid);
+			return -ENOMEM;
+		}
+		INIT_LIST_HEAD(&priv->selectors);
+		INIT_LIST_HEAD(&priv->sids);
+		snd_mixer_sbasic_set_private(class, priv);
+		snd_mixer_sbasic_set_private_free(class, sbasic_cpriv_free);
+	}
+	sid->sids = sids;
+	sid->count = count;
+	list_add(&sid->list, &priv->sids);
+	return 0;
+}
diff --git a/modules/mixer/simple/sbase.h b/modules/mixer/simple/sbase.h
new file mode 100644
index 0000000..ee5fe03
--- /dev/null
+++ b/modules/mixer/simple/sbase.h
@@ -0,0 +1,111 @@
+/*
+ *  Mixer Interface - simple abstact module - base library
+ *  Copyright (c) 2005 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#ifndef __SMIXER_BASE_H
+
+#include "list.h"
+
+#define MAX_CHANNEL	6
+
+#define SID_MASTER	0
+#define SID_HEADPHONE	1
+#define SID_FRONT	2
+#define SID_PCM		3
+#define SID_CD		4
+
+struct melem_sids {
+	unsigned short sid;
+	const char *sname;
+	unsigned short sindex;
+	unsigned short weight;
+	unsigned int chanmap[2];
+	struct sm_elem_ops *sops;
+};
+
+#define PURPOSE_VOLUME		0
+#define PURPOSE_SWITCH		1
+#define PURPOSE_ENUMLIST	2
+
+struct helem_selector {
+	snd_ctl_elem_iface_t iface;
+	const char *name;
+	unsigned short index;
+	unsigned short sid;
+	unsigned short purpose;
+	unsigned short caps;
+};
+
+struct helem_base {
+	struct list_head list;
+	snd_hctl_elem_t *helem;
+	unsigned short purpose;
+	unsigned int caps;
+	unsigned int inactive: 1;
+	long min, max;
+	unsigned int count;
+};
+
+struct selem_base {
+	sm_selem_t selem;
+	struct list_head helems;
+	unsigned short sid;
+	struct {
+		unsigned int chanmap;
+		unsigned int forced_range: 1;
+		long min, max;
+		long vol[MAX_CHANNEL];
+	} dir[2];
+};
+
+struct bclass_selector {
+	struct list_head list;
+	struct helem_selector *selectors;
+	unsigned int count;
+};
+
+struct bclass_sid {
+	struct list_head list;
+	struct melem_sids *sids;
+	unsigned int count;
+};
+
+typedef struct bclass_base_ops {
+	int (*event)(snd_mixer_class_t *class, unsigned int mask,
+		     snd_hctl_elem_t *helem, snd_mixer_elem_t *melem);
+	int (*selreg)(snd_mixer_class_t *class,
+		      struct helem_selector *selectors,
+		      unsigned int count);
+	int (*sidreg)(snd_mixer_class_t *class,
+		      struct melem_sids *sids,
+		      unsigned int count);
+} bclass_base_ops_t;
+
+struct bclass_private {
+	struct list_head selectors;
+	struct list_head sids;
+	void *dl_sbase;
+	bclass_base_ops_t ops;
+};
+
+int mixer_simple_basic_dlopen(snd_mixer_class_t *class,
+			      bclass_base_ops_t **ops);
+
+#endif /* __SMIXER_BASE_H */
diff --git a/modules/mixer/simple/sbasedl.c b/modules/mixer/simple/sbasedl.c
new file mode 100644
index 0000000..494802f
--- /dev/null
+++ b/modules/mixer/simple/sbasedl.c
@@ -0,0 +1,106 @@
+/*
+ *  Mixer Interface - simple abstact module - base library (dlopen function)
+ *  Copyright (c) 2005 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <math.h>
+#include <dlfcn.h>
+#include "config.h"
+#include "asoundlib.h"
+#include "mixer_abst.h"
+#include "sbase.h"
+
+#define SO_PATH ALSA_PLUGIN_DIR "/smixer"
+
+int mixer_simple_basic_dlopen(snd_mixer_class_t *class,
+			      bclass_base_ops_t **ops)
+{
+	struct bclass_private *priv = snd_mixer_sbasic_get_private(class);
+	const char *lib = "smixer-sbase.so";
+	void (*initpriv)(snd_mixer_class_t *class, struct bclass_private *priv);
+	char *xlib, *path;
+	void *h;
+	int initflag = 0;
+
+	if (priv == NULL) {
+		priv = calloc(1, sizeof(*priv));
+		if (priv == NULL)
+			return -ENOMEM;
+		initflag = 1;
+	}
+	path = getenv("ALSA_MIXER_SIMPLE_MODULES");
+	if (!path)
+		path = SO_PATH;
+	xlib = malloc(strlen(lib) + strlen(path) + 1 + 1);
+	if (xlib == NULL) {
+		if (initflag)
+			free(priv);
+		return -ENOMEM;
+	}
+	strcpy(xlib, path);
+	strcat(xlib, "/");
+	strcat(xlib, lib);
+	h = snd_dlopen(xlib, RTLD_NOW);
+	if (h == NULL) {
+		SNDERR("Unable to open library '%s'", xlib);
+		goto __error;
+	}
+	initpriv = dlsym(h, "alsa_mixer_sbasic_initpriv");
+	if (initpriv == NULL) {
+		SNDERR("Symbol 'alsa_mixer_sbasic_initpriv' was not found in '%s'", xlib);
+		goto __error;
+	}
+	priv->ops.event = dlsym(h, "alsa_mixer_sbasic_event");
+	if (priv->ops.event == NULL) {
+		SNDERR("Symbol 'alsa_mixer_sbasic_event' was not found in '%s'", xlib);
+		goto __error;
+	}
+	priv->ops.selreg = dlsym(h, "alsa_mixer_sbasic_selreg");
+	if (priv->ops.selreg == NULL) {
+		SNDERR("Symbol 'alsa_mixer_sbasic_selreg' was not found in '%s'", xlib);
+		goto __error;
+	}
+	priv->ops.sidreg = dlsym(h, "alsa_mixer_sbasic_sidreg");
+	if (priv->ops.sidreg == NULL) {
+		SNDERR("Symbol 'alsa_mixer_sbasic_sidreg' was not found in '%s'", xlib);
+		goto __error;
+	}
+	free(xlib);
+	if (initflag)
+		initpriv(class, priv);
+	priv->dl_sbase = h;
+	if (ops)
+		*ops = &priv->ops;
+	return 1;
+
+      __error:
+      	if (initflag)
+      		free(priv);
+	if (h)
+		snd_dlclose(h);
+	free(xlib);
+	return -ENXIO;
+}
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..08f482a
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,93 @@
+EXTRA_DIST=Versions
+COMPATNUM=@LIBTOOL_VERSION_INFO@
+
+if VERSIONED_SYMBOLS
+VSYMS = -Wl,--version-script=Versions
+else
+VSYMS =
+endif
+
+if SYMBOLIC_FUNCTIONS
+SYMFUNCS = -Wl,-Bsymbolic-functions
+else
+SYMFUNCS =
+endif
+
+lib_LTLIBRARIES = libasound.la
+libasound_la_SOURCES = conf.c confmisc.c input.c output.c async.c error.c dlmisc.c socket.c shmarea.c userfile.c names.c
+
+SUBDIRS=control
+libasound_la_LIBADD = control/libcontrol.la
+if BUILD_MIXER
+SUBDIRS += mixer
+libasound_la_LIBADD += mixer/libmixer.la
+endif
+if BUILD_PCM
+SUBDIRS += pcm timer
+libasound_la_LIBADD += pcm/libpcm.la timer/libtimer.la
+endif
+if BUILD_RAWMIDI
+SUBDIRS += rawmidi
+libasound_la_LIBADD += rawmidi/librawmidi.la
+endif
+if BUILD_HWDEP
+SUBDIRS += hwdep
+libasound_la_LIBADD += hwdep/libhwdep.la
+endif
+if BUILD_SEQ
+SUBDIRS += seq
+libasound_la_LIBADD += seq/libseq.la
+endif
+if BUILD_UCM
+SUBDIRS += ucm
+libasound_la_LIBADD += ucm/libucm.la
+endif
+if BUILD_ALISP
+SUBDIRS += alisp
+libasound_la_LIBADD += alisp/libalisp.la
+endif
+SUBDIRS += compat conf
+libasound_la_LIBADD += compat/libcompat.la @ALSA_DEPLIBS@
+
+libasound_la_LDFLAGS = -version-info $(COMPATNUM) $(VSYMS) $(SYMFUNCS) $(LDFLAGS_NOUNDEFINED)
+
+control/libcontrol.la:
+	$(MAKE) -C control libcontrol.la
+
+mixer/libmixer.la:
+	$(MAKE) -C mixer libmixer.la
+
+ordinary_mixer/libordinarymixer.la:
+	$(MAKE) -C ordinary_mixer libordinarymixer.la
+
+pcm/libpcm.la:
+	$(MAKE) -C pcm libpcm.la
+
+ordinary_pcm/libordinarypcm.la:
+	$(MAKE) -C ordinary_pcm libordinarypcm.la
+
+rawmidi/librawmidi.la:
+	$(MAKE) -C rawmidi librawmidi.la
+
+timer/libtimer.la:
+	$(MAKE) -C timer libtimer.la
+
+hwdep/libhwdep.la:
+	$(MAKE) -C hwdep libhwdep.la
+
+seq/libseq.la:
+	$(MAKE) -C seq libseq.la
+
+ucm/libucm.la:
+	$(MAKE) -C ucm libucm.la
+
+instr/libinstr.la:
+	$(MAKE) -C instr libinstr.la
+
+compat/libcompat.la:
+	$(MAKE) -C compat libcompat.la
+
+alisp/libalisp.la:
+	$(MAKE) -C alisp libalisp.la
+
+INCLUDES=-I$(top_srcdir)/include
diff --git a/src/Versions.in b/src/Versions.in
new file mode 100644
index 0000000..8d2dd11
--- /dev/null
+++ b/src/Versions.in
@@ -0,0 +1,131 @@
+ALSA_0.9 {
+  global:
+    @SYMBOL_PREFIX@snd_*;
+
+    @SYMBOL_PREFIX@_snd_*_open;
+    @SYMBOL_PREFIX@_snd_*_dlsym_*;
+    @SYMBOL_PREFIX@_snd_*_poll_descriptor;
+    @SYMBOL_PREFIX@_snd_pcm_hook_*;
+
+    @SYMBOL_PREFIX@__snd_pcm_hw_params_*;
+    @SYMBOL_PREFIX@__snd_pcm_sw_params_*;
+    @SYMBOL_PREFIX@__snd_*_dlsym_*;
+
+  local:
+    *;
+};
+
+ALSA_0.9.0rc4 {
+  global:
+
+    @SYMBOL_PREFIX@snd_pcm_hw_params_get_access;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_set_access_first;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_set_access_last;
+
+    @SYMBOL_PREFIX@snd_pcm_hw_params_get_format;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_set_format_first;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_set_format_last;
+
+    @SYMBOL_PREFIX@snd_pcm_hw_params_get_subformat;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_set_subformat_first;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_set_subformat_last;
+
+    @SYMBOL_PREFIX@snd_pcm_hw_params_get_channels;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_get_channels_min;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_get_channels_max;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_set_channels_near;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_set_channels_first;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_set_channels_last;
+
+    @SYMBOL_PREFIX@snd_pcm_hw_params_get_rate;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_get_rate_min;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_get_rate_max;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_set_rate_near;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_set_rate_first;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_set_rate_last;
+
+    @SYMBOL_PREFIX@snd_pcm_hw_params_get_period_time;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_get_period_time_min;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_get_period_time_max;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_set_period_time_near;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_set_period_time_first;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_set_period_time_last;
+
+    @SYMBOL_PREFIX@snd_pcm_hw_params_get_period_size;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_get_period_size_min;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_get_period_size_max;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_set_period_size_near;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_set_period_size_first;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_set_period_size_last;
+
+    @SYMBOL_PREFIX@snd_pcm_hw_params_get_periods;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_get_periods_min;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_get_periods_max;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_set_periods_near;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_set_periods_first;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_set_periods_last;
+
+    @SYMBOL_PREFIX@snd_pcm_hw_params_get_buffer_time;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_get_buffer_time_min;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_get_buffer_time_max;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_set_buffer_time_near;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_set_buffer_time_first;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_set_buffer_time_last;
+
+    @SYMBOL_PREFIX@snd_pcm_hw_params_get_buffer_size;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_get_buffer_size_min;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_get_buffer_size_max;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_set_buffer_size_near;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_set_buffer_size_first;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_set_buffer_size_last;
+
+    @SYMBOL_PREFIX@snd_pcm_hw_params_get_tick_time;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_get_tick_time_min;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_get_tick_time_max;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_set_tick_time_near;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_set_tick_time_first;
+    @SYMBOL_PREFIX@snd_pcm_hw_params_set_tick_time_last;
+
+} ALSA_0.9;
+
+ALSA_0.9.0rc8 {
+  global:
+
+    @SYMBOL_PREFIX@snd_pcm_forward;
+    @SYMBOL_PREFIX@snd_pcm_status_get_trigger_htstamp;
+    @SYMBOL_PREFIX@snd_pcm_status_get_htstamp;
+
+} ALSA_0.9.0rc4;
+
+ALSA_0.9.0 {
+  global:
+
+    @SYMBOL_PREFIX@snd_pcm_type_name;
+    @SYMBOL_PREFIX@snd_timer_query_info;
+    @SYMBOL_PREFIX@snd_timer_query_params;
+    @SYMBOL_PREFIX@snd_timer_query_status;
+    @SYMBOL_PREFIX@snd_timer_params_set_exclusive;
+    @SYMBOL_PREFIX@snd_timer_params_get_exclusive;
+    @SYMBOL_PREFIX@snd_timer_params_set_filter;
+    @SYMBOL_PREFIX@snd_timer_params_get_filter;
+} ALSA_0.9.0rc8;
+
+ALSA_0.9.3 {
+  global:
+
+    @SYMBOL_PREFIX@snd_ctl_elem_info_get_dimensions;
+    @SYMBOL_PREFIX@snd_ctl_elem_info_get_dimension;
+} ALSA_0.9.0;
+
+ALSA_0.9.5 {
+  global:
+
+    @SYMBOL_PREFIX@alsa_lisp;
+} ALSA_0.9.3;
+
+ALSA_0.9.7 {
+  global:
+
+    @SYMBOL_PREFIX@alsa_lisp_*;
+} ALSA_0.9.5;
+
diff --git a/src/alisp/Makefile.am b/src/alisp/Makefile.am
new file mode 100644
index 0000000..e6d4ac5
--- /dev/null
+++ b/src/alisp/Makefile.am
@@ -0,0 +1,11 @@
+EXTRA_LTLIBRARIES = libalisp.la
+
+EXTRA_DIST = alisp_snd.c
+
+libalisp_la_SOURCES = alisp.c
+
+noinst_HEADERS = alisp_local.h
+
+all: libalisp.la
+
+INCLUDES=-I$(top_srcdir)/include
diff --git a/src/alisp/alisp.c b/src/alisp/alisp.c
new file mode 100644
index 0000000..7575f55
--- /dev/null
+++ b/src/alisp/alisp.c
@@ -0,0 +1,3495 @@
+/*
+ *  ALSA lisp implementation
+ *  Copyright (c) 2003 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *  Based on work of Sandro Sigala (slisp-1.2)
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <assert.h>
+
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <math.h>
+#include <err.h>
+
+#define alisp_seq_iterator alisp_object
+
+#include "local.h"
+#include "alisp.h"
+#include "alisp_local.h"
+
+struct alisp_object alsa_lisp_nil;
+struct alisp_object alsa_lisp_t;
+
+/* parser prototypes */
+static struct alisp_object * parse_object(struct alisp_instance *instance, int havetoken);
+static void princ_cons(snd_output_t *out, struct alisp_object * p);
+static void princ_object(snd_output_t *out, struct alisp_object * p);
+static struct alisp_object * eval(struct alisp_instance *instance, struct alisp_object * p);
+
+/* functions */
+static struct alisp_object *F_eval(struct alisp_instance *instance, struct alisp_object *);
+static struct alisp_object *F_progn(struct alisp_instance *instance, struct alisp_object *);
+static struct alisp_object *F_funcall(struct alisp_instance *instance, struct alisp_object *);
+
+/* others */
+static int alisp_include_file(struct alisp_instance *instance, const char *filename);
+
+/*
+ *  object handling
+ */
+
+static int get_string_hash(const char *s)
+{
+	int val = 0;
+	if (s == NULL)
+		return val;
+	while (*s)
+		val += *s++;
+	return val & ALISP_OBJ_PAIR_HASH_MASK;
+}
+
+static void nomem(void)
+{
+	SNDERR("alisp: no enough memory");
+}
+
+static void lisp_verbose(struct alisp_instance *instance, const char *fmt, ...)
+{
+	va_list ap;
+
+	if (!instance->verbose)
+		return;
+	va_start(ap, fmt);
+	snd_output_printf(instance->vout, "alisp: ");
+	snd_output_vprintf(instance->vout, fmt, ap);
+	snd_output_putc(instance->vout, '\n');
+	va_end(ap);
+}
+
+static void lisp_error(struct alisp_instance *instance, const char *fmt, ...)
+{
+	va_list ap;
+
+	if (!instance->warning)
+		return;
+	va_start(ap, fmt);
+	snd_output_printf(instance->eout, "alisp error: ");
+	snd_output_vprintf(instance->eout, fmt, ap);
+	snd_output_putc(instance->eout, '\n');
+	va_end(ap);
+}
+
+static void lisp_warn(struct alisp_instance *instance, const char *fmt, ...)
+{
+	va_list ap;
+
+	if (!instance->warning)
+		return;
+	va_start(ap, fmt);
+	snd_output_printf(instance->wout, "alisp warning: ");
+	snd_output_vprintf(instance->wout, fmt, ap);
+	snd_output_putc(instance->wout, '\n');
+	va_end(ap);
+}
+
+static void lisp_debug(struct alisp_instance *instance, const char *fmt, ...)
+{
+	va_list ap;
+
+	if (!instance->debug)
+		return;
+	va_start(ap, fmt);
+	snd_output_printf(instance->dout, "alisp debug: ");
+	snd_output_vprintf(instance->dout, fmt, ap);
+	snd_output_putc(instance->dout, '\n');
+	va_end(ap);
+}
+
+static struct alisp_object * new_object(struct alisp_instance *instance, int type)
+{
+	struct alisp_object * p;
+
+	if (list_empty(&instance->free_objs_list)) {
+		p = (struct alisp_object *)malloc(sizeof(struct alisp_object));
+		if (p == NULL) {
+			nomem();
+			return NULL;
+		}
+		lisp_debug(instance, "allocating cons %p", p);
+	} else {
+		p = (struct alisp_object *)instance->free_objs_list.next;
+		list_del(&p->list);
+		instance->free_objs--;
+		lisp_debug(instance, "recycling cons %p", p);
+	}
+
+	instance->used_objs++;
+
+	alisp_set_type(p, type);
+	alisp_set_refs(p, 1);
+	if (type == ALISP_OBJ_CONS) {
+		p->value.c.car = &alsa_lisp_nil;
+		p->value.c.cdr = &alsa_lisp_nil;
+		list_add(&p->list, &instance->used_objs_list[0][ALISP_OBJ_CONS]);
+	}
+
+	if (instance->used_objs + instance->free_objs > instance->max_objs)
+		instance->max_objs = instance->used_objs + instance->free_objs;
+
+	return p;
+}
+
+static void free_object(struct alisp_object * p)
+{
+	switch (alisp_get_type(p)) {
+	case ALISP_OBJ_STRING:
+	case ALISP_OBJ_IDENTIFIER:
+		free(p->value.s);
+		alisp_set_type(p, ALISP_OBJ_INTEGER);
+		break;
+	default:
+		break;
+	}
+}
+
+static void delete_object(struct alisp_instance *instance, struct alisp_object * p)
+{
+	if (p == NULL || p == &alsa_lisp_nil || p == &alsa_lisp_t)
+		return;
+	if (alisp_compare_type(p, ALISP_OBJ_NIL) ||
+	    alisp_compare_type(p, ALISP_OBJ_T))
+		return;
+	assert(alisp_get_refs(p) > 0);
+	lisp_debug(instance, "delete cons %p (type = %i, refs = %i) (s = '%s')", p, alisp_get_type(p), alisp_get_refs(p),
+			alisp_compare_type(p, ALISP_OBJ_STRING) ||
+			alisp_compare_type(p, ALISP_OBJ_IDENTIFIER) ? p->value.s : "???");
+	if (alisp_dec_refs(p))
+		return;
+	list_del(&p->list);
+	instance->used_objs--;
+	free_object(p);
+	if (instance->free_objs >= ALISP_FREE_OBJ_POOL) {
+		lisp_debug(instance, "freed cons %p", p);
+		free(p);
+		return;
+	}
+	lisp_debug(instance, "moved cons %p to free list", p);
+	list_add(&p->list, &instance->free_objs_list);
+	instance->free_objs++;
+}
+
+static void delete_tree(struct alisp_instance *instance, struct alisp_object * p)
+{
+	if (p == NULL)
+		return;
+	if (alisp_compare_type(p, ALISP_OBJ_CONS)) {
+		delete_tree(instance, p->value.c.car);
+		delete_tree(instance, p->value.c.cdr);
+	}
+	delete_object(instance, p);
+}
+
+static struct alisp_object * incref_object(struct alisp_instance *instance ATTRIBUTE_UNUSED, struct alisp_object * p)
+{
+	if (p == NULL || p == &alsa_lisp_nil || p == &alsa_lisp_t)
+		return p;
+	if (alisp_get_refs(p) == ALISP_MAX_REFS) {
+		assert(0);
+		fprintf(stderr, "OOPS: alsa lisp: incref fatal error\n");
+		exit(EXIT_FAILURE);
+	}
+	alisp_inc_refs(p);
+	return p;
+}
+
+static struct alisp_object * incref_tree(struct alisp_instance *instance, struct alisp_object * p)
+{
+	if (p == NULL)
+		return NULL;
+	if (alisp_compare_type(p, ALISP_OBJ_CONS)) {
+		incref_tree(instance, p->value.c.car);
+		incref_tree(instance, p->value.c.cdr);
+	}
+	return incref_object(instance, p);
+}
+
+/* Function not used yet. Leave it commented out until we actually use it to
+ * avoid compiler complaints */
+#if 0
+static struct alisp_object * incref_tree_explicit(struct alisp_instance *instance, struct alisp_object * p, struct alisp_object * e)
+{
+	if (p == NULL)
+		return NULL;
+	if (alisp_compare_type(p, ALISP_OBJ_CONS)) {
+		if (e == p) {
+			incref_tree(instance, p->value.c.car);
+			incref_tree(instance, p->value.c.cdr);
+		} else {
+			incref_tree_explicit(instance, p->value.c.car, e);
+			incref_tree_explicit(instance, p->value.c.cdr, e);
+		}
+	}
+	if (e == p)
+		return incref_object(instance, p);
+	return p;
+}
+#endif
+
+static void free_objects(struct alisp_instance *instance)
+{
+	struct list_head *pos, *pos1;
+	struct alisp_object * p;
+	struct alisp_object_pair * pair;
+	int i, j;
+
+	for (i = 0; i < ALISP_OBJ_PAIR_HASH_SIZE; i++) {
+		list_for_each_safe(pos, pos1, &instance->setobjs_list[i]) {
+			pair = list_entry(pos, struct alisp_object_pair, list);
+			lisp_debug(instance, "freeing pair: '%s' -> %p", pair->name, pair->value);
+			delete_tree(instance, pair->value);
+			free((void *)pair->name);
+			free(pair);
+		}
+	}
+	for (i = 0; i < ALISP_OBJ_PAIR_HASH_SIZE; i++)
+		for (j = 0; j <= ALISP_OBJ_LAST_SEARCH; j++) {
+			list_for_each_safe(pos, pos1, &instance->used_objs_list[i][j]) {
+				p = list_entry(pos, struct alisp_object, list);
+				lisp_warn(instance, "object %p is still referenced %i times!", p, alisp_get_refs(p));
+#if 0
+				snd_output_printf(instance->wout, ">>>> ");
+				princ_object(instance->wout, p);
+				snd_output_printf(instance->wout, " <<<<\n");
+#endif
+				if (alisp_get_refs(p) > 0)
+					alisp_set_refs(p, 1);
+				delete_object(instance, p);
+			}
+		}
+	list_for_each_safe(pos, pos1, &instance->free_objs_list) {
+		p = list_entry(pos, struct alisp_object, list);
+		list_del(&p->list);
+		free(p);
+		lisp_debug(instance, "freed (all) cons %p", p);
+	}
+}
+
+static struct alisp_object * search_object_identifier(struct alisp_instance *instance, const char *s)
+{
+	struct list_head * pos;
+	struct alisp_object * p;
+
+	list_for_each(pos, &instance->used_objs_list[get_string_hash(s)][ALISP_OBJ_IDENTIFIER]) {
+		p = list_entry(pos, struct alisp_object, list);
+		if (alisp_get_refs(p) > ALISP_MAX_REFS_LIMIT)
+			continue;
+		if (!strcmp(p->value.s, s))
+			return incref_object(instance, p);
+	}
+
+	return NULL;
+}
+
+static struct alisp_object * search_object_string(struct alisp_instance *instance, const char *s)
+{
+	struct list_head * pos;
+	struct alisp_object * p;
+
+	list_for_each(pos, &instance->used_objs_list[get_string_hash(s)][ALISP_OBJ_STRING]) {
+		p = list_entry(pos, struct alisp_object, list);
+		if (!strcmp(p->value.s, s)) {
+			if (alisp_get_refs(p) > ALISP_MAX_REFS_LIMIT)
+				continue;
+			return incref_object(instance, p);
+		}
+	}
+
+	return NULL;
+}
+
+static struct alisp_object * search_object_integer(struct alisp_instance *instance, long in)
+{
+	struct list_head * pos;
+	struct alisp_object * p;
+
+	list_for_each(pos, &instance->used_objs_list[in & ALISP_OBJ_PAIR_HASH_MASK][ALISP_OBJ_INTEGER]) {
+		p = list_entry(pos, struct alisp_object, list);
+		if (p->value.i == in) {
+			if (alisp_get_refs(p) > ALISP_MAX_REFS_LIMIT)
+				continue;
+			return incref_object(instance, p);
+		}
+	}
+
+	return NULL;
+}
+
+static struct alisp_object * search_object_float(struct alisp_instance *instance, double in)
+{
+	struct list_head * pos;
+	struct alisp_object * p;
+
+	list_for_each(pos, &instance->used_objs_list[(long)in & ALISP_OBJ_PAIR_HASH_MASK][ALISP_OBJ_FLOAT]) {
+		p = list_entry(pos, struct alisp_object, list);
+		if (p->value.i == in) {
+			if (alisp_get_refs(p) > ALISP_MAX_REFS_LIMIT)
+				continue;
+			return incref_object(instance, p);
+		}
+	}
+
+	return NULL;
+}
+
+static struct alisp_object * search_object_pointer(struct alisp_instance *instance, const void *ptr)
+{
+	struct list_head * pos;
+	struct alisp_object * p;
+
+	list_for_each(pos, &instance->used_objs_list[(long)ptr & ALISP_OBJ_PAIR_HASH_MASK][ALISP_OBJ_POINTER]) {
+		p = list_entry(pos, struct alisp_object, list);
+		if (p->value.ptr == ptr) {
+			if (alisp_get_refs(p) > ALISP_MAX_REFS_LIMIT)
+				continue;
+			return incref_object(instance, p);
+		}
+	}
+
+	return NULL;
+}
+
+static struct alisp_object * new_integer(struct alisp_instance *instance, long value)
+{
+	struct alisp_object * obj;
+	
+	obj = search_object_integer(instance, value);
+	if (obj != NULL)
+		return obj;
+	obj = new_object(instance, ALISP_OBJ_INTEGER);
+	if (obj) {
+		list_add(&obj->list, &instance->used_objs_list[value & ALISP_OBJ_PAIR_HASH_MASK][ALISP_OBJ_INTEGER]);
+		obj->value.i = value;
+	}
+	return obj;
+}
+
+static struct alisp_object * new_float(struct alisp_instance *instance, double value)
+{
+	struct alisp_object * obj;
+	
+	obj = search_object_float(instance, value);
+	if (obj != NULL)
+		return obj;
+	obj = new_object(instance, ALISP_OBJ_FLOAT);
+	if (obj) {
+		list_add(&obj->list, &instance->used_objs_list[(long)value & ALISP_OBJ_PAIR_HASH_MASK][ALISP_OBJ_FLOAT]);
+		obj->value.f = value;
+	}
+	return obj;
+}
+
+static struct alisp_object * new_string(struct alisp_instance *instance, const char *str)
+{
+	struct alisp_object * obj;
+	
+	obj = search_object_string(instance, str);
+	if (obj != NULL)
+		return obj;
+	obj = new_object(instance, ALISP_OBJ_STRING);
+	if (obj)
+		list_add(&obj->list, &instance->used_objs_list[get_string_hash(str)][ALISP_OBJ_STRING]);
+	if (obj && (obj->value.s = strdup(str)) == NULL) {
+		delete_object(instance, obj);
+		nomem();
+		return NULL;
+	}
+	return obj;
+}
+
+static struct alisp_object * new_identifier(struct alisp_instance *instance, const char *id)
+{
+	struct alisp_object * obj;
+	
+	obj = search_object_identifier(instance, id);
+	if (obj != NULL)
+		return obj;
+	obj = new_object(instance, ALISP_OBJ_IDENTIFIER);
+	if (obj)
+		list_add(&obj->list, &instance->used_objs_list[get_string_hash(id)][ALISP_OBJ_IDENTIFIER]);
+	if (obj && (obj->value.s = strdup(id)) == NULL) {
+		delete_object(instance, obj);
+		nomem();
+		return NULL;
+	}
+	return obj;
+}
+
+static struct alisp_object * new_pointer(struct alisp_instance *instance, const void *ptr)
+{
+	struct alisp_object * obj;
+	
+	obj = search_object_pointer(instance, ptr);
+	if (obj != NULL)
+		return obj;
+	obj = new_object(instance, ALISP_OBJ_POINTER);
+	if (obj) {
+		list_add(&obj->list, &instance->used_objs_list[(long)ptr & ALISP_OBJ_PAIR_HASH_MASK][ALISP_OBJ_POINTER]);
+		obj->value.ptr = ptr;
+	}
+	return obj;
+}
+
+static struct alisp_object * new_cons_pointer(struct alisp_instance * instance, const char *ptr_id, void *ptr)
+{
+	struct alisp_object * lexpr;
+
+	if (ptr == NULL)
+		return &alsa_lisp_nil;
+	lexpr = new_object(instance, ALISP_OBJ_CONS);
+	if (lexpr == NULL)
+		return NULL;
+	lexpr->value.c.car = new_string(instance, ptr_id);
+	if (lexpr->value.c.car == NULL)
+		goto __end;
+	lexpr->value.c.cdr = new_pointer(instance, ptr);
+	if (lexpr->value.c.cdr == NULL) {
+		delete_object(instance, lexpr->value.c.car);
+	      __end:
+		delete_object(instance, lexpr);
+		return NULL;
+	}
+	return lexpr;
+}
+
+void alsa_lisp_init_objects(void) __attribute__ ((constructor));
+
+void alsa_lisp_init_objects(void)
+{
+	memset(&alsa_lisp_nil, 0, sizeof(alsa_lisp_nil));
+	alisp_set_type(&alsa_lisp_nil, ALISP_OBJ_NIL);
+	INIT_LIST_HEAD(&alsa_lisp_nil.list);
+	memset(&alsa_lisp_t, 0, sizeof(alsa_lisp_t));
+	alisp_set_type(&alsa_lisp_t, ALISP_OBJ_T);
+	INIT_LIST_HEAD(&alsa_lisp_t.list);
+}
+
+/*
+ * lexer
+ */ 
+
+static int xgetc(struct alisp_instance *instance)
+{
+	instance->charno++;
+	if (instance->lex_bufp > instance->lex_buf)
+		return *--(instance->lex_bufp);
+	return snd_input_getc(instance->in);
+}
+
+static inline void xungetc(struct alisp_instance *instance, int c)
+{
+	*(instance->lex_bufp)++ = c;
+	instance->charno--;
+}
+
+static int init_lex(struct alisp_instance *instance)
+{
+	instance->charno = instance->lineno = 1;
+	instance->token_buffer_max = 10;
+	if ((instance->token_buffer = (char *)malloc(instance->token_buffer_max)) == NULL) {
+		nomem();
+		return -ENOMEM;
+	}
+	instance->lex_bufp = instance->lex_buf;
+	return 0;
+}
+
+static void done_lex(struct alisp_instance *instance)
+{
+	free(instance->token_buffer);
+}
+
+static char * extend_buf(struct alisp_instance *instance, char *p)
+{
+	int off = p - instance->token_buffer;
+
+	instance->token_buffer_max += 10;
+	instance->token_buffer = (char *)realloc(instance->token_buffer, instance->token_buffer_max);
+	if (instance->token_buffer == NULL) {
+		nomem();
+		return NULL;
+	}
+
+	return instance->token_buffer + off;
+}
+
+static int gettoken(struct alisp_instance *instance)
+{
+	char *p;
+	int c;
+
+	for (;;) {
+		c = xgetc(instance);
+		switch (c) {
+		case '\n':
+			++instance->lineno;
+			break;
+
+		case ' ': case '\f': case '\t': case '\v': case '\r':
+			break;
+
+		case ';':
+			/* Comment: ";".*"\n" */
+			while ((c = xgetc(instance)) != '\n' && c != EOF)
+				;
+			if (c != EOF)
+				++instance->lineno;
+			break;
+
+		case '?':
+			/* Character: "?". */
+			c = xgetc(instance);
+			sprintf(instance->token_buffer, "%d", c);
+			return instance->thistoken = ALISP_INTEGER;
+
+		case '-':
+			/* Minus sign: "-". */
+			c = xgetc(instance);
+			if (!isdigit(c)) {
+				xungetc(instance, c);
+				c = '-';
+				goto got_id;
+			}
+			xungetc(instance, c);
+			c = '-';
+			/* FALLTRHU */
+
+		case '0':
+		case '1': case '2': case '3':
+		case '4': case '5': case '6':
+		case '7': case '8': case '9':
+			/* Integer: [0-9]+ */
+			p = instance->token_buffer;
+			instance->thistoken = ALISP_INTEGER;
+			do {
+			      __ok:
+				if (p - instance->token_buffer >= instance->token_buffer_max - 1) {
+					p = extend_buf(instance, p);
+					if (p == NULL)
+						return instance->thistoken = EOF;
+				}
+				*p++ = c;
+				c = xgetc(instance);
+				if (c == '.' && instance->thistoken == ALISP_INTEGER) {
+					c = xgetc(instance);
+					xungetc(instance, c);
+					if (isdigit(c)) {
+						instance->thistoken = ALISP_FLOAT;
+						c = '.';
+						goto __ok;
+					} else {
+						c = '.';
+					}
+				} else if (c == 'e' && instance->thistoken == ALISP_FLOAT) {
+					c = xgetc(instance);
+					if (isdigit(c)) {
+						instance->thistoken = ALISP_FLOATE;
+						goto __ok;
+					}
+				}
+			} while (isdigit(c));
+			xungetc(instance, c);
+			*p = '\0';
+			return instance->thistoken;
+
+		got_id:
+		case '!': case '_': case '+': case '*': case '/': case '%':
+		case '<': case '>': case '=': case '&':
+		case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
+		case 'g': case 'h': case 'i': case 'j': case 'k': case 'l':
+		case 'm': case 'n': case 'o': case 'p': case 'q': case 'r':
+		case 's': case 't': case 'u': case 'v': case 'w': case 'x':
+		case 'y': case 'z':
+		case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
+		case 'G': case 'H': case 'I': case 'J': case 'K': case 'L':
+		case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R':
+		case 'S': case 'T': case 'U': case 'V': case 'W': case 'X':
+		case 'Y': case 'Z':
+			/* Identifier: [!-/+*%<>=&a-zA-Z_][-/+*%<>=&a-zA-Z_0-9]* */
+			p = instance->token_buffer;
+			do {
+				if (p - instance->token_buffer >= instance->token_buffer_max - 1) {
+					p = extend_buf(instance, p);
+					if (p == NULL)
+						return instance->thistoken = EOF;
+				}
+				*p++ = c;
+				c = xgetc(instance);
+			} while (isalnum(c) || strchr("!_-+*/%<>=&", c) != NULL);
+			xungetc(instance, c);
+			*p = '\0';
+			return instance->thistoken = ALISP_IDENTIFIER;
+
+		case '"':
+			/* String: "\""([^"]|"\\".)*"\"" */
+			p = instance->token_buffer;
+			while ((c = xgetc(instance)) != '"' && c != EOF) {
+				if (p - instance->token_buffer >= instance->token_buffer_max - 1) {
+					p = extend_buf(instance, p);
+					if (p == NULL)
+						return instance->thistoken = EOF;
+				}
+				if (c == '\\') {
+					c = xgetc(instance);
+					switch (c) {
+					case '\n': ++instance->lineno; break;
+					case 'a': *p++ = '\a'; break;
+					case 'b': *p++ = '\b'; break;
+					case 'f': *p++ = '\f'; break;
+					case 'n': *p++ = '\n'; break;
+					case 'r': *p++ = '\r'; break;
+					case 't': *p++ = '\t'; break;
+					case 'v': *p++ = '\v'; break;
+					default: *p++ = c;
+					}
+				} else {
+					if (c == '\n')
+						++instance->lineno;
+					*p++ = c;
+				}
+			}
+			*p = '\0';
+			return instance->thistoken = ALISP_STRING;
+
+		default:
+			return instance->thistoken = c;
+		}
+	}
+}
+
+/*
+ *  parser
+ */
+
+static struct alisp_object * parse_form(struct alisp_instance *instance)
+{
+	int thistoken;
+	struct alisp_object * p, * first = NULL, * prev = NULL;
+
+	while ((thistoken = gettoken(instance)) != ')' && thistoken != EOF) {
+		/*
+		 * Parse a dotted pair notation.
+		 */
+		if (thistoken == '.') {
+			gettoken(instance);
+			if (prev == NULL) {
+				lisp_error(instance, "unexpected '.'");
+			      __err:
+				delete_tree(instance, first);
+				return NULL;
+			}
+			prev->value.c.cdr = parse_object(instance, 1);
+			if (prev->value.c.cdr == NULL)
+				goto __err;
+			if ((thistoken = gettoken(instance)) != ')') {
+				lisp_error(instance, "expected ')'");
+				goto __err;
+			}
+			break;
+		}
+
+		p = new_object(instance, ALISP_OBJ_CONS);
+		if (p == NULL)
+			goto __err;
+
+		if (first == NULL)
+			first = p;
+		if (prev != NULL)
+			prev->value.c.cdr = p;
+
+		p->value.c.car = parse_object(instance, 1);
+		if (p->value.c.car == NULL)
+			goto __err;
+
+		prev = p;
+	}
+
+	if (first == NULL)
+		return &alsa_lisp_nil;
+	else
+		return first;
+}
+
+static struct alisp_object * quote_object(struct alisp_instance *instance, struct alisp_object * obj)
+{
+	struct alisp_object * p;
+
+	if (obj == NULL)
+		goto __end1;
+
+	p = new_object(instance, ALISP_OBJ_CONS);
+	if (p == NULL)
+		goto __end1;
+
+	p->value.c.car = new_identifier(instance, "quote");
+	if (p->value.c.car == NULL)
+		goto __end;
+	p->value.c.cdr = new_object(instance, ALISP_OBJ_CONS);
+	if (p->value.c.cdr == NULL) {
+		delete_object(instance, p->value.c.car);
+	      __end:
+		delete_object(instance, p);
+	      __end1:
+		delete_tree(instance, obj);
+		return NULL;
+	}
+
+	p->value.c.cdr->value.c.car = obj;
+	return p;
+}
+
+static inline struct alisp_object * parse_quote(struct alisp_instance *instance)
+{
+	return quote_object(instance, parse_object(instance, 0));
+}
+
+static struct alisp_object * parse_object(struct alisp_instance *instance, int havetoken)
+{
+	int thistoken;
+	struct alisp_object * p = NULL;
+
+	if (!havetoken)
+		thistoken = gettoken(instance);
+	else
+		thistoken = instance->thistoken;
+
+	switch (thistoken) {
+	case EOF:
+		break;
+	case '(':
+		p = parse_form(instance);
+		break;
+	case '\'':
+		p = parse_quote(instance);
+		break;
+	case ALISP_IDENTIFIER:
+		if (!strcmp(instance->token_buffer, "t"))
+			p = &alsa_lisp_t;
+		else if (!strcmp(instance->token_buffer, "nil"))
+			p = &alsa_lisp_nil;
+		else {
+			p = new_identifier(instance, instance->token_buffer);
+		}
+		break;
+	case ALISP_INTEGER: {
+		p = new_integer(instance, atol(instance->token_buffer));
+		break;
+	}
+	case ALISP_FLOAT:
+	case ALISP_FLOATE: {
+		p = new_float(instance, atof(instance->token_buffer));
+		break;
+	}
+	case ALISP_STRING:
+		p = new_string(instance, instance->token_buffer);
+		break;
+	default:
+		lisp_warn(instance, "%d:%d: unexpected character `%c'", instance->lineno, instance->charno, thistoken);
+		break;
+	}
+
+	return p;
+}
+
+/*
+ *  object manipulation
+ */
+
+static struct alisp_object_pair * set_object_direct(struct alisp_instance *instance, struct alisp_object * name, struct alisp_object * value)
+{
+	struct alisp_object_pair *p;
+	const char *id;
+
+	id = name->value.s;
+	p = (struct alisp_object_pair *)malloc(sizeof(struct alisp_object_pair));
+	if (p == NULL) {
+		nomem();
+		return NULL;
+	}
+	p->name = strdup(id);
+	if (p->name == NULL) {
+		delete_tree(instance, value);
+		free(p);
+		return NULL;
+	}
+	list_add(&p->list, &instance->setobjs_list[get_string_hash(id)]);
+	p->value = value;
+	return p;
+}
+
+static int check_set_object(struct alisp_instance * instance, struct alisp_object * name)
+{
+	if (name == &alsa_lisp_nil) {
+		lisp_warn(instance, "setting the value of a nil object");
+		return 0;
+	}
+	if (name == &alsa_lisp_t) {
+		lisp_warn(instance, "setting the value of a t object");
+		return 0;
+	}
+	if (!alisp_compare_type(name, ALISP_OBJ_IDENTIFIER) &&
+	    !alisp_compare_type(name, ALISP_OBJ_STRING)) {
+		lisp_warn(instance, "setting the value of an object with non-indentifier");
+		return 0;
+	}
+	return 1;
+}
+
+static struct alisp_object_pair * set_object(struct alisp_instance *instance, struct alisp_object * name, struct alisp_object * value)
+{
+	struct list_head *pos;
+	struct alisp_object_pair *p;
+	const char *id;
+
+	if (name == NULL || value == NULL)
+		return NULL;
+
+	id = name->value.s;
+
+	list_for_each(pos, &instance->setobjs_list[get_string_hash(id)]) {
+		p = list_entry(pos, struct alisp_object_pair, list);
+		if (!strcmp(p->name, id)) {
+			delete_tree(instance, p->value);
+			p->value = value;
+			return p;
+		}
+	}
+
+	p = (struct alisp_object_pair *)malloc(sizeof(struct alisp_object_pair));
+	if (p == NULL) {
+		nomem();
+		return NULL;
+	}
+	p->name = strdup(id);
+	if (p->name == NULL) {
+		delete_tree(instance, value);
+		free(p);
+		return NULL;
+	}
+	list_add(&p->list, &instance->setobjs_list[get_string_hash(id)]);
+	p->value = value;
+	return p;
+}
+
+static struct alisp_object * unset_object(struct alisp_instance *instance, struct alisp_object * name)
+{
+	struct list_head *pos;
+	struct alisp_object *res;
+	struct alisp_object_pair *p;
+	const char *id;
+	
+	if (!alisp_compare_type(name, ALISP_OBJ_IDENTIFIER) &&
+	    !alisp_compare_type(name, ALISP_OBJ_STRING)) {
+	    	lisp_warn(instance, "unset object with a non-indentifier");
+		return &alsa_lisp_nil;
+	}
+	id = name->value.s;
+
+	list_for_each(pos, &instance->setobjs_list[get_string_hash(id)]) {
+		p = list_entry(pos, struct alisp_object_pair, list);
+		if (!strcmp(p->name, id)) {
+			list_del(&p->list);
+			res = p->value;
+			free((void *)p->name);
+			free(p);
+			return res;
+		}
+	}
+	
+	return &alsa_lisp_nil;
+}
+
+static struct alisp_object * get_object1(struct alisp_instance *instance, const char *id)
+{
+	struct alisp_object_pair *p;
+	struct list_head *pos;
+
+	list_for_each(pos, &instance->setobjs_list[get_string_hash(id)]) {
+		p = list_entry(pos, struct alisp_object_pair, list);
+		if (!strcmp(p->name, id))
+			return p->value;
+	}
+
+	return &alsa_lisp_nil;
+}
+
+static struct alisp_object * get_object(struct alisp_instance *instance, struct alisp_object * name)
+{
+	if (!alisp_compare_type(name, ALISP_OBJ_IDENTIFIER) &&
+	    !alisp_compare_type(name, ALISP_OBJ_STRING)) {
+	    	delete_tree(instance, name);
+		return &alsa_lisp_nil;
+	}
+	return get_object1(instance, name->value.s);
+}
+
+static struct alisp_object * replace_object(struct alisp_instance *instance, struct alisp_object * name, struct alisp_object * onew)
+{
+	struct alisp_object_pair *p;
+	struct alisp_object *r;
+	struct list_head *pos;
+	const char *id;
+
+	if (!alisp_compare_type(name, ALISP_OBJ_IDENTIFIER) &&
+	    !alisp_compare_type(name, ALISP_OBJ_STRING)) {
+	    	delete_tree(instance, name);
+		return &alsa_lisp_nil;
+	}
+	id = name->value.s;
+	list_for_each(pos, &instance->setobjs_list[get_string_hash(id)]) {
+		p = list_entry(pos, struct alisp_object_pair, list);
+		if (!strcmp(p->name, id)) {
+			r = p->value;
+			p->value = onew;
+			return r;
+		}
+	}
+
+	return NULL;
+}
+
+static void dump_objects(struct alisp_instance *instance, const char *fname)
+{
+	struct alisp_object_pair *p;
+	snd_output_t *out;
+	struct list_head *pos;
+	int i, err;
+
+	if (!strcmp(fname, "-"))
+		err = snd_output_stdio_attach(&out, stdout, 0);
+	else
+		err = snd_output_stdio_open(&out, fname, "w+");
+	if (err < 0) {
+		SNDERR("alisp: cannot open file '%s' for writting (%s)", fname, snd_strerror(errno));
+		return;
+	}
+
+	for (i = 0; i < ALISP_OBJ_PAIR_HASH_SIZE; i++) {
+		list_for_each(pos, &instance->setobjs_list[i]) {
+			p = list_entry(pos, struct alisp_object_pair, list);
+			if (alisp_compare_type(p->value, ALISP_OBJ_CONS) &&
+			    alisp_compare_type(p->value->value.c.car, ALISP_OBJ_IDENTIFIER) &&
+			    !strcmp(p->value->value.c.car->value.s, "lambda")) {
+			    	snd_output_printf(out, "(defun %s ", p->name);
+			    	princ_cons(out, p->value->value.c.cdr);
+			    	snd_output_printf(out, ")\n");
+			    	continue;
+			}
+			snd_output_printf(out, "(setq %s '", p->name);
+ 			princ_object(out, p->value);
+			snd_output_printf(out, ")\n");
+		}
+	}
+	snd_output_close(out);
+}
+
+static const char *obj_type_str(struct alisp_object * p)
+{
+	switch (alisp_get_type(p)) {
+	case ALISP_OBJ_NIL: return "nil";
+	case ALISP_OBJ_T: return "t";
+	case ALISP_OBJ_INTEGER: return "integer";
+	case ALISP_OBJ_FLOAT: return "float";
+	case ALISP_OBJ_IDENTIFIER: return "identifier";
+	case ALISP_OBJ_STRING: return "string";
+	case ALISP_OBJ_POINTER: return "pointer";
+	case ALISP_OBJ_CONS: return "cons";
+	default: assert(0);
+	}
+}
+
+static void print_obj_lists(struct alisp_instance *instance, snd_output_t *out)
+{
+	struct list_head *pos;
+	struct alisp_object * p;
+	int i, j;
+
+	snd_output_printf(out, "** used objects\n");
+	for (i = 0; i < ALISP_OBJ_PAIR_HASH_SIZE; i++)
+		for (j = 0; j <= ALISP_OBJ_LAST_SEARCH; j++)
+			list_for_each(pos, &instance->used_objs_list[i][j]) {
+				p = list_entry(pos, struct alisp_object, list);
+				snd_output_printf(out, "**   %p (%s) (", p, obj_type_str(p));
+				if (!alisp_compare_type(p, ALISP_OBJ_CONS))
+					princ_object(out, p);
+				else
+					snd_output_printf(out, "cons");
+				snd_output_printf(out, ") refs=%i\n", alisp_get_refs(p));
+			}
+	snd_output_printf(out, "** free objects\n");
+	list_for_each(pos, &instance->free_objs_list) {
+		p = list_entry(pos, struct alisp_object, list);
+		snd_output_printf(out, "**   %p\n", p);
+	}
+}
+
+static void dump_obj_lists(struct alisp_instance *instance, const char *fname)
+{
+	snd_output_t *out;
+	int err;
+
+	if (!strcmp(fname, "-"))
+		err = snd_output_stdio_attach(&out, stdout, 0);
+	else
+		err = snd_output_stdio_open(&out, fname, "w+");
+	if (err < 0) {
+		SNDERR("alisp: cannot open file '%s' for writting (%s)", fname, snd_strerror(errno));
+		return;
+	}
+
+	print_obj_lists(instance, out);
+
+	snd_output_close(out);
+}
+
+/*
+ *  functions
+ */
+
+static int count_list(struct alisp_object * p)
+{
+	int i = 0;
+
+	while (p != &alsa_lisp_nil && alisp_compare_type(p, ALISP_OBJ_CONS)) {
+		p = p->value.c.cdr;
+		++i;
+	}
+
+	return i;
+}
+
+static inline struct alisp_object * car(struct alisp_object * p)
+{
+	if (alisp_compare_type(p, ALISP_OBJ_CONS))
+		return p->value.c.car;
+
+	return &alsa_lisp_nil;
+}
+
+static inline struct alisp_object * cdr(struct alisp_object * p)
+{
+	if (alisp_compare_type(p, ALISP_OBJ_CONS))
+		return p->value.c.cdr;
+
+	return &alsa_lisp_nil;
+}
+
+/*
+ * Syntax: (car expr)
+ */
+static struct alisp_object * F_car(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object *p1 = car(args), *p2;
+	delete_tree(instance, cdr(args));
+	delete_object(instance, args);
+	p1 = eval(instance, p1);
+	delete_tree(instance, cdr(p1));
+	p2 = car(p1);
+	delete_object(instance, p1);
+	return p2;
+}
+
+/*
+ * Syntax: (cdr expr)
+ */
+static struct alisp_object * F_cdr(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object *p1 = car(args), *p2;
+	delete_tree(instance, cdr(args));
+	delete_object(instance, args);
+	p1 = eval(instance, p1);
+	delete_tree(instance, car(p1));
+	p2 = cdr(p1);
+	delete_object(instance, p1);
+	return p2;
+}
+
+/*
+ * Syntax: (+ expr...)
+ */
+static struct alisp_object * F_add(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p = args, * p1, * n;
+	long v = 0;
+	double f = 0;
+	int type = ALISP_OBJ_INTEGER;
+
+	p1 = eval(instance, car(p));
+	for (;;) {
+		if (alisp_compare_type(p1, ALISP_OBJ_INTEGER)) {
+			if (type == ALISP_OBJ_FLOAT)
+				f += p1->value.i;
+			else
+				v += p1->value.i;
+		} else if (alisp_compare_type(p1, ALISP_OBJ_FLOAT)) {
+			f += p1->value.f + v;
+			v = 0;
+			type = ALISP_OBJ_FLOAT;
+		} else {
+			lisp_warn(instance, "sum with a non integer or float operand");
+		}
+		delete_tree(instance, p1);
+		p = cdr(n = p);
+		delete_object(instance, n);
+		if (p == &alsa_lisp_nil)
+			break;
+		p1 = eval(instance, car(p));
+	}
+	if (type == ALISP_OBJ_INTEGER) {
+		return new_integer(instance, v);
+	} else {
+		return new_float(instance, f);
+	}
+}
+
+/*
+ * Syntax: (concat expr...)
+ */
+static struct alisp_object * F_concat(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p = args, * p1, * n;
+	char *str = NULL, *str1;
+	
+	p1 = eval(instance, car(p));
+	for (;;) {
+		if (alisp_compare_type(p1, ALISP_OBJ_STRING)) {
+			str1 = realloc(str, (str ? strlen(str) : 0) + strlen(p1->value.s) + 1);
+			if (str1 == NULL) {
+				nomem();
+				free(str);
+				return NULL;
+			}
+			if (str == NULL)
+				strcpy(str1, p1->value.s);
+			else
+				strcat(str1, p1->value.s);
+			str = str1;
+		} else {
+			lisp_warn(instance, "concat with a non string or identifier operand");
+		}
+		delete_tree(instance, p1);
+		p = cdr(n = p);
+		delete_object(instance, n);
+		if (p == &alsa_lisp_nil)
+			break;
+		p1 = eval(instance, car(p));
+	}
+	if (str) {
+		p = new_string(instance, str);
+		free(str);
+	} else {
+		p = &alsa_lisp_nil;
+	}
+	return p;
+}
+
+/*
+ * Syntax: (- expr...)
+ */
+static struct alisp_object * F_sub(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p = args, * p1, * n;
+	long v = 0;
+	double f = 0;
+	int type = ALISP_OBJ_INTEGER;
+
+	do {
+		p1 = eval(instance, car(p));
+		if (alisp_compare_type(p1, ALISP_OBJ_INTEGER)) {
+			if (p == args && cdr(p) != &alsa_lisp_nil) {
+				v = p1->value.i;
+			} else {
+				if (type == ALISP_OBJ_FLOAT)
+					f -= p1->value.i;
+				else
+					v -= p1->value.i;
+			}
+		} else if (alisp_compare_type(p1, ALISP_OBJ_FLOAT)) {
+			if (type == ALISP_OBJ_INTEGER) {
+				f = v;
+				type = ALISP_OBJ_FLOAT;
+			}
+			if (p == args && cdr(p) != &alsa_lisp_nil)
+				f = p1->value.f;
+			else {
+				f -= p1->value.f;
+			}
+		} else
+			lisp_warn(instance, "difference with a non integer or float operand");
+		delete_tree(instance, p1);
+		n = cdr(p);
+		delete_object(instance, p);
+		p = n;
+	} while (p != &alsa_lisp_nil);
+
+	if (type == ALISP_OBJ_INTEGER) {
+		return new_integer(instance, v);
+	} else {
+		return new_float(instance, f);
+	}
+}
+
+/*
+ * Syntax: (* expr...)
+ */
+static struct alisp_object * F_mul(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p = args, * p1, * n;
+	long v = 1;
+	double f = 1;
+	int type = ALISP_OBJ_INTEGER;
+
+	do {
+		p1 = eval(instance, car(p));
+		if (alisp_compare_type(p1, ALISP_OBJ_INTEGER)) {
+			if (type == ALISP_OBJ_FLOAT)
+				f *= p1->value.i;
+			else
+				v *= p1->value.i;
+		} else if (alisp_compare_type(p1, ALISP_OBJ_FLOAT)) {
+			f *= p1->value.f * v; v = 1;
+			type = ALISP_OBJ_FLOAT;
+		} else {
+			lisp_warn(instance, "product with a non integer or float operand");
+		}
+		delete_tree(instance, p1);
+		n = cdr(p);
+		delete_object(instance, p);
+		p = n;
+	} while (p != &alsa_lisp_nil);
+
+	if (type == ALISP_OBJ_INTEGER) {
+		return new_integer(instance, v);
+	} else {
+		return new_float(instance, f);
+	}
+}
+
+/*
+ * Syntax: (/ expr...)
+ */
+static struct alisp_object * F_div(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p = args, * p1, * n;
+	long v = 0;
+	double f = 0;
+	int type = ALISP_OBJ_INTEGER;
+
+	do {
+		p1 = eval(instance, car(p));
+		if (alisp_compare_type(p1, ALISP_OBJ_INTEGER)) {
+			if (p == args && cdr(p) != &alsa_lisp_nil) {
+				v = p1->value.i;
+			} else {
+				if (p1->value.i == 0) {
+					lisp_warn(instance, "division by zero");
+					v = 0;
+					f = 0;
+					break;
+				} else {
+					if (type == ALISP_OBJ_FLOAT)
+						f /= p1->value.i;
+					else
+						v /= p1->value.i;
+				}
+			}
+		} else if (alisp_compare_type(p1, ALISP_OBJ_FLOAT)) {
+			if (type == ALISP_OBJ_INTEGER) {
+				f = v;
+				type = ALISP_OBJ_FLOAT;
+			}
+			if (p == args && cdr(p) != &alsa_lisp_nil) {
+				f = p1->value.f;
+			} else {
+				if (p1->value.f == 0) {
+					lisp_warn(instance, "division by zero");
+					f = 0;
+					break;
+				} else {
+					f /= p1->value.i;
+				}
+			}
+		} else
+			lisp_warn(instance, "quotient with a non integer or float operand");
+		delete_tree(instance, p1);
+		n = cdr(p);
+		delete_object(instance, p);
+		p = n;
+	} while (p != &alsa_lisp_nil);
+
+	if (type == ALISP_OBJ_INTEGER) {
+		return new_integer(instance, v);
+	} else {
+		return new_float(instance, f);
+	}
+}
+
+/*
+ * Syntax: (% expr1 expr2)
+ */
+static struct alisp_object * F_mod(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p1, * p2, * p3;
+
+	p1 = eval(instance, car(args));
+	p2 = eval(instance, car(cdr(args)));
+	delete_tree(instance, cdr(cdr(args)));
+	delete_object(instance, cdr(args));
+	delete_object(instance, args);
+
+	if (alisp_compare_type(p1, ALISP_OBJ_INTEGER) &&
+	    alisp_compare_type(p2, ALISP_OBJ_INTEGER)) {
+		if (p2->value.i == 0) {
+			lisp_warn(instance, "module by zero");
+			p3 = new_integer(instance, 0);
+		} else {
+			p3 = new_integer(instance, p1->value.i % p2->value.i);
+		}
+	} else if ((alisp_compare_type(p1, ALISP_OBJ_INTEGER) || 
+	            alisp_compare_type(p1, ALISP_OBJ_FLOAT)) &&
+		   (alisp_compare_type(p2, ALISP_OBJ_INTEGER) ||
+		    alisp_compare_type(p2, ALISP_OBJ_FLOAT))) {
+		double f1, f2;
+		f1 = alisp_compare_type(p1, ALISP_OBJ_INTEGER) ? p1->value.i : p1->value.f;
+		f2 = alisp_compare_type(p2, ALISP_OBJ_INTEGER) ? p2->value.i : p2->value.f;
+		f1 = fmod(f1, f2);
+		if (f1 == EDOM) {
+			lisp_warn(instance, "module by zero");
+			p3 = new_float(instance, 0);
+		} else {
+			p3 = new_float(instance, f1);
+		}
+	} else {
+		lisp_warn(instance, "module with a non integer or float operand");
+		delete_tree(instance, p1);
+		delete_tree(instance, p2);
+		return &alsa_lisp_nil;
+	}
+
+	delete_tree(instance, p1);
+	delete_tree(instance, p2);
+	return p3;
+}
+
+/*
+ * Syntax: (< expr1 expr2)
+ */
+static struct alisp_object * F_lt(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p1, * p2;
+
+	p1 = eval(instance, car(args));
+	p2 = eval(instance, car(cdr(args)));
+	delete_tree(instance, cdr(cdr(args)));
+	delete_object(instance, cdr(args));
+	delete_object(instance, args);
+
+	if (alisp_compare_type(p1, ALISP_OBJ_INTEGER) &&
+	    alisp_compare_type(p2, ALISP_OBJ_INTEGER)) {
+		if (p1->value.i < p2->value.i) {
+		      __true:
+			delete_tree(instance, p1);
+			delete_tree(instance, p2);
+			return &alsa_lisp_t;
+		}
+	} else if ((alisp_compare_type(p1, ALISP_OBJ_INTEGER) ||
+	            alisp_compare_type(p1, ALISP_OBJ_FLOAT)) &&
+		   (alisp_compare_type(p2, ALISP_OBJ_INTEGER) ||
+		    alisp_compare_type(p2, ALISP_OBJ_FLOAT))) {
+		double f1, f2;
+		f1 = alisp_compare_type(p1, ALISP_OBJ_INTEGER) ? p1->value.i : p1->value.f;
+		f2 = alisp_compare_type(p2, ALISP_OBJ_INTEGER) ? p2->value.i : p2->value.f;
+		if (f1 < f2)
+			goto __true;
+	} else {
+		lisp_warn(instance, "comparison with a non integer or float operand");
+	}
+
+	delete_tree(instance, p1);
+	delete_tree(instance, p2);
+	return &alsa_lisp_nil;
+}
+
+/*
+ * Syntax: (> expr1 expr2)
+ */
+static struct alisp_object * F_gt(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p1, * p2;
+
+	p1 = eval(instance, car(args));
+	p2 = eval(instance, car(cdr(args)));
+	delete_tree(instance, cdr(cdr(args)));
+	delete_object(instance, cdr(args));
+	delete_object(instance, args);
+
+	if (alisp_compare_type(p1, ALISP_OBJ_INTEGER) &&
+	    alisp_compare_type(p2, ALISP_OBJ_INTEGER)) {
+		if (p1->value.i > p2->value.i) {
+		      __true:
+			delete_tree(instance, p1);
+			delete_tree(instance, p2);
+			return &alsa_lisp_t;
+		}
+	} else if ((alisp_compare_type(p1, ALISP_OBJ_INTEGER) ||
+	            alisp_compare_type(p1, ALISP_OBJ_FLOAT)) &&
+		   (alisp_compare_type(p2, ALISP_OBJ_INTEGER) ||
+		    alisp_compare_type(p2, ALISP_OBJ_FLOAT))) {
+		double f1, f2;
+		f1 = alisp_compare_type(p1, ALISP_OBJ_INTEGER) ? p1->value.i : p1->value.f;
+		f2 = alisp_compare_type(p2, ALISP_OBJ_INTEGER) ? p2->value.i : p2->value.f;
+		if (f1 > f2)
+			goto __true;
+	} else {
+		lisp_warn(instance, "comparison with a non integer or float operand");
+	}
+
+	delete_tree(instance, p1);
+	delete_tree(instance, p2);
+	return &alsa_lisp_nil;
+}
+
+/*
+ * Syntax: (<= expr1 expr2)
+ */
+static struct alisp_object * F_le(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p1, * p2;
+
+	p1 = eval(instance, car(args));
+	p2 = eval(instance, car(cdr(args)));
+	delete_tree(instance, cdr(cdr(args)));
+	delete_object(instance, cdr(args));
+	delete_object(instance, args);
+
+	if (alisp_compare_type(p1, ALISP_OBJ_INTEGER) &&
+	    alisp_compare_type(p2, ALISP_OBJ_INTEGER)) {
+		if (p1->value.i <= p2->value.i) {
+		      __true:
+			delete_tree(instance, p1);
+			delete_tree(instance, p2);
+			return &alsa_lisp_t;
+		}
+	} else if ((alisp_compare_type(p1, ALISP_OBJ_INTEGER) ||
+	            alisp_compare_type(p1, ALISP_OBJ_FLOAT)) &&
+		   (alisp_compare_type(p2, ALISP_OBJ_INTEGER) ||
+		    alisp_compare_type(p2, ALISP_OBJ_FLOAT))) {
+		double f1, f2;
+		f1 = alisp_compare_type(p1, ALISP_OBJ_INTEGER) ? p1->value.i : p1->value.f;
+		f2 = alisp_compare_type(p2, ALISP_OBJ_INTEGER) ? p2->value.i : p2->value.f;
+		if (f1 <= f2)
+			goto __true;
+	} else {
+		lisp_warn(instance, "comparison with a non integer or float operand");
+	}
+
+	delete_tree(instance, p1);
+	delete_tree(instance, p2);
+	return &alsa_lisp_nil;
+}
+
+/*
+ * Syntax: (>= expr1 expr2)
+ */
+static struct alisp_object * F_ge(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p1, * p2;
+
+	p1 = eval(instance, car(args));
+	p2 = eval(instance, car(cdr(args)));
+	delete_tree(instance, cdr(cdr(args)));
+	delete_object(instance, cdr(args));
+	delete_object(instance, args);
+
+	if (alisp_compare_type(p1, ALISP_OBJ_INTEGER) &&
+	    alisp_compare_type(p2, ALISP_OBJ_INTEGER)) {
+		if (p1->value.i >= p2->value.i) {
+		      __true:
+			delete_tree(instance, p1);
+			delete_tree(instance, p2);
+			return &alsa_lisp_t;
+		}
+	} else if ((alisp_compare_type(p1, ALISP_OBJ_INTEGER) ||
+	            alisp_compare_type(p1, ALISP_OBJ_FLOAT)) &&
+		   (alisp_compare_type(p2, ALISP_OBJ_INTEGER) ||
+		    alisp_compare_type(p2, ALISP_OBJ_FLOAT))) {
+		double f1, f2;
+		f1 = alisp_compare_type(p1, ALISP_OBJ_INTEGER) ? p1->value.i : p1->value.f;
+		f2 = alisp_compare_type(p2, ALISP_OBJ_INTEGER) ? p2->value.i : p2->value.f;
+		if (f1 >= f2)
+			goto __true;
+	} else {
+		lisp_warn(instance, "comparison with a non integer or float operand");
+	}
+
+	delete_tree(instance, p1);
+	delete_tree(instance, p2);
+	return &alsa_lisp_nil;
+}
+
+/*
+ * Syntax: (= expr1 expr2)
+ */
+static struct alisp_object * F_numeq(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p1, * p2;
+
+	p1 = eval(instance, car(args));
+	p2 = eval(instance, car(cdr(args)));
+	delete_tree(instance, cdr(cdr(args)));
+	delete_object(instance, cdr(args));
+	delete_object(instance, args);
+
+	if (alisp_compare_type(p1, ALISP_OBJ_INTEGER) &&
+	    alisp_compare_type(p2, ALISP_OBJ_INTEGER)) {
+		if (p1->value.i == p2->value.i) {
+		      __true:
+			delete_tree(instance, p1);
+			delete_tree(instance, p2);
+			return &alsa_lisp_t;
+		}
+	} else if ((alisp_compare_type(p1, ALISP_OBJ_INTEGER) ||
+	            alisp_compare_type(p1, ALISP_OBJ_FLOAT)) &&
+		   (alisp_compare_type(p2, ALISP_OBJ_INTEGER) ||
+		    alisp_compare_type(p2, ALISP_OBJ_FLOAT))) {
+		double f1, f2;
+		f1 = alisp_compare_type(p1, ALISP_OBJ_INTEGER) ? p1->value.i : p1->value.f;
+		f2 = alisp_compare_type(p2, ALISP_OBJ_INTEGER) ? p2->value.i : p2->value.f;
+		if (f1 == f2)
+			goto __true;
+	} else {
+		lisp_warn(instance, "comparison with a non integer or float operand");
+	}
+
+	delete_tree(instance, p1);
+	delete_tree(instance, p2);
+	return &alsa_lisp_nil;
+}
+
+/*
+ * Syntax: (!= expr1 expr2)
+ */
+static struct alisp_object * F_numneq(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p;
+	
+	p = F_numeq(instance, args);
+	if (p == &alsa_lisp_nil)
+		return &alsa_lisp_t;
+	return &alsa_lisp_nil;
+}
+
+/*
+ * Syntax: (exfun name)
+ * Test, if a function exists
+ */
+static struct alisp_object * F_exfun(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p1, * p2;
+
+	p1 = eval(instance, car(args));
+	delete_tree(instance, cdr(args));
+	delete_object(instance, args);
+	p2 = get_object(instance, p1);
+	if (p2 == &alsa_lisp_nil) {
+		delete_tree(instance, p1);
+		return &alsa_lisp_nil;
+	}
+	p2 = car(p2);
+	if (alisp_compare_type(p2, ALISP_OBJ_IDENTIFIER) &&
+	    !strcmp(p2->value.s, "lambda")) {
+		delete_tree(instance, p1);
+		return &alsa_lisp_t;
+	}
+	delete_tree(instance, p1);
+	return &alsa_lisp_nil;
+}
+
+static void princ_string(snd_output_t *out, char *s)
+{
+	char *p;
+
+	snd_output_putc(out, '"');
+	for (p = s; *p != '\0'; ++p)
+		switch (*p) {
+		case '\a': snd_output_putc(out, '\\'); snd_output_putc(out, 'a'); break;
+		case '\b': snd_output_putc(out, '\\'); snd_output_putc(out, 'b'); break;
+		case '\f': snd_output_putc(out, '\\'); snd_output_putc(out, 'f'); break;
+		case '\n': snd_output_putc(out, '\\'); snd_output_putc(out, 'n'); break;
+		case '\r': snd_output_putc(out, '\\'); snd_output_putc(out, 'r'); break;
+		case '\t': snd_output_putc(out, '\\'); snd_output_putc(out, 't'); break;
+		case '\v': snd_output_putc(out, '\\'); snd_output_putc(out, 'v'); break;
+		case '"': snd_output_putc(out, '\\'); snd_output_putc(out, '"'); break;
+		default: snd_output_putc(out, *p);
+		}
+	snd_output_putc(out, '"');
+}
+
+static void princ_cons(snd_output_t *out, struct alisp_object * p)
+{
+	do {
+		princ_object(out, p->value.c.car);
+		p = p->value.c.cdr;
+		if (p != &alsa_lisp_nil) {
+			snd_output_putc(out, ' ');
+			if (!alisp_compare_type(p, ALISP_OBJ_CONS)) {
+				snd_output_printf(out, ". ");
+				princ_object(out, p);
+			}
+		}
+	} while (p != &alsa_lisp_nil && alisp_compare_type(p, ALISP_OBJ_CONS));
+}
+
+static void princ_object(snd_output_t *out, struct alisp_object * p)
+{
+	switch (alisp_get_type(p)) {
+	case ALISP_OBJ_NIL:
+		snd_output_printf(out, "nil");
+		break;
+	case ALISP_OBJ_T:
+		snd_output_putc(out, 't');
+		break;
+	case ALISP_OBJ_IDENTIFIER:
+		snd_output_printf(out, "%s", p->value.s);
+		break;
+	case ALISP_OBJ_STRING:
+		princ_string(out, p->value.s);
+		break;
+	case ALISP_OBJ_INTEGER:
+		snd_output_printf(out, "%ld", p->value.i);
+		break;
+	case ALISP_OBJ_FLOAT:
+		snd_output_printf(out, "%f", p->value.f);
+		break;
+	case ALISP_OBJ_POINTER:
+		snd_output_printf(out, "<%p>", p->value.ptr);
+		break;
+	case ALISP_OBJ_CONS:
+		snd_output_putc(out, '(');
+		princ_cons(out, p);
+		snd_output_putc(out, ')');
+	}
+}
+
+/*
+ * Syntax: (princ expr...)
+ */
+static struct alisp_object * F_princ(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p = args, * p1 = NULL, * n;
+
+	do {
+		if (p1)
+			delete_tree(instance, p1);
+		p1 = eval(instance, car(p));
+		if (alisp_compare_type(p1, ALISP_OBJ_STRING))
+			snd_output_printf(instance->out, "%s", p1->value.s);
+		else
+			princ_object(instance->out, p1);
+		n = cdr(p);
+		delete_object(instance, p);
+		p = n;
+	} while (p != &alsa_lisp_nil);
+
+	return p1;
+}
+
+/*
+ * Syntax: (atom expr)
+ */
+static struct alisp_object * F_atom(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p;
+
+	p = eval(instance, car(args));
+	delete_tree(instance, cdr(args));
+	delete_object(instance, args);
+	if (p == NULL)
+		return NULL;
+
+	switch (alisp_get_type(p)) {
+	case ALISP_OBJ_T:
+	case ALISP_OBJ_NIL:
+	case ALISP_OBJ_INTEGER:
+	case ALISP_OBJ_FLOAT:
+	case ALISP_OBJ_STRING:
+	case ALISP_OBJ_IDENTIFIER:
+	case ALISP_OBJ_POINTER:
+		delete_tree(instance, p);
+		return &alsa_lisp_t;
+	default:
+		break;
+	}
+
+	delete_tree(instance, p);
+	return &alsa_lisp_nil;
+}
+
+/*
+ * Syntax: (cons expr1 expr2)
+ */
+static struct alisp_object * F_cons(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p;
+
+	p = new_object(instance, ALISP_OBJ_CONS);
+	if (p) {
+		p->value.c.car = eval(instance, car(args));
+		p->value.c.cdr = eval(instance, car(cdr(args)));
+		delete_tree(instance, cdr(cdr(args)));
+		delete_object(instance, cdr(args));
+		delete_object(instance, args);
+	} else {
+		delete_tree(instance, args);
+	}
+
+	return p;
+}
+
+/*
+ * Syntax: (list expr1...)
+ */
+static struct alisp_object * F_list(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p = args, * first = NULL, * prev = NULL, * p1;
+
+	if (p == &alsa_lisp_nil)
+		return &alsa_lisp_nil;
+
+	do {
+		p1 = new_object(instance, ALISP_OBJ_CONS);
+		if (p1 == NULL) {
+			delete_tree(instance, p);
+			delete_tree(instance, first);
+			return NULL;
+		}
+		p1->value.c.car = eval(instance, car(p));
+		if (p1->value.c.car == NULL) {
+			delete_tree(instance, first);
+			delete_tree(instance, cdr(p));
+			delete_object(instance, p);
+			return NULL;
+		}
+		if (first == NULL)
+			first = p1;
+		if (prev != NULL)
+			prev->value.c.cdr = p1;
+		prev = p1;
+		p = cdr(p1 = p);
+		delete_object(instance, p1);
+	} while (p != &alsa_lisp_nil);
+
+	return first;
+}
+
+static inline int eq(struct alisp_object * p1, struct alisp_object * p2)
+{
+	return p1 == p2;
+}
+
+static int equal(struct alisp_object * p1, struct alisp_object * p2)
+{
+	int type1, type2;
+
+	if (eq(p1, p2))
+		return 1;
+
+	type1 = alisp_get_type(p1);
+	type2 = alisp_get_type(p2);
+
+	if (type1 == ALISP_OBJ_CONS || type2 == ALISP_OBJ_CONS)
+		return 0;
+
+	if (type1 == type2) {
+		switch (type1) {
+		case ALISP_OBJ_STRING:
+			return !strcmp(p1->value.s, p2->value.s);
+		case ALISP_OBJ_INTEGER:
+			return p1->value.i == p2->value.i;
+		case ALISP_OBJ_FLOAT:
+			return p1->value.i == p2->value.i;
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * Syntax: (eq expr1 expr2)
+ */
+static struct alisp_object * F_eq(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p1, * p2;
+
+	p1 = eval(instance, car(args));
+	p2 = eval(instance, car(cdr(args)));
+	delete_tree(instance, cdr(cdr(args)));
+	delete_object(instance, cdr(args));
+	delete_object(instance, args);
+
+	if (eq(p1, p2)) {
+		delete_tree(instance, p1);
+		delete_tree(instance, p2);
+		return &alsa_lisp_t;
+	}
+	delete_tree(instance, p1);
+	delete_tree(instance, p2);
+	return &alsa_lisp_nil;
+}
+
+/*
+ * Syntax: (equal expr1 expr2)
+ */
+static struct alisp_object * F_equal(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p1, * p2;
+
+	p1 = eval(instance, car(args));
+	p2 = eval(instance, car(cdr(args)));
+	delete_tree(instance, cdr(cdr(args)));
+	delete_object(instance, cdr(args));
+	delete_object(instance, args);
+
+	if (equal(p1, p2)) {
+		delete_tree(instance, p1);
+		delete_tree(instance, p2);
+		return &alsa_lisp_t;
+	}
+	delete_tree(instance, p1);
+	delete_tree(instance, p2);
+	return &alsa_lisp_nil;
+}
+
+/*
+ * Syntax: (quote expr)
+ */
+static struct alisp_object * F_quote(struct alisp_instance *instance ATTRIBUTE_UNUSED, struct alisp_object * args)
+{
+	struct alisp_object *p = car(args);
+	
+	delete_tree(instance, cdr(args));
+	delete_object(instance, args);
+	return p;
+}
+
+/*
+ * Syntax: (and expr...)
+ */
+static struct alisp_object * F_and(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p = args, * p1 = NULL, * n;
+
+	do {
+		if (p1)
+			delete_tree(instance, p1);
+		p1 = eval(instance, car(p));
+		if (p1 == &alsa_lisp_nil) {
+			delete_tree(instance, p1);
+			delete_tree(instance, cdr(p));
+			delete_object(instance, p);
+			return &alsa_lisp_nil;
+		}
+		p = cdr(n = p);
+		delete_object(instance, n);
+	} while (p != &alsa_lisp_nil);
+
+	return p1;
+}
+
+/*
+ * Syntax: (or expr...)
+ */
+static struct alisp_object * F_or(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p = args, * p1 = NULL, * n;
+
+	do {
+		if (p1)
+			delete_tree(instance, p1);
+		p1 = eval(instance, car(p));
+		if (p1 != &alsa_lisp_nil) {
+			delete_tree(instance, cdr(p));
+			delete_object(instance, p);
+			return p1;
+		}
+		p = cdr(n = p);
+		delete_object(instance, n);
+	} while (p != &alsa_lisp_nil);
+
+	return &alsa_lisp_nil;
+}
+
+/*
+ * Syntax: (not expr)
+ * Syntax: (null expr)
+ */
+static struct alisp_object * F_not(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p = eval(instance, car(args));
+
+	delete_tree(instance, cdr(args));
+	delete_object(instance, args);
+	if (p != &alsa_lisp_nil) {
+		delete_tree(instance, p);
+		return &alsa_lisp_nil;
+	}
+
+	delete_tree(instance, p);
+	return &alsa_lisp_t;
+}
+
+/*
+ * Syntax: (cond (expr1 [expr2])...)
+ */
+static struct alisp_object * F_cond(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p = args, * p1, * p2, * p3;
+
+	do {
+		p1 = car(p);
+		if ((p2 = eval(instance, car(p1))) != &alsa_lisp_nil) {
+			p3 = cdr(p1);
+			delete_object(instance, p1);
+			delete_tree(instance, cdr(p));
+			delete_object(instance, p);
+			if (p3 != &alsa_lisp_nil) {
+				delete_tree(instance, p2);
+				return F_progn(instance, p3);
+			} else {
+				delete_tree(instance, p3);
+				return p2;
+			}
+		} else {
+			delete_tree(instance, p2);
+			delete_tree(instance, cdr(p1));
+			delete_object(instance, p1);
+		}
+		p = cdr(p2 = p);
+		delete_object(instance, p2);
+	} while (p != &alsa_lisp_nil);
+
+	return &alsa_lisp_nil;
+}
+
+/*
+ * Syntax: (if expr then-expr else-expr...)
+ */
+static struct alisp_object * F_if(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p1, * p2, * p3;
+
+	p1 = car(args);
+	p2 = car(cdr(args));
+	p3 = cdr(cdr(args));
+	delete_object(instance, cdr(args));
+	delete_object(instance, args);
+
+	p1 = eval(instance, p1);
+	if (p1 != &alsa_lisp_nil) {
+		delete_tree(instance, p1);
+		delete_tree(instance, p3);
+		return eval(instance, p2);
+	}
+
+	delete_tree(instance, p1);
+	delete_tree(instance, p2);
+	return F_progn(instance, p3);
+}
+
+/*
+ * Syntax: (when expr then-expr...)
+ */
+static struct alisp_object * F_when(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p1, * p2;
+
+	p1 = car(args);
+	p2 = cdr(args);
+	delete_object(instance, args);
+	if ((p1 = eval(instance, p1)) != &alsa_lisp_nil) {
+		delete_tree(instance, p1);
+		return F_progn(instance, p2);
+	} else {
+		delete_tree(instance, p1);
+		delete_tree(instance, p2);
+	}
+
+	return &alsa_lisp_nil;
+}
+
+/*
+ * Syntax: (unless expr else-expr...)
+ */
+static struct alisp_object * F_unless(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p1, * p2;
+
+	p1 = car(args);
+	p2 = cdr(args);
+	delete_object(instance, args);
+	if ((p1 = eval(instance, p1)) == &alsa_lisp_nil) {
+		return F_progn(instance, p2);
+	} else {
+		delete_tree(instance, p1);
+		delete_tree(instance, p2);
+	}
+
+	return &alsa_lisp_nil;
+}
+
+/*
+ * Syntax: (while expr exprs...)
+ */
+static struct alisp_object * F_while(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p1, * p2, * p3;
+
+	p1 = car(args);
+	p2 = cdr(args);
+
+	delete_object(instance, args);
+	while (1) {
+		incref_tree(instance, p1);
+		if ((p3 = eval(instance, p1)) == &alsa_lisp_nil)
+			break;
+		delete_tree(instance, p3);
+		incref_tree(instance, p2);
+		delete_tree(instance, F_progn(instance, p2));
+	}
+
+	delete_tree(instance, p1);
+	delete_tree(instance, p2);
+	return &alsa_lisp_nil;
+}
+
+/*
+ * Syntax: (progn expr...)
+ */
+static struct alisp_object * F_progn(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p = args, * p1 = NULL, * n;
+
+	do {
+		if (p1)
+			delete_tree(instance, p1);
+		p1 = eval(instance, car(p));
+		n = cdr(p);
+		delete_object(instance, p);
+		p = n;
+	} while (p != &alsa_lisp_nil);
+
+	return p1;
+}
+
+/*
+ * Syntax: (prog1 expr...)
+ */
+static struct alisp_object * F_prog1(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p = args, * first = NULL, * p1;
+
+	do {
+		p1 = eval(instance, car(p));
+		if (first == NULL)
+			first = p1;
+		else
+			delete_tree(instance, p1);
+		p1 = cdr(p);
+		delete_object(instance, p);
+		p = p1;
+	} while (p != &alsa_lisp_nil);
+
+	if (first == NULL)
+		first = &alsa_lisp_nil;
+
+	return first;
+}
+
+/*
+ * Syntax: (prog2 expr...)
+ */
+static struct alisp_object * F_prog2(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p = args, * second = NULL, * p1;
+	int i = 0;
+
+	do {
+		++i;
+		p1 = eval(instance, car(p));
+		if (i == 2)
+			second = p1;
+		else
+			delete_tree(instance, p1);
+		p1 = cdr(p);
+		delete_object(instance, p);
+		p = p1;
+	} while (p != &alsa_lisp_nil);
+
+	if (second == NULL)
+		second = &alsa_lisp_nil;
+
+	return second;
+}
+
+/*
+ * Syntax: (set name value)
+ */
+static struct alisp_object * F_set(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p1 = eval(instance, car(args)),
+			    * p2 = eval(instance, car(cdr(args)));
+
+	delete_tree(instance, cdr(cdr(args)));
+	delete_object(instance, cdr(args));
+	delete_object(instance, args);
+	if (!check_set_object(instance, p1)) {
+		delete_tree(instance, p2);
+		p2 = &alsa_lisp_nil;
+	} else {
+		if (set_object(instance, p1, p2) == NULL) {
+			delete_tree(instance, p1);
+			delete_tree(instance, p2);
+			return NULL;
+		}
+	}
+	delete_tree(instance, p1);
+	return incref_tree(instance, p2);
+}
+
+/*
+ * Syntax: (unset name)
+ */
+static struct alisp_object * F_unset(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p1 = eval(instance, car(args));
+
+	delete_tree(instance, unset_object(instance, p1));
+	delete_tree(instance, cdr(args));
+	delete_object(instance, args);
+	return p1;
+}
+
+/*
+ * Syntax: (setq name value...)
+ * Syntax: (setf name value...)
+ * `name' is not evalled
+ */
+static struct alisp_object * F_setq(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p = args, * p1, * p2 = NULL, *n;
+
+	do {
+		p1 = car(p);
+		p2 = eval(instance, car(cdr(p)));
+		n = cdr(cdr(p));
+		delete_object(instance, cdr(p));
+		delete_object(instance, p);
+		if (!check_set_object(instance, p1)) {
+			delete_tree(instance, p2);
+			p2 = &alsa_lisp_nil;
+		} else {
+			if (set_object(instance, p1, p2) == NULL) {
+				delete_tree(instance, p1);
+				delete_tree(instance, p2);
+				return NULL;
+			}
+		}
+		delete_tree(instance, p1);
+		p = n;
+	} while (p != &alsa_lisp_nil);
+
+	return incref_tree(instance, p2);
+}
+
+/*
+ * Syntax: (unsetq name...)
+ * Syntax: (unsetf name...)
+ * `name' is not evalled
+ */
+static struct alisp_object * F_unsetq(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p = args, * p1 = NULL, * n;
+
+	do {
+		if (p1)
+			delete_tree(instance, p1);
+		p1 = unset_object(instance, car(p));
+		delete_tree(instance, car(p));
+		p = cdr(n = p);
+		delete_object(instance, n);
+	} while (p != &alsa_lisp_nil);
+
+	return p1;
+}
+
+/*
+ * Syntax: (defun name arglist expr...)
+ * `name' is not evalled
+ * `arglist' is not evalled
+ */
+static struct alisp_object * F_defun(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p1 = car(args),
+			    * p2 = car(cdr(args)),
+			    * p3 = cdr(cdr(args));
+	struct alisp_object * lexpr;
+
+	lexpr = new_object(instance, ALISP_OBJ_CONS);
+	if (lexpr) {
+		lexpr->value.c.car = new_identifier(instance, "lambda");
+		if (lexpr->value.c.car == NULL) {
+			delete_object(instance, lexpr);
+			delete_tree(instance, args);
+			return NULL;
+		}
+		if ((lexpr->value.c.cdr = new_object(instance, ALISP_OBJ_CONS)) == NULL) {
+			delete_object(instance, lexpr->value.c.car);
+			delete_object(instance, lexpr);
+			delete_tree(instance, args);
+			return NULL;
+		}
+		lexpr->value.c.cdr->value.c.car = p2;
+		lexpr->value.c.cdr->value.c.cdr = p3;
+		delete_object(instance, cdr(args));
+		delete_object(instance, args);
+		if (set_object(instance, p1, lexpr) == NULL) {
+			delete_tree(instance, p1);
+			delete_tree(instance, lexpr);
+			return NULL;
+		}
+		delete_tree(instance, p1);
+	} else {
+		delete_tree(instance, args);
+	}
+	return &alsa_lisp_nil;
+}
+
+static struct alisp_object * eval_func(struct alisp_instance *instance, struct alisp_object * p, struct alisp_object * args)
+{
+	struct alisp_object * p1, * p2, * p3, * p4;
+	struct alisp_object ** eval_objs, ** save_objs;
+	int i;
+
+	p1 = car(p);
+	if (alisp_compare_type(p1, ALISP_OBJ_IDENTIFIER) &&
+	    !strcmp(p1->value.s, "lambda")) {
+		p2 = car(cdr(p));
+		p3 = args;
+
+		if ((i = count_list(p2)) != count_list(p3)) {
+			lisp_warn(instance, "wrong number of parameters");
+			goto _delete;
+		}
+
+		eval_objs = malloc(2 * i * sizeof(struct alisp_object *));
+		if (eval_objs == NULL) {
+			nomem();
+			goto _delete;
+		}
+		save_objs = eval_objs + i;
+		
+		/*
+		 * Save the new variable values.
+		 */
+		i = 0;
+		while (p3 != &alsa_lisp_nil) {
+			eval_objs[i++] = eval(instance, car(p3));
+			p3 = cdr(p4 = p3);
+			delete_object(instance, p4);
+		}
+
+		/*
+		 * Save the old variable values and set the new ones.
+		 */
+		i = 0;
+		while (p2 != &alsa_lisp_nil) {
+			p3 = car(p2);
+			save_objs[i] = replace_object(instance, p3, eval_objs[i]);
+			if (save_objs[i] == NULL &&
+			    set_object_direct(instance, p3, eval_objs[i]) == NULL) {
+			    	p4 = NULL;
+				goto _end;
+			}
+			p2 = cdr(p2);
+			++i;
+		}
+
+		p4 = F_progn(instance, cdr(incref_tree(instance, p3 = cdr(p))));
+
+		/*
+		 * Restore the old variable values.
+		 */
+		p2 = car(p3);
+		delete_object(instance, p3);
+		i = 0;
+		while (p2 != &alsa_lisp_nil) {
+			p3 = car(p2);
+			if (save_objs[i] == NULL) {
+				p3 = unset_object(instance, p3);
+			} else {
+				p3 = replace_object(instance, p3, save_objs[i]);
+			}
+			i++;
+			delete_tree(instance, p3);
+			delete_tree(instance, car(p2));
+			p2 = cdr(p3 = p2);
+			delete_object(instance, p3);
+		}
+
+               _end:
+		free(eval_objs);
+
+		return p4;
+	} else {
+	       _delete:
+		delete_tree(instance, args);
+	}
+	return &alsa_lisp_nil;
+}
+
+struct alisp_object * F_gc(struct alisp_instance *instance ATTRIBUTE_UNUSED, struct alisp_object * args ATTRIBUTE_UNUSED)
+{
+	/* improved: no more traditional gc */
+	return &alsa_lisp_t;
+}
+
+/*
+ * Syntax: (path what)
+ * what is string ('data')
+ */
+struct alisp_object * F_path(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p1;
+
+	p1 = eval(instance, car(args));
+	delete_tree(instance, cdr(args));
+	delete_object(instance, args);
+	if (!alisp_compare_type(p1, ALISP_OBJ_STRING)) {
+		delete_tree(instance, p1);
+		return &alsa_lisp_nil;
+	}
+	if (!strcmp(p1->value.s, "data")) {
+		delete_tree(instance, p1);
+		return new_string(instance, ALSA_CONFIG_DIR);
+	}
+	delete_tree(instance, p1);
+	return &alsa_lisp_nil;
+}
+
+/*
+ * Syntax: (include filename...)
+ */
+struct alisp_object * F_include(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p = args, * p1;
+	int res = -ENOENT;
+
+	do {
+		p1 = eval(instance, car(p));
+		if (alisp_compare_type(p1, ALISP_OBJ_STRING))
+			res = alisp_include_file(instance, p1->value.s);
+		delete_tree(instance, p1);
+		p = cdr(p1 = p);
+		delete_object(instance, p1);
+	} while (p != &alsa_lisp_nil);
+
+	return new_integer(instance, res);
+}
+
+/*
+ * Syntax: (string-to-integer value)
+ * 'value' can be integer or float type
+ */
+struct alisp_object * F_string_to_integer(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p = eval(instance, car(args)), * p1;
+
+	delete_tree(instance, cdr(args));
+	delete_object(instance, args);
+	if (alisp_compare_type(p, ALISP_OBJ_INTEGER))
+		return p;
+	if (alisp_compare_type(p, ALISP_OBJ_FLOAT)) {
+		p1 = new_integer(instance, floor(p->value.f));
+	} else {
+		lisp_warn(instance, "expected an integer or float for integer conversion");
+		p1 = &alsa_lisp_nil;
+	}
+	delete_tree(instance, p);
+	return p1;
+}
+
+/*
+ * Syntax: (string-to-float value)
+ * 'value' can be integer or float type
+ */
+struct alisp_object * F_string_to_float(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p = eval(instance, car(args)), * p1;
+
+	delete_tree(instance, cdr(args));
+	delete_object(instance, args);
+	if (alisp_compare_type(p, ALISP_OBJ_FLOAT))
+		return p;
+	if (alisp_compare_type(p, ALISP_OBJ_INTEGER)) {
+		p1 = new_float(instance, p->value.i);
+	} else {
+		lisp_warn(instance, "expected an integer or float for integer conversion");
+		p1 = &alsa_lisp_nil;
+	}
+	delete_tree(instance, p);
+	return p1;
+}
+
+static int append_to_string(char **s, int *len, char *from, int size)
+{
+	if (*len == 0) {
+		*s = malloc(*len = size + 1);
+		if (*s == NULL) {
+			nomem();
+			return -ENOMEM;
+		}
+		memcpy(*s, from, size);
+	} else {
+		*len += size;
+		*s = realloc(*s, *len);
+		if (*s == NULL) {
+			nomem();
+			return -ENOMEM;
+		}
+		memcpy(*s + strlen(*s), from, size);
+	}
+	(*s)[*len - 1] = '\0';
+	return 0;
+}
+
+static int format_parse_char(struct alisp_instance *instance, char **s, int *len, struct alisp_object *p)
+{
+	char b;
+
+	if (!alisp_compare_type(p, ALISP_OBJ_INTEGER)) {
+		lisp_warn(instance, "format: expected integer\n");
+		return 0;
+	}
+	b = p->value.i;
+	return append_to_string(s, len, &b, 1);
+}
+
+static int format_parse_integer(struct alisp_instance *instance, char **s, int *len, struct alisp_object *p)
+{
+	int res;
+	char *s1;
+
+	if (!alisp_compare_type(p, ALISP_OBJ_INTEGER) &&
+	    !alisp_compare_type(p, ALISP_OBJ_FLOAT)) {
+		lisp_warn(instance, "format: expected integer or float\n");
+		return 0;
+	}
+	s1 = malloc(64);
+	if (s1 == NULL) {
+		nomem();
+		return -ENOMEM;
+	}
+	sprintf(s1, "%li", alisp_compare_type(p, ALISP_OBJ_FLOAT) ? (long)floor(p->value.f) : p->value.i);
+	res = append_to_string(s, len, s1, strlen(s1));
+	free(s1);
+	return res;
+}
+
+static int format_parse_float(struct alisp_instance *instance, char **s, int *len, struct alisp_object *p)
+{
+	int res;
+	char *s1;
+
+	if (!alisp_compare_type(p, ALISP_OBJ_INTEGER) &&
+	    !alisp_compare_type(p, ALISP_OBJ_FLOAT)) {
+		lisp_warn(instance, "format: expected integer or float\n");
+		return 0;
+	}
+	s1 = malloc(64);
+	if (s1 == NULL) {
+		nomem();
+		return -ENOMEM;
+	}
+	sprintf(s1, "%f", alisp_compare_type(p, ALISP_OBJ_FLOAT) ? p->value.f : (double)p->value.i);
+	res = append_to_string(s, len, s1, strlen(s1));
+	free(s1);
+	return res;
+}
+
+static int format_parse_string(struct alisp_instance *instance, char **s, int *len, struct alisp_object *p)
+{
+	if (!alisp_compare_type(p, ALISP_OBJ_STRING)) {
+		lisp_warn(instance, "format: expected string\n");
+		return 0;
+	}
+	return append_to_string(s, len, p->value.s, strlen(p->value.s));
+}
+
+/*
+ * Syntax: (format format value...)
+ * 'format' is C-like format string
+ */
+struct alisp_object * F_format(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p = eval(instance, car(args)), * p1 = cdr(args), * n;
+	char *s, *s1, *s2;
+	int len;
+
+	delete_object(instance, args);
+	if (!alisp_compare_type(p, ALISP_OBJ_STRING)) {
+		delete_tree(instance, p1);
+		delete_tree(instance, p);
+		lisp_warn(instance, "format: expected an format string");
+		return &alsa_lisp_nil;
+	}
+	s = p->value.s;
+	s1 = NULL;
+	len = 0;
+	n = eval(instance, car(p1));
+	do {
+		while (1) {
+			s2 = s;
+			while (*s2 && *s2 != '%')
+				s2++;
+			if (s2 != s) {
+				if (append_to_string(&s1, &len, s, s2 - s) < 0) {
+				      __error:
+					delete_tree(instance, n);
+					delete_tree(instance, cdr(p1));
+					delete_object(instance, p1);
+					delete_tree(instance, p);
+					return NULL;
+				}
+			}
+			if (*s2 == '%')
+				s2++;
+			switch (*s2) {
+			case '%':
+				if (append_to_string(&s1, &len, s2, 1) < 0)
+					goto __error;
+				s = s2 + 1;
+				break;
+			case 'c':
+				if (format_parse_char(instance, &s1, &len, n) < 0)
+					goto __error;
+				s = s2 + 1;
+				goto __next;
+			case 'd':
+			case 'i':
+				if (format_parse_integer(instance, &s1, &len, n) < 0)
+					goto __error;
+				s = s2 + 1;
+				goto __next;
+			case 'f':
+				if (format_parse_float(instance, &s1, &len, n) < 0)
+					goto __error;
+				s = s2 + 1;
+				goto __next;
+			case 's':
+				if (format_parse_string(instance, &s1, &len, n) < 0)
+					goto __error;
+				s = s2 + 1;
+				goto __next;
+			case '\0':
+				goto __end;
+			default:
+				lisp_warn(instance, "unknown format char '%c'", *s2);
+				s = s2 + 1;
+				goto __next;
+			}
+		}
+	      __next:
+		delete_tree(instance, n);
+		p1 = cdr(n = p1);
+		delete_object(instance, n);
+		n = eval(instance, car(p1));
+	} while (*s);
+      __end:
+	delete_tree(instance, n);
+	delete_tree(instance, cdr(p1));
+	delete_object(instance, p1);
+	delete_tree(instance, p);
+	if (len > 0) {
+		p1 = new_string(instance, s1);
+		free(s1);
+	} else {
+		p1 = &alsa_lisp_nil;
+	}
+	return p1;
+}
+
+/*
+ * Syntax: (compare-strings str1 start1 end1 str2 start2 end2 /opt-case-insensitive)
+ * 'str1' is first compared string
+ * 'start1' is first char (0..)
+ * 'end1' is last char (0..)
+ * 'str2' is second compared string
+ * 'start2' is first char (0..)
+ * 'end2' is last char (0..)
+ * /opt-case-insensitive true - case insensitive match
+ */
+struct alisp_object * F_compare_strings(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p1 = args, * n, * p[7];
+	char *s1, *s2;
+	int start1, end1, start2, end2;
+	
+	for (start1 = 0; start1 < 7; start1++) {
+		p[start1] = eval(instance, car(p1));
+		p1 = cdr(n = p1);
+		delete_object(instance, n);
+	}
+	delete_tree(instance, p1);
+	if (alisp_compare_type(p[0], ALISP_OBJ_STRING)) {
+		lisp_warn(instance, "compare-strings: first argument must be string\n");
+		p1 = &alsa_lisp_nil;
+		goto __err;
+	}
+	if (alisp_compare_type(p[1], ALISP_OBJ_INTEGER)) {
+		lisp_warn(instance, "compare-strings: second argument must be integer\n");
+		p1 = &alsa_lisp_nil;
+		goto __err;
+	}
+	if (alisp_compare_type(p[2], ALISP_OBJ_INTEGER)) {
+		lisp_warn(instance, "compare-strings: third argument must be integer\n");
+		p1 = &alsa_lisp_nil;
+		goto __err;
+	}
+	if (alisp_compare_type(p[3], ALISP_OBJ_STRING)) {
+		lisp_warn(instance, "compare-strings: fifth argument must be string\n");
+		p1 = &alsa_lisp_nil;
+		goto __err;
+	}
+	if (!alisp_compare_type(p[4], ALISP_OBJ_NIL) &&
+	    !alisp_compare_type(p[4], ALISP_OBJ_INTEGER)) {
+		lisp_warn(instance, "compare-strings: fourth argument must be integer\n");
+		p1 = &alsa_lisp_nil;
+		goto __err;
+	}
+	if (!alisp_compare_type(p[5], ALISP_OBJ_NIL) &&
+	    !alisp_compare_type(p[5], ALISP_OBJ_INTEGER)) {
+		lisp_warn(instance, "compare-strings: sixth argument must be integer\n");
+		p1 = &alsa_lisp_nil;
+		goto __err;
+	}
+	s1 = p[0]->value.s;
+	start1 = p[1]->value.i;
+	end1 = p[2]->value.i;
+	s2 = p[3]->value.s;
+	start2 = alisp_compare_type(p[4], ALISP_OBJ_NIL) ? 0 : p[4]->value.i;
+	end2 = alisp_compare_type(p[5], ALISP_OBJ_NIL) ? start2 + (end1 - start1) : p[5]->value.i;
+	if (start1 < 0 || start2 < 0 || end1 < 0 || end2 < 0 ||
+	    start1 >= (int)strlen(s1) || start2 >= (int)strlen(s2) ||
+	    (end1 - start1) != (end2 - start2)) {
+	    	p1 = &alsa_lisp_nil;
+	    	goto __err;
+	}
+	if (p[6] != &alsa_lisp_nil) {
+		while (start1 < end1) {
+			if (s1[start1] == '\0' ||
+			    s2[start2] == '\0' ||
+			    tolower(s1[start1]) != tolower(s2[start2])) {
+				p1 = &alsa_lisp_nil;
+				goto __err;
+			}
+			start1++;
+			start2++;
+		}
+	} else {
+		while (start1 < end1) {
+			if (s1[start1] == '\0' ||
+			    s2[start2] == '\0' ||
+			    s1[start1] != s2[start2]) {
+				p1 = &alsa_lisp_nil;
+				goto __err;
+			}
+			start1++;
+			start2++;
+		}
+	}
+	p1 = &alsa_lisp_t;
+	
+      __err:
+      	for (start1 = 0; start1 < 7; start1++)
+      		delete_tree(instance, p[start1]);
+      	return p1;	
+}
+
+/*
+ *  Syntax: (assoc key alist)
+ */
+struct alisp_object * F_assoc(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p1, * p2, * n;
+
+	p1 = eval(instance, car(args));
+	p2 = eval(instance, car(cdr(args)));
+	delete_tree(instance, cdr(cdr(args)));
+	delete_object(instance, cdr(args));
+	delete_object(instance, args);
+
+	do {
+		if (eq(p1, car(car(p2)))) {
+			n = car(p2);
+			delete_tree(instance, p1);
+			delete_tree(instance, cdr(p2));
+			delete_object(instance, p2);
+			return n;
+		}
+		delete_tree(instance, car(p2));
+		p2 = cdr(n = p2);
+		delete_object(instance, n);
+	} while (p2 != &alsa_lisp_nil);
+
+	delete_tree(instance, p1);
+	return &alsa_lisp_nil;	
+}
+
+/*
+ *  Syntax: (rassoc value alist)
+ */
+struct alisp_object * F_rassoc(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p1, *p2, * n;
+
+	p1 = eval(instance, car(args));
+	p2 = eval(instance, car(cdr(args)));
+	delete_tree(instance, cdr(cdr(args)));
+	delete_object(instance, cdr(args));
+	delete_object(instance, args);
+
+	do {
+		if (eq(p1, cdr(car(p2)))) {
+			n = car(p2);
+			delete_tree(instance, p1);
+			delete_tree(instance, cdr(p2));
+			delete_object(instance, p2);
+			return n;
+		}
+		delete_tree(instance, car(p2));
+		p2 = cdr(n = p2);
+		delete_object(instance, n);
+	} while (p2 != &alsa_lisp_nil);
+
+	delete_tree(instance, p1);
+	return &alsa_lisp_nil;	
+}
+
+/*
+ *  Syntax: (assq key alist)
+ */
+struct alisp_object * F_assq(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p1, * p2, * n;
+
+	p1 = eval(instance, car(args));
+	p2 = eval(instance, car(cdr(args)));
+	delete_tree(instance, cdr(cdr(args)));
+	delete_object(instance, cdr(args));
+	delete_object(instance, args);
+
+	do {
+		if (equal(p1, car(car(p2)))) {
+			n = car(p2);
+			delete_tree(instance, p1);
+			delete_tree(instance, cdr(p2));
+			delete_object(instance, p2);
+			return n;
+		}
+		delete_tree(instance, car(p2));
+		p2 = cdr(n = p2);
+		delete_object(instance, n);
+	} while (p2 != &alsa_lisp_nil);
+
+	delete_tree(instance, p1);
+	return &alsa_lisp_nil;	
+}
+
+/*
+ *  Syntax: (nth index alist)
+ */
+struct alisp_object * F_nth(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p1, * p2, * n;
+	long idx;
+
+	p1 = eval(instance, car(args));
+	p2 = eval(instance, car(cdr(args)));
+	delete_tree(instance, cdr(cdr(args)));
+	delete_object(instance, cdr(args));
+	delete_object(instance, args);
+
+	if (!alisp_compare_type(p1, ALISP_OBJ_INTEGER)) {
+		delete_tree(instance, p1);
+		delete_tree(instance, p2);
+		return &alsa_lisp_nil;
+	}
+	if (!alisp_compare_type(p2, ALISP_OBJ_CONS)) {
+		delete_object(instance, p1);
+		delete_tree(instance, p2);
+		return &alsa_lisp_nil;
+	}
+	idx = p1->value.i;
+	delete_object(instance, p1);
+	while (idx-- > 0) {
+		delete_tree(instance, car(p2));
+		p2 = cdr(n = p2);
+		delete_object(instance, n);
+	}
+	n = car(p2);
+	delete_tree(instance, cdr(p2));
+	delete_object(instance, p2);
+	return n;
+}
+
+/*
+ *  Syntax: (rassq value alist)
+ */
+struct alisp_object * F_rassq(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p1, * p2, * n;
+
+	p1 = eval(instance, car(args));
+	p2 = eval(instance, car(cdr(args)));
+	delete_tree(instance, cdr(cdr(args)));
+	delete_object(instance, cdr(args));
+	delete_object(instance, args);
+
+	do {
+		if (equal(p1, cdr(car(p2)))) {
+			n = car(p2);
+			delete_tree(instance, p1);
+			delete_tree(instance, cdr(p2));
+			delete_object(instance, p2);
+			return n;
+		}
+		delete_tree(instance, car(p2));
+		p2 = cdr(n = p2);
+		delete_object(instance, n);
+	} while (p2 != &alsa_lisp_nil);
+
+	delete_tree(instance, p1);
+	return &alsa_lisp_nil;	
+}
+
+static struct alisp_object * F_dump_memory(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p = car(args);
+
+	if (p != &alsa_lisp_nil && cdr(args) == &alsa_lisp_nil &&
+	    alisp_compare_type(p, ALISP_OBJ_STRING)) {
+		if (strlen(p->value.s) > 0) {
+			dump_objects(instance, p->value.s);
+			delete_tree(instance, args);
+			return &alsa_lisp_t;
+		} else
+			lisp_warn(instance, "expected filename");
+	} else
+		lisp_warn(instance, "wrong number of parameters (expected string)");
+
+	delete_tree(instance, args);
+	return &alsa_lisp_nil;
+}
+
+static struct alisp_object * F_stat_memory(struct alisp_instance *instance, struct alisp_object * args)
+{
+	snd_output_printf(instance->out, "*** Memory stats\n");
+	snd_output_printf(instance->out, "  used_objs = %li, free_objs = %li, max_objs = %li, obj_size = %i (total bytes = %li, max bytes = %li)\n",
+			  instance->used_objs,
+			  instance->free_objs,
+			  instance->max_objs,
+			  (int)sizeof(struct alisp_object),
+			  (long)((instance->used_objs + instance->free_objs) * sizeof(struct alisp_object)),
+			  (long)(instance->max_objs * sizeof(struct alisp_object)));
+	delete_tree(instance, args);
+	return &alsa_lisp_nil;
+}
+
+static struct alisp_object * F_check_memory(struct alisp_instance *instance, struct alisp_object * args)
+{
+	delete_tree(instance, args);
+	if (instance->used_objs > 0) {
+		fprintf(stderr, "!!!alsa lisp - check memory failed!!!\n");
+		F_stat_memory(instance, &alsa_lisp_nil);
+		exit(EXIT_FAILURE);
+	}
+	return &alsa_lisp_t;
+}
+
+static struct alisp_object * F_dump_objects(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p = car(args);
+
+	if (p != &alsa_lisp_nil && cdr(args) == &alsa_lisp_nil &&
+	    alisp_compare_type(p, ALISP_OBJ_STRING)) {
+		if (strlen(p->value.s) > 0) {
+			dump_obj_lists(instance, p->value.s);
+			delete_tree(instance, args);
+			return &alsa_lisp_t;
+		} else
+			lisp_warn(instance, "expected filename");
+	} else
+		lisp_warn(instance, "wrong number of parameters (expected string)");
+
+	delete_tree(instance, args);
+	return &alsa_lisp_nil;
+}
+
+struct intrinsic {
+	const char *name;
+	struct alisp_object * (*func)(struct alisp_instance *instance, struct alisp_object * args);
+};
+
+static const struct intrinsic intrinsics[] = {
+	{ "!=", F_numneq },
+	{ "%", F_mod },
+	{ "&check-memory", F_check_memory },
+	{ "&dump-memory", F_dump_memory },
+	{ "&dump-objects", F_dump_objects },
+	{ "&stat-memory", F_stat_memory },
+	{ "*", F_mul },
+	{ "+", F_add },
+	{ "-", F_sub },
+	{ "/", F_div },
+	{ "<", F_lt },
+	{ "<=", F_le },
+	{ "=", F_numeq },
+	{ ">", F_gt },
+	{ ">=", F_ge },
+	{ "and", F_and },
+	{ "assoc", F_assoc },
+	{ "assq", F_assq },
+	{ "atom", F_atom },
+	{ "car", F_car },
+	{ "cdr", F_cdr },
+	{ "compare-strings", F_compare_strings },
+	{ "concat", F_concat },
+	{ "cond", F_cond },
+	{ "cons", F_cons },
+	{ "defun", F_defun },
+	{ "eq", F_eq },
+	{ "equal", F_equal },
+	{ "eval", F_eval },
+	{ "exfun", F_exfun },
+	{ "format", F_format },
+	{ "funcall", F_funcall },
+	{ "garbage-collect", F_gc },
+	{ "gc", F_gc },
+	{ "if", F_if },
+	{ "include", F_include },
+	{ "list", F_list },
+	{ "not", F_not },
+	{ "nth", F_nth },
+	{ "null", F_not },
+	{ "or", F_or },
+	{ "path", F_path },
+	{ "princ", F_princ },
+	{ "prog1", F_prog1 },
+	{ "prog2", F_prog2 },
+	{ "progn", F_progn },
+	{ "quote", F_quote },
+	{ "rassoc", F_rassoc },
+	{ "rassq", F_rassq },
+	{ "set", F_set },
+	{ "setf", F_setq },
+	{ "setq", F_setq },
+	{ "string-equal", F_equal },
+	{ "string-to-float", F_string_to_float },
+	{ "string-to-integer", F_string_to_integer },
+	{ "string-to-number", F_string_to_float },
+	{ "string=", F_equal },
+	{ "unless", F_unless },
+	{ "unset", F_unset },
+	{ "unsetf", F_unsetq },
+	{ "unsetq", F_unsetq },
+	{ "when", F_when },
+	{ "while", F_while },
+};
+
+#include "alisp_snd.c"
+
+static int compar(const void *p1, const void *p2)
+{
+	return strcmp(((struct intrinsic *)p1)->name,
+		      ((struct intrinsic *)p2)->name);
+}
+
+static inline struct alisp_object * eval_cons1(struct alisp_instance *instance, struct alisp_object * p1, struct alisp_object * p2)
+{
+	struct alisp_object * p3;
+	struct intrinsic key, *item;
+
+	key.name = p1->value.s;
+
+	if ((item = bsearch(&key, intrinsics,
+			    sizeof intrinsics / sizeof intrinsics[0],
+			    sizeof intrinsics[0], compar)) != NULL) {
+		delete_object(instance, p1);
+		return item->func(instance, p2);
+	}
+
+	if ((item = bsearch(&key, snd_intrinsics,
+			    sizeof snd_intrinsics / sizeof snd_intrinsics[0],
+			    sizeof snd_intrinsics[0], compar)) != NULL) {
+		delete_object(instance, p1);
+		return item->func(instance, p2);
+	}
+
+	if ((p3 = get_object(instance, p1)) != &alsa_lisp_nil) {
+		delete_object(instance, p1);
+		return eval_func(instance, p3, p2);
+	} else {
+		lisp_warn(instance, "function `%s' is undefined", p1->value.s);
+		delete_object(instance, p1);
+		delete_tree(instance, p2);
+	}
+
+	return &alsa_lisp_nil;
+}
+
+/*
+ * Syntax: (funcall function args...)
+ */
+static struct alisp_object * F_funcall(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p = eval(instance, car(args)), * p1;
+
+	if (!alisp_compare_type(p, ALISP_OBJ_IDENTIFIER) &&
+	    !alisp_compare_type(p, ALISP_OBJ_STRING)) {
+		lisp_warn(instance, "expected an function name");
+		delete_tree(instance, p);
+		delete_tree(instance, cdr(args));
+		delete_object(instance, args);
+		return &alsa_lisp_nil;
+	}
+	p1 = cdr(args);
+	delete_object(instance, args);
+	return eval_cons1(instance, p, p1);
+}
+
+static inline struct alisp_object * eval_cons(struct alisp_instance *instance, struct alisp_object * p)
+{
+	struct alisp_object * p1 = car(p), * p2;
+
+	if (p1 != &alsa_lisp_nil && alisp_compare_type(p1, ALISP_OBJ_IDENTIFIER)) {
+		if (!strcmp(p1->value.s, "lambda"))
+			return p;
+
+		p2 = cdr(p);
+		delete_object(instance, p);
+		return eval_cons1(instance, p1, p2);
+	} else {
+		delete_tree(instance, p);
+	}
+
+	return &alsa_lisp_nil;
+}
+
+static struct alisp_object * eval(struct alisp_instance *instance, struct alisp_object * p)
+{
+	switch (alisp_get_type(p)) {
+	case ALISP_OBJ_IDENTIFIER: {
+		struct alisp_object *r = incref_tree(instance, get_object(instance, p));
+		delete_object(instance, p);
+		return r;
+	}
+	case ALISP_OBJ_INTEGER:
+	case ALISP_OBJ_FLOAT:
+	case ALISP_OBJ_STRING:
+	case ALISP_OBJ_POINTER:
+		return p;
+	case ALISP_OBJ_CONS:
+		return eval_cons(instance, p);
+	default:
+		break;
+	}
+
+	return p;
+}
+
+static struct alisp_object * F_eval(struct alisp_instance *instance, struct alisp_object * args)
+{
+	return eval(instance, eval(instance, car(args)));
+}
+
+/*
+ *  main routine
+ */
+
+static int alisp_include_file(struct alisp_instance *instance, const char *filename)
+{
+	snd_input_t *old_in;
+	struct alisp_object *p, *p1;
+	char *name;
+	int retval = 0, err;
+
+	err = snd_user_file(filename, &name);
+	if (err < 0)
+		return err;
+	old_in = instance->in;
+	err = snd_input_stdio_open(&instance->in, name, "r");
+	if (err < 0) {
+		retval = err;
+		goto _err;
+	}
+	if (instance->verbose)
+		lisp_verbose(instance, "** include filename '%s'", name);
+
+	for (;;) {
+		if ((p = parse_object(instance, 0)) == NULL)
+			break;
+		if (instance->verbose) {
+			lisp_verbose(instance, "** code");
+			princ_object(instance->vout, p);
+			snd_output_putc(instance->vout, '\n');
+		}
+		p1 = eval(instance, p);
+		if (p1 == NULL) {
+			retval = -ENOMEM;
+			break;
+		}
+		if (instance->verbose) {
+			lisp_verbose(instance, "** result");
+			princ_object(instance->vout, p1);
+			snd_output_putc(instance->vout, '\n');
+		}
+		delete_tree(instance, p1);
+		if (instance->debug) {
+			lisp_debug(instance, "** objects after operation");
+			print_obj_lists(instance, instance->dout);
+		}
+	}	
+
+	snd_input_close(instance->in);
+       _err:
+	free(name);
+	instance->in = old_in;
+	return retval;
+}
+ 
+int alsa_lisp(struct alisp_cfg *cfg, struct alisp_instance **_instance)
+{
+	struct alisp_instance *instance;
+	struct alisp_object *p, *p1;
+	int i, j, retval = 0;
+	
+	instance = (struct alisp_instance *)malloc(sizeof(struct alisp_instance));
+	if (instance == NULL) {
+		nomem();
+		return -ENOMEM;
+	}
+	memset(instance, 0, sizeof(struct alisp_instance));
+	instance->verbose = cfg->verbose && cfg->vout;
+	instance->warning = cfg->warning && cfg->wout;
+	instance->debug = cfg->debug && cfg->dout;
+	instance->in = cfg->in;
+	instance->out = cfg->out;
+	instance->vout = cfg->vout;
+	instance->eout = cfg->eout;
+	instance->wout = cfg->wout;
+	instance->dout = cfg->dout;
+	INIT_LIST_HEAD(&instance->free_objs_list);
+	for (i = 0; i < ALISP_OBJ_PAIR_HASH_SIZE; i++) {
+		for (j = 0; j <= ALISP_OBJ_LAST_SEARCH; j++)
+			INIT_LIST_HEAD(&instance->used_objs_list[i][j]);
+		INIT_LIST_HEAD(&instance->setobjs_list[i]);
+	}
+	
+	init_lex(instance);
+
+	for (;;) {
+		if ((p = parse_object(instance, 0)) == NULL)
+			break;
+		if (instance->verbose) {
+			lisp_verbose(instance, "** code");
+			princ_object(instance->vout, p);
+			snd_output_putc(instance->vout, '\n');
+		}
+		p1 = eval(instance, p);
+		if (p1 == NULL) {
+			retval = -ENOMEM;
+			break;
+		}
+		if (instance->verbose) {
+			lisp_verbose(instance, "** result");
+			princ_object(instance->vout, p1);
+			snd_output_putc(instance->vout, '\n');
+		}
+		delete_tree(instance, p1);
+		if (instance->debug) {
+			lisp_debug(instance, "** objects after operation");
+			print_obj_lists(instance, instance->dout);
+		}
+	}
+
+	if (_instance)
+		*_instance = instance;
+	else
+		alsa_lisp_free(instance); 
+	
+	return 0;
+}
+
+void alsa_lisp_free(struct alisp_instance *instance)
+{
+	if (instance == NULL)
+		return;
+	done_lex(instance);
+	free_objects(instance);
+	free(instance);
+}
+
+struct alisp_cfg *alsa_lisp_default_cfg(snd_input_t *input)
+{
+	snd_output_t *output, *eoutput;
+	struct alisp_cfg *cfg;
+	int err;
+	
+	err = snd_output_stdio_attach(&output, stdout, 0);
+	if (err < 0)
+		return NULL;
+	err = snd_output_stdio_attach(&eoutput, stderr, 0);
+	if (err < 0) {
+		snd_output_close(output);
+		return NULL;
+	}
+	cfg = calloc(1, sizeof(struct alisp_cfg));
+	if (cfg == NULL) {
+		snd_output_close(eoutput);
+		snd_output_close(output);
+		return NULL;
+	}
+	cfg->out = output;
+	cfg->wout = eoutput;
+	cfg->eout = eoutput;
+	cfg->dout = eoutput;
+	cfg->in = input;
+	return cfg;
+}
+
+void alsa_lisp_default_cfg_free(struct alisp_cfg *cfg)
+{
+	snd_input_close(cfg->in);
+	snd_output_close(cfg->out);
+	snd_output_close(cfg->dout);
+	free(cfg);
+}
+
+int alsa_lisp_function(struct alisp_instance *instance, struct alisp_seq_iterator **result,
+		       const char *id, const char *args, ...)
+{
+	int err = 0;
+	struct alisp_object *aargs = NULL, *obj, *res;
+
+	if (args && *args != 'n') {
+		va_list ap;
+		struct alisp_object *p;
+		p = NULL;
+		va_start(ap, args);
+		while (*args) {
+			if (*args++ != '%') {
+				err = -EINVAL;
+				break;
+			}
+			if (*args == '\0') {
+				err = -EINVAL;
+				break;
+			}
+			obj = NULL;
+			err = 0;
+			switch (*args++) {
+			case 's':
+				obj = new_string(instance, va_arg(ap, char *));
+				break;
+			case 'i':
+				obj = new_integer(instance, va_arg(ap, int));
+				break;
+			case 'l':
+				obj = new_integer(instance, va_arg(ap, long));
+				break;
+			case 'f':
+			case 'd':
+				obj = new_integer(instance, va_arg(ap, double));
+				break;
+			case 'p': {
+				char _ptrid[24];
+				char *ptrid = _ptrid;
+				while (*args && *args != '%')
+					*ptrid++ = *args++;
+				*ptrid = 0;
+				if (ptrid == _ptrid) {
+					err = -EINVAL;
+					break;
+				}
+				obj = new_cons_pointer(instance, _ptrid, va_arg(ap, void *));
+				obj = quote_object(instance, obj);
+				break;
+			}
+			default:
+				err = -EINVAL;
+				break;
+			}
+			if (err < 0)
+				goto __args_end;
+			if (obj == NULL) {
+				err = -ENOMEM;
+				goto __args_end;
+			}
+			if (p == NULL) {
+				p = aargs = new_object(instance, ALISP_OBJ_CONS);
+			} else {
+				p->value.c.cdr = new_object(instance, ALISP_OBJ_CONS);
+				p = p->value.c.cdr;
+			}
+			if (p == NULL) {
+				err = -ENOMEM;
+				goto __args_end;
+			}
+			p->value.c.car = obj;
+		}
+	      __args_end:
+		va_end(ap);
+		if (err < 0)
+			return err;
+#if 0
+		snd_output_printf(instance->wout, ">>>");
+		princ_object(instance->wout, aargs);
+		snd_output_printf(instance->wout, "<<<\n");
+#endif
+	}
+
+	err = -ENOENT;
+	if (aargs == NULL)
+		aargs = &alsa_lisp_nil;
+	if ((obj = get_object1(instance, id)) != &alsa_lisp_nil) {
+		res = eval_func(instance, obj, aargs);
+		err = 0;
+	} else {
+		struct intrinsic key, *item;
+		key.name = id;
+		if ((item = bsearch(&key, intrinsics,
+				    sizeof intrinsics / sizeof intrinsics[0],
+				    sizeof intrinsics[0], compar)) != NULL) {
+			res = item->func(instance, aargs);
+			err = 0;
+		} else if ((item = bsearch(&key, snd_intrinsics,
+				         sizeof snd_intrinsics / sizeof snd_intrinsics[0],
+					 sizeof snd_intrinsics[0], compar)) != NULL) {
+			res = item->func(instance, aargs);
+			err = 0;
+		} else {
+			res = &alsa_lisp_nil;
+		}
+	}
+	if (res == NULL)
+		err = -ENOMEM;
+	if (err == 0 && result) {
+		*result = res;
+	} else {
+		delete_tree(instance, res);
+	}
+
+	return 0;
+}
+
+void alsa_lisp_result_free(struct alisp_instance *instance,
+			   struct alisp_seq_iterator *result)
+{
+	delete_tree(instance, result);
+}
+
+int alsa_lisp_seq_first(struct alisp_instance *instance, const char *id,
+			struct alisp_seq_iterator **seq)
+{
+	struct alisp_object * p1;
+
+	p1 = get_object1(instance, id);
+	if (p1 == NULL)
+		return -ENOMEM;
+	*seq = p1;
+	return 0;
+}
+
+int alsa_lisp_seq_next(struct alisp_seq_iterator **seq)
+{
+	struct alisp_object * p1 = *seq;
+
+	p1 = cdr(p1);
+	if (p1 == &alsa_lisp_nil)
+		return -ENOENT;
+	*seq = p1;
+	return 0;
+}
+
+int alsa_lisp_seq_count(struct alisp_seq_iterator *seq)
+{
+	int count = 0;
+	
+	while (seq != &alsa_lisp_nil) {
+		count++;
+		seq = cdr(seq);
+	}
+	return count;
+}
+
+int alsa_lisp_seq_integer(struct alisp_seq_iterator *seq, long *val)
+{
+	if (alisp_compare_type(seq, ALISP_OBJ_CONS))
+		seq = seq->value.c.cdr;
+	if (alisp_compare_type(seq, ALISP_OBJ_INTEGER))
+		*val = seq->value.i;
+	else
+		return -EINVAL;
+	return 0;
+}
+
+int alsa_lisp_seq_pointer(struct alisp_seq_iterator *seq, const char *ptr_id, void **ptr)
+{
+	struct alisp_object * p2;
+	
+	if (alisp_compare_type(seq, ALISP_OBJ_CONS) &&
+	    alisp_compare_type(seq->value.c.car, ALISP_OBJ_CONS))
+		seq = seq->value.c.car;
+	if (alisp_compare_type(seq, ALISP_OBJ_CONS)) {
+		p2 = seq->value.c.car;
+		if (!alisp_compare_type(p2, ALISP_OBJ_STRING))
+			return -EINVAL;
+		if (strcmp(p2->value.s, ptr_id))
+			return -EINVAL;
+		p2 = seq->value.c.cdr;
+		if (!alisp_compare_type(p2, ALISP_OBJ_POINTER))
+			return -EINVAL;
+		*ptr = (void *)seq->value.ptr;
+	} else
+		return -EINVAL;
+	return 0;
+}
diff --git a/src/alisp/alisp_local.h b/src/alisp/alisp_local.h
new file mode 100644
index 0000000..4558ebb
--- /dev/null
+++ b/src/alisp/alisp_local.h
@@ -0,0 +1,151 @@
+/*
+ *  ALSA lisp implementation
+ *  Copyright (c) 2003 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *  Based on work of Sandro Sigala (slisp-1.2)
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include "list.h"
+
+enum alisp_tokens {
+	ALISP_IDENTIFIER,
+	ALISP_INTEGER,
+	ALISP_FLOAT,
+	ALISP_FLOATE,
+	ALISP_STRING
+};
+
+enum alisp_objects {
+	ALISP_OBJ_INTEGER,
+	ALISP_OBJ_FLOAT,
+	ALISP_OBJ_IDENTIFIER,
+	ALISP_OBJ_STRING,
+	ALISP_OBJ_POINTER,
+	ALISP_OBJ_CONS,
+	ALISP_OBJ_LAST_SEARCH = ALISP_OBJ_CONS,
+	ALISP_OBJ_NIL,
+	ALISP_OBJ_T,
+};
+
+struct alisp_object;
+
+#define ALISP_TYPE_MASK	0xf0000000
+#define ALISP_TYPE_SHIFT 28
+#define ALISP_REFS_MASK 0x0fffffff
+#define ALISP_REFS_SHIFT 0
+#define ALISP_MAX_REFS (ALISP_REFS_MASK>>ALISP_REFS_SHIFT)
+#define ALISP_MAX_REFS_LIMIT ((ALISP_MAX_REFS + 1) / 2)
+
+struct alisp_object {
+	struct list_head list;
+	unsigned int	type_refs;	/* type and count of references */
+	union {
+		char	*s;
+		long	i;
+		double	f;
+		const void *ptr;
+		struct {
+			struct alisp_object *car;
+			struct alisp_object *cdr;
+		} c;
+	} value;
+};
+
+static inline enum alisp_objects alisp_get_type(struct alisp_object *p)
+{
+	return (p->type_refs >> ALISP_TYPE_SHIFT);
+}
+
+static inline void alisp_set_type(struct alisp_object *p, enum alisp_objects type)
+{
+	p->type_refs &= ~ALISP_TYPE_MASK;
+	p->type_refs |= (unsigned int)type << ALISP_TYPE_SHIFT;
+}
+
+static inline int alisp_compare_type(struct alisp_object *p, enum alisp_objects type)
+{
+	return ((unsigned int)type << ALISP_TYPE_SHIFT) ==
+	       (p->type_refs & ALISP_TYPE_MASK);
+}
+
+static inline void alisp_set_refs(struct alisp_object *p, unsigned int refs)
+{
+	p->type_refs &= ~ALISP_REFS_MASK;
+	p->type_refs |= refs & ALISP_REFS_MASK;
+}
+
+static inline unsigned int alisp_get_refs(struct alisp_object *p)
+{
+	return p->type_refs & ALISP_REFS_MASK;
+}
+
+static inline unsigned int alisp_inc_refs(struct alisp_object *p)
+{
+	unsigned r = alisp_get_refs(p) + 1;
+	alisp_set_refs(p, r);
+	return r;
+}
+
+static inline unsigned int alisp_dec_refs(struct alisp_object *p)
+{
+	unsigned r = alisp_get_refs(p) - 1;
+	alisp_set_refs(p, r);
+	return r;
+}
+
+struct alisp_object_pair {
+	struct list_head list;
+	const char *name;
+ 	struct alisp_object *value;
+};
+
+#define ALISP_LEX_BUF_MAX	16
+#define ALISP_OBJ_PAIR_HASH_SHIFT 4
+#define ALISP_OBJ_PAIR_HASH_SIZE (1<<ALISP_OBJ_PAIR_HASH_SHIFT)
+#define ALISP_OBJ_PAIR_HASH_MASK (ALISP_OBJ_PAIR_HASH_SIZE-1)
+#define ALISP_FREE_OBJ_POOL	512	/* free objects above this pool */
+
+struct alisp_instance {
+	int verbose: 1,
+	    warning: 1,
+	    debug: 1;
+	/* i/o */
+	snd_input_t *in;
+	snd_output_t *out;
+	snd_output_t *eout;	/* error output */
+	snd_output_t *vout;	/* verbose output */
+	snd_output_t *wout;	/* warning output */
+	snd_output_t *dout;	/* debug output */
+	/* lexer */
+	int charno;
+	int lineno;
+	int lex_buf[ALISP_LEX_BUF_MAX];
+	int *lex_bufp;
+	char *token_buffer;
+	int token_buffer_max;
+	int thistoken;
+	/* object allocator / storage */
+	long free_objs;
+	long used_objs;
+	long max_objs;
+	struct list_head free_objs_list;
+	struct list_head used_objs_list[ALISP_OBJ_PAIR_HASH_SIZE][ALISP_OBJ_LAST_SEARCH + 1];
+	/* set object */
+	struct list_head setobjs_list[ALISP_OBJ_PAIR_HASH_SIZE];
+};
diff --git a/src/alisp/alisp_snd.c b/src/alisp/alisp_snd.c
new file mode 100644
index 0000000..de429d9
--- /dev/null
+++ b/src/alisp/alisp_snd.c
@@ -0,0 +1,943 @@
+/*
+ *  ALSA lisp implementation - sound related commands
+ *  Copyright (c) 2003 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+struct acall_table {
+	const char *name;
+	struct alisp_object * (*func) (struct alisp_instance *instance, struct acall_table * item, struct alisp_object * args);
+	void * xfunc;
+	const char *prefix;
+};
+
+/*
+ *  helper functions
+ */
+
+static inline int get_integer(struct alisp_object * obj)
+{
+	if (alisp_compare_type(obj, ALISP_OBJ_INTEGER))
+		return obj->value.i;
+	return 0;
+}
+
+static inline const void *get_pointer(struct alisp_object * obj)
+{
+	if (alisp_compare_type(obj, ALISP_OBJ_POINTER))
+		return obj->value.ptr;
+	return NULL;
+}
+
+static const char *get_string(struct alisp_object * obj, const char * deflt)
+{
+	if (obj == &alsa_lisp_t)
+		return "true";
+	if (alisp_compare_type(obj, ALISP_OBJ_STRING) ||
+	    alisp_compare_type(obj, ALISP_OBJ_IDENTIFIER))
+		return obj->value.s;
+	return deflt;
+}
+
+struct flags {
+	const char *key;
+	unsigned int mask;
+}; 
+
+static unsigned int get_flags(struct alisp_instance * instance,
+			      struct alisp_object * obj,
+			      const struct flags * flags,
+			      unsigned int deflt)
+{
+	const char *key;
+	int invert;
+	unsigned int result;
+	const struct flags *ptr;
+	struct alisp_object *n;
+
+	if (obj == &alsa_lisp_nil)
+		return deflt;
+	result = deflt;
+	do {
+		key = get_string(obj, NULL);
+		if (key) {
+			invert = key[0] == '!';
+			key += invert;
+			ptr = flags;
+			while (ptr->key) {
+				if (!strcmp(ptr->key, key)) {
+					if (invert)
+						result &= ~ptr->mask;
+					else
+						result |= ptr->mask;
+					break;
+				}
+				ptr++;
+			}
+		}
+		delete_tree(instance, car(obj));
+		obj = cdr(n = obj);
+		delete_object(instance, n);
+	} while (obj != &alsa_lisp_nil);
+	return result;
+}
+
+static const void *get_ptr(struct alisp_instance * instance,
+			   struct alisp_object * obj,
+			   const char *_ptr_id)
+{
+	const char *ptr_id;
+	const void *ptr;
+	
+	ptr_id = get_string(car(obj), NULL);
+	if (ptr_id == NULL) {
+		delete_tree(instance, obj);
+		return NULL;
+	}
+	if (strcmp(ptr_id, _ptr_id)) {
+		delete_tree(instance, obj);
+		return NULL;
+	}
+	ptr = get_pointer(cdr(obj));
+	delete_tree(instance, obj);
+	return ptr;
+}
+
+static struct alisp_object * new_lexpr(struct alisp_instance * instance, int err)
+{
+	struct alisp_object * lexpr;
+
+	lexpr = new_object(instance, ALISP_OBJ_CONS);
+	if (lexpr == NULL)
+		return NULL;
+	lexpr->value.c.car = new_integer(instance, err);
+	if (lexpr->value.c.car == NULL) {
+		delete_object(instance, lexpr);
+		return NULL;
+	}
+	lexpr->value.c.cdr = new_object(instance, ALISP_OBJ_CONS);
+	if (lexpr->value.c.cdr == NULL) {
+		delete_object(instance, lexpr->value.c.car);
+		delete_object(instance, lexpr);
+		return NULL;
+	}
+	return lexpr;
+}
+
+static struct alisp_object * add_cons(struct alisp_instance * instance,
+				      struct alisp_object *lexpr,
+				      int cdr, const char *id,
+				      struct alisp_object *obj)
+{
+	struct alisp_object * p1, * p2;
+
+	if (lexpr == NULL || obj == NULL) {
+		delete_tree(instance, obj);
+		return NULL;
+	}
+	if (cdr) {
+		p1 = lexpr->value.c.cdr = new_object(instance, ALISP_OBJ_CONS);
+	} else {
+		p1 = lexpr->value.c.car = new_object(instance, ALISP_OBJ_CONS);
+	}
+	lexpr = p1;
+	if (p1 == NULL) {
+		delete_tree(instance, obj);
+		return NULL;
+	}
+	p1->value.c.car = new_object(instance, ALISP_OBJ_CONS);
+	if ((p2 = p1->value.c.car) == NULL)
+		goto __err;
+	p2->value.c.car = new_string(instance, id);
+	if (p2->value.c.car == NULL) {
+	      __err:
+		if (cdr)
+			lexpr->value.c.cdr = NULL;
+		else
+			lexpr->value.c.car = NULL;
+		delete_tree(instance, p1);
+		delete_tree(instance, obj);
+		return NULL;
+	}
+	p2->value.c.cdr = obj;
+	return lexpr;
+}
+
+static struct alisp_object * add_cons2(struct alisp_instance * instance,
+				       struct alisp_object *lexpr,
+				       int cdr, struct alisp_object *obj)
+{
+	struct alisp_object * p1;
+
+	if (lexpr == NULL || obj == NULL) {
+		delete_tree(instance, obj);
+		return NULL;
+	}
+	if (cdr) {
+		p1 = lexpr->value.c.cdr = new_object(instance, ALISP_OBJ_CONS);
+	} else {
+		p1 = lexpr->value.c.car = new_object(instance, ALISP_OBJ_CONS);
+	}
+	lexpr = p1;
+	if (p1 == NULL) {
+		delete_tree(instance, obj);
+		return NULL;
+	}
+	p1->value.c.car = obj;
+	return lexpr;
+}
+
+static struct alisp_object * new_result1(struct alisp_instance * instance,
+					 int err, const char *ptr_id, void *ptr)
+{
+	struct alisp_object * lexpr, * p1;
+
+	if (err < 0)
+		ptr = NULL;
+	lexpr = new_object(instance, ALISP_OBJ_CONS);
+	if (lexpr == NULL)
+		return NULL;
+	lexpr->value.c.car = new_integer(instance, err);
+	if (lexpr->value.c.car == NULL) {
+		delete_object(instance, lexpr);
+		return NULL;
+	}
+	p1 = add_cons(instance, lexpr, 1, ptr_id, new_pointer(instance, ptr));
+	if (p1 == NULL) {
+		delete_object(instance, lexpr);
+		return NULL;
+	}
+	return lexpr;
+}
+
+static struct alisp_object * new_result2(struct alisp_instance * instance,
+					 int err, int val)
+{
+	struct alisp_object * lexpr, * p1;
+
+	if (err < 0)
+		val = 0;
+	lexpr = new_lexpr(instance, err);
+	if (lexpr == NULL)
+		return NULL;
+	p1 = lexpr->value.c.cdr;
+	p1->value.c.car = new_integer(instance, val);
+	if (p1->value.c.car == NULL) {
+		delete_object(instance, lexpr);
+		return NULL;
+	}
+	return lexpr;
+}
+
+static struct alisp_object * new_result3(struct alisp_instance * instance,
+					 int err, const char *str)
+{
+	struct alisp_object * lexpr, * p1;
+
+	if (err < 0)
+		str = "";
+	lexpr = new_lexpr(instance, err);
+	if (lexpr == NULL)
+		return NULL;
+	p1 = lexpr->value.c.cdr;
+	p1->value.c.car = new_string(instance, str);
+	if (p1->value.c.car == NULL) {
+		delete_object(instance, lexpr);
+		return NULL;
+	}
+	return lexpr;
+}
+
+/*
+ *  macros
+ */
+
+/*
+ *  HCTL functions
+ */
+
+typedef int (*snd_int_pp_strp_int_t)(void **rctl, const char *name, int mode);
+typedef int (*snd_int_pp_p_t)(void **rctl, void *handle);
+typedef int (*snd_int_p_t)(void *rctl);
+typedef char * (*snd_str_p_t)(void *rctl);
+typedef int (*snd_int_intp_t)(int *val);
+typedef int (*snd_int_str_t)(const char *str);
+typedef int (*snd_int_int_strp_t)(int val, char **str);
+typedef void *(*snd_p_p_t)(void *handle);
+
+static struct alisp_object * FA_int_pp_strp_int(struct alisp_instance * instance, struct acall_table * item, struct alisp_object * args)
+{
+	const char *name;
+	int err, mode;
+	void *handle;
+	struct alisp_object *p1, *p2;
+	static const struct flags flags[] = {
+		{ "nonblock", SND_CTL_NONBLOCK },
+		{ "async", SND_CTL_ASYNC },
+		{ "readonly", SND_CTL_READONLY },
+		{ NULL, 0 }
+	};
+
+	name = get_string(p1 = eval(instance, car(args)), NULL);
+	if (name == NULL)
+		return &alsa_lisp_nil;
+	mode = get_flags(instance, p2 = eval(instance, car(cdr(args))), flags, 0);
+	delete_tree(instance, cdr(cdr(args)));
+	delete_object(instance, cdr(args));
+	delete_object(instance, args);
+	delete_tree(instance, p2);
+	err = ((snd_int_pp_strp_int_t)item->xfunc)(&handle, name, mode);
+	delete_tree(instance, p1);
+	return new_result1(instance, err, item->prefix, handle);
+}
+
+static struct alisp_object * FA_int_pp_p(struct alisp_instance * instance, struct acall_table * item, struct alisp_object * args)
+{
+	int err;
+	void *handle;
+	const char *prefix1;
+	struct alisp_object *p1;
+
+	if (item->xfunc == &snd_hctl_open_ctl)
+		prefix1 = "ctl";
+	else {
+		delete_tree(instance, args);
+		return &alsa_lisp_nil;
+	}
+	p1 = eval(instance, car(args));
+	delete_tree(instance, cdr(args));
+	delete_object(instance, args);
+	handle = (void *)get_ptr(instance, p1, prefix1);
+	if (handle == NULL)
+		return &alsa_lisp_nil;
+	err = ((snd_int_pp_p_t)item->xfunc)(&handle, handle);
+	return new_result1(instance, err, item->prefix, handle);
+}
+
+static struct alisp_object * FA_p_p(struct alisp_instance * instance, struct acall_table * item, struct alisp_object * args)
+{
+	void *handle;
+	const char *prefix1;
+	struct alisp_object * p1;
+
+	if (item->xfunc == &snd_hctl_first_elem ||
+	    item->xfunc == &snd_hctl_last_elem ||
+	    item->xfunc == &snd_hctl_elem_next ||
+	    item->xfunc == &snd_hctl_elem_prev)
+		prefix1 = "hctl_elem";
+	else if (item->xfunc == &snd_hctl_ctl)
+		prefix1 = "ctl";
+	else {
+		delete_tree(instance, args);
+		return &alsa_lisp_nil;
+	}
+	p1 = eval(instance, car(args));
+	delete_tree(instance, cdr(args));
+	delete_object(instance, args);
+	handle = (void *)get_ptr(instance, p1, item->prefix);
+	if (handle == NULL)
+		return &alsa_lisp_nil;
+	handle = ((snd_p_p_t)item->xfunc)(handle);
+	return new_cons_pointer(instance, prefix1, handle);
+}
+
+static struct alisp_object * FA_int_p(struct alisp_instance * instance, struct acall_table * item, struct alisp_object * args)
+{
+	void *handle;
+	struct alisp_object * p1;
+
+	p1 = eval(instance, car(args));
+	delete_tree(instance, cdr(args));
+	delete_object(instance, args);
+	handle = (void *)get_ptr(instance, p1, item->prefix);
+	if (handle == NULL)
+		return &alsa_lisp_nil;
+	return new_integer(instance, ((snd_int_p_t)item->xfunc)(handle));
+}
+
+static struct alisp_object * FA_str_p(struct alisp_instance * instance, struct acall_table * item, struct alisp_object * args)
+{
+	void *handle;
+	struct alisp_object * p1;
+
+	p1 = eval(instance, car(args));
+	delete_tree(instance, cdr(args));
+	delete_object(instance, args);
+	handle = (void *)get_ptr(instance, p1, item->prefix);
+	if (handle == NULL)
+		return &alsa_lisp_nil;
+	return new_string(instance, ((snd_str_p_t)item->xfunc)(handle));
+}
+
+static struct alisp_object * FA_int_intp(struct alisp_instance * instance, struct acall_table * item, struct alisp_object * args)
+{
+	int val, err;
+	struct alisp_object * p1;
+
+	p1 = eval(instance, car(args));
+	delete_tree(instance, cdr(args));
+	delete_object(instance, args);
+	if (!alisp_compare_type(p1, ALISP_OBJ_INTEGER)) {
+		delete_tree(instance, p1);
+		return &alsa_lisp_nil;
+	}
+	val = p1->value.i;
+	delete_tree(instance, p1);
+	err = ((snd_int_intp_t)item->xfunc)(&val);
+	return new_result2(instance, err, val);
+}
+
+static struct alisp_object * FA_int_str(struct alisp_instance * instance, struct acall_table * item, struct alisp_object * args)
+{
+	int err;
+	struct alisp_object * p1;
+
+	p1 = eval(instance, car(args));
+	delete_tree(instance, cdr(args));
+	delete_object(instance, args);
+	if (!alisp_compare_type(p1, ALISP_OBJ_STRING) &&
+	    !alisp_compare_type(p1, ALISP_OBJ_IDENTIFIER)) {
+		delete_tree(instance, p1);
+		return &alsa_lisp_nil;
+	}
+	err = ((snd_int_str_t)item->xfunc)(p1->value.s);
+	delete_tree(instance, p1);
+	return new_integer(instance, err);
+}
+
+static struct alisp_object * FA_int_int_strp(struct alisp_instance * instance, struct acall_table * item, struct alisp_object * args)
+{
+	int err;
+	char *str;
+	long val;
+	struct alisp_object * p1;
+
+	p1 = eval(instance, car(args));
+	delete_tree(instance, cdr(args));
+	delete_object(instance, args);
+	if (!alisp_compare_type(p1, ALISP_OBJ_INTEGER)) {
+		delete_tree(instance, p1);
+		return &alsa_lisp_nil;
+	}
+	val = p1->value.i;
+	delete_tree(instance, p1);
+	err = ((snd_int_int_strp_t)item->xfunc)(val, &str);
+	return new_result3(instance, err, str);
+}
+
+static struct alisp_object * FA_card_info(struct alisp_instance * instance, struct acall_table * item, struct alisp_object * args)
+{
+	snd_ctl_t *handle;
+	struct alisp_object * lexpr, * p1;
+	snd_ctl_card_info_t * info;
+	int err;
+
+	p1 = eval(instance, car(args));
+	delete_tree(instance, cdr(args));
+	delete_object(instance, args);
+	handle = (snd_ctl_t *)get_ptr(instance, p1, item->prefix);
+	if (handle == NULL)
+		return &alsa_lisp_nil;
+	snd_ctl_card_info_alloca(&info);
+	err = snd_ctl_card_info(handle, info);
+	lexpr = new_lexpr(instance, err);
+	if (err < 0)
+		return lexpr;
+	p1 = add_cons(instance, lexpr->value.c.cdr, 0, "id", new_string(instance, snd_ctl_card_info_get_id(info)));
+	p1 = add_cons(instance, p1, 1, "driver", new_string(instance, snd_ctl_card_info_get_driver(info)));
+	p1 = add_cons(instance, p1, 1, "name", new_string(instance, snd_ctl_card_info_get_name(info)));
+	p1 = add_cons(instance, p1, 1, "longname", new_string(instance, snd_ctl_card_info_get_longname(info)));
+	p1 = add_cons(instance, p1, 1, "mixername", new_string(instance, snd_ctl_card_info_get_mixername(info)));
+	p1 = add_cons(instance, p1, 1, "components", new_string(instance, snd_ctl_card_info_get_components(info)));
+	if (p1 == NULL) {
+		delete_tree(instance, lexpr);
+		return NULL;
+	}
+	return lexpr;
+}
+
+static struct alisp_object * create_ctl_elem_id(struct alisp_instance * instance, snd_ctl_elem_id_t * id, struct alisp_object * cons)
+{
+	cons = add_cons(instance, cons, 0, "numid", new_integer(instance, snd_ctl_elem_id_get_numid(id)));
+	cons = add_cons(instance, cons, 1, "iface", new_string(instance, snd_ctl_elem_iface_name(snd_ctl_elem_id_get_interface(id))));
+	cons = add_cons(instance, cons, 1, "dev", new_integer(instance, snd_ctl_elem_id_get_device(id)));
+	cons = add_cons(instance, cons, 1, "subdev", new_integer(instance, snd_ctl_elem_id_get_subdevice(id)));
+	cons = add_cons(instance, cons, 1, "name", new_string(instance, snd_ctl_elem_id_get_name(id)));
+	cons = add_cons(instance, cons, 1, "index", new_integer(instance, snd_ctl_elem_id_get_index(id)));
+	return cons;
+}
+
+static int parse_ctl_elem_id(struct alisp_instance * instance,
+			     struct alisp_object * cons,
+			     snd_ctl_elem_id_t * id)
+{
+	struct alisp_object *p1;
+	const char *xid;
+
+	if (cons == NULL)
+		return -ENOMEM;
+	snd_ctl_elem_id_clear(id);
+	id->numid = 0;
+	do {
+		p1 = car(cons);
+		if (alisp_compare_type(p1, ALISP_OBJ_CONS)) {
+			xid = get_string(p1->value.c.car, NULL);
+			if (xid == NULL) {
+				/* noop */
+			} else if (!strcmp(xid, "numid")) {
+				snd_ctl_elem_id_set_numid(id, get_integer(p1->value.c.cdr));
+			} else if (!strcmp(xid, "iface")) {
+				snd_ctl_elem_id_set_interface(id, snd_config_get_ctl_iface_ascii(get_string(p1->value.c.cdr, "0")));
+			} else if (!strcmp(xid, "dev")) {
+				snd_ctl_elem_id_set_device(id, get_integer(p1->value.c.cdr));
+			} else if (!strcmp(xid, "subdev")) {
+				snd_ctl_elem_id_set_subdevice(id, get_integer(p1->value.c.cdr));
+			} else if (!strcmp(xid, "name")) {
+				snd_ctl_elem_id_set_name(id, get_string(p1->value.c.cdr, "?"));
+			} else if (!strcmp(xid, "index")) {
+				snd_ctl_elem_id_set_index(id, get_integer(p1->value.c.cdr));
+			}
+		}
+		delete_tree(instance, p1);
+	        cons = cdr(p1 = cons);
+	        delete_object(instance, p1);
+	} while (cons != &alsa_lisp_nil);
+	return 0;
+}
+
+static struct alisp_object * FA_hctl_find_elem(struct alisp_instance * instance, struct acall_table * item, struct alisp_object * args)
+{
+	snd_hctl_t *handle;
+	snd_ctl_elem_id_t *id;
+	struct alisp_object *p1;
+
+	handle = (snd_hctl_t *)get_ptr(instance, car(args), item->prefix);
+	if (handle == NULL) {
+		delete_tree(instance, cdr(args));
+		delete_object(instance, args);
+		return &alsa_lisp_nil;
+	}
+	snd_ctl_elem_id_alloca(&id);
+	p1 = car(cdr(args));
+	delete_tree(instance, cdr(cdr(args)));
+	delete_object(instance, cdr(args));
+	delete_object(instance, args);
+	if (parse_ctl_elem_id(instance, eval(instance, p1), id) < 0)
+		return &alsa_lisp_nil;
+	return new_cons_pointer(instance, "hctl_elem", snd_hctl_find_elem(handle, id));
+}
+
+static struct alisp_object * FA_hctl_elem_info(struct alisp_instance * instance, struct acall_table * item, struct alisp_object * args)
+{
+	snd_hctl_elem_t *handle;
+	struct alisp_object * lexpr, * p1, * p2;
+	snd_ctl_elem_info_t *info;
+	snd_ctl_elem_id_t *id;
+	snd_ctl_elem_type_t type;
+	int err;
+
+	p1 = eval(instance, car(args));
+	delete_tree(instance, cdr(args));
+	delete_object(instance, args);
+	handle = (snd_hctl_elem_t *)get_ptr(instance, p1, item->prefix);
+	if (handle == NULL)
+		return &alsa_lisp_nil;
+	snd_ctl_elem_info_alloca(&info);
+	snd_ctl_elem_id_alloca(&id);
+	err = snd_hctl_elem_info(handle, info);
+	lexpr = new_lexpr(instance, err);
+	if (err < 0)
+		return lexpr;
+	type = snd_ctl_elem_info_get_type(info);
+	p1 = add_cons(instance, lexpr->value.c.cdr, 0, "id", p2 = new_object(instance, ALISP_OBJ_CONS));
+	snd_ctl_elem_info_get_id(info, id);
+	if (create_ctl_elem_id(instance, id, p2) == NULL) {
+		delete_tree(instance, lexpr);
+		return NULL;
+	}
+	p1 = add_cons(instance, p1, 1, "type", new_string(instance, snd_ctl_elem_type_name(type)));
+	p1 = add_cons(instance, p1, 1, "readable", new_integer(instance, snd_ctl_elem_info_is_readable(info)));
+	p1 = add_cons(instance, p1, 1, "writeable", new_integer(instance, snd_ctl_elem_info_is_writable(info)));
+	p1 = add_cons(instance, p1, 1, "volatile", new_integer(instance, snd_ctl_elem_info_is_volatile(info)));
+	p1 = add_cons(instance, p1, 1, "inactive", new_integer(instance, snd_ctl_elem_info_is_inactive(info)));
+	p1 = add_cons(instance, p1, 1, "locked", new_integer(instance, snd_ctl_elem_info_is_locked(info)));
+	p1 = add_cons(instance, p1, 1, "isowner", new_integer(instance, snd_ctl_elem_info_is_owner(info)));
+	p1 = add_cons(instance, p1, 1, "owner", new_integer(instance, snd_ctl_elem_info_get_owner(info)));
+	p1 = add_cons(instance, p1, 1, "count", new_integer(instance, snd_ctl_elem_info_get_count(info)));
+	err = snd_ctl_elem_info_get_dimensions(info);
+	if (err > 0) {
+		int idx;
+		p1 = add_cons(instance, p1, 1, "dimensions", p2 = new_object(instance, ALISP_OBJ_CONS));
+		for (idx = 0; idx < err; idx++)
+			p2 = add_cons2(instance, p2, idx > 0, new_integer(instance, snd_ctl_elem_info_get_dimension(info, idx)));
+	}
+	switch (type) {
+	case SND_CTL_ELEM_TYPE_ENUMERATED: {
+		unsigned int items, item;
+		items = snd_ctl_elem_info_get_items(info);
+		p1 = add_cons(instance, p1, 1, "items", p2 = new_object(instance, ALISP_OBJ_CONS));
+		for (item = 0; item < items; item++) {
+			snd_ctl_elem_info_set_item(info, item);
+			err = snd_hctl_elem_info(handle, info);
+			if (err < 0) {
+				p2 = add_cons2(instance, p2, item, &alsa_lisp_nil);
+			} else {
+				p2 = add_cons2(instance, p2, item, new_string(instance, snd_ctl_elem_info_get_item_name(info)));
+			}
+		}
+		break;
+	}
+	case SND_CTL_ELEM_TYPE_INTEGER:
+		p1 = add_cons(instance, p1, 1, "min", new_integer(instance, snd_ctl_elem_info_get_min(info)));
+		p1 = add_cons(instance, p1, 1, "max", new_integer(instance, snd_ctl_elem_info_get_max(info)));
+		p1 = add_cons(instance, p1, 1, "step", new_integer(instance, snd_ctl_elem_info_get_step(info)));
+		break;
+	case SND_CTL_ELEM_TYPE_INTEGER64:
+		p1 = add_cons(instance, p1, 1, "min64", new_float(instance, snd_ctl_elem_info_get_min64(info)));
+		p1 = add_cons(instance, p1, 1, "max64", new_float(instance, snd_ctl_elem_info_get_max64(info)));
+		p1 = add_cons(instance, p1, 1, "step64", new_float(instance, snd_ctl_elem_info_get_step64(info)));
+		break;
+	default:
+		break;
+	}
+	if (p1 == NULL) {
+		delete_tree(instance, lexpr);
+		return NULL;
+	}
+	return lexpr;
+}
+
+static struct alisp_object * FA_hctl_elem_read(struct alisp_instance * instance, struct acall_table * item, struct alisp_object * args)
+{
+	snd_hctl_elem_t *handle;
+	struct alisp_object * lexpr, * p1 = NULL, * obj;
+	snd_ctl_elem_info_t *info;
+	snd_ctl_elem_value_t *value;
+	snd_ctl_elem_type_t type;
+	unsigned int idx, count;
+	int err;
+
+	p1 = eval(instance, car(args));
+	delete_tree(instance, cdr(args));
+	delete_object(instance, args);
+	handle = (snd_hctl_elem_t *)get_ptr(instance, p1, item->prefix);
+	if (handle == NULL)
+		return &alsa_lisp_nil;
+	snd_ctl_elem_info_alloca(&info);
+	snd_ctl_elem_value_alloca(&value);
+	err = snd_hctl_elem_info(handle, info);
+	if (err >= 0)
+		err = snd_hctl_elem_read(handle, value);
+	lexpr = new_lexpr(instance, err);
+	if (err < 0)
+		return lexpr;
+	type = snd_ctl_elem_info_get_type(info);
+	count = snd_ctl_elem_info_get_count(info);
+	if (type == SND_CTL_ELEM_TYPE_IEC958) {
+		count = sizeof(snd_aes_iec958_t);
+		type = SND_CTL_ELEM_TYPE_BYTES;
+	}
+	for (idx = 0; idx < count; idx++) {
+		switch (type) {
+		case SND_CTL_ELEM_TYPE_BOOLEAN:
+			obj = new_integer(instance, snd_ctl_elem_value_get_boolean(value, idx));
+			break;
+		case SND_CTL_ELEM_TYPE_INTEGER:
+			obj = new_integer(instance, snd_ctl_elem_value_get_integer(value, idx));
+			break;
+		case SND_CTL_ELEM_TYPE_INTEGER64:
+			obj = new_integer(instance, snd_ctl_elem_value_get_integer64(value, idx));
+			break;
+		case SND_CTL_ELEM_TYPE_ENUMERATED:
+			obj = new_integer(instance, snd_ctl_elem_value_get_enumerated(value, idx));
+			break;
+		case SND_CTL_ELEM_TYPE_BYTES:
+			obj = new_integer(instance, snd_ctl_elem_value_get_byte(value, idx));
+			break;
+		default:
+			obj = NULL;
+			break;
+		}
+		if (idx == 0) {
+			p1 = add_cons2(instance, lexpr->value.c.cdr, 0, obj);
+		} else {
+			p1 = add_cons2(instance, p1, 1, obj);
+		}
+	}
+	if (p1 == NULL) {
+		delete_tree(instance, lexpr);
+		return &alsa_lisp_nil;
+	}
+	return lexpr;
+}
+
+static struct alisp_object * FA_hctl_elem_write(struct alisp_instance * instance, struct acall_table * item, struct alisp_object * args)
+{
+	snd_hctl_elem_t *handle;
+	struct alisp_object * p1 = NULL, * obj;
+	snd_ctl_elem_info_t *info;
+	snd_ctl_elem_value_t *value;
+	snd_ctl_elem_type_t type;
+	unsigned int idx, count;
+	int err;
+
+	p1 = car(cdr(args));
+	obj = eval(instance, car(args));
+	delete_tree(instance, cdr(cdr(args)));
+	delete_object(instance, cdr(args));
+	delete_object(instance, args);
+	handle = (snd_hctl_elem_t *)get_ptr(instance, obj, item->prefix);
+	if (handle == NULL) {
+		delete_tree(instance, p1);
+		return &alsa_lisp_nil;
+	}
+	snd_ctl_elem_info_alloca(&info);
+	snd_ctl_elem_value_alloca(&value);
+	err = snd_hctl_elem_info(handle, info);
+	if (err < 0) {
+		delete_tree(instance, p1);
+		return new_integer(instance, err);
+	}
+	type = snd_ctl_elem_info_get_type(info);
+	count = snd_ctl_elem_info_get_count(info);
+	if (type == SND_CTL_ELEM_TYPE_IEC958) {
+		count = sizeof(snd_aes_iec958_t);
+		type = SND_CTL_ELEM_TYPE_BYTES;
+	}
+	idx = -1;
+	do {
+		if (++idx >= count) {
+			delete_tree(instance, p1);
+			break;
+		}
+		obj = car(p1);
+		switch (type) {
+		case SND_CTL_ELEM_TYPE_BOOLEAN:
+			snd_ctl_elem_value_set_boolean(value, idx, get_integer(obj));
+			break;
+		case SND_CTL_ELEM_TYPE_INTEGER:
+			snd_ctl_elem_value_set_integer(value, idx, get_integer(obj));
+			break;
+		case SND_CTL_ELEM_TYPE_INTEGER64:
+			snd_ctl_elem_value_set_integer64(value, idx, get_integer(obj));
+			break;
+		case SND_CTL_ELEM_TYPE_ENUMERATED:
+			snd_ctl_elem_value_set_enumerated(value, idx, get_integer(obj));
+			break;
+		case SND_CTL_ELEM_TYPE_BYTES:
+			snd_ctl_elem_value_set_byte(value, idx, get_integer(obj));
+			break;
+		default:
+			break;
+		}
+		delete_tree(instance, obj);
+		p1 = cdr(obj = p1);
+		delete_object(instance, obj);
+	} while (p1 != &alsa_lisp_nil);
+	err = snd_hctl_elem_write(handle, value);
+	return new_integer(instance, err);
+}
+
+static struct alisp_object * FA_pcm_info(struct alisp_instance * instance, struct acall_table * item, struct alisp_object * args)
+{
+	snd_pcm_t *handle;
+	struct alisp_object * lexpr, * p1;
+	snd_pcm_info_t *info;
+	int err;
+
+	p1 = eval(instance, car(args));
+	delete_tree(instance, cdr(args));
+	delete_object(instance, args);
+	handle = (snd_pcm_t *)get_ptr(instance, p1, item->prefix);
+	if (handle == NULL)
+		return &alsa_lisp_nil;
+	snd_pcm_info_alloca(&info);
+	err = snd_pcm_info(handle, info);
+	lexpr = new_lexpr(instance, err);
+	if (err < 0)
+		return lexpr;
+	p1 = add_cons(instance, lexpr->value.c.cdr, 0, "card", new_integer(instance, snd_pcm_info_get_card(info)));
+	p1 = add_cons(instance, p1, 1, "device", new_integer(instance, snd_pcm_info_get_device(info)));
+	p1 = add_cons(instance, p1, 1, "subdevice", new_integer(instance, snd_pcm_info_get_subdevice(info)));
+	p1 = add_cons(instance, p1, 1, "id", new_string(instance, snd_pcm_info_get_id(info)));
+	p1 = add_cons(instance, p1, 1, "name", new_string(instance, snd_pcm_info_get_name(info)));
+	p1 = add_cons(instance, p1, 1, "subdevice_name", new_string(instance, snd_pcm_info_get_subdevice_name(info)));
+	p1 = add_cons(instance, p1, 1, "class", new_integer(instance, snd_pcm_info_get_class(info)));
+	p1 = add_cons(instance, p1, 1, "subclass", new_integer(instance, snd_pcm_info_get_subclass(info)));
+	p1 = add_cons(instance, p1, 1, "subdevices_count", new_integer(instance, snd_pcm_info_get_subdevices_count(info)));
+	p1 = add_cons(instance, p1, 1, "subdevices_avail", new_integer(instance, snd_pcm_info_get_subdevices_avail(info)));
+	//p1 = add_cons(instance, p1, 1, "sync", new_string(instance, snd_pcm_info_get_sync(info)));
+	return lexpr;
+}
+
+/*
+ *  main code
+ */
+
+static const struct acall_table acall_table[] = {
+	{ "card_get_index", &FA_int_str, (void *)snd_card_get_index, NULL },
+	{ "card_get_longname", &FA_int_int_strp, (void *)snd_card_get_longname, NULL },
+	{ "card_get_name", &FA_int_int_strp, (void *)snd_card_get_name, NULL },
+	{ "card_next", &FA_int_intp, (void *)&snd_card_next, NULL },
+	{ "ctl_card_info", &FA_card_info, NULL, "ctl" },
+	{ "ctl_close", &FA_int_p, (void *)&snd_ctl_close, "ctl" },
+	{ "ctl_open", &FA_int_pp_strp_int, (void *)&snd_ctl_open, "ctl" },
+	{ "hctl_close", &FA_int_p, (void *)&snd_hctl_close, "hctl" },
+	{ "hctl_ctl", &FA_p_p, (void *)&snd_hctl_ctl, "hctl" },
+	{ "hctl_elem_info", &FA_hctl_elem_info, (void *)&snd_hctl_elem_info, "hctl_elem" },
+	{ "hctl_elem_next", &FA_p_p, (void *)&snd_hctl_elem_next, "hctl_elem" },
+	{ "hctl_elem_prev", &FA_p_p, (void *)&snd_hctl_elem_prev, "hctl_elem" },
+	{ "hctl_elem_read", &FA_hctl_elem_read, (void *)&snd_hctl_elem_read, "hctl_elem" },
+	{ "hctl_elem_write", &FA_hctl_elem_write, (void *)&snd_hctl_elem_write, "hctl_elem" },
+	{ "hctl_find_elem", &FA_hctl_find_elem, (void *)&snd_hctl_find_elem, "hctl" },
+	{ "hctl_first_elem", &FA_p_p, (void *)&snd_hctl_first_elem, "hctl" },
+	{ "hctl_free", &FA_int_p, (void *)&snd_hctl_free, "hctl" },
+	{ "hctl_last_elem", &FA_p_p, (void *)&snd_hctl_last_elem, "hctl" },
+	{ "hctl_load", &FA_int_p, (void *)&snd_hctl_load, "hctl" },
+	{ "hctl_open", &FA_int_pp_strp_int, (void *)&snd_hctl_open, "hctl" },
+	{ "hctl_open_ctl", &FA_int_pp_p, (void *)&snd_hctl_open_ctl, "hctl" },
+	{ "pcm_info", &FA_pcm_info, NULL, "pcm" },
+	{ "pcm_name", &FA_str_p, (void *)&snd_pcm_name, "pcm" },
+};
+
+static int acall_compar(const void *p1, const void *p2)
+{
+	return strcmp(((struct acall_table *)p1)->name,
+        	      ((struct acall_table *)p2)->name);
+}
+
+static struct alisp_object * F_acall(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p1, *p2;
+	struct acall_table key, *item;
+
+	p1 = eval(instance, car(args));
+	p2 = cdr(args);
+	delete_object(instance, args);
+	if (!alisp_compare_type(p1, ALISP_OBJ_IDENTIFIER) &&
+	    !alisp_compare_type(p1, ALISP_OBJ_STRING)) {
+	    	delete_tree(instance, p2);
+		return &alsa_lisp_nil;
+	}
+	key.name = p1->value.s;
+	if ((item = bsearch(&key, acall_table,
+			    sizeof acall_table / sizeof acall_table[0],
+			    sizeof acall_table[0], acall_compar)) != NULL) {
+		delete_tree(instance, p1);
+		return item->func(instance, item, p2);
+	}
+	delete_tree(instance, p1);
+	delete_tree(instance, p2);
+	lisp_warn(instance, "acall function %s' is undefined", p1->value.s);
+	return &alsa_lisp_nil;
+}
+
+static struct alisp_object * F_ahandle(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object *p1;
+
+	p1 = eval(instance, car(args));
+	delete_tree(instance, cdr(args));
+	delete_object(instance, args);
+	args = car(cdr(p1));
+	delete_tree(instance, cdr(cdr(p1)));
+	delete_object(instance, cdr(p1));
+	delete_tree(instance, car(p1));
+	delete_object(instance, p1);
+	return args;
+}
+
+static struct alisp_object * F_aerror(struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object *p1;
+
+	p1 = eval(instance, car(args));
+	delete_tree(instance, cdr(args));
+	delete_object(instance, args);
+	args = car(p1);
+	if (args == &alsa_lisp_nil) {
+		delete_tree(instance, p1);
+		return new_integer(instance, SND_ERROR_ALISP_NIL);
+	} else {
+		delete_tree(instance, cdr(p1));
+		delete_object(instance, p1);
+	}
+	return args;
+}
+
+static int common_error(snd_output_t **rout, struct alisp_instance *instance, struct alisp_object * args)
+{
+	struct alisp_object * p = args, * p1;
+	snd_output_t *out;
+	int err;
+	
+	err = snd_output_buffer_open(&out);
+	if (err < 0) {
+		delete_tree(instance, args);
+		return err;
+	}
+
+	do {
+		p1 = eval(instance, car(p));
+		if (alisp_compare_type(p1, ALISP_OBJ_STRING))
+			snd_output_printf(out, "%s", p1->value.s);
+		else
+			princ_object(out, p1);
+		delete_tree(instance, p1);
+		p = cdr(p1 = p);
+		delete_object(instance, p1);
+	} while (p != &alsa_lisp_nil);
+
+	*rout = out;
+	return 0;
+}
+
+static struct alisp_object * F_snderr(struct alisp_instance *instance, struct alisp_object * args)
+{
+	snd_output_t *out;
+	char *str;
+
+	if (common_error(&out, instance, args) < 0)
+		return &alsa_lisp_nil;
+	snd_output_buffer_string(out, &str);
+	SNDERR(str);
+	snd_output_close(out);
+	return &alsa_lisp_t;
+}
+
+static struct alisp_object * F_syserr(struct alisp_instance *instance, struct alisp_object * args)
+{
+	snd_output_t *out;
+	char *str;
+
+	if (common_error(&out, instance, args) < 0)
+		return &alsa_lisp_nil;
+	snd_output_buffer_string(out, &str);
+	SYSERR(str);
+	snd_output_close(out);
+	return &alsa_lisp_t;
+}
+
+static const struct intrinsic snd_intrinsics[] = {
+	{ "Acall", F_acall },
+	{ "Aerror", F_aerror },
+	{ "Ahandle", F_ahandle },
+	{ "Aresult", F_ahandle },
+	{ "Asnderr", F_snderr },
+	{ "Asyserr", F_syserr }
+};
diff --git a/src/async.c b/src/async.c
new file mode 100644
index 0000000..98aec78
--- /dev/null
+++ b/src/async.c
@@ -0,0 +1,206 @@
+/**
+ * \file async.c
+ * \brief Async notification helpers
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \date 2001
+ */
+/*
+ *  Async notification helpers
+ *  Copyright (c) 2001 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include "pcm/pcm_local.h"
+#include "control/control_local.h"
+#include <signal.h>
+
+#ifdef SND_ASYNC_RT_SIGNAL
+/** async signal number */
+static int snd_async_signo;
+
+void snd_async_init(void) __attribute__ ((constructor));
+
+void snd_async_init(void)
+{
+	snd_async_signo = __libc_allocate_rtsig(0);
+	if (snd_async_signo < 0) {
+		SNDERR("Unable to find a RT signal to use for snd_async");
+		exit(1);
+	}
+}
+#else
+/** async signal number */
+static const int snd_async_signo = SIGIO;
+#endif
+
+static LIST_HEAD(snd_async_handlers);
+
+static void snd_async_handler(int signo ATTRIBUTE_UNUSED, siginfo_t *siginfo, void *context ATTRIBUTE_UNUSED)
+{
+	int fd;
+	struct list_head *i;
+	//assert(siginfo->si_code == SI_SIGIO);
+	fd = siginfo->si_fd;
+	list_for_each(i, &snd_async_handlers) {
+		snd_async_handler_t *h = list_entry(i, snd_async_handler_t, glist);
+		if (h->fd == fd && h->callback)
+			h->callback(h);
+	}
+}
+
+/**
+ * \brief Registers an async handler.
+ * \param handler The function puts the pointer to the new async handler
+ *                object at the address specified by \p handler.
+ * \param fd The file descriptor to be associated with the callback.
+ * \param callback The async callback function.
+ * \param private_data Private data for the async callback function.
+ * \result Zero if successful, otherwise a negative error code.
+ *
+ * This function associates the callback function with the given file,
+ * and saves this association in a \c snd_async_handler_t object.
+ *
+ * Whenever the \c SIGIO signal is raised for the file \p fd, the callback
+ * function will be called with its parameter pointing to the async handler
+ * object returned by this function.
+ *
+ * The ALSA \c sigaction handler for the \c SIGIO signal automatically
+ * multiplexes the notifications to the registered async callbacks.
+ * However, the application is responsible for instructing the device driver
+ * to generate the \c SIGIO signal.
+ *
+ * The \c SIGIO signal may have been replaced with another signal,
+ * see #snd_async_handler_get_signo.
+ *
+ * When the async handler isn't needed anymore, you must delete it with
+ * #snd_async_del_handler.
+ *
+ * \see snd_async_add_pcm_handler, snd_async_add_ctl_handler
+ */
+int snd_async_add_handler(snd_async_handler_t **handler, int fd, 
+			  snd_async_callback_t callback, void *private_data)
+{
+	snd_async_handler_t *h;
+	int was_empty;
+	assert(handler);
+	h = malloc(sizeof(*h));
+	if (!h)
+		return -ENOMEM;
+	h->fd = fd;
+	h->callback = callback;
+	h->private_data = private_data;
+	was_empty = list_empty(&snd_async_handlers);
+	list_add_tail(&h->glist, &snd_async_handlers);
+	INIT_LIST_HEAD(&h->hlist);
+	*handler = h;
+	if (was_empty) {
+		int err;
+		struct sigaction act;
+		memset(&act, 0, sizeof(act));
+		act.sa_flags = SA_RESTART | SA_SIGINFO;
+		act.sa_sigaction = snd_async_handler;
+		sigemptyset(&act.sa_mask);
+		err = sigaction(snd_async_signo, &act, NULL);
+		if (err < 0) {
+			SYSERR("sigaction");
+			return -errno;
+		}
+	}
+	return 0;
+}
+
+/**
+ * \brief Deletes an async handler.
+ * \param handler Handle of the async handler to delete.
+ * \result Zero if successful, otherwise a negative error code.
+ */
+int snd_async_del_handler(snd_async_handler_t *handler)
+{
+	int err = 0;
+	assert(handler);
+	list_del(&handler->glist);
+	if (list_empty(&snd_async_handlers)) {
+		struct sigaction act;
+		memset(&act, 0, sizeof(act));
+		act.sa_flags = 0;
+		act.sa_handler = SIG_DFL;
+		err = sigaction(snd_async_signo, &act, NULL);
+		if (err < 0) {
+			SYSERR("sigaction");
+			return -errno;
+		}
+	}
+	if (handler->type == SND_ASYNC_HANDLER_GENERIC)
+		goto _end;
+	if (!list_empty(&handler->hlist))
+		list_del(&handler->hlist);
+	if (!list_empty(&handler->hlist))
+		goto _end;
+	switch (handler->type) {
+#ifdef BUILD_PCM
+	case SND_ASYNC_HANDLER_PCM:
+		err = snd_pcm_async(handler->u.pcm, -1, 1);
+		break;
+#endif
+	case SND_ASYNC_HANDLER_CTL:
+		err = snd_ctl_async(handler->u.ctl, -1, 1);
+		break;
+	default:
+		assert(0);
+	}
+ _end:
+	free(handler);
+	return err;
+}
+
+/**
+ * \brief Returns the signal number assigned to an async handler.
+ * \param handler Handle to an async handler.
+ * \result The signal number if successful, otherwise a negative error code.
+ *
+ * The signal number for async handlers usually is \c SIGIO,
+ * but wizards can redefine it to a realtime signal
+ * when compiling the ALSA library.
+ */
+int snd_async_handler_get_signo(snd_async_handler_t *handler)
+{
+	assert(handler);
+	return snd_async_signo;
+}
+
+/**
+ * \brief Returns the file descriptor assigned to an async handler.
+ * \param handler Handle to an async handler.
+ * \result The file descriptor if successful, otherwise a negative error code.
+ */
+int snd_async_handler_get_fd(snd_async_handler_t *handler)
+{
+	assert(handler);
+	return handler->fd;
+}
+
+/**
+ * \brief Returns the private data assigned to an async handler.
+ * \param handler Handle to an async handler.
+ * \result The \c private_data value registered with the async handler.
+ */
+void *snd_async_handler_get_callback_private(snd_async_handler_t *handler)
+{
+	assert(handler);
+	return handler->private_data;
+}
+
diff --git a/src/compat/Makefile.am b/src/compat/Makefile.am
new file mode 100644
index 0000000..01f54fc
--- /dev/null
+++ b/src/compat/Makefile.am
@@ -0,0 +1,8 @@
+noinst_LTLIBRARIES = libcompat.la
+EXTRA_libcompat_la_SOURCES = hsearch_r.c
+
+if ALSA_HSEARCH_R
+libcompat_la_SOURCES = empty.c hsearch_r.c
+else
+libcompat_la_SOURCES = empty.c
+endif
diff --git a/src/compat/empty.c b/src/compat/empty.c
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/compat/empty.c
diff --git a/src/compat/hsearch_r.c b/src/compat/hsearch_r.c
new file mode 100644
index 0000000..96ceac1
--- /dev/null
+++ b/src/compat/hsearch_r.c
@@ -0,0 +1,236 @@
+/* Copyright (C) 1993, 1995, 1996, 1997 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+   Contributed by Ulrich Drepper <drepper@gnu.ai.mit.edu>, 1993.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public License as
+   published by the Free Software Foundation; either version 2.1 of the
+   License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.  */
+
+#include <errno.h>
+#include <malloc.h>
+#include <string.h>
+
+#define __USE_GNU
+#ifndef __set_errno
+#define __set_errno(e) errno = (e)
+#endif
+#include <search.h>
+
+/* [Aho,Sethi,Ullman] Compilers: Principles, Techniques and Tools, 1986
+   [Knuth]            The Art of Computer Programming, part 3 (6.4)  */
+
+
+/* The reentrant version has no static variables to maintain the state.
+   Instead the interface of all functions is extended to take an argument
+   which describes the current status.  */
+typedef struct _ENTRY
+{
+  unsigned int used;
+  ENTRY entry;
+}
+_ENTRY;
+
+
+/* For the used double hash method the table size has to be a prime. To
+   correct the user given table size we need a prime test.  This trivial
+   algorithm is adequate because
+   a)  the code is (most probably) called a few times per program run and
+   b)  the number is small because the table must fit in the core  */
+static int
+isprime (unsigned int number)
+{
+  /* no even number will be passed */
+  unsigned int div = 3;
+
+  while (div * div < number && number % div != 0)
+    div += 2;
+
+  return number % div != 0;
+}
+
+
+/* Before using the hash table we must allocate memory for it.
+   Test for an existing table are done. We allocate one element
+   more as the found prime number says. This is done for more effective
+   indexing as explained in the comment for the hsearch function.
+   The contents of the table is zeroed, especially the field used
+   becomes zero.  */
+int
+hcreate_r (nel, htab)
+     size_t nel;
+     struct hsearch_data *htab;
+{
+  /* Test for correct arguments.  */
+  if (htab == NULL)
+    {
+      __set_errno (EINVAL);
+      return 0;
+    }
+
+  /* There is still another table active. Return with error. */
+  if (htab->table != NULL)
+    return 0;
+
+  /* Change nel to the first prime number not smaller as nel. */
+  nel |= 1;      /* make odd */
+  while (!isprime (nel))
+    nel += 2;
+
+  htab->size = nel;
+  htab->filled = 0;
+
+  /* allocate memory and zero out */
+  htab->table = (_ENTRY *) calloc (htab->size + 1, sizeof (_ENTRY));
+  if (htab->table == NULL)
+    return 0;
+
+  /* everything went alright */
+  return 1;
+}
+
+
+/* After using the hash table it has to be destroyed. The used memory can
+   be freed and the local static variable can be marked as not used.  */
+void
+hdestroy_r (htab)
+     struct hsearch_data *htab;
+{
+  /* Test for correct arguments.  */
+  if (htab == NULL)
+    {
+      __set_errno (EINVAL);
+      return;
+    }
+
+  if (htab->table != NULL)
+    /* free used memory */
+    free (htab->table);
+
+  /* the sign for an existing table is an value != NULL in htable */
+  htab->table = NULL;
+}
+
+
+/* This is the search function. It uses double hashing with open addressing.
+   The argument item.key has to be a pointer to an zero terminated, most
+   probably strings of chars. The function for generating a number of the
+   strings is simple but fast. It can be replaced by a more complex function
+   like ajw (see [Aho,Sethi,Ullman]) if the needs are shown.
+
+   We use an trick to speed up the lookup. The table is created by hcreate
+   with one more element available. This enables us to use the index zero
+   special. This index will never be used because we store the first hash
+   index in the field used where zero means not used. Every other value
+   means used. The used field can be used as a first fast comparison for
+   equality of the stored and the parameter value. This helps to prevent
+   unnecessary expensive calls of strcmp.  */
+int
+hsearch_r (item, action, retval, htab)
+     ENTRY item;
+     ACTION action;
+     ENTRY **retval;
+     struct hsearch_data *htab;
+{
+  unsigned int hval;
+  unsigned int count;
+  unsigned int len = strlen (item.key);
+  unsigned int idx;
+
+  /* Compute an value for the given string. Perhaps use a better method. */
+  hval = len;
+  count = len;
+  while (count-- > 0)
+    {
+      hval <<= 4;
+      hval += item.key[count];
+    }
+
+  /* First hash function: simply take the modulo but prevent zero. */
+  hval %= htab->size;
+  if (hval == 0)
+    ++hval;
+
+  /* The first index tried. */
+  idx = hval;
+
+  if (htab->table[idx].used)
+    {
+      /* Further action might be required according to the action value. */
+      unsigned hval2;
+
+      if (htab->table[idx].used == hval
+	  && strcmp (item.key, htab->table[idx].entry.key) == 0)
+	{
+          if (action == ENTER)
+	    htab->table[idx].entry.data = item.data;
+
+	  *retval = &htab->table[idx].entry;
+	  return 1;
+	}
+
+      /* Second hash function, as suggested in [Knuth] */
+      hval2 = 1 + hval % (htab->size - 2);
+
+      do
+	{
+	  /* Because SIZE is prime this guarantees to step through all
+             available indexes.  */
+          if (idx <= hval2)
+	    idx = htab->size + idx - hval2;
+	  else
+	    idx -= hval2;
+
+	  /* If we visited all entries leave the loop unsuccessfully.  */
+	  if (idx == hval)
+	    break;
+
+            /* If entry is found use it. */
+          if (htab->table[idx].used == hval
+	      && strcmp (item.key, htab->table[idx].entry.key) == 0)
+	    {
+              if (action == ENTER)
+	        htab->table[idx].entry.data = item.data;
+
+	      *retval = &htab->table[idx].entry;
+	      return 1;
+	    }
+	}
+      while (htab->table[idx].used);
+    }
+
+  /* An empty bucket has been found. */
+  if (action == ENTER)
+    {
+      /* If table is full and another entry should be entered return
+	 with error.  */
+      if (action == ENTER && htab->filled == htab->size)
+	{
+	  __set_errno (ENOMEM);
+	  *retval = NULL;
+	  return 0;
+	}
+
+      htab->table[idx].used  = hval;
+      htab->table[idx].entry = item;
+
+      ++htab->filled;
+
+      *retval = &htab->table[idx].entry;
+      return 1;
+    }
+
+  __set_errno (ESRCH);
+  *retval = NULL;
+  return 0;
+}
diff --git a/src/conf.c b/src/conf.c
new file mode 100644
index 0000000..5b1b5a6
--- /dev/null
+++ b/src/conf.c
@@ -0,0 +1,4827 @@
+/**
+ * \file conf.c
+ * \ingroup Configuration
+ * \brief Configuration helper functions
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \date 2000-2001
+ *
+ * Tree based, full nesting configuration functions.
+ *
+ * See the \ref conf page for more details.
+ */
+/*
+ *  Configuration helper functions
+ *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>,
+ *			  Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+/*! \page conf Configuration files
+
+<P>Configuration files use a simple format allowing modern
+data description like nesting and array assignments.</P>
+
+\section conf_whitespace Whitespace
+
+Whitespace is the collective name given to spaces (blanks), horizontal and
+vertical tabs, newline characters, and comments. Whitespace can
+indicate where configuration tokens start and end, but beyond this function,
+any surplus whitespace is discarded. For example, the two sequences
+
+\code
+  a 1 b 2
+\endcode
+
+and
+
+\code
+  a 1 
+     b 2
+\endcode
+
+are lexically equivalent and parse identically to give the four tokens:
+
+\code
+a
+1
+b
+2
+\endcode
+
+The ASCII characters representing whitespace can occur within literal
+strings, in which case they are protected from the normal parsing process
+(they remain as part of the string). For example:
+
+\code
+  name "John Smith"
+\endcode
+
+parses to two tokens, including the single literal-string token "John
+Smith".
+
+\section conf_linesplicing Line continuation with \
+
+A special case occurs if a newline character in a string is preceded
+by a backslash (\). The backslash and the new line are both discarded,
+allowing two physical lines of text to be treated as one unit.
+
+\code
+"John \
+Smith"
+\endcode
+
+is parsed as "John Smith".
+
+\section conf_comments Comments
+
+A single-line comment begins with the character #. The comment can start
+at any position, and extends to the end of the line.
+
+\code
+  a 1  # this is a comment
+\endcode
+
+\section conf_include Including configuration files
+
+To include another configuration file, write the file name in angle brackets.
+The prefix \c confdir: will reference the global configuration directory.
+
+\code
+</etc/alsa1.conf>
+<confdir:pcm/surround.conf>
+\endcode
+
+\section conf_punctuators Punctuators
+
+The configuration punctuators (also known as separators) are:
+
+\code
+  {} [] , ; = . ' " new-line form-feed carriage-return whitespace
+\endcode
+
+\subsection conf_braces Braces
+
+Opening and closing braces { } indicate the start and end of a compound
+statement:
+
+\code
+a {
+  b 1
+}
+\endcode
+
+\subsection conf_brackets Brackets
+
+Opening and closing brackets indicate a single array definition. The
+identifiers are automatically generated starting with zero.
+
+\code
+a [
+  "first"
+  "second"
+]
+\endcode
+
+The above code is equal to
+
+\code
+a.0 "first"
+a.1 "second"
+\endcode
+
+\subsection conf_comma_semicolon Comma and semicolon
+
+The comma (,) or semicolon (;) can separate value assignments. It is not
+strictly required to use these separators because whitespace suffices to
+separate tokens.
+
+\code
+a 1;
+b 1,
+\endcode
+
+\subsection conf_equal Equal sign
+
+The equal sign (=) can separate variable declarations from
+initialization lists:
+
+\code
+a=1
+b=2
+\endcode
+
+Using equal signs is not required because whitespace suffices to separate
+tokens.
+
+\section conf_assigns Assignments
+
+The configuration file defines id (key) and value pairs. The id (key) can be
+composed from ASCII digits, characters from a to z and A to Z, and the
+underscore (_). The value can be either a string, an integer, a real number,
+or a compound statement.
+
+\subsection conf_single Single assignments
+
+\code
+a 1	# is equal to
+a=1	# is equal to
+a=1;	# is equal to
+a 1,
+\endcode
+
+\subsection conf_compound Compound assignments (definitions using braces)
+
+\code
+a {
+  b = 1
+}
+a={
+  b 1,
+}
+\endcode
+
+\section conf_compound1 Compound assignments (one key definitions)
+
+\code
+a.b 1
+a.b=1
+\endcode
+
+\subsection conf_array Array assignments (definitions using brackets)
+
+\code
+a [
+  "first"
+  "second"
+]
+\endcode
+
+\subsection conf_array1 Array assignments (one key definitions)
+
+\code
+a.0 "first"
+a.1 "second"
+\endcode
+
+\section conf_mode Operation modes for parsing nodes
+
+By default, the node operation mode is 'merge+create', i.e., if
+a configuration node is not present a new one is created, otherwise
+the latest assignment is merged (if possible - type checking). The
+'merge+create' operation mode is specified with the prefix character plus (+).
+
+The operation mode 'merge' merges the node with the old one (which must
+exist). Type checking is done, so strings cannot be assigned to integers
+and so on. This mode is specified with the prefix character minus (-).
+
+The operation mode 'do not override' ignores a new configuration node
+if a configuration node with the same name exists. This mode is specified with
+the prefix character question mark (?).
+
+The operation mode 'override' always overrides the old configuration node
+with new contents. This mode is specified with the prefix character
+exclamation mark (!).
+
+\code
+defaults.pcm.!device 1
+\endcode
+
+\section conf_syntax_summary Syntax summary
+
+\code
+# Configuration file syntax
+
+# Include a new configuration file
+<filename>
+
+# Simple assignment
+name [=] value [,|;]
+
+# Compound assignment (first style)
+name [=] {
+        name1 [=] value [,|;]
+        ...
+}
+
+# Compound assignment (second style)
+name.name1 [=] value [,|;]
+
+# Array assignment (first style)
+name [
+        value0 [,|;]
+        value1 [,|;]
+        ...
+]
+
+# Array assignment (second style)
+name.0 [=] value0 [,|;]
+name.1 [=] value1 [,|;]
+\endcode
+
+\section conf_syntax_ref References
+
+\ref confarg
+\ref conffunc
+\ref confhooks
+
+*/
+
+/*! \page confarg Runtime arguments in configuration files
+
+<P>The ALSA library can accept runtime arguments for some configuration
+blocks. This extension is built on top of the basic configuration file
+syntax.<P>
+
+\section confarg_define Defining arguments
+
+Arguments are defined using the id (key) \c \@args and array values containing
+the string names of the arguments:
+
+\code
+@args [ CARD ]	# or
+@args.0 CARD
+\endcode
+
+\section confarg_type Defining argument types and default values
+
+An argument's type is specified with the id (key) \c \@args and the argument
+name. The type and the default value are specified in the compound block:
+
+\code
+@args.CARD {
+  type string
+  default "abcd"
+}
+\endcode
+
+\section confarg_refer Referring to arguments
+
+Arguments are referred to with a dollar-sign ($) and the name of the argument:
+
+\code
+  card $CARD
+\endcode
+
+\section confarg_usage Usage
+
+To use a block with arguments, write the argument values after the key,
+separated with a colon (:). For example, all these names for PCM interfaces
+give the same result:
+
+\code
+hw:0,1
+hw:CARD=0,DEV=1
+hw:{CARD 0 DEV 1}
+plug:"hw:0,1"
+plug:{SLAVE="hw:{CARD 0 DEV 1}"}
+\endcode
+
+As you see, arguments can be specified in their proper order or by name.
+Note that arguments enclosed in braces are parsed in the same way as in
+configuration files, but using the override method by default.
+
+\section confarg_example Example
+
+\code
+pcm.demo {
+	@args [ CARD DEVICE ]
+	@args.CARD {
+		type string
+		default "supersonic"
+	}
+	@args.DEVICE {
+		type integer
+		default 0
+	}
+	type hw
+	card $CARD
+	device $DEVICE
+}
+\endcode
+
+*/
+
+/*! \page conffunc Runtime functions in configuration files
+
+<P>The ALSA library can modify the configuration at runtime.
+Several built-in functions are available.</P>
+
+<P>A function is defined with the id \c \@func and the function name. All other
+values in the current compound are used as configuration for the function.
+If the compound func.\<function_name\> is defined in the root node, then the
+library and function from this compound configuration are used, otherwise
+'snd_func_' is prefixed to the string and code from the ALSA library is used.
+The definition of a function looks like:</P> 
+
+\code
+func.remove_first_char {
+	lib "/usr/lib/libasoundextend.so"
+	func "extend_remove_first_char"
+}
+\endcode
+
+*/
+
+/*! \page confhooks Hooks in configuration files
+
+<P>The hook extension in the ALSA library allows expansion of configuration
+nodes at run-time. The existence of a hook is determined by the
+presence of a \@hooks compound node.</P>
+
+<P>This example defines a hook which loads two configuration files at the
+beginning:</P>
+
+\code
+@hooks [
+	{
+		func load
+		files [
+			"/etc/asound.conf"
+			"~/.asoundrc"
+		]
+		errors false
+	}
+]
+\endcode
+
+\section confhooks_ref Function reference
+
+<UL>
+  <LI>The function load - \c snd_config_hook_load() - loads and parses the
+      given configuration files.
+  <LI>The function load_for_all_cards - \c snd_config_hook_load_for_all_cards() -
+      loads and parses the given configuration files for each installed sound
+      card. The driver name (the type of the sound card) is passed in the
+      private configuration node.
+</UL>
+
+*/
+
+
+#include <stdarg.h>
+#include <limits.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <locale.h>
+#include "local.h"
+#ifdef HAVE_LIBPTHREAD
+#include <pthread.h>
+#endif
+
+#ifndef DOC_HIDDEN
+
+#ifdef HAVE_LIBPTHREAD
+static pthread_mutex_t snd_config_update_mutex =
+				PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
+#endif
+
+struct _snd_config {
+	char *id;
+	snd_config_type_t type;
+	union {
+		long integer;
+		long long integer64;
+		char *string;
+		double real;
+		const void *ptr;
+		struct {
+			struct list_head fields;
+			int join;
+		} compound;
+	} u;
+	struct list_head list;
+	snd_config_t *parent;
+	int hop;
+};
+
+struct filedesc {
+	char *name;
+	snd_input_t *in;
+	unsigned int line, column;
+	struct filedesc *next;
+};
+
+#define LOCAL_ERROR			(-0x68000000)
+
+#define LOCAL_UNTERMINATED_STRING 	(LOCAL_ERROR - 0)
+#define LOCAL_UNTERMINATED_QUOTE	(LOCAL_ERROR - 1)
+#define LOCAL_UNEXPECTED_CHAR		(LOCAL_ERROR - 2)
+#define LOCAL_UNEXPECTED_EOF		(LOCAL_ERROR - 3)
+
+typedef struct {
+	struct filedesc *current;
+	int unget;
+	int ch;
+} input_t;
+
+#ifdef HAVE_LIBPTHREAD
+
+static inline void snd_config_lock(void)
+{
+	pthread_mutex_lock(&snd_config_update_mutex);
+}
+
+static inline void snd_config_unlock(void)
+{
+	pthread_mutex_unlock(&snd_config_update_mutex);
+}
+
+#else
+
+static inline void snd_config_lock(void) { }
+static inline void snd_config_unlock(void) { }
+
+#endif
+
+static int safe_strtoll(const char *str, long long *val)
+{
+	long long v;
+	int endidx;
+	if (!*str)
+		return -EINVAL;
+	errno = 0;
+	if (sscanf(str, "%Li%n", &v, &endidx) < 1)
+		return -EINVAL;
+	if (str[endidx])
+		return -EINVAL;
+	*val = v;
+	return 0;
+}
+
+int safe_strtol(const char *str, long *val)
+{
+	char *end;
+	long v;
+	if (!*str)
+		return -EINVAL;
+	errno = 0;
+	v = strtol(str, &end, 0);
+	if (errno)
+		return -errno;
+	if (*end)
+		return -EINVAL;
+	*val = v;
+	return 0;
+}
+
+static int safe_strtod(const char *str, double *val)
+{
+	char *end;
+	double v;
+#ifdef HAVE_USELOCALE
+	locale_t saved_locale, c_locale;
+#else
+	char *saved_locale;
+	char locstr[64]; /* enough? */
+#endif
+	int err;
+
+	if (!*str)
+		return -EINVAL;
+#ifdef HAVE_USELOCALE
+	c_locale = newlocale(LC_NUMERIC_MASK, "C", 0);
+	saved_locale = uselocale(c_locale);
+#else
+	saved_locale = setlocale(LC_NUMERIC, NULL);
+	if (saved_locale) {
+		snprintf(locstr, sizeof(locstr), "%s", saved_locale);
+		setlocale(LC_NUMERIC, "C");
+	}
+#endif
+	errno = 0;
+	v = strtod(str, &end);
+	err = -errno;
+#ifdef HAVE_USELOCALE
+	if (c_locale != (locale_t)0) {
+		uselocale(saved_locale);
+		freelocale(c_locale);
+	}
+#else
+	if (saved_locale)
+		setlocale(LC_NUMERIC, locstr);
+#endif
+	if (err)
+		return err;
+	if (*end)
+		return -EINVAL;
+	*val = v;
+	return 0;
+}
+
+static int get_char(input_t *input)
+{
+	int c;
+	struct filedesc *fd;
+	if (input->unget) {
+		input->unget = 0;
+		return input->ch;
+	}
+ again:
+	fd = input->current;
+	c = snd_input_getc(fd->in);
+	switch (c) {
+	case '\n':
+		fd->column = 0;
+		fd->line++;
+		break;
+	case '\t':
+		fd->column += 8 - fd->column % 8;
+		break;
+	case EOF:
+		if (fd->next) {
+			snd_input_close(fd->in);
+			free(fd->name);
+			input->current = fd->next;
+			free(fd);
+			goto again;
+		}
+		return LOCAL_UNEXPECTED_EOF;
+	default:
+		fd->column++;
+		break;
+	}
+	return (unsigned char)c;
+}
+
+static void unget_char(int c, input_t *input)
+{
+	assert(!input->unget);
+	input->ch = c;
+	input->unget = 1;
+}
+
+static int get_delimstring(char **string, int delim, input_t *input);
+
+static int get_char_skip_comments(input_t *input)
+{
+	int c;
+	while (1) {
+		c = get_char(input);
+		if (c == '<') {
+			char *str;
+			snd_input_t *in;
+			struct filedesc *fd;
+			int err = get_delimstring(&str, '>', input);
+			if (err < 0)
+				return err;
+			if (!strncmp(str, "confdir:", 8)) {
+				char *tmp = malloc(strlen(ALSA_CONFIG_DIR) + 1 + strlen(str + 8) + 1);
+				if (tmp == NULL) {
+					free(str);
+					return -ENOMEM;
+				}
+				sprintf(tmp, ALSA_CONFIG_DIR "/%s", str + 8);
+				free(str);
+				str = tmp;
+			}
+			err = snd_input_stdio_open(&in, str, "r");
+			if (err < 0) {
+				SNDERR("Cannot access file %s", str);
+				free(str);
+				return err;
+			}
+			fd = malloc(sizeof(*fd));
+			if (!fd) {
+				free(str);
+				return -ENOMEM;
+			}
+			fd->name = str;
+			fd->in = in;
+			fd->next = input->current;
+			fd->line = 1;
+			fd->column = 0;
+			input->current = fd;
+			continue;
+		}
+		if (c != '#')
+			break;
+		while (1) {
+			c = get_char(input);
+			if (c < 0)
+				return c;
+			if (c == '\n')
+				break;
+		}
+	}
+		
+	return c;
+}
+			
+
+static int get_nonwhite(input_t *input)
+{
+	int c;
+	while (1) {
+		c = get_char_skip_comments(input);
+		switch (c) {
+		case ' ':
+		case '\f':
+		case '\t':
+		case '\n':
+		case '\r':
+			break;
+		default:
+			return c;
+		}
+	}
+}
+
+static int get_quotedchar(input_t *input)
+{
+	int c;
+	c = get_char(input);
+	switch (c) {
+	case 'n':
+		return '\n';
+	case 't':
+		return '\t';
+	case 'v':
+		return '\v';
+	case 'b':
+		return '\b';
+	case 'r':
+		return '\r';
+	case 'f':
+		return '\f';
+	case '0' ... '7':
+	{
+		int num = c - '0';
+		int i = 1;
+		do {
+			c = get_char(input);
+			if (c < '0' || c > '7') {
+				unget_char(c, input);
+				break;
+			}
+			num = num * 8 + c - '0';
+			i++;
+		} while (i < 3);
+		return num;
+	}
+	default:
+		return c;
+	}
+}
+
+#define LOCAL_STR_BUFSIZE	64
+struct local_string {
+	char *buf;
+	size_t alloc;
+	size_t idx;
+	char tmpbuf[LOCAL_STR_BUFSIZE];
+};
+
+static void init_local_string(struct local_string *s)
+{
+	memset(s, 0, sizeof(*s));
+	s->buf = s->tmpbuf;
+	s->alloc = LOCAL_STR_BUFSIZE;
+}
+
+static void free_local_string(struct local_string *s)
+{
+	if (s->buf != s->tmpbuf)
+		free(s->buf);
+}
+
+static int add_char_local_string(struct local_string *s, int c)
+{
+	if (s->idx >= s->alloc) {
+		size_t nalloc = s->alloc * 2;
+		if (s->buf == s->tmpbuf) {
+			s->buf = malloc(nalloc);
+			if (s->buf == NULL)
+				return -ENOMEM;
+			memcpy(s->buf, s->tmpbuf, s->alloc);
+		} else {
+			char *ptr = realloc(s->buf, nalloc);
+			if (ptr == NULL)
+				return -ENOMEM;
+			s->buf = ptr;
+		}
+		s->alloc = nalloc;
+	}
+	s->buf[s->idx++] = c;
+	return 0;
+}
+
+static char *copy_local_string(struct local_string *s)
+{
+	char *dst = malloc(s->idx + 1);
+	if (dst) {
+		memcpy(dst, s->buf, s->idx);
+		dst[s->idx] = '\0';
+	}
+	return dst;
+}
+
+static int get_freestring(char **string, int id, input_t *input)
+{
+	struct local_string str;
+	int c;
+
+	init_local_string(&str);
+	while (1) {
+		c = get_char(input);
+		if (c < 0) {
+			if (c == LOCAL_UNEXPECTED_EOF) {
+				*string = copy_local_string(&str);
+				if (! *string)
+					c = -ENOMEM;
+				else
+					c = 0;
+			}
+			break;
+		}
+		switch (c) {
+		case '.':
+			if (!id)
+				break;
+		case ' ':
+		case '\f':
+		case '\t':
+		case '\n':
+		case '\r':
+		case '=':
+		case ',':
+		case ';':
+		case '{':
+		case '}':
+		case '[':
+		case ']':
+		case '\'':
+		case '"':
+		case '\\':
+		case '#':
+			*string = copy_local_string(&str);
+			if (! *string)
+				c = -ENOMEM;
+			else {
+				unget_char(c, input);
+				c = 0;
+			}
+			goto _out;
+		default:
+			break;
+		}
+		if (add_char_local_string(&str, c) < 0) {
+			c = -ENOMEM;
+			break;
+		}
+	}
+ _out:
+	free_local_string(&str);
+	return c;
+}
+			
+static int get_delimstring(char **string, int delim, input_t *input)
+{
+	struct local_string str;
+	int c;
+
+	init_local_string(&str);
+	while (1) {
+		c = get_char(input);
+		if (c < 0)
+			break;
+		if (c == '\\') {
+			c = get_quotedchar(input);
+			if (c < 0)
+				break;
+			if (c == '\n')
+				continue;
+		} else if (c == delim) {
+			*string = copy_local_string(&str);
+			if (! *string)
+				c = -ENOMEM;
+			else
+				c = 0;
+			break;
+		}
+		if (add_char_local_string(&str, c) < 0) {
+			c = -ENOMEM;
+			break;
+		}
+	}
+	 free_local_string(&str);
+	 return c;
+}
+
+/* Return 0 for free string, 1 for delimited string */
+static int get_string(char **string, int id, input_t *input)
+{
+	int c = get_nonwhite(input), err;
+	if (c < 0)
+		return c;
+	switch (c) {
+	case '=':
+	case ',':
+	case ';':
+	case '.':
+	case '{':
+	case '}':
+	case '[':
+	case ']':
+	case '\\':
+		return LOCAL_UNEXPECTED_CHAR;
+	case '\'':
+	case '"':
+		err = get_delimstring(string, c, input);
+		if (err < 0)
+			return err;
+		return 1;
+	default:
+		unget_char(c, input);
+		err = get_freestring(string, id, input);
+		if (err < 0)
+			return err;
+		return 0;
+	}
+}
+
+static int _snd_config_make(snd_config_t **config, char **id, snd_config_type_t type)
+{
+	snd_config_t *n;
+	assert(config);
+	n = calloc(1, sizeof(*n));
+	if (n == NULL) {
+		if (*id) {
+			free(*id);
+			*id = NULL;
+		}
+		return -ENOMEM;
+	}
+	if (id) {
+		n->id = *id;
+		*id = NULL;
+	}
+	n->type = type;
+	if (type == SND_CONFIG_TYPE_COMPOUND)
+		INIT_LIST_HEAD(&n->u.compound.fields);
+	*config = n;
+	return 0;
+}
+	
+
+static int _snd_config_make_add(snd_config_t **config, char **id,
+				snd_config_type_t type, snd_config_t *parent)
+{
+	snd_config_t *n;
+	int err;
+	assert(parent->type == SND_CONFIG_TYPE_COMPOUND);
+	err = _snd_config_make(&n, id, type);
+	if (err < 0)
+		return err;
+	n->parent = parent;
+	list_add_tail(&n->list, &parent->u.compound.fields);
+	*config = n;
+	return 0;
+}
+
+static int _snd_config_search(snd_config_t *config, 
+			      const char *id, int len, snd_config_t **result)
+{
+	snd_config_iterator_t i, next;
+	snd_config_for_each(i, next, config) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		if (len < 0) {
+			if (strcmp(n->id, id) != 0)
+				continue;
+		} else if (strlen(n->id) != (size_t) len ||
+			   memcmp(n->id, id, (size_t) len) != 0)
+				continue;
+		if (result)
+			*result = n;
+		return 0;
+	}
+	return -ENOENT;
+}
+
+static int parse_value(snd_config_t **_n, snd_config_t *parent, input_t *input, char **id, int skip)
+{
+	snd_config_t *n = *_n;
+	char *s;
+	int err;
+
+	err = get_string(&s, 0, input);
+	if (err < 0)
+		return err;
+	if (skip) {
+		free(s);
+		return 0;
+	}
+	if (err == 0 && ((s[0] >= '0' && s[0] <= '9') || s[0] == '-')) {
+		long long i;
+		errno = 0;
+		err = safe_strtoll(s, &i);
+		if (err < 0) {
+			double r;
+			err = safe_strtod(s, &r);
+			if (err >= 0) {
+				free(s);
+				if (n) {
+					if (n->type != SND_CONFIG_TYPE_REAL) {
+						SNDERR("%s is not a real", *id);
+						return -EINVAL;
+					}
+				} else {
+					err = _snd_config_make_add(&n, id, SND_CONFIG_TYPE_REAL, parent);
+					if (err < 0)
+						return err;
+				}
+				n->u.real = r;
+				*_n = n;
+				return 0;
+			}
+		} else {
+			free(s);
+			if (n) {
+				if (n->type != SND_CONFIG_TYPE_INTEGER && n->type != SND_CONFIG_TYPE_INTEGER64) {
+					SNDERR("%s is not an integer", *id);
+					return -EINVAL;
+				}
+			} else {
+				if (i <= INT_MAX) 
+					err = _snd_config_make_add(&n, id, SND_CONFIG_TYPE_INTEGER, parent);
+				else
+					err = _snd_config_make_add(&n, id, SND_CONFIG_TYPE_INTEGER64, parent);
+				if (err < 0)
+					return err;
+			}
+			if (n->type == SND_CONFIG_TYPE_INTEGER) 
+				n->u.integer = (long) i;
+			else 
+				n->u.integer64 = i;
+			*_n = n;
+			return 0;
+		}
+	}
+	if (n) {
+		if (n->type != SND_CONFIG_TYPE_STRING) {
+			SNDERR("%s is not a string", *id);
+			free(s);
+			return -EINVAL;
+		}
+	} else {
+		err = _snd_config_make_add(&n, id, SND_CONFIG_TYPE_STRING, parent);
+		if (err < 0)
+			return err;
+	}
+	free(n->u.string);
+	n->u.string = s;
+	*_n = n;
+	return 0;
+}
+
+static int parse_defs(snd_config_t *parent, input_t *input, int skip, int override);
+static int parse_array_defs(snd_config_t *farther, input_t *input, int skip, int override);
+
+static int parse_array_def(snd_config_t *parent, input_t *input, int idx, int skip, int override)
+{
+	char *id = NULL;
+	int c;
+	int err;
+	snd_config_t *n = NULL;
+
+	if (!skip) {
+		char static_id[12];
+		snprintf(static_id, sizeof(static_id), "%i", idx);
+		id = strdup(static_id);
+		if (id == NULL)
+			return -ENOMEM;
+	}
+	c = get_nonwhite(input);
+	if (c < 0) {
+		err = c;
+		goto __end;
+	}
+	switch (c) {
+	case '{':
+	case '[':
+	{
+		char endchr;
+		if (!skip) {
+			if (n) {
+				if (n->type != SND_CONFIG_TYPE_COMPOUND) {
+					SNDERR("%s is not a compound", id);
+					err = -EINVAL;
+					goto __end;
+				}
+			} else {
+				err = _snd_config_make_add(&n, &id, SND_CONFIG_TYPE_COMPOUND, parent);
+				if (err < 0)
+					goto __end;
+			}
+		}
+		if (c == '{') {
+			err = parse_defs(n, input, skip, override);
+			endchr = '}';
+		} else {
+			err = parse_array_defs(n, input, skip, override);
+			endchr = ']';
+		}
+		c = get_nonwhite(input);
+		if (c < 0) {
+			err = c;
+			goto __end;
+		}
+		if (c != endchr) {
+			if (n)
+				snd_config_delete(n);
+			err = LOCAL_UNEXPECTED_CHAR;
+			goto __end;
+		}
+		break;
+	}
+	default:
+		unget_char(c, input);
+		err = parse_value(&n, parent, input, &id, skip);
+		if (err < 0)
+			goto __end;
+		break;
+	}
+	err = 0;
+      __end:
+	free(id);
+      	return err;
+}
+
+static int parse_array_defs(snd_config_t *parent, input_t *input, int skip, int override)
+{
+	int idx = 0;
+	while (1) {
+		int c = get_nonwhite(input), err;
+		if (c < 0)
+			return c;
+		unget_char(c, input);
+		if (c == ']')
+			return 0;
+		err = parse_array_def(parent, input, idx++, skip, override);
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+static int parse_def(snd_config_t *parent, input_t *input, int skip, int override)
+{
+	char *id = NULL;
+	int c;
+	int err;
+	snd_config_t *n;
+	enum {MERGE_CREATE, MERGE, OVERRIDE, DONT_OVERRIDE} mode;
+	while (1) {
+		c = get_nonwhite(input);
+		if (c < 0)
+			return c;
+		switch (c) {
+		case '+':
+			mode = MERGE_CREATE;
+			break;
+		case '-':
+			mode = MERGE;
+			break;
+		case '?':
+			mode = DONT_OVERRIDE;
+			break;
+		case '!':
+			mode = OVERRIDE;
+			break;
+		default:
+			mode = !override ? MERGE_CREATE : OVERRIDE;
+			unget_char(c, input);
+		}
+		err = get_string(&id, 1, input);
+		if (err < 0)
+			return err;
+		c = get_nonwhite(input);
+		if (c != '.')
+			break;
+		if (skip) {
+			free(id);
+			continue;
+		}
+		if (_snd_config_search(parent, id, -1, &n) == 0) {
+			if (mode == DONT_OVERRIDE) {
+				skip = 1;
+				free(id);
+				continue;
+			}
+			if (mode != OVERRIDE) {
+				if (n->type != SND_CONFIG_TYPE_COMPOUND) {
+					SNDERR("%s is not a compound", id);
+					return -EINVAL;
+				}
+				n->u.compound.join = 1;
+				parent = n;
+				free(id);
+				continue;
+			}
+			snd_config_delete(n);
+		}
+		if (mode == MERGE) {
+			SNDERR("%s does not exists", id);
+			err = -ENOENT;
+			goto __end;
+		}
+		err = _snd_config_make_add(&n, &id, SND_CONFIG_TYPE_COMPOUND, parent);
+		if (err < 0)
+			goto __end;
+		n->u.compound.join = 1;
+		parent = n;
+	}
+	if (c == '=') {
+		c = get_nonwhite(input);
+		if (c < 0)
+			return c;
+	}
+	if (!skip) {
+		if (_snd_config_search(parent, id, -1, &n) == 0) {
+			if (mode == DONT_OVERRIDE) {
+				skip = 1;
+				n = NULL;
+			} else if (mode == OVERRIDE) {
+				snd_config_delete(n);
+				n = NULL;
+			}
+		} else {
+			n = NULL;
+			if (mode == MERGE) {
+				SNDERR("%s does not exists", id);
+				err = -ENOENT;
+				goto __end;
+			}
+		}
+	}
+	switch (c) {
+	case '{':
+	case '[':
+	{
+		char endchr;
+		if (!skip) {
+			if (n) {
+				if (n->type != SND_CONFIG_TYPE_COMPOUND) {
+					SNDERR("%s is not a compound", id);
+					err = -EINVAL;
+					goto __end;
+				}
+			} else {
+				err = _snd_config_make_add(&n, &id, SND_CONFIG_TYPE_COMPOUND, parent);
+				if (err < 0)
+					goto __end;
+			}
+		}
+		if (c == '{') {
+			err = parse_defs(n, input, skip, override);
+			endchr = '}';
+		} else {
+			err = parse_array_defs(n, input, skip, override);
+			endchr = ']';
+		}
+		c = get_nonwhite(input);
+		if (c != endchr) {
+			if (n)
+				snd_config_delete(n);
+			err = LOCAL_UNEXPECTED_CHAR;
+			goto __end;
+		}
+		break;
+	}
+	default:
+		unget_char(c, input);
+		err = parse_value(&n, parent, input, &id, skip);
+		if (err < 0)
+			goto __end;
+		break;
+	}
+	c = get_nonwhite(input);
+	switch (c) {
+	case ';':
+	case ',':
+		break;
+	default:
+		unget_char(c, input);
+	}
+      __end:
+	free(id);
+	return err;
+}
+		
+static int parse_defs(snd_config_t *parent, input_t *input, int skip, int override)
+{
+	int c, err;
+	while (1) {
+		c = get_nonwhite(input);
+		if (c < 0)
+			return c == LOCAL_UNEXPECTED_EOF ? 0 : c;
+		unget_char(c, input);
+		if (c == '}')
+			return 0;
+		err = parse_def(parent, input, skip, override);
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+static void string_print(char *str, int id, snd_output_t *out)
+{
+	unsigned char *p = (unsigned char *)str;
+	if (!p || !*p) {
+		snd_output_puts(out, "''");
+		return;
+	}
+	if (!id) {
+		switch (*p) {
+		case '0' ... '9':
+		case '-':
+			goto quoted;
+		}
+	}
+ loop:
+	switch (*p) {
+	case 0:
+		goto nonquoted;
+	case 1 ... 31:
+	case 127 ... 255:
+	case ' ':
+	case '=':
+	case ';':
+	case ',':
+	case '.':
+	case '{':
+	case '}':
+	case '\'':
+	case '"':
+		goto quoted;
+	default:
+		p++;
+		goto loop;
+	}
+ nonquoted:
+	snd_output_puts(out, str);
+	return;
+ quoted:
+	snd_output_putc(out, '\'');
+	p = (unsigned char *)str;
+	while (*p) {
+		int c;
+		c = *p;
+		switch (c) {
+		case '\n':
+			snd_output_putc(out, '\\');
+			snd_output_putc(out, 'n');
+			break;
+		case '\t':
+			snd_output_putc(out, '\\');
+			snd_output_putc(out, 't');
+			break;
+		case '\v':
+			snd_output_putc(out, '\\');
+			snd_output_putc(out, 'v');
+			break;
+		case '\b':
+			snd_output_putc(out, '\\');
+			snd_output_putc(out, 'b');
+			break;
+		case '\r':
+			snd_output_putc(out, '\\');
+			snd_output_putc(out, 'r');
+			break;
+		case '\f':
+			snd_output_putc(out, '\\');
+			snd_output_putc(out, 'f');
+			break;
+		case '\'':
+			snd_output_putc(out, '\\');
+			snd_output_putc(out, c);
+			break;
+		case 32 ... '\'' - 1:
+		case '\'' + 1 ... 126:
+			snd_output_putc(out, c);
+			break;
+		default:
+			snd_output_printf(out, "\\%04o", c);
+			break;
+		}
+		p++;
+	}
+	snd_output_putc(out, '\'');
+}
+
+static int _snd_config_save_children(snd_config_t *config, snd_output_t *out,
+				     unsigned int level, unsigned int joins);
+
+static int _snd_config_save_node_value(snd_config_t *n, snd_output_t *out,
+				       unsigned int level)
+{
+	int err;
+	unsigned int k;
+	switch (n->type) {
+	case SND_CONFIG_TYPE_INTEGER:
+		snd_output_printf(out, "%ld", n->u.integer);
+		break;
+	case SND_CONFIG_TYPE_INTEGER64:
+		snd_output_printf(out, "%Ld", n->u.integer64);
+		break;
+	case SND_CONFIG_TYPE_REAL:
+		snd_output_printf(out, "%-16g", n->u.real);
+		break;
+	case SND_CONFIG_TYPE_STRING:
+		string_print(n->u.string, 0, out);
+		break;
+	case SND_CONFIG_TYPE_POINTER:
+		SNDERR("cannot save runtime pointer type");
+		return -EINVAL;
+	case SND_CONFIG_TYPE_COMPOUND:
+		snd_output_putc(out, '{');
+		snd_output_putc(out, '\n');
+		err = _snd_config_save_children(n, out, level + 1, 0);
+		if (err < 0)
+			return err;
+		for (k = 0; k < level; ++k) {
+			snd_output_putc(out, '\t');
+		}
+		snd_output_putc(out, '}');
+		break;
+	}
+	return 0;
+}
+
+static void id_print(snd_config_t *n, snd_output_t *out, unsigned int joins)
+{
+	if (joins > 0) {
+		assert(n->parent);
+		id_print(n->parent, out, joins - 1);
+		snd_output_putc(out, '.');
+	}
+	string_print(n->id, 1, out);
+}
+
+static int _snd_config_save_children(snd_config_t *config, snd_output_t *out,
+				     unsigned int level, unsigned int joins)
+{
+	unsigned int k;
+	int err;
+	snd_config_iterator_t i, next;
+	assert(config && out);
+	snd_config_for_each(i, next, config) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		if (n->type == SND_CONFIG_TYPE_COMPOUND &&
+		    n->u.compound.join) {
+			err = _snd_config_save_children(n, out, level, joins + 1);
+			if (err < 0)
+				return err;
+			continue;
+		}
+		for (k = 0; k < level; ++k) {
+			snd_output_putc(out, '\t');
+		}
+		id_print(n, out, joins);
+#if 0
+		snd_output_putc(out, ' ');
+		snd_output_putc(out, '=');
+#endif
+		snd_output_putc(out, ' ');
+		err = _snd_config_save_node_value(n, out, level);
+		if (err < 0)
+			return err;
+#if 0
+		snd_output_putc(out, ';');
+#endif
+		snd_output_putc(out, '\n');
+	}
+	return 0;
+}
+#endif
+
+
+/**
+ * \brief Substitutes one configuration node to another.
+ * \param dst Handle to the destination node.
+ * \param src Handle to the source node. Must not be the same as \a dst.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * If both nodes are compounds, the source compound node members are
+ * appended to the destination compound node.
+ *
+ * If the destination node is a compound and the source node is
+ * an ordinary type, the compound members are deleted (including
+ * their contents).
+ *
+ * Otherwise, the source node's value replaces the destination node's
+ * value.
+ *
+ * In any case, a successful call to this function frees the source
+ * node.
+ */
+int snd_config_substitute(snd_config_t *dst, snd_config_t *src)
+{
+	assert(dst && src);
+	if (dst->type == SND_CONFIG_TYPE_COMPOUND &&
+	    src->type == SND_CONFIG_TYPE_COMPOUND) {	/* append */
+		snd_config_iterator_t i, next;
+		snd_config_for_each(i, next, src) {
+			snd_config_t *n = snd_config_iterator_entry(i);
+			n->parent = dst;
+		}
+		src->u.compound.fields.next->prev = &dst->u.compound.fields;
+		src->u.compound.fields.prev->next = &dst->u.compound.fields;
+	} else if (dst->type == SND_CONFIG_TYPE_COMPOUND) {
+		int err;
+		err = snd_config_delete_compound_members(dst);
+		if (err < 0)
+			return err;
+	}
+	free(dst->id);
+	dst->id = src->id;
+	dst->type = src->type;
+	dst->u = src->u;
+	free(src);
+	return 0;
+}
+
+/**
+ * \brief Converts an ASCII string to a configuration node type.
+ * \param[in] ascii A string containing a configuration node type.
+ * \param[out] type The node type corresponding to \a ascii.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * This function recognizes at least the following node types:
+ * <dl>
+ * <dt>integer<dt>#SND_CONFIG_TYPE_INTEGER
+ * <dt>integer64<dt>#SND_CONFIG_TYPE_INTEGER64
+ * <dt>real<dt>#SND_CONFIG_TYPE_REAL
+ * <dt>string<dt>#SND_CONFIG_TYPE_STRING
+ * <dt>compound<dt>#SND_CONFIG_TYPE_COMPOUND
+ * </dl>
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-EINVAL<dd>Unknown note type in \a type.
+ * </dl>
+ */
+int snd_config_get_type_ascii(const char *ascii, snd_config_type_t *type)
+{
+	assert(ascii && type);
+	if (!strcmp(ascii, "integer")) {
+		*type = SND_CONFIG_TYPE_INTEGER;
+		return 0;
+	}
+	if (!strcmp(ascii, "integer64")) {
+		*type = SND_CONFIG_TYPE_INTEGER64;
+		return 0;
+	}
+	if (!strcmp(ascii, "real")) {
+		*type = SND_CONFIG_TYPE_REAL;
+		return 0;
+	}
+	if (!strcmp(ascii, "string")) {
+		*type = SND_CONFIG_TYPE_STRING;
+		return 0;
+	}
+	if (!strcmp(ascii, "compound")) {
+		*type = SND_CONFIG_TYPE_COMPOUND;
+		return 0;
+	}
+	return -EINVAL;
+}
+
+/**
+ * \brief Returns the type of a configuration node.
+ * \param config Handle to the configuration node.
+ * \return The node's type.
+ *
+ * \par Conforming to:
+ * LSB 3.2
+ */
+snd_config_type_t snd_config_get_type(const snd_config_t *config)
+{
+	return config->type;
+}
+
+/**
+ * \brief Returns the id of a configuration node.
+ * \param[in] config Handle to the configuration node.
+ * \param[out] id The function puts the pointer to the id string at the
+ *                address specified by \a id.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * The returned string is owned by the configuration node; the application
+ * must not modify or delete it, and the string becomes invalid when the
+ * node's id changes or when the node is freed.
+ *
+ * If the node does not have an id, \a *id is set to \c NULL.
+ *
+ * \par Conforming to:
+ * LSB 3.2
+ */
+int snd_config_get_id(const snd_config_t *config, const char **id)
+{
+	assert(config && id);
+	*id = config->id;
+	return 0;
+}
+
+/**
+ * \brief Sets the id of a configuration node.
+ * \param config Handle to the configuration node.
+ * \param id The new node id, must not be \c NULL.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * This function stores a copy of \a id in the node.
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-EEXIST<dd>One of \a config's siblings already has the id \a id.
+ * <dt>-EINVAL<dd>The id of a node with a parent cannot be set to \c NULL.
+ * <dt>-ENOMEM<dd>Out of memory.
+ * </dl>
+ */
+int snd_config_set_id(snd_config_t *config, const char *id)
+{
+	snd_config_iterator_t i, next;
+	char *new_id;
+	assert(config);
+	if (id) {
+		if (config->parent) {
+			snd_config_for_each(i, next, config->parent) {
+				snd_config_t *n = snd_config_iterator_entry(i);
+				if (n != config && strcmp(id, n->id) == 0)
+					return -EEXIST;
+			}
+		}
+		new_id = strdup(id);
+		if (!new_id)
+			return -ENOMEM;
+	} else {
+		if (config->parent)
+			return -EINVAL;
+		new_id = NULL;
+	}
+	free(config->id);
+	config->id = new_id;
+	return 0;
+}
+
+/**
+ * \brief Creates a top level configuration node.
+ * \param[out] config Handle to the new node.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * The returned node is an empty compound node without a parent and
+ * without an id.
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-ENOMEM<dd>Out of memory.
+ * </dl>
+ *
+ * \par Conforming to:
+ * LSB 3.2
+ */
+int snd_config_top(snd_config_t **config)
+{
+	assert(config);
+	return _snd_config_make(config, 0, SND_CONFIG_TYPE_COMPOUND);
+}
+
+static int snd_config_load1(snd_config_t *config, snd_input_t *in, int override)
+{
+	int err;
+	input_t input;
+	struct filedesc *fd, *fd_next;
+	assert(config && in);
+	fd = malloc(sizeof(*fd));
+	if (!fd)
+		return -ENOMEM;
+	fd->name = NULL;
+	fd->in = in;
+	fd->line = 1;
+	fd->column = 0;
+	fd->next = NULL;
+	input.current = fd;
+	input.unget = 0;
+	err = parse_defs(config, &input, 0, override);
+	fd = input.current;
+	if (err < 0) {
+		const char *str;
+		switch (err) {
+		case LOCAL_UNTERMINATED_STRING:
+			str = "Unterminated string";
+			err = -EINVAL;
+			break;
+		case LOCAL_UNTERMINATED_QUOTE:
+			str = "Unterminated quote";
+			err = -EINVAL;
+			break;
+		case LOCAL_UNEXPECTED_CHAR:
+			str = "Unexpected char";
+			err = -EINVAL;
+			break;
+		case LOCAL_UNEXPECTED_EOF:
+			str = "Unexpected end of file";
+			err = -EINVAL;
+			break;
+		default:
+			str = strerror(-err);
+			break;
+		}
+		SNDERR("%s:%d:%d:%s", fd->name ? fd->name : "_toplevel_", fd->line, fd->column, str);
+		goto _end;
+	}
+	if (get_char(&input) != LOCAL_UNEXPECTED_EOF) {
+		SNDERR("%s:%d:%d:Unexpected }", fd->name ? fd->name : "", fd->line, fd->column);
+		err = -EINVAL;
+		goto _end;
+	}
+ _end:
+	while (fd->next) {
+		fd_next = fd->next;
+		snd_input_close(fd->in);
+		free(fd->name);
+		free(fd);
+		fd = fd_next;
+	}
+	free(fd);
+	return err;
+}
+
+/**
+ * \brief Loads a configuration tree.
+ * \param config Handle to a top level configuration node.
+ * \param in Input handle to read the configuration from.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * The definitions loaded from the input are added to \a config, which
+ * must be a compound node.
+ *
+ * \par Errors:
+ * Any errors encountered when parsing the input or returned by hooks or
+ * functions.
+ *
+ * \par Conforming to:
+ * LSB 3.2
+ */
+int snd_config_load(snd_config_t *config, snd_input_t *in)
+{
+	return snd_config_load1(config, in, 0);
+}
+
+/**
+ * \brief Loads a configuration tree and overrides existing configuration nodes.
+ * \param config Handle to a top level configuration node.
+ * \param in Input handle to read the configuration from.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * This function loads definitions from \a in into \a config like
+ * #snd_config_load, but the default mode for input nodes is 'override'
+ * (!) instead of 'merge+create' (+).
+ */
+int snd_config_load_override(snd_config_t *config, snd_input_t *in)
+{
+	return snd_config_load1(config, in, 1);
+}
+
+/**
+ * \brief Adds a child to a compound configuration node.
+ * \param parent Handle to a compound configuration node.
+ * \param child Handle to the configuration node to be added.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * This function makes the node \a child a child of the node \a parent.
+ *
+ * The parent node then owns the child node, i.e., the child node gets
+ * deleted together with its parent.
+ *
+ * \a child must have an id.
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-EINVAL<dd>\a child does not have an id.
+ * <dt>-EINVAL<dd>\a child already has a parent.
+ * <dt>-EEXIST<dd>\a parent already contains a child node with the same
+ *                id as \a child.
+ * </dl>
+ *
+ * \par Conforming to:
+ * LSB 3.2
+ */
+int snd_config_add(snd_config_t *parent, snd_config_t *child)
+{
+	snd_config_iterator_t i, next;
+	assert(parent && child);
+	if (!child->id || child->parent)
+		return -EINVAL;
+	snd_config_for_each(i, next, parent) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		if (strcmp(child->id, n->id) == 0)
+			return -EEXIST;
+	}
+	child->parent = parent;
+	list_add_tail(&child->list, &parent->u.compound.fields);
+	return 0;
+}
+
+/**
+ * \brief Removes a configuration node from its tree.
+ * \param config Handle to the configuration node to be removed.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * This function makes \a config a top-level node, i.e., if \a config
+ * has a parent, then \a config is removed from the list of the parent's
+ * children.
+ *
+ * This functions does \e not free the removed node.
+ *
+ * \sa snd_config_delete
+ */
+int snd_config_remove(snd_config_t *config)
+{
+	assert(config);
+	if (config->parent)
+		list_del(&config->list);
+	config->parent = NULL;
+	return 0;
+}
+
+/**
+ * \brief Frees a configuration node.
+ * \param config Handle to the configuration node to be deleted.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * This function frees a configuration node and all its resources.
+ *
+ * If the node is a child node, it is removed from the tree before being
+ * deleted.
+ *
+ * If the node is a compound node, its descendants (the whole subtree)
+ * are deleted recursively.
+ *
+ * \par Conforming to:
+ * LSB 3.2
+ *
+ * \sa snd_config_remove
+ */
+int snd_config_delete(snd_config_t *config)
+{
+	assert(config);
+	switch (config->type) {
+	case SND_CONFIG_TYPE_COMPOUND:
+	{
+		int err;
+		struct list_head *i;
+		i = config->u.compound.fields.next;
+		while (i != &config->u.compound.fields) {
+			struct list_head *nexti = i->next;
+			snd_config_t *child = snd_config_iterator_entry(i);
+			err = snd_config_delete(child);
+			if (err < 0)
+				return err;
+			i = nexti;
+		}
+		break;
+	}
+	case SND_CONFIG_TYPE_STRING:
+		free(config->u.string);
+		break;
+	default:
+		break;
+	}
+	if (config->parent)
+		list_del(&config->list);
+	free(config->id);
+	free(config);
+	return 0;
+}
+
+/**
+ * \brief Deletes the children of a node.
+ * \param config Handle to the compound configuration node.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * This function removes and frees all children of a configuration node.
+ *
+ * Any compound nodes among the children of \a config are deleted
+ * recursively.
+ *
+ * After a successful call to this function, \a config is an empty
+ * compound node.
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-EINVAL<dd>\a config is not a compound node.
+ * </dl>
+ */
+int snd_config_delete_compound_members(const snd_config_t *config)
+{
+	int err;
+	struct list_head *i;
+
+	assert(config);
+	if (config->type != SND_CONFIG_TYPE_COMPOUND)
+		return -EINVAL;
+	i = config->u.compound.fields.next;
+	while (i != &config->u.compound.fields) {
+		struct list_head *nexti = i->next;
+		snd_config_t *child = snd_config_iterator_entry(i);
+		err = snd_config_delete(child);
+		if (err < 0)
+			return err;
+		i = nexti;
+	}
+	return 0;
+}
+
+/**
+ * \brief Creates a configuration node.
+ * \param[out] config The function puts the handle to the new node at
+ *                    the address specified by \a config.
+ * \param[in] id The id of the new node.
+ * \param[in] type The type of the new node.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * This functions creates a new node of the specified type.
+ * The new node has id \a id, which may be \c NULL.
+ *
+ * The value of the new node is zero (for numbers), or \c NULL (for
+ * strings and pointers), or empty (for compound nodes).
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-ENOMEM<dd>Out of memory.
+ * </dl>
+ */
+int snd_config_make(snd_config_t **config, const char *id,
+		    snd_config_type_t type)
+{
+	char *id1;
+	assert(config);
+	if (id) {
+		id1 = strdup(id);
+		if (!id1)
+			return -ENOMEM;
+	} else
+		id1 = NULL;
+	return _snd_config_make(config, &id1, type);
+}
+
+/**
+ * \brief Creates an integer configuration node.
+ * \param[out] config The function puts the handle to the new node at
+ *                    the address specified by \a config.
+ * \param[in] id The id of the new node.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * This function creates a new node of type #SND_CONFIG_TYPE_INTEGER and
+ * with value \c 0.
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-ENOMEM<dd>Out of memory.
+ * </dl>
+ *
+ * \par Conforming to:
+ * LSB 3.2
+ *
+ * \sa snd_config_imake_integer
+ */
+int snd_config_make_integer(snd_config_t **config, const char *id)
+{
+	return snd_config_make(config, id, SND_CONFIG_TYPE_INTEGER);
+}
+
+/**
+ * \brief Creates a 64-bit-integer configuration node.
+ * \param[out] config The function puts the handle to the new node at
+ *                    the address specified by \a config.
+ * \param[in] id The id of the new node.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * This function creates a new node of type #SND_CONFIG_TYPE_INTEGER64
+ * and with value \c 0.
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-ENOMEM<dd>Out of memory.
+ * </dl>
+ *
+ * \par Conforming to:
+ * LSB 3.2
+ *
+ * \sa snd_config_imake_integer64
+ */
+int snd_config_make_integer64(snd_config_t **config, const char *id)
+{
+	return snd_config_make(config, id, SND_CONFIG_TYPE_INTEGER64);
+}
+
+/**
+ * \brief Creates a real number configuration node.
+ * \param[out] config The function puts the handle to the new node at
+ *                    the address specified by \a config.
+ * \param[in] id The id of the new node.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * This function creates a new node of type #SND_CONFIG_TYPE_REAL and
+ * with value \c 0.0.
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-ENOMEM<dd>Out of memory.
+ * </dl>
+ *
+ * \sa snd_config_imake_real
+ */
+int snd_config_make_real(snd_config_t **config, const char *id)
+{
+	return snd_config_make(config, id, SND_CONFIG_TYPE_REAL);
+}
+
+/**
+ * \brief Creates a string configuration node.
+ * \param[out] config The function puts the handle to the new node at
+ *                    the address specified by \a config.
+ * \param[in] id The id of the new node.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * This function creates a new node of type #SND_CONFIG_TYPE_STRING and
+ * with value \c NULL.
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-ENOMEM<dd>Out of memory.
+ * </dl>
+ *
+ * \par Conforming to:
+ * LSB 3.2
+ *
+ * \sa snd_config_imake_string
+ */
+int snd_config_make_string(snd_config_t **config, const char *id)
+{
+	return snd_config_make(config, id, SND_CONFIG_TYPE_STRING);
+}
+
+/**
+ * \brief Creates a pointer configuration node.
+ * \param[out] config The function puts the handle to the new node at
+ *                    the address specified by \a config.
+ * \param[in] id The id of the new node.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * This function creates a new node of type #SND_CONFIG_TYPE_POINTER and
+ * with value \c NULL.
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-ENOMEM<dd>Out of memory.
+ * </dl>
+ *
+ * \sa snd_config_imake_pointer
+ */
+int snd_config_make_pointer(snd_config_t **config, const char *id)
+{
+	return snd_config_make(config, id, SND_CONFIG_TYPE_POINTER);
+}
+
+/**
+ * \brief Creates an empty compound configuration node.
+ * \param[out] config The function puts the handle to the new node at
+ *                    the address specified by \a config.
+ * \param[in] id The id of the new node.
+ * \param[in] join Join flag.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * This function creates a new empty node of type
+ * #SND_CONFIG_TYPE_COMPOUND.
+ *
+ * \a join determines how the compound node's id is printed when the
+ * configuration is saved to a text file.  For example, if the join flag
+ * of compound node \c a is zero, the output will look as follows:
+ * \code
+ * a {
+ *     b "hello"
+ *     c 42
+ * }
+ * \endcode
+ * If, however, the join flag of \c a is nonzero, its id will be joined
+ * with its children's ids, like this:
+ * \code
+ * a.b "hello"
+ * a.c 42
+ * \endcode
+ * An \e empty compound node with its join flag set would result in no
+ * output, i.e., after saving and reloading the configuration file, that
+ * compound node would be lost.
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-ENOMEM<dd>Out of memory.
+ * </dl>
+ *
+ * \par Conforming to:
+ * LSB 3.2
+ */
+int snd_config_make_compound(snd_config_t **config, const char *id,
+			     int join)
+{
+	int err;
+	err = snd_config_make(config, id, SND_CONFIG_TYPE_COMPOUND);
+	if (err < 0)
+		return err;
+	(*config)->u.compound.join = join;
+	return 0;
+}
+
+/**
+ * \brief Creates an integer configuration node with the given initial value.
+ * \param[out] config The function puts the handle to the new node at
+ *                    the address specified by \a config.
+ * \param[in] id The id of the new node.
+ * \param[in] value The initial value of the new node.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * This function creates a new node of type #SND_CONFIG_TYPE_INTEGER and
+ * with value \a value.
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-ENOMEM<dd>Out of memory.
+ * </dl>
+ *
+ * \par Conforming to:
+ * LSB 3.2
+ */
+int snd_config_imake_integer(snd_config_t **config, const char *id, const long value)
+{
+	int err;
+	
+	err = snd_config_make(config, id, SND_CONFIG_TYPE_INTEGER);
+	if (err < 0)
+		return err;
+	(*config)->u.integer = value;
+	return 0;
+}
+
+/**
+ * \brief Creates a 64-bit-integer configuration node with the given initial value.
+ * \param[out] config The function puts the handle to the new node at
+ *                    the address specified by \a config.
+ * \param[in] id The id of the new node.
+ * \param[in] value The initial value of the new node.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * This function creates a new node of type #SND_CONFIG_TYPE_INTEGER64
+ * and with value \a value.
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-ENOMEM<dd>Out of memory.
+ * </dl>
+ *
+ * \par Conforming to:
+ * LSB 3.2
+ */
+int snd_config_imake_integer64(snd_config_t **config, const char *id, const long long value)
+{
+	int err;
+	
+	err = snd_config_make(config, id, SND_CONFIG_TYPE_INTEGER64);
+	if (err < 0)
+		return err;
+	(*config)->u.integer64 = value;
+	return 0;
+}
+
+/**
+ * \brief Creates a real number configuration node with the given initial value.
+ * \param[out] config The function puts the handle to the new node at
+ *                    the address specified by \a config.
+ * \param[in] id The id of the new node.
+ * \param[in] value The initial value of the new node.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * This function creates a new node of type #SND_CONFIG_TYPE_REAL and
+ * with value \a value.
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-ENOMEM<dd>Out of memory.
+ * </dl>
+ */
+int snd_config_imake_real(snd_config_t **config, const char *id, const double value)
+{
+	int err;
+	
+	err = snd_config_make(config, id, SND_CONFIG_TYPE_REAL);
+	if (err < 0)
+		return err;
+	(*config)->u.real = value;
+	return 0;
+}
+
+/**
+ * \brief Creates a string configuration node with the given initial value.
+ * \param[out] config The function puts the handle to the new node at
+ *                    the address specified by \a config.
+ * \param[in] id The id of the new node.
+ * \param[in] value The initial value of the new node.  May be \c NULL.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * This function creates a new node of type #SND_CONFIG_TYPE_STRING and
+ * with a copy of the string \c value.
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-ENOMEM<dd>Out of memory.
+ * </dl>
+ *
+ * \par Conforming to:
+ * LSB 3.2
+ */
+int snd_config_imake_string(snd_config_t **config, const char *id, const char *value)
+{
+	int err;
+	snd_config_t *tmp;
+	
+	err = snd_config_make(&tmp, id, SND_CONFIG_TYPE_STRING);
+	if (err < 0)
+		return err;
+	if (value) {
+		tmp->u.string = strdup(value);
+		if (!tmp->u.string) {
+			snd_config_delete(tmp);
+			return -ENOMEM;
+		}
+	} else {
+		tmp->u.string = NULL;
+	}
+	*config = tmp;
+	return 0;
+}
+
+/**
+ * \brief Creates a pointer configuration node with the given initial value.
+ * \param[out] config The function puts the handle to the new node at
+ *                    the address specified by \a config.
+ * \param[in] id The id of the new node.
+ * \param[in] value The initial value of the new node.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * This function creates a new node of type #SND_CONFIG_TYPE_POINTER and
+ * with value \c value.
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-ENOMEM<dd>Out of memory.
+ * </dl>
+ */
+int snd_config_imake_pointer(snd_config_t **config, const char *id, const void *value)
+{
+	int err;
+	
+	err = snd_config_make(config, id, SND_CONFIG_TYPE_POINTER);
+	if (err < 0)
+		return err;
+	(*config)->u.ptr = value;
+	return 0;
+}
+
+/**
+ * \brief Changes the value of an integer configuration node.
+ * \param config Handle to the configuration node.
+ * \param value The new value for the node.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-EINVAL<dd>\a config is not an integer node.
+ * </dl>
+ *
+ * \par Conforming to:
+ * LSB 3.2
+ */
+int snd_config_set_integer(snd_config_t *config, long value)
+{
+	assert(config);
+	if (config->type != SND_CONFIG_TYPE_INTEGER)
+		return -EINVAL;
+	config->u.integer = value;
+	return 0;
+}
+
+/**
+ * \brief Changes the value of a 64-bit-integer configuration node.
+ * \param config Handle to the configuration node.
+ * \param value The new value for the node.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-EINVAL<dd>\a config is not a 64-bit-integer node.
+ * </dl>
+ *
+ * \par Conforming to:
+ * LSB 3.2
+ */
+int snd_config_set_integer64(snd_config_t *config, long long value)
+{
+	assert(config);
+	if (config->type != SND_CONFIG_TYPE_INTEGER64)
+		return -EINVAL;
+	config->u.integer64 = value;
+	return 0;
+}
+
+/**
+ * \brief Changes the value of a real-number configuration node.
+ * \param config Handle to the configuration node.
+ * \param value The new value for the node.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-EINVAL<dd>\a config is not a real-number node.
+ * </dl>
+ */
+int snd_config_set_real(snd_config_t *config, double value)
+{
+	assert(config);
+	if (config->type != SND_CONFIG_TYPE_REAL)
+		return -EINVAL;
+	config->u.real = value;
+	return 0;
+}
+
+/**
+ * \brief Changes the value of a string configuration node.
+ * \param config Handle to the configuration node.
+ * \param value The new value for the node.  May be \c NULL.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * This function deletes the old string in the node and stores a copy of
+ * \a value string in the node.
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-EINVAL<dd>\a config is not a string node.
+ * </dl>
+ *
+ * \par Conforming to:
+ * LSB 3.2
+ */
+int snd_config_set_string(snd_config_t *config, const char *value)
+{
+	char *new_string;
+	assert(config);
+	if (config->type != SND_CONFIG_TYPE_STRING)
+		return -EINVAL;
+	if (value) {
+		new_string = strdup(value);
+		if (!new_string)
+			return -ENOMEM;
+	} else {
+		new_string = NULL;
+	}
+	free(config->u.string);
+	config->u.string = new_string;
+	return 0;
+}
+
+/**
+ * \brief Changes the value of a pointer configuration node.
+ * \param config Handle to the configuration node.
+ * \param value The new value for the node.  May be \c NULL.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * This function does not free the old pointer in the node.
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-EINVAL<dd>\a config is not a pointer node.
+ * </dl>
+ */
+int snd_config_set_pointer(snd_config_t *config, const void *value)
+{
+	assert(config);
+	if (config->type != SND_CONFIG_TYPE_POINTER)
+		return -EINVAL;
+	config->u.ptr = value;
+	return 0;
+}
+
+/**
+ * \brief Changes the value of a configuration node.
+ * \param config Handle to the configuration node.
+ * \param ascii The new value for the node, as an ASCII string.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * This function changes the node's value to a new value that is parsed
+ * from the string \a ascii.  \a ascii must not be \c NULL, not even for
+ * a string node.
+ *
+ * The node's type does not change, i.e., the string must contain a
+ * valid value with the same type as the node's type.  For a string
+ * node, the node's new value is a copy of \a ascii.
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-EINVAL<dd>\a config is not a number or string node.
+ * <dt>-EINVAL<dd>The value in \a ascii cannot be parsed.
+ * <dt>-ERANGE<dd>The value in \a ascii is too big for the node's type.
+ * <dt>-ENOMEM<dd>Out of memory.
+ * </dl>
+ *
+ * \par Conforming to:
+ * LSB 3.2
+ */
+int snd_config_set_ascii(snd_config_t *config, const char *ascii)
+{
+	assert(config && ascii);
+	switch (config->type) {
+	case SND_CONFIG_TYPE_INTEGER:
+		{
+			long i;
+			int err = safe_strtol(ascii, &i);
+			if (err < 0)
+				return err;
+			config->u.integer = i;
+		}
+		break;
+	case SND_CONFIG_TYPE_INTEGER64:
+		{
+			long long i;
+			int err = safe_strtoll(ascii, &i);
+			if (err < 0)
+				return err;
+			config->u.integer64 = i;
+		}
+		break;
+	case SND_CONFIG_TYPE_REAL:
+		{
+			double d;
+			int err = safe_strtod(ascii, &d);
+			if (err < 0)
+				return err;
+			config->u.real = d;
+			break;
+		}
+	case SND_CONFIG_TYPE_STRING:
+		{
+			char *ptr = strdup(ascii);
+			if (ptr == NULL)
+				return -ENOMEM;
+			free(config->u.string);
+			config->u.string = ptr;
+		}
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+/**
+ * \brief Returns the value of an integer configuration node.
+ * \param[in] config Handle to the configuration node.
+ * \param[out] ptr The node's value.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-EINVAL<dd>\a config is not an integer node.
+ * </dl>
+ *
+ * \par Conforming to:
+ * LSB 3.2
+ */
+int snd_config_get_integer(const snd_config_t *config, long *ptr)
+{
+	assert(config && ptr);
+	if (config->type != SND_CONFIG_TYPE_INTEGER)
+		return -EINVAL;
+	*ptr = config->u.integer;
+	return 0;
+}
+
+/**
+ * \brief Returns the value of a 64-bit-integer configuration node.
+ * \param[in] config Handle to the configuration node.
+ * \param[out] ptr The node's value.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-EINVAL<dd>\a config is not a 64-bit-integer node.
+ * </dl>
+ *
+ * \par Conforming to:
+ * LSB 3.2
+ */
+int snd_config_get_integer64(const snd_config_t *config, long long *ptr)
+{
+	assert(config && ptr);
+	if (config->type != SND_CONFIG_TYPE_INTEGER64)
+		return -EINVAL;
+	*ptr = config->u.integer64;
+	return 0;
+}
+
+/**
+ * \brief Returns the value of a real-number configuration node.
+ * \param[in] config Handle to the configuration node.
+ * \param[out] ptr The node's value.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-EINVAL<dd>\a config is not a real-number node.
+ * </dl>
+ */
+int snd_config_get_real(const snd_config_t *config, double *ptr)
+{
+	assert(config && ptr);
+	if (config->type != SND_CONFIG_TYPE_REAL)
+		return -EINVAL;
+	*ptr = config->u.real;
+	return 0;
+}
+
+/**
+ * \brief Returns the value of a real or integer configuration node.
+ * \param[in] config Handle to the configuration node.
+ * \param[out] ptr The node's value.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * If the node's type is integer or integer64, the value is converted
+ * to the \c double type on the fly.
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-EINVAL<dd>\a config is not a number node.
+ * </dl>
+ */
+int snd_config_get_ireal(const snd_config_t *config, double *ptr)
+{
+	assert(config && ptr);
+	if (config->type == SND_CONFIG_TYPE_REAL)
+		*ptr = config->u.real;
+	else if (config->type == SND_CONFIG_TYPE_INTEGER)
+		*ptr = config->u.integer;
+	else if (config->type == SND_CONFIG_TYPE_INTEGER64)
+		*ptr = config->u.integer64;
+	else
+		return -EINVAL;
+	return 0;
+}
+
+/**
+ * \brief Returns the value of a string configuration node.
+ * \param[in] config Handle to the configuration node.
+ * \param[out] ptr The function puts the node's value at the address
+ *                 specified by \a ptr.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * The returned string is owned by the configuration node; the
+ * application must not modify or delete it, and the string becomes
+ * invalid when the node's value changes or when the node is freed.
+ *
+ * The string may be \c NULL.
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-EINVAL<dd>\a config is not a string node.
+ * </dl>
+ *
+ * \par Conforming to:
+ * LSB 3.2
+ */
+int snd_config_get_string(const snd_config_t *config, const char **ptr)
+{
+	assert(config && ptr);
+	if (config->type != SND_CONFIG_TYPE_STRING)
+		return -EINVAL;
+	*ptr = config->u.string;
+	return 0;
+}
+
+/**
+ * \brief Returns the value of a pointer configuration node.
+ * \param[in] config Handle to the configuration node.
+ * \param[out] ptr The function puts the node's value at the address
+ *                 specified by \a ptr.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-EINVAL<dd>\a config is not a string node.
+ * </dl>
+ */
+int snd_config_get_pointer(const snd_config_t *config, const void **ptr)
+{
+	assert(config && ptr);
+	if (config->type != SND_CONFIG_TYPE_POINTER)
+		return -EINVAL;
+	*ptr = config->u.ptr;
+	return 0;
+}
+
+/**
+ * \brief Returns the value of a configuration node as a string.
+ * \param[in] config Handle to the configuration node.
+ * \param[out] ascii The function puts the pointer to the returned
+ *                   string at the address specified by \a ascii.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * This function dynamically allocates the returned string.  The
+ * application is responsible for deleting it with \c free() when it is
+ * no longer used.
+ *
+ * For a string node with \c NULL value, the returned string is \c NULL.
+ *
+ * Supported node types are #SND_CONFIG_TYPE_INTEGER,
+ * #SND_CONFIG_TYPE_INTEGER64, #SND_CONFIG_TYPE_REAL, and
+ * #SND_CONFIG_TYPE_STRING.
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-EINVAL<dd>\a config is not a (64-bit) integer or real number or
+ *                string node.
+ * <dt>-ENOMEM<dd>Out of memory.
+ * </dl>
+ *
+ * \par Conforming to:
+ * LSB 3.2
+ */
+int snd_config_get_ascii(const snd_config_t *config, char **ascii)
+{
+	assert(config && ascii);
+	switch (config->type) {
+	case SND_CONFIG_TYPE_INTEGER:
+		{
+			char res[12];
+			int err;
+			err = snprintf(res, sizeof(res), "%li", config->u.integer);
+			if (err < 0 || err == sizeof(res)) {
+				assert(0);
+				return -ENOMEM;
+			}
+			*ascii = strdup(res);
+		}
+		break;
+	case SND_CONFIG_TYPE_INTEGER64:
+		{
+			char res[32];
+			int err;
+			err = snprintf(res, sizeof(res), "%Li", config->u.integer64);
+			if (err < 0 || err == sizeof(res)) {
+				assert(0);
+				return -ENOMEM;
+			}
+			*ascii = strdup(res);
+		}
+		break;
+	case SND_CONFIG_TYPE_REAL:
+		{
+			char res[32];
+			int err;
+			err = snprintf(res, sizeof(res), "%-16g", config->u.real);
+			if (err < 0 || err == sizeof(res)) {
+				assert(0);
+				return -ENOMEM;
+			}
+			if (res[0]) {		/* trim the string */
+				char *ptr;
+				ptr = res + strlen(res) - 1;
+				while (ptr != res && *ptr == ' ')
+					ptr--;
+				if (*ptr != ' ')
+					ptr++;
+				*ptr = '\0';
+			}
+			*ascii = strdup(res);
+		}
+		break;
+	case SND_CONFIG_TYPE_STRING:
+		if (config->u.string)
+			*ascii = strdup(config->u.string);
+		else {
+			*ascii = NULL;
+			return 0;
+		}
+		break;
+	default:
+		return -EINVAL;
+	}
+	if (*ascii == NULL)
+		return -ENOMEM;
+	return 0;
+}
+
+/**
+ * \brief Compares the id of a configuration node to a given string.
+ * \param config Handle to the configuration node.
+ * \param id ASCII id.
+ * \return The same value as the result of the \c strcmp function, i.e.,
+ *         less than zero if \a config's id is lexicographically less
+ *         than \a id, zero if \a config's id is equal to id, greater
+ *         than zero otherwise.
+ */
+int snd_config_test_id(const snd_config_t *config, const char *id)
+{
+	assert(config && id);
+	if (config->id)
+		return strcmp(config->id, id);
+	else
+		return -1;
+}
+
+/**
+ * \brief Dumps the contents of a configuration node or tree.
+ * \param config Handle to the (root) configuration node.
+ * \param out Output handle.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * This function writes a textual representation of \a config's value to
+ * the output \a out.
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-EINVAL<dd>A node in the tree has a type that cannot be printed,
+ *                i.e., #SND_CONFIG_TYPE_POINTER.
+ * </dl>
+ *
+ * \par Conforming to:
+ * LSB 3.2
+ */
+int snd_config_save(snd_config_t *config, snd_output_t *out)
+{
+	assert(config && out);
+	if (config->type == SND_CONFIG_TYPE_COMPOUND)
+		return _snd_config_save_children(config, out, 0, 0);
+	else
+		return _snd_config_save_node_value(config, out, 0);
+}
+
+/*
+ *  *** search macros ***
+ */
+
+#ifndef DOC_HIDDEN
+
+#define SND_CONFIG_SEARCH(config, key, result, extra_code) \
+{ \
+	snd_config_t *n; \
+	int err; \
+	const char *p; \
+	assert(config && key); \
+	while (1) { \
+		if (config->type != SND_CONFIG_TYPE_COMPOUND) \
+			return -ENOENT; \
+		{ extra_code ; } \
+		p = strchr(key, '.'); \
+		if (p) { \
+			err = _snd_config_search(config, key, p - key, &n); \
+			if (err < 0) \
+				return err; \
+			config = n; \
+			key = p + 1; \
+		} else \
+			return _snd_config_search(config, key, -1, result); \
+	} \
+}
+
+#define SND_CONFIG_SEARCHA(root, config, key, result, fcn, extra_code) \
+{ \
+	snd_config_t *n; \
+	int err; \
+	const char *p; \
+	assert(config && key); \
+	while (1) { \
+		if (config->type != SND_CONFIG_TYPE_COMPOUND) { \
+			if (snd_config_get_string(config, &p) < 0) \
+				return -ENOENT; \
+			err = fcn(root, root, p, &config); \
+			if (err < 0) \
+				return err; \
+		} \
+		{ extra_code ; } \
+		p = strchr(key, '.'); \
+		if (p) { \
+			err = _snd_config_search(config, key, p - key, &n); \
+			if (err < 0) \
+				return err; \
+			config = n; \
+			key = p + 1; \
+		} else \
+			return _snd_config_search(config, key, -1, result); \
+	} \
+}
+
+#define SND_CONFIG_SEARCHV(config, result, fcn) \
+{ \
+	snd_config_t *n; \
+	va_list arg; \
+	assert(config); \
+	va_start(arg, result); \
+	while (1) { \
+		const char *k = va_arg(arg, const char *); \
+		int err; \
+		if (!k) \
+			break; \
+		err = fcn(config, k, &n); \
+		if (err < 0) \
+			return err; \
+		config = n; \
+	} \
+	va_end(arg); \
+	if (result) \
+		*result = n; \
+	return 0; \
+}
+
+#define SND_CONFIG_SEARCHVA(root, config, result, fcn) \
+{ \
+	snd_config_t *n; \
+	va_list arg; \
+	assert(config); \
+	va_start(arg, result); \
+	while (1) { \
+		const char *k = va_arg(arg, const char *); \
+		int err; \
+		if (!k) \
+			break; \
+		err = fcn(root, config, k, &n); \
+		if (err < 0) \
+			return err; \
+		config = n; \
+	} \
+	va_end(arg); \
+	if (result) \
+		*result = n; \
+	return 0; \
+}
+
+#define SND_CONFIG_SEARCH_ALIAS(config, base, key, result, fcn1, fcn2) \
+{ \
+	snd_config_t *res = NULL; \
+	char *old_key; \
+	int err, first = 1, maxloop = 1000; \
+	assert(config && key); \
+	while (1) { \
+		old_key = strdup(key); \
+		if (old_key == NULL) { \
+			err = -ENOMEM; \
+			res = NULL; \
+			break; \
+		} \
+		err = first && base ? -EIO : fcn1(config, config, key, &res); \
+		if (err < 0) { \
+			if (!base) \
+				break; \
+			err = fcn2(config, config, &res, base, key, NULL); \
+			if (err < 0) \
+				break; \
+		} \
+		if (snd_config_get_string(res, &key) < 0) \
+			break; \
+		assert(key); \
+		if (!first && (strcmp(key, old_key) == 0 || maxloop <= 0)) { \
+			if (maxloop == 0) \
+				SNDERR("maximum loop count reached (circular configuration?)"); \
+			else \
+				SNDERR("key %s refers to itself", key); \
+			err = -EINVAL; \
+			res = NULL; \
+			break; \
+		} \
+		free(old_key); \
+		first = 0; \
+		maxloop--; \
+	} \
+	free(old_key); \
+	if (!res) \
+		return err; \
+	if (result) \
+		*result = res; \
+	return 0; \
+}
+
+#endif /* DOC_HIDDEN */
+
+/**
+ * \brief Searches for a node in a configuration tree.
+ * \param[in] config Handle to the root of the configuration (sub)tree to search.
+ * \param[in] key Search key: one or more node ids, separated with dots.
+ * \param[out] result When \a result != \c NULL, the function puts the
+ *                    handle to the node found at the address specified
+ *                    by \a result.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * This function searches for a child node of \a config that is
+ * identified by \a key, which contains either the id of a direct child
+ * node of \a config, or a series of ids, separated with dots, where
+ * each id specifies a node that is contained in the previous compound
+ * node.
+ *
+ * In the following example, the comment after each node shows the
+ * search key to find that node, assuming that \a config is a handle to
+ * the compound node with id \c config:
+ * \code
+ * config {
+ *     a 42               # "a"
+ *     b {                # "b"
+ *         c "cee"        # "b.c"
+ *         d {            # "b.d"
+ *             e 2.71828  # "b.d.e"
+ *         }
+ *     }
+ * }
+ * \endcode
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-ENOENT<dd>An id in \a key does not exist.
+ * <dt>-ENOENT<dd>\a config or one of its child nodes to be searched is
+ *                not a compound node.
+ * </dl>
+ *
+ * \par Conforming to:
+ * LSB 3.2
+ */
+int snd_config_search(snd_config_t *config, const char *key, snd_config_t **result)
+{
+	SND_CONFIG_SEARCH(config, key, result, );
+}
+
+/**
+ * \brief Searches for a node in a configuration tree, expanding aliases.
+ * \param[in] root Handle to the root configuration node containing
+ *                 alias definitions.
+ * \param[in] config Handle to the root of the configuration (sub)tree to search.
+ * \param[in] key Search key: one or more node keys, separated with dots.
+ * \param[out] result When \a result != \c NULL, the function puts the
+ *                    handle to the node found at the address specified
+ *                    by \a result.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * This functions searches for a child node of \a config like
+ * #snd_config_search.  However, any compound node can also be
+ * identified by an alias, which is a string node whose value is taken
+ * as the id of a compound node below \a root.
+ *
+ * \a root must be a compound node.
+ * \a root and \a config may be the same node.
+ *
+ * For example, with the following configuration, the call
+ * \code
+ * snd_config_searcha(root, config, "a.b.c.d", &result);
+ * \endcode
+ * would return the node with id \c d:
+ * \code
+ * config {
+ *     a {
+ *         b bb
+ *     }
+ * }
+ * root {
+ *     bb {
+ *         c cc
+ *     }
+ *     cc ccc
+ *     ccc {
+ *         d {
+ *             x "icks"
+ *         }
+ *     }
+ * }
+ * \endcode
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-ENOENT<dd>An id in \a key or an alias id does not exist.
+ * <dt>-ENOENT<dd>\a config or one of its child nodes to be searched is
+ *                not a compound or string node.
+ * </dl>
+ */
+int snd_config_searcha(snd_config_t *root, snd_config_t *config, const char *key, snd_config_t **result)
+{
+	SND_CONFIG_SEARCHA(root, config, key, result, snd_config_searcha, );
+}
+
+/**
+ * \brief Searches for a node in a configuration tree.
+ * \param[in] config Handle to the root of the configuration (sub)tree to search.
+ * \param[out] result When \a result != \c NULL, the function puts the
+ *                    handle to the node found at the address specified
+ *                    by \a result.
+ * \param[in] ... One or more concatenated dot-separated search keys,
+ *                terminated with \c NULL.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * This functions searches for a child node of \a config like
+ * #snd_config_search, but the search key is the concatenation of all
+ * passed search key strings.  For example, the call
+ * \code
+ * snd_config_searchv(cfg, &res, "a", "b.c", "d.e", NULL);
+ * \endcode
+ * is equivalent to the call
+ * \code
+ * snd_config_search(cfg, "a.b.c.d.e", &res);
+ * \endcode
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-ENOENT<dd>An id in a search key does not exist.
+ * <dt>-ENOENT<dd>\a config or one of its child nodes to be searched is
+ *                not a compound node.
+ * </dl>
+ *
+ * \par Conforming to:
+ * LSB 3.2
+ */
+int snd_config_searchv(snd_config_t *config, snd_config_t **result, ...)
+{
+	SND_CONFIG_SEARCHV(config, result, snd_config_search);
+}
+
+/**
+ * \brief Searches for a node in a configuration tree, expanding aliases.
+ * \param[in] root Handle to the root configuration node containing
+ *                 alias definitions.
+ * \param[in] config Handle to the root of the configuration (sub)tree to search.
+ * \param[out] result When \a result != \c NULL, the function puts the
+ *                    handle to the node found at the address specified
+ *                    by \a result.
+ * \param[in] ... One or more concatenated dot separated search keys,
+ *                terminated with \c NULL.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * This function searches for a child node of \a config, allowing
+ * aliases, like #snd_config_searcha, but the search key is the
+ * concatenation of all passed seach key strings, like with
+ * #snd_config_searchv.
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-ENOENT<dd>An id in a search key does not exist.
+ * <dt>-ENOENT<dd>\a config or one of its child nodes to be searched is
+ *                not a compound or string node.
+ * </dl>
+ */
+int snd_config_searchva(snd_config_t *root, snd_config_t *config, snd_config_t **result, ...)
+{
+	SND_CONFIG_SEARCHVA(root, config, result, snd_config_searcha);
+}
+
+/**
+ * \brief Searches for a node in a configuration tree, expanding aliases.
+ * \param[in] config Handle to the root of the configuration (sub)tree to search.
+ * \param[in] base Search key base, or \c NULL.
+ * \param[in] key Search key suffix.
+ * \param[out] result When \a result != \c NULL, the function puts the
+ *                    handle to the node found at the address specified
+ *                    by \a result.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * This functions searches for a child node of \a config, allowing
+ * aliases, like #snd_config_searcha.  However, alias definitions are
+ * searched below \a config (there is no separate \a root parameter),
+ * and \a base specifies a seach key that identifies a compound node
+ * that is used to search for an alias definitions that is not found
+ * directly below \a config and that does not contain a period.  In
+ * other words, when \c "id" is not found in \a config, this function
+ * also tries \c "base.id".
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-ENOENT<dd>An id in \a key or an alias id does not exist.
+ * <dt>-ENOENT<dd>\a config or one of its child nodes to be searched is
+ *                not a compound or string node.
+ * </dl>
+ */
+int snd_config_search_alias(snd_config_t *config,
+			    const char *base, const char *key,
+			    snd_config_t **result)
+{
+	SND_CONFIG_SEARCH_ALIAS(config, base, key, result,
+				snd_config_searcha, snd_config_searchva);
+}
+
+static int snd_config_hooks(snd_config_t *config, snd_config_t *private_data);
+
+/**
+ * \brief Searches for a node in a configuration tree and expands hooks.
+ * \param[in,out] config Handle to the root of the configuration
+ *                       (sub)tree to search.
+ * \param[in] key Search key: one or more node keys, separated with dots.
+ * \param[out] result The function puts the handle to the node found at
+ *                    the address specified by \a result.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * This functions searches for a child node of \a config like
+ * #snd_config_search, but any compound nodes to be searched that
+ * contain hooks are modified by the respective hook functions.
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-ENOENT<dd>An id in \a key does not exist.
+ * <dt>-ENOENT<dd>\a config or one of its child nodes to be searched is
+ *                not a compound node.
+ * </dl>
+ * Additionally, any errors encountered when parsing the hook
+ * definitions or returned by the hook functions.
+ */
+int snd_config_search_hooks(snd_config_t *config, const char *key, snd_config_t **result)
+{
+	SND_CONFIG_SEARCH(config, key, result, \
+					err = snd_config_hooks(config, NULL); \
+					if (err < 0) \
+						return err; \
+			 );
+}
+
+/**
+ * \brief Searches for a node in a configuration tree, expanding aliases and hooks.
+ * \param[in] root Handle to the root configuration node containing
+ *                 alias definitions.
+ * \param[in,out] config Handle to the root of the configuration
+ *                       (sub)tree to search.
+ * \param[in] key Search key: one or more node keys, separated with dots.
+ * \param[out] result The function puts the handle to the node found at
+ *                    the address specified by \a result.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * This function searches for a child node of \a config, allowing
+ * aliases, like #snd_config_searcha, and expanding hooks, like
+ * #snd_config_search_hooks.
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-ENOENT<dd>An id in \a key or an alias id does not exist.
+ * <dt>-ENOENT<dd>\a config or one of its child nodes to be searched is
+ *                not a compound node.
+ * </dl>
+ * Additionally, any errors encountered when parsing the hook
+ * definitions or returned by the hook functions.
+ */
+int snd_config_searcha_hooks(snd_config_t *root, snd_config_t *config, const char *key, snd_config_t **result)
+{
+	SND_CONFIG_SEARCHA(root, config, key, result,
+					snd_config_searcha_hooks,
+					err = snd_config_hooks(config, NULL); \
+					if (err < 0) \
+						return err; \
+			 );
+}
+
+/**
+ * \brief Searches for a node in a configuration tree, expanding aliases and hooks.
+ * \param[in] root Handle to the root configuration node containing
+ *                 alias definitions.
+ * \param[in,out] config Handle to the root of the configuration
+ *                       (sub)tree to search.
+ * \param[out] result The function puts the handle to the node found at
+ *                    the address specified by \a result.
+ * \param[in] ... One or more concatenated dot separated search keys,
+ *                terminated with \c NULL.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * This function searches for a child node of \a config, allowing
+ * aliases and expanding hooks like #snd_config_searcha_hooks, but the
+ * search key is the concatenation of all passed seach key strings, like
+ * with #snd_config_searchv.
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-ENOENT<dd>An id in \a key or an alias id does not exist.
+ * <dt>-ENOENT<dd>\a config or one of its child nodes to be searched is
+ *                not a compound node.
+ * </dl>
+ * Additionally, any errors encountered when parsing the hook
+ * definitions or returned by the hook functions.
+ */
+int snd_config_searchva_hooks(snd_config_t *root, snd_config_t *config,
+			      snd_config_t **result, ...)
+{
+	SND_CONFIG_SEARCHVA(root, config, result, snd_config_searcha_hooks);
+}
+
+/**
+ * \brief Searches for a node in a configuration tree, using an alias and expanding hooks.
+ * \param[in] config Handle to the root of the configuration (sub)tree
+ *                   to search.
+ * \param[in] base Search key base, or \c NULL.
+ * \param[in] key Search key suffix.
+ * \param[out] result The function puts the handle to the node found at
+ *                    the address specified by \a result.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * This functions searches for a child node of \a config, allowing
+ * aliases, like #snd_config_search_alias, and expanding hooks, like
+ * #snd_config_search_hooks.
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-ENOENT<dd>An id in \a key or an alias id does not exist.
+ * <dt>-ENOENT<dd>\a config or one of its child nodes to be searched is
+ *                not a compound node.
+ * </dl>
+ * Additionally, any errors encountered when parsing the hook
+ * definitions or returned by the hook functions.
+ */
+int snd_config_search_alias_hooks(snd_config_t *config,
+				  const char *base, const char *key,
+				  snd_config_t **result)
+{
+	SND_CONFIG_SEARCH_ALIAS(config, base, key, result,
+				snd_config_searcha_hooks,
+				snd_config_searchva_hooks);
+}
+
+/** The name of the environment variable containing the files list for #snd_config_update. */
+#define ALSA_CONFIG_PATH_VAR "ALSA_CONFIG_PATH"
+
+/** The name of the default files used by #snd_config_update. */
+#define ALSA_CONFIG_PATH_DEFAULT ALSA_CONFIG_DIR "/alsa.conf"
+
+/**
+ * \ingroup Config
+ * \brief Configuration top-level node (the global configuration).
+ *
+ * This variable contains a handle to the top-level configuration node,
+ * as loaded from global configuration file.
+ *
+ * This variable is initialized or updated by #snd_config_update.
+ * Functions like #snd_pcm_open (that use a device name from the global
+ * configuration) automatically call #snd_config_update.  Before the
+ * first call to #snd_config_update, this variable is \c NULL.
+ *
+ * The global configuration files are specified in the environment
+ * variable \c ALSA_CONFIG_PATH.  If this is not set, the default value
+ * is "/usr/share/alsa/alsa.conf".
+ *
+ * \warning Whenever the configuration tree is updated, all string
+ * pointers and configuration node handles previously obtained from this
+ * variable may become invalid.
+ *
+ * \par Conforming to:
+ * LSB 3.2
+ */
+snd_config_t *snd_config = NULL;
+
+#ifndef DOC_HIDDEN
+struct finfo {
+	char *name;
+	dev_t dev;
+	ino_t ino;
+	time_t mtime;
+};
+
+struct _snd_config_update {
+	unsigned int count;
+	struct finfo *finfo;
+};
+#endif /* DOC_HIDDEN */
+
+static snd_config_update_t *snd_config_global_update = NULL;
+
+static int snd_config_hooks_call(snd_config_t *root, snd_config_t *config, snd_config_t *private_data)
+{
+	void *h = NULL;
+	snd_config_t *c, *func_conf = NULL;
+	char *buf = NULL;
+	const char *lib = NULL, *func_name = NULL;
+	const char *str;
+	int (*func)(snd_config_t *root, snd_config_t *config, snd_config_t **dst, snd_config_t *private_data) = NULL;
+	int err;
+
+	err = snd_config_search(config, "func", &c);
+	if (err < 0) {
+		SNDERR("Field func is missing");
+		return err;
+	}
+	err = snd_config_get_string(c, &str);
+	if (err < 0) {
+		SNDERR("Invalid type for field func");
+		return err;
+	}
+	assert(str);
+	err = snd_config_search_definition(root, "hook_func", str, &func_conf);
+	if (err >= 0) {
+		snd_config_iterator_t i, next;
+		if (snd_config_get_type(func_conf) != SND_CONFIG_TYPE_COMPOUND) {
+			SNDERR("Invalid type for func %s definition", str);
+			err = -EINVAL;
+			goto _err;
+		}
+		snd_config_for_each(i, next, func_conf) {
+			snd_config_t *n = snd_config_iterator_entry(i);
+			const char *id = n->id;
+			if (strcmp(id, "comment") == 0)
+				continue;
+			if (strcmp(id, "lib") == 0) {
+				err = snd_config_get_string(n, &lib);
+				if (err < 0) {
+					SNDERR("Invalid type for %s", id);
+					goto _err;
+				}
+				continue;
+			}
+			if (strcmp(id, "func") == 0) {
+				err = snd_config_get_string(n, &func_name);
+				if (err < 0) {
+					SNDERR("Invalid type for %s", id);
+					goto _err;
+				}
+				continue;
+			}
+			SNDERR("Unknown field %s", id);
+		}
+	}
+	if (!func_name) {
+		int len = 16 + strlen(str) + 1;
+		buf = malloc(len);
+		if (! buf) {
+			err = -ENOMEM;
+			goto _err;
+		}
+		snprintf(buf, len, "snd_config_hook_%s", str);
+		buf[len-1] = '\0';
+		func_name = buf;
+	}
+	h = snd_dlopen(lib, RTLD_NOW);
+	func = h ? snd_dlsym(h, func_name, SND_DLSYM_VERSION(SND_CONFIG_DLSYM_VERSION_HOOK)) : NULL;
+	err = 0;
+	if (!h) {
+		SNDERR("Cannot open shared library %s", lib);
+		err = -ENOENT;
+	} else if (!func) {
+		SNDERR("symbol %s is not defined inside %s", func_name, lib);
+		snd_dlclose(h);
+		err = -ENXIO;
+	}
+	_err:
+	if (func_conf)
+		snd_config_delete(func_conf);
+	if (err >= 0) {
+		snd_config_t *nroot;
+		err = func(root, config, &nroot, private_data);
+		if (err < 0)
+			SNDERR("function %s returned error: %s", func_name, snd_strerror(err));
+		snd_dlclose(h);
+		if (err >= 0 && nroot)
+			err = snd_config_substitute(root, nroot);
+	}
+	free(buf);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static int snd_config_hooks(snd_config_t *config, snd_config_t *private_data)
+{
+	snd_config_t *n;
+	snd_config_iterator_t i, next;
+	int err, hit, idx = 0;
+
+	if ((err = snd_config_search(config, "@hooks", &n)) < 0)
+		return 0;
+	snd_config_lock();
+	snd_config_remove(n);
+	do {
+		hit = 0;
+		snd_config_for_each(i, next, n) {
+			snd_config_t *n = snd_config_iterator_entry(i);
+			const char *id = n->id;
+			long i;
+			err = safe_strtol(id, &i);
+			if (err < 0) {
+				SNDERR("id of field %s is not and integer", id);
+				err = -EINVAL;
+				goto _err;
+			}
+			if (i == idx) {
+				err = snd_config_hooks_call(config, n, private_data);
+				if (err < 0)
+					goto _err;
+				idx++;
+				hit = 1;
+			}
+		}
+	} while (hit);
+	err = 0;
+       _err:
+	snd_config_delete(n);
+	snd_config_unlock();
+	return err;
+}
+
+static int config_filename_filter(const struct dirent *dirent)
+{
+	size_t flen;
+
+	if (dirent == NULL)
+		return 0;
+	if (dirent->d_type == DT_DIR)
+		return 0;
+
+	flen = strlen(dirent->d_name);
+	if (flen <= 5)
+		return 0;
+
+	if (strncmp(&dirent->d_name[flen-5], ".conf", 5) == 0)
+		return 1;
+
+	return 0;
+}
+
+static int config_file_open(snd_config_t *root, const char *filename)
+{
+	snd_input_t *in;
+	int err;
+
+	err = snd_input_stdio_open(&in, filename, "r");
+	if (err >= 0) {
+		err = snd_config_load(root, in);
+		snd_input_close(in);
+		if (err < 0)
+			SNDERR("%s may be old or corrupted: consider to remove or fix it", filename);
+	} else
+		SNDERR("cannot access file %s", filename);
+
+	return err;
+}
+
+/**
+ * \brief Loads and parses the given configurations files.
+ * \param[in] root Handle to the root configuration node.
+ * \param[in] config Handle to the configuration node for this hook.
+ * \param[out] dst The function puts the handle to the configuration
+ *                 node loaded from the file(s) at the address specified
+ *                 by \a dst.
+ * \param[in] private_data Handle to the private data configuration node.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * See \ref confhooks for an example.
+ */
+int snd_config_hook_load(snd_config_t *root, snd_config_t *config, snd_config_t **dst, snd_config_t *private_data)
+{
+	snd_config_t *n;
+	snd_config_iterator_t i, next;
+	struct finfo *fi = NULL;
+	int err, idx = 0, fi_count = 0, errors = 1, hit;
+
+	assert(root && dst);
+	if ((err = snd_config_search(config, "errors", &n)) >= 0) {
+		char *tmp;
+		err = snd_config_get_ascii(n, &tmp);
+		if (err < 0)
+			return err;
+		errors = snd_config_get_bool_ascii(tmp);
+		free(tmp);
+		if (errors < 0) {
+			SNDERR("Invalid bool value in field errors");
+			return errors;
+		}
+	}
+	if ((err = snd_config_search(config, "files", &n)) < 0) {
+		SNDERR("Unable to find field files in the pre-load section");
+		return -EINVAL;
+	}
+	if ((err = snd_config_expand(n, root, NULL, private_data, &n)) < 0) {
+		SNDERR("Unable to expand filenames in the pre-load section");
+		return err;
+	}
+	if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) {
+		SNDERR("Invalid type for field filenames");
+		goto _err;
+	}
+	snd_config_for_each(i, next, n) {
+		snd_config_t *c = snd_config_iterator_entry(i);
+		const char *str;
+		if ((err = snd_config_get_string(c, &str)) < 0) {
+			SNDERR("Field %s is not a string", c->id);
+			goto _err;
+		}
+		fi_count++;
+	}
+	fi = calloc(fi_count, sizeof(*fi));
+	if (fi == NULL) {
+		err = -ENOMEM;
+		goto _err;
+	}
+	do {
+		hit = 0;
+		snd_config_for_each(i, next, n) {
+			snd_config_t *n = snd_config_iterator_entry(i);
+			const char *id = n->id;
+			long i;
+			err = safe_strtol(id, &i);
+			if (err < 0) {
+				SNDERR("id of field %s is not and integer", id);
+				err = -EINVAL;
+				goto _err;
+			}
+			if (i == idx) {
+				char *name;
+				if ((err = snd_config_get_ascii(n, &name)) < 0)
+					goto _err;
+				if ((err = snd_user_file(name, &fi[idx].name)) < 0)
+					fi[idx].name = name;
+				else
+					free(name);
+				idx++;
+				hit = 1;
+			}
+		}
+	} while (hit);
+	for (idx = 0; idx < fi_count; idx++) {
+		struct stat st;
+		if (!errors && access(fi[idx].name, R_OK) < 0)
+			continue;
+		if (stat(fi[idx].name, &st) < 0) {
+			SNDERR("cannot stat file/directory %s", fi[idx].name);
+			continue;
+		}
+		if (S_ISDIR(st.st_mode)) {
+			struct dirent **namelist;
+			int n;
+
+			n = scandir(fi[idx].name, &namelist, config_filename_filter, versionsort);
+			if (n > 0) {
+				int j;
+				err = 0;
+				for (j = 0; j < n; ++j) {
+					if (err >= 0) {
+						int sl = strlen(fi[idx].name) + strlen(namelist[j]->d_name) + 2;
+						char *filename = malloc(sl);
+						snprintf(filename, sl, "%s/%s", fi[idx].name, namelist[j]->d_name);
+						filename[sl-1] = '\0';
+
+						err = config_file_open(root, filename);
+						free(filename);
+					}
+					free(namelist[j]);
+				}
+				free(namelist);
+				if (err < 0)
+					goto _err;
+			}
+		} else if (config_file_open(root, fi[idx].name) < 0)
+			goto _err;
+	}
+	*dst = NULL;
+	err = 0;
+       _err:
+	if (fi)
+		for (idx = 0; idx < fi_count; idx++)
+			free(fi[idx].name);
+	free(fi);
+	snd_config_delete(n);
+	return err;
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(snd_config_hook_load, SND_CONFIG_DLSYM_VERSION_HOOK);
+#endif
+
+#ifndef DOC_HIDDEN
+int snd_determine_driver(int card, char **driver);
+#endif
+
+/**
+ * \brief Loads and parses the given configurations files for each
+ *        installed sound card.
+ * \param[in] root Handle to the root configuration node.
+ * \param[in] config Handle to the configuration node for this hook.
+ * \param[out] dst The function puts the handle to the configuration
+ *                 node loaded from the file(s) at the address specified
+ *                 by \a dst.
+ * \param[in] private_data Handle to the private data configuration node.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * This function works like #snd_config_hook_load, but the files are
+ * loaded once for each sound card.  The driver name is available with
+ * the \c private_string function to customize the file name.
+ */
+int snd_config_hook_load_for_all_cards(snd_config_t *root, snd_config_t *config, snd_config_t **dst, snd_config_t *private_data ATTRIBUTE_UNUSED)
+{
+	int card = -1, err;
+	
+	do {
+		err = snd_card_next(&card);
+		if (err < 0)
+			return err;
+		if (card >= 0) {
+			snd_config_t *n, *private_data = NULL;
+			const char *driver;
+			char *fdriver = NULL;
+			err = snd_determine_driver(card, &fdriver);
+			if (err < 0)
+				return err;
+			if (snd_config_search(root, fdriver, &n) >= 0) {
+				if (snd_config_get_string(n, &driver) < 0)
+					goto __err;
+				assert(driver);
+				while (1) {
+					char *s = strchr(driver, '.');
+					if (s == NULL)
+						break;
+					driver = s + 1;
+				}
+				if (snd_config_search(root, driver, &n) >= 0)
+					goto __err;
+			} else {
+				driver = fdriver;
+			}
+			err = snd_config_imake_string(&private_data, "string", driver);
+			if (err < 0)
+				goto __err;
+			err = snd_config_hook_load(root, config, &n, private_data);
+		      __err:
+			if (private_data)
+				snd_config_delete(private_data);
+			free(fdriver);
+			if (err < 0)
+				return err;
+		}
+	} while (card >= 0);
+	*dst = NULL;
+	return 0;
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(snd_config_hook_load_for_all_cards, SND_CONFIG_DLSYM_VERSION_HOOK);
+#endif
+
+/** 
+ * \brief Updates a configuration tree by rereading the configuration files (if needed).
+ * \param[in,out] _top Address of the handle to the top-level node.
+ * \param[in,out] _update Address of a pointer to private update information.
+ * \param[in] cfgs A list of configuration file names, delimited with ':'.
+ *                 If \p cfgs is \c NULL, the default global
+ *                 configuration file is used.
+ * \return 0 if \a _top was up to date, 1 if the configuration files
+ *         have been reread, otherwise a negative error code.
+ *
+ * The variables pointed to by \a _top and \a _update can be initialized
+ * to \c NULL before the first call to this function.  The private
+ * update information holds information about all used configuration
+ * files that allows this function to detects changes to them; this data
+ * can be freed with #snd_config_update_free.
+ *
+ * The global configuration files are specified in the environment variable
+ * \c ALSA_CONFIG_PATH.
+ *
+ * \warning If the configuration tree is reread, all string pointers and
+ * configuration node handles previously obtained from this tree become
+ * invalid.
+ *
+ * \par Errors:
+ * Any errors encountered when parsing the input or returned by hooks or
+ * functions.
+ */
+int snd_config_update_r(snd_config_t **_top, snd_config_update_t **_update, const char *cfgs)
+{
+	int err;
+	const char *configs, *c;
+	unsigned int k;
+	size_t l;
+	snd_config_update_t *local;
+	snd_config_update_t *update;
+	snd_config_t *top;
+	
+	assert(_top && _update);
+	top = *_top;
+	update = *_update;
+	configs = cfgs;
+	if (!configs) {
+		configs = getenv(ALSA_CONFIG_PATH_VAR);
+		if (!configs || !*configs)
+			configs = ALSA_CONFIG_PATH_DEFAULT;
+	}
+	for (k = 0, c = configs; (l = strcspn(c, ": ")) > 0; ) {
+		c += l;
+		k++;
+		if (!*c)
+			break;
+		c++;
+	}
+	if (k == 0) {
+		local = NULL;
+		goto _reread;
+	}
+	local = (snd_config_update_t *)calloc(1, sizeof(snd_config_update_t));
+	if (!local)
+		return -ENOMEM;
+	local->count = k;
+	local->finfo = calloc(local->count, sizeof(struct finfo));
+	if (!local->finfo) {
+		free(local);
+		return -ENOMEM;
+	}
+	for (k = 0, c = configs; (l = strcspn(c, ": ")) > 0; ) {
+		char name[l + 1];
+		memcpy(name, c, l);
+		name[l] = 0;
+		err = snd_user_file(name, &local->finfo[k].name);
+		if (err < 0)
+			goto _end;
+		c += l;
+		k++;
+		if (!*c)
+			break;
+		c++;
+	}
+	for (k = 0; k < local->count; ++k) {
+		struct stat st;
+		struct finfo *lf = &local->finfo[k];
+		if (stat(lf->name, &st) >= 0) {
+			lf->dev = st.st_dev;
+			lf->ino = st.st_ino;
+			lf->mtime = st.st_mtime;
+		} else {
+			SNDERR("Cannot access file %s", lf->name);
+			free(lf->name);
+			memmove(&local->finfo[k], &local->finfo[k+1], sizeof(struct finfo) * (local->count - k - 1));
+			k--;
+			local->count--;
+		}
+	}
+	if (!update)
+		goto _reread;
+	if (local->count != update->count)
+		goto _reread;
+	for (k = 0; k < local->count; ++k) {
+		struct finfo *lf = &local->finfo[k];
+		struct finfo *uf = &update->finfo[k];
+		if (strcmp(lf->name, uf->name) != 0 ||
+		    lf->dev != uf->dev ||
+		    lf->ino != uf->ino ||
+		    lf->mtime != uf->mtime)
+			goto _reread;
+	}
+	err = 0;
+
+ _end:
+	if (err < 0) {
+		if (top) {
+			snd_config_delete(top);
+			*_top = NULL;
+		}
+		if (update) {
+			snd_config_update_free(update);
+			*_update = NULL;
+		}
+	}
+	if (local)
+		snd_config_update_free(local);
+	return err;
+
+ _reread:
+ 	*_top = NULL;
+ 	*_update = NULL;
+ 	if (update) {
+ 		snd_config_update_free(update);
+ 		update = NULL;
+ 	}
+	if (top) {
+		snd_config_delete(top);
+		top = NULL;
+	}
+	err = snd_config_top(&top);
+	if (err < 0)
+		goto _end;
+	if (!local)
+		goto _skip;
+	for (k = 0; k < local->count; ++k) {
+		snd_input_t *in;
+		err = snd_input_stdio_open(&in, local->finfo[k].name, "r");
+		if (err >= 0) {
+			err = snd_config_load(top, in);
+			snd_input_close(in);
+			if (err < 0) {
+				SNDERR("%s may be old or corrupted: consider to remove or fix it", local->finfo[k].name);
+				goto _end;
+			}
+		} else {
+			SNDERR("cannot access file %s", local->finfo[k].name);
+		}
+	}
+ _skip:
+	err = snd_config_hooks(top, NULL);
+	if (err < 0) {
+		SNDERR("hooks failed, removing configuration");
+		goto _end;
+	}
+	*_top = top;
+	*_update = local;
+	return 1;
+}
+
+/** 
+ * \brief Updates #snd_config by rereading the global configuration files (if needed).
+ * \return 0 if #snd_config was up to date, 1 if #snd_config was
+ *         updated, otherwise a negative error code.
+ *
+ * \warning Whenever #snd_config is updated, all string pointers and
+ * configuration node handles previously obtained from it may become
+ * invalid.
+ *
+ * \par Errors:
+ * Any errors encountered when parsing the input or returned by hooks or
+ * functions.
+ *
+ * \par Conforming to:
+ * LSB 3.2
+ */
+int snd_config_update(void)
+{
+	int err;
+
+	snd_config_lock();
+	err = snd_config_update_r(&snd_config, &snd_config_global_update, NULL);
+	snd_config_unlock();
+	return err;
+}
+
+/** 
+ * \brief Frees a private update structure.
+ * \param[in] update The private update structure to free.
+ * \return Zero if successful, otherwise a negative error code.
+ */
+int snd_config_update_free(snd_config_update_t *update)
+{
+	unsigned int k;
+
+	assert(update);
+	for (k = 0; k < update->count; k++)
+		free(update->finfo[k].name);
+	free(update->finfo);
+	free(update);
+	return 0;
+}
+
+/** 
+ * \brief Frees the global configuration tree in #snd_config.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * This functions releases all resources of the global configuration
+ * tree, and sets #snd_config to \c NULL.
+ *
+ * \par Conforming to:
+ * LSB 3.2
+ */
+int snd_config_update_free_global(void)
+{
+	snd_config_lock();
+	if (snd_config)
+		snd_config_delete(snd_config);
+	snd_config = NULL;
+	if (snd_config_global_update)
+		snd_config_update_free(snd_config_global_update);
+	snd_config_global_update = NULL;
+	snd_config_unlock();
+	/* FIXME: better to place this in another place... */
+	snd_dlobj_cache_cleanup();
+
+	return 0;
+}
+
+/**
+ * \brief Returns an iterator pointing to a node's first child.
+ * \param[in] config Handle to a configuration node.
+ * \return An iterator pointing to \a config's first child.
+ *
+ * \a config must be a compound node.
+ *
+ * The returned iterator is valid if it is not equal to the return value
+ * of #snd_config_iterator_end on \a config.
+ *
+ * Use #snd_config_iterator_entry to get the handle of the node pointed
+ * to.
+ *
+ * \par Conforming to:
+ * LSB 3.2
+ */
+snd_config_iterator_t snd_config_iterator_first(const snd_config_t *config)
+{
+	assert(config->type == SND_CONFIG_TYPE_COMPOUND);
+	return config->u.compound.fields.next;
+}
+
+/**
+ * \brief Returns an iterator pointing to the next sibling.
+ * \param[in] iterator An iterator pointing to a child configuration node.
+ * \return An iterator pointing to the next sibling of \a iterator.
+ *
+ * The returned iterator is valid if it is not equal to the return value
+ * of #snd_config_iterator_end on the node's parent.
+ *
+ * Use #snd_config_iterator_entry to get the handle of the node pointed
+ * to.
+ *
+ * \par Conforming to:
+ * LSB 3.2
+ */
+snd_config_iterator_t snd_config_iterator_next(const snd_config_iterator_t iterator)
+{
+	return iterator->next;
+}
+
+/**
+ * \brief Returns an iterator that ends a node's children list.
+ * \param[in] config Handle to a configuration node.
+ * \return An iterator that indicates the end of \a config's children list.
+ *
+ * \a config must be a compound node.
+ *
+ * The return value can be understood as pointing past the last child of
+ * \a config.
+ *
+ * \par Conforming to:
+ * LSB 3.2
+ */
+snd_config_iterator_t snd_config_iterator_end(const snd_config_t *config)
+{
+	assert(config->type == SND_CONFIG_TYPE_COMPOUND);
+	return (const snd_config_iterator_t)&config->u.compound.fields;
+}
+
+/**
+ * \brief Returns the configuration node handle pointed to by an iterator.
+ * \param[in] iterator A configuration node iterator.
+ * \return The configuration node handle pointed to by \a iterator.
+ *
+ * \par Conforming to:
+ * LSB 3.2
+ */
+snd_config_t *snd_config_iterator_entry(const snd_config_iterator_t iterator)
+{
+	return list_entry(iterator, snd_config_t, list);
+}
+
+#ifndef DOC_HIDDEN
+typedef enum _snd_config_walk_pass {
+	SND_CONFIG_WALK_PASS_PRE,
+	SND_CONFIG_WALK_PASS_POST,
+	SND_CONFIG_WALK_PASS_LEAF,
+} snd_config_walk_pass_t;
+#endif
+
+/* Return 1 if node needs to be attached to parent */
+/* Return 2 if compound is replaced with standard node */
+#ifndef DOC_HIDDEN
+typedef int (*snd_config_walk_callback_t)(snd_config_t *src,
+					  snd_config_t *root,
+					  snd_config_t **dst,
+					  snd_config_walk_pass_t pass,
+					  snd_config_t *private_data);
+#endif
+
+static int snd_config_walk(snd_config_t *src,
+			   snd_config_t *root,
+			   snd_config_t **dst, 
+			   snd_config_walk_callback_t callback,
+			   snd_config_t *private_data)
+{
+	int err;
+	snd_config_iterator_t i, next;
+
+	switch (snd_config_get_type(src)) {
+	case SND_CONFIG_TYPE_COMPOUND:
+		err = callback(src, root, dst, SND_CONFIG_WALK_PASS_PRE, private_data);
+		if (err <= 0)
+			return err;
+		snd_config_for_each(i, next, src) {
+			snd_config_t *s = snd_config_iterator_entry(i);
+			snd_config_t *d = NULL;
+
+			err = snd_config_walk(s, root, (dst && *dst) ? &d : NULL,
+					      callback, private_data);
+			if (err < 0)
+				goto _error;
+			if (err && d) {
+				err = snd_config_add(*dst, d);
+				if (err < 0)
+					goto _error;
+			}
+		}
+		err = callback(src, root, dst, SND_CONFIG_WALK_PASS_POST, private_data);
+		if (err <= 0) {
+		_error:
+			if (dst && *dst)
+				snd_config_delete(*dst);
+		}
+		break;
+	default:
+		err = callback(src, root, dst, SND_CONFIG_WALK_PASS_LEAF, private_data);
+		break;
+	}
+	return err;
+}
+
+static int _snd_config_copy(snd_config_t *src,
+			    snd_config_t *root ATTRIBUTE_UNUSED,
+			    snd_config_t **dst,
+			    snd_config_walk_pass_t pass,
+			    snd_config_t *private_data ATTRIBUTE_UNUSED)
+{
+	int err;
+	const char *id = src->id;
+	snd_config_type_t type = snd_config_get_type(src);
+	switch (pass) {
+	case SND_CONFIG_WALK_PASS_PRE:
+		err = snd_config_make_compound(dst, id, src->u.compound.join);
+		if (err < 0)
+			return err;
+		break;
+	case SND_CONFIG_WALK_PASS_LEAF:
+		err = snd_config_make(dst, id, type);
+		if (err < 0)
+			return err;
+		switch (type) {
+		case SND_CONFIG_TYPE_INTEGER:
+		{
+			long v;
+			err = snd_config_get_integer(src, &v);
+			assert(err >= 0);
+			snd_config_set_integer(*dst, v);
+			break;
+		}
+		case SND_CONFIG_TYPE_INTEGER64:
+		{
+			long long v;
+			err = snd_config_get_integer64(src, &v);
+			assert(err >= 0);
+			snd_config_set_integer64(*dst, v);
+			break;
+		}
+		case SND_CONFIG_TYPE_REAL:
+		{
+			double v;
+			err = snd_config_get_real(src, &v);
+			assert(err >= 0);
+			snd_config_set_real(*dst, v);
+			break;
+		}
+		case SND_CONFIG_TYPE_STRING:
+		{
+			const char *s;
+			err = snd_config_get_string(src, &s);
+			assert(err >= 0);
+			err = snd_config_set_string(*dst, s);
+			if (err < 0)
+				return err;
+			break;
+		}
+		default:
+			assert(0);
+		}
+		break;
+	default:
+		break;
+	}
+	return 1;
+}
+
+/**
+ * \brief Creates a copy of a configuration node.
+ * \param[out] dst The function puts the handle to the new configuration
+ *                 node at the address specified by \a dst.
+ * \param[in] src Handle to the source configuration node.
+ * \return A non-negative value if successful, otherwise a negative error code.
+ *
+ * This function creates a deep copy, i.e., if \a src is a compound
+ * node, all children are copied recursively.
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-ENOMEM<dd>Out of memory.
+ * </dl>
+ *
+ * \par Conforming to:
+ * LSB 3.2
+ */
+int snd_config_copy(snd_config_t **dst,
+		    snd_config_t *src)
+{
+	return snd_config_walk(src, NULL, dst, _snd_config_copy, NULL);
+}
+
+static int _snd_config_expand(snd_config_t *src,
+			      snd_config_t *root ATTRIBUTE_UNUSED,
+			      snd_config_t **dst,
+			      snd_config_walk_pass_t pass,
+			      snd_config_t *private_data)
+{
+	int err;
+	const char *id = src->id;
+	snd_config_type_t type = snd_config_get_type(src);
+	switch (pass) {
+	case SND_CONFIG_WALK_PASS_PRE:
+	{
+		if (id && strcmp(id, "@args") == 0)
+			return 0;
+		err = snd_config_make_compound(dst, id, src->u.compound.join);
+		if (err < 0)
+			return err;
+		break;
+	}
+	case SND_CONFIG_WALK_PASS_LEAF:
+		switch (type) {
+		case SND_CONFIG_TYPE_INTEGER:
+		{
+			long v;
+			err = snd_config_get_integer(src, &v);
+			assert(err >= 0);
+			err = snd_config_imake_integer(dst, id, v);
+			if (err < 0)
+				return err;
+			break;
+		}
+		case SND_CONFIG_TYPE_INTEGER64:
+		{
+			long long v;
+			err = snd_config_get_integer64(src, &v);
+			assert(err >= 0);
+			err = snd_config_imake_integer64(dst, id, v);
+			if (err < 0)
+				return err;
+			break;
+		}
+		case SND_CONFIG_TYPE_REAL:
+		{
+			double v;
+			err = snd_config_get_real(src, &v);
+			assert(err >= 0);
+			err = snd_config_imake_real(dst, id, v);
+			if (err < 0)
+				return err;
+			break;
+		}
+		case SND_CONFIG_TYPE_STRING:
+		{
+			const char *s;
+			snd_config_t *val;
+			snd_config_t *vars = private_data;
+			snd_config_get_string(src, &s);
+			if (s && *s == '$') {
+				s++;
+				if (snd_config_search(vars, s, &val) < 0)
+					return 0;
+				err = snd_config_copy(dst, val);
+				if (err < 0)
+					return err;
+				err = snd_config_set_id(*dst, id);
+				if (err < 0) {
+					snd_config_delete(*dst);
+					return err;
+				}
+			} else {
+				err = snd_config_imake_string(dst, id, s);
+				if (err < 0)
+					return err;
+			}
+			break;
+		}
+		default:
+			assert(0);
+		}
+		break;
+	default:
+		break;
+	}
+	return 1;
+}
+
+static int _snd_config_evaluate(snd_config_t *src,
+				snd_config_t *root,
+				snd_config_t **dst ATTRIBUTE_UNUSED,
+				snd_config_walk_pass_t pass,
+				snd_config_t *private_data)
+{
+	int err;
+	if (pass == SND_CONFIG_WALK_PASS_PRE) {
+		char *buf = NULL;
+		const char *lib = NULL, *func_name = NULL;
+		const char *str;
+		int (*func)(snd_config_t **dst, snd_config_t *root,
+			    snd_config_t *src, snd_config_t *private_data) = NULL;
+		void *h = NULL;
+		snd_config_t *c, *func_conf = NULL;
+		err = snd_config_search(src, "@func", &c);
+		if (err < 0)
+			return 1;
+		err = snd_config_get_string(c, &str);
+		if (err < 0) {
+			SNDERR("Invalid type for @func");
+			return err;
+		}
+		assert(str);
+		err = snd_config_search_definition(root, "func", str, &func_conf);
+		if (err >= 0) {
+			snd_config_iterator_t i, next;
+			if (snd_config_get_type(func_conf) != SND_CONFIG_TYPE_COMPOUND) {
+				SNDERR("Invalid type for func %s definition", str);
+				goto _err;
+			}
+			snd_config_for_each(i, next, func_conf) {
+				snd_config_t *n = snd_config_iterator_entry(i);
+				const char *id = n->id;
+				if (strcmp(id, "comment") == 0)
+					continue;
+				if (strcmp(id, "lib") == 0) {
+					err = snd_config_get_string(n, &lib);
+					if (err < 0) {
+						SNDERR("Invalid type for %s", id);
+						goto _err;
+					}
+					continue;
+				}
+				if (strcmp(id, "func") == 0) {
+					err = snd_config_get_string(n, &func_name);
+					if (err < 0) {
+						SNDERR("Invalid type for %s", id);
+						goto _err;
+					}
+					continue;
+				}
+				SNDERR("Unknown field %s", id);
+			}
+		}
+		if (!func_name) {
+			int len = 9 + strlen(str) + 1;
+			buf = malloc(len);
+			if (! buf) {
+				err = -ENOMEM;
+				goto _err;
+			}
+			snprintf(buf, len, "snd_func_%s", str);
+			buf[len-1] = '\0';
+			func_name = buf;
+		}
+		h = snd_dlopen(lib, RTLD_NOW);
+		if (h)
+			func = snd_dlsym(h, func_name, SND_DLSYM_VERSION(SND_CONFIG_DLSYM_VERSION_EVALUATE));
+		err = 0;
+		if (!h) {
+			SNDERR("Cannot open shared library %s", lib);
+			err = -ENOENT;
+			goto _errbuf;
+		} else if (!func) {
+			SNDERR("symbol %s is not defined inside %s", func_name, lib);
+			snd_dlclose(h);
+			err = -ENXIO;
+			goto _errbuf;
+		}
+	       _err:
+		if (func_conf)
+			snd_config_delete(func_conf);
+		if (err >= 0) {
+			snd_config_t *eval;
+			err = func(&eval, root, src, private_data);
+			if (err < 0)
+				SNDERR("function %s returned error: %s", func_name, snd_strerror(err));
+			snd_dlclose(h);
+			if (err >= 0 && eval) {
+				/* substitute merges compound members */
+				/* we don't want merging at all */
+				err = snd_config_delete_compound_members(src);
+				if (err >= 0)
+					err = snd_config_substitute(src, eval);
+			}
+		}
+	       _errbuf:
+		free(buf);
+		if (err < 0)
+			return err;
+		return 0;
+	}
+	return 1;
+}
+
+/**
+ * \brief Evaluates a configuration node at runtime.
+ * \param[in,out] config Handle to the source configuration node.
+ * \param[in] root Handle to the root of the source configuration.
+ * \param[in] private_data Handle to the private data node for runtime evaluation.
+ * \param result Must be \c NULL.
+ * \return A non-negative value if successful, otherwise a negative error code.
+ *
+ * This function evaluates any functions (\c \@func) in \a config and
+ * replaces those nodes with the respective function results.
+ */
+int snd_config_evaluate(snd_config_t *config, snd_config_t *root,
+		        snd_config_t *private_data, snd_config_t **result)
+{
+	/* FIXME: Only in place evaluation is currently implemented */
+	assert(result == NULL);
+	return snd_config_walk(config, root, result, _snd_config_evaluate, private_data);
+}
+
+static int load_defaults(snd_config_t *subs, snd_config_t *defs)
+{
+	snd_config_iterator_t d, dnext;
+	snd_config_for_each(d, dnext, defs) {
+		snd_config_t *def = snd_config_iterator_entry(d);
+		snd_config_iterator_t f, fnext;
+		if (snd_config_get_type(def) != SND_CONFIG_TYPE_COMPOUND)
+			continue;
+		snd_config_for_each(f, fnext, def) {
+			snd_config_t *fld = snd_config_iterator_entry(f);
+			const char *id = fld->id;
+			if (strcmp(id, "type") == 0)
+				continue;
+			if (strcmp(id, "default") == 0) {
+				snd_config_t *deflt;
+				int err;
+				err = snd_config_copy(&deflt, fld);
+				if (err < 0)
+					return err;
+				err = snd_config_set_id(deflt, def->id);
+				if (err < 0) {
+					snd_config_delete(deflt);
+					return err;
+				}
+				err = snd_config_add(subs, deflt);
+				if (err < 0) {
+					snd_config_delete(deflt);
+					return err;
+				}
+				continue;
+			}
+			SNDERR("Unknown field %s", id);
+			return -EINVAL;
+		}
+	}
+	return 0;
+}
+
+static void skip_blank(const char **ptr)
+{
+	while (1) {
+		switch (**ptr) {
+		case ' ':
+		case '\f':
+		case '\t':
+		case '\n':
+		case '\r':
+			break;
+		default:
+			return;
+		}
+		(*ptr)++;
+	}
+}
+
+static int parse_char(const char **ptr)
+{
+	int c;
+	assert(**ptr == '\\');
+	(*ptr)++;
+	c = **ptr;
+	switch (c) {
+	case 'n':
+		c = '\n';
+		break;
+	case 't':
+		c = '\t';
+		break;
+	case 'v':
+		c = '\v';
+		break;
+	case 'b':
+		c = '\b';
+		break;
+	case 'r':
+		c = '\r';
+		break;
+	case 'f':
+		c = '\f';
+		break;
+	case '0' ... '7':
+	{
+		int num = c - '0';
+		int i = 1;
+		(*ptr)++;
+		do {
+			c = **ptr;
+			if (c < '0' || c > '7')
+				break;
+			num = num * 8 + c - '0';
+			i++;
+			(*ptr)++;
+		} while (i < 3);
+		return num;
+	}
+	default:
+		break;
+	}
+	(*ptr)++;
+	return c;
+}
+
+static int parse_id(const char **ptr)
+{
+	if (!**ptr)
+		return -EINVAL;
+	while (1) {
+		switch (**ptr) {
+		case '\f':
+		case '\t':
+		case '\n':
+		case '\r':
+		case ',':
+		case '=':
+		case '\0':
+			return 0;
+		default:
+			break;
+		}
+		(*ptr)++;
+	}
+}
+
+static int parse_string(const char **ptr, char **val)
+{
+	const size_t bufsize = 256;
+	char _buf[bufsize];
+	char *buf = _buf;
+	size_t alloc = bufsize;
+	char delim = **ptr;
+	size_t idx = 0;
+	(*ptr)++;
+	while (1) {
+		int c = **ptr;
+		switch (c) {
+		case '\0':
+			SNDERR("Unterminated string");
+			return -EINVAL;
+		case '\\':
+			c = parse_char(ptr);
+			if (c < 0)
+				return c;
+			break;
+		default:
+			(*ptr)++;
+			if (c == delim) {
+				*val = malloc(idx + 1);
+				if (!*val)
+					return -ENOMEM;
+				memcpy(*val, buf, idx);
+				(*val)[idx] = 0;
+				if (alloc > bufsize)
+					free(buf);
+				return 0;
+			}
+		}
+		if (idx >= alloc) {
+			size_t old_alloc = alloc;
+			alloc *= 2;
+			if (old_alloc == bufsize) {
+				buf = malloc(alloc);
+				memcpy(buf, _buf, old_alloc);
+			} else {
+				buf = realloc(buf, alloc);
+			}
+			if (!buf)
+				return -ENOMEM;
+		}
+		buf[idx++] = c;
+	}
+}
+				
+
+/* Parse var=val or val */
+static int parse_arg(const char **ptr, unsigned int *varlen, char **val)
+{
+	const char *str;
+	int err, vallen;
+	skip_blank(ptr);
+	str = *ptr;
+	if (*str == '"' || *str == '\'') {
+		err = parse_string(ptr, val);
+		if (err < 0)
+			return err;
+		*varlen = 0;
+		return 0;
+	}
+	err = parse_id(ptr);
+	if (err < 0)
+		return err;
+	vallen = *ptr - str;
+	skip_blank(ptr);
+	if (**ptr != '=') {
+		*varlen = 0;
+		goto _value;
+	}
+	*varlen = vallen;
+	(*ptr)++;
+	skip_blank(ptr);
+	str = *ptr;
+	if (*str == '"' || *str == '\'') {
+		err = parse_string(ptr, val);
+		if (err < 0)
+			return err;
+		return 0;
+	}
+	err = parse_id(ptr);
+	if (err < 0)
+		return err;
+	vallen = *ptr - str;
+ _value:
+	*val = malloc(vallen + 1);
+	if (!*val)
+		return -ENOMEM;
+	memcpy(*val, str, vallen);
+	(*val)[vallen] = 0;
+	return 0;
+}
+
+
+/* val1, val2, ...
+ * var1=val1,var2=val2,...
+ * { conf syntax }
+ */
+static int parse_args(snd_config_t *subs, const char *str, snd_config_t *defs)
+{
+	int err;
+	int arg = 0;
+	if (str == NULL)
+		return 0;
+	skip_blank(&str);
+	if (!*str)
+		return 0;
+	if (*str == '{') {
+		int len = strlen(str);
+		snd_input_t *input;
+		snd_config_iterator_t i, next;
+		while (1) {
+			switch (str[--len]) {
+			case ' ':
+			case '\f':
+			case '\t':
+			case '\n':
+			case '\r':
+				continue;
+			default:
+				break;
+			}
+			break;
+		}
+		if (str[len] != '}')
+			return -EINVAL;
+		err = snd_input_buffer_open(&input, str + 1, len - 1);
+		if (err < 0)
+			return err;
+		err = snd_config_load_override(subs, input);
+		snd_input_close(input);
+		if (err < 0)
+			return err;
+		snd_config_for_each(i, next, subs) {
+			snd_config_t *n = snd_config_iterator_entry(i);
+			snd_config_t *d;
+			const char *id = n->id;
+			err = snd_config_search(defs, id, &d);
+			if (err < 0) {
+				SNDERR("Unknown parameter %s", id);
+				return err;
+			}
+		}
+		return 0;
+	}
+	
+	while (1) {
+		char buf[256];
+		const char *var = buf;
+		unsigned int varlen;
+		snd_config_t *def, *sub, *typ;
+		const char *new = str;
+		const char *tmp;
+		char *val = NULL;
+		err = parse_arg(&new, &varlen, &val);
+		if (err < 0)
+			goto _err;
+		if (varlen > 0) {
+			assert(varlen < sizeof(buf));
+			memcpy(buf, str, varlen);
+			buf[varlen] = 0;
+		} else {
+			sprintf(buf, "%d", arg);
+		}
+		err = snd_config_search_alias(defs, NULL, var, &def);
+		if (err < 0) {
+			SNDERR("Unknown parameter %s", var);
+			goto _err;
+		}
+		if (snd_config_get_type(def) != SND_CONFIG_TYPE_COMPOUND) {
+			SNDERR("Parameter %s definition is not correct", var);
+			err = -EINVAL;
+			goto _err;
+		}
+		var = def->id;
+		err = snd_config_search(subs, var, &sub);
+		if (err >= 0)
+			snd_config_delete(sub);
+		err = snd_config_search(def, "type", &typ);
+		if (err < 0) {
+		_invalid_type:
+			SNDERR("Parameter %s definition is missing a valid type info", var);
+			goto _err;
+		}
+		err = snd_config_get_string(typ, &tmp);
+		if (err < 0 || !tmp)
+			goto _invalid_type;
+		if (strcmp(tmp, "integer") == 0) {
+			long v;
+			err = snd_config_make(&sub, var, SND_CONFIG_TYPE_INTEGER);
+			if (err < 0)
+				goto _err;
+			err = safe_strtol(val, &v);
+			if (err < 0) {
+				SNDERR("Parameter %s must be an integer", var);
+				goto _err;
+			}
+			err = snd_config_set_integer(sub, v);
+			if (err < 0)
+				goto _err;
+		} else if (strcmp(tmp, "integer64") == 0) {
+			long long v;
+			err = snd_config_make(&sub, var, SND_CONFIG_TYPE_INTEGER64);
+			if (err < 0)
+				goto _err;
+			err = safe_strtoll(val, &v);
+			if (err < 0) {
+				SNDERR("Parameter %s must be an integer", var);
+				goto _err;
+			}
+			err = snd_config_set_integer64(sub, v);
+			if (err < 0)
+				goto _err;
+		} else if (strcmp(tmp, "real") == 0) {
+			double v;
+			err = snd_config_make(&sub, var, SND_CONFIG_TYPE_REAL);
+			if (err < 0)
+				goto _err;
+			err = safe_strtod(val, &v);
+			if (err < 0) {
+				SNDERR("Parameter %s must be a real", var);
+				goto _err;
+			}
+			err = snd_config_set_real(sub, v);
+			if (err < 0)
+				goto _err;
+		} else if (strcmp(tmp, "string") == 0) {
+			err = snd_config_make(&sub, var, SND_CONFIG_TYPE_STRING);
+			if (err < 0)
+				goto _err;
+			err = snd_config_set_string(sub, val);
+			if (err < 0)
+				goto _err;
+		} else {
+			err = -EINVAL;
+			goto _invalid_type;
+		}
+		err = snd_config_set_id(sub, var);
+		if (err < 0)
+			goto _err;
+		err = snd_config_add(subs, sub);
+		if (err < 0) {
+		_err:
+			free(val);
+			return err;
+		}
+		free(val);
+		if (!*new)
+			break;
+		if (*new != ',')
+			return -EINVAL;
+		str = new + 1;
+		arg++;
+	}
+	return 0;
+}
+
+/**
+ * \brief Expands a configuration node, applying arguments and functions.
+ * \param[in] config Handle to the configuration node.
+ * \param[in] root Handle to the root configuration node.
+ * \param[in] args Arguments string, can be \c NULL.
+ * \param[in] private_data Handle to the private data node for functions.
+ * \param[out] result The function puts the handle to the result
+ *                    configuration node at the address specified by
+ *                    \a result.
+ * \return A non-negative value if successful, otherwise a negative error code.
+ *
+ * If \a config has arguments (defined by a child with id \c \@args),
+ * this function replaces any string node beginning with $ with the
+ * respective argument value, or the default argument value, or nothing.
+ * Furthermore, any functions are evaluated (see #snd_config_evaluate).
+ * The resulting copy of \a config is returned in \a result.
+ */
+int snd_config_expand(snd_config_t *config, snd_config_t *root, const char *args,
+		      snd_config_t *private_data, snd_config_t **result)
+{
+	int err;
+	snd_config_t *defs, *subs = NULL, *res;
+	err = snd_config_search(config, "@args", &defs);
+	if (err < 0) {
+		if (args != NULL) {
+			SNDERR("Unknown parameters %s", args);
+			return -EINVAL;
+		}
+		err = snd_config_copy(&res, config);
+		if (err < 0)
+			return err;
+	} else {
+		err = snd_config_top(&subs);
+		if (err < 0)
+			return err;
+		err = load_defaults(subs, defs);
+		if (err < 0) {
+			SNDERR("Load defaults error: %s", snd_strerror(err));
+			goto _end;
+		}
+		err = parse_args(subs, args, defs);
+		if (err < 0) {
+			SNDERR("Parse arguments error: %s", snd_strerror(err));
+			goto _end;
+		}
+		err = snd_config_evaluate(subs, root, private_data, NULL);
+		if (err < 0) {
+			SNDERR("Args evaluate error: %s", snd_strerror(err));
+			goto _end;
+		}
+		err = snd_config_walk(config, root, &res, _snd_config_expand, subs);
+		if (err < 0) {
+			SNDERR("Expand error (walk): %s", snd_strerror(err));
+			goto _end;
+		}
+	}
+	err = snd_config_evaluate(res, root, private_data, NULL);
+	if (err < 0) {
+		SNDERR("Evaluate error: %s", snd_strerror(err));
+		snd_config_delete(res);
+		goto _end;
+	}
+	*result = res;
+	err = 1;
+ _end:
+ 	if (subs)
+		snd_config_delete(subs);
+	return err;
+}
+
+/**
+ * \brief Searches for a definition in a configuration tree, using
+ *        aliases and expanding hooks and arguments.
+ * \param[in] config Handle to the configuration (sub)tree to search.
+ * \param[in] base Implicit key base, or \c NULL for none.
+ * \param[in] name Key suffix, optionally with arguments.
+ * \param[out] result The function puts the handle to the expanded found
+ *                    node at the address specified by \a result.
+ * \return A non-negative value if successful, otherwise a negative error code.
+ *
+ * This functions searches for a child node of \a config, allowing
+ * aliases and expanding hooks, like #snd_config_search_alias_hooks.
+ *
+ * If \a name contains a colon (:), the rest of the string after the
+ * colon contains arguments that are expanded as with
+ * #snd_config_expand.
+ *
+ * In any case, \a result is a new node that must be freed by the
+ * caller.
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-ENOENT<dd>An id in \a key or an alias id does not exist.
+ * <dt>-ENOENT<dd>\a config or one of its child nodes to be searched is
+ *                not a compound node.
+ * </dl>
+ * Additionally, any errors encountered when parsing the hook
+ * definitions or arguments, or returned by (hook) functions.
+ */
+int snd_config_search_definition(snd_config_t *config,
+				 const char *base, const char *name,
+				 snd_config_t **result)
+{
+	snd_config_t *conf;
+	char *key;
+	const char *args = strchr(name, ':');
+	int err;
+	if (args) {
+		args++;
+		key = alloca(args - name);
+		memcpy(key, name, args - name - 1);
+		key[args - name - 1] = '\0';
+	} else {
+		key = (char *) name;
+	}
+	/*
+	 *  if key contains dot (.), the implicit base is ignored
+	 *  and the key starts from root given by the 'config' parameter
+	 */
+	snd_config_lock();
+	err = snd_config_search_alias_hooks(config, strchr(key, '.') ? NULL : base, key, &conf);
+	if (err < 0) {
+		snd_config_unlock();
+		return err;
+	}
+	err = snd_config_expand(conf, config, args, NULL, result);
+	snd_config_unlock();
+	return err;
+}
+
+#ifndef DOC_HIDDEN
+void snd_config_set_hop(snd_config_t *conf, int hop)
+{
+	conf->hop = hop;
+}
+
+int snd_config_check_hop(snd_config_t *conf)
+{
+	if (conf) {
+		if (conf->hop >= SND_CONF_MAX_HOPS) {
+			SYSERR("Too many definition levels (looped?)\n");
+			return -EINVAL;
+		}
+		return conf->hop;
+	}
+	return 0;
+}
+#endif
+
+#if 0
+/* Not strictly needed, but useful to check for memory leaks */
+void _snd_config_end(void) __attribute__ ((destructor));
+
+static void _snd_config_end(void)
+{
+	int k;
+	if (snd_config)
+		snd_config_delete(snd_config);
+	snd_config = 0;
+	for (k = 0; k < files_info_count; ++k)
+		free(files_info[k].name);
+	free(files_info);
+	files_info = NULL;
+	files_info_count = 0;
+}
+#endif
diff --git a/src/conf/Makefile.am b/src/conf/Makefile.am
new file mode 100644
index 0000000..456454f
--- /dev/null
+++ b/src/conf/Makefile.am
@@ -0,0 +1,15 @@
+SUBDIRS=cards pcm alsa.conf.d
+
+cfg_files = alsa.conf
+if BUILD_ALISP
+cfg_files += sndo-mixer.alisp
+endif
+if BUILD_MODULES
+cfg_files += smixer.conf
+endif
+
+EXTRA_DIST = $(cfg_files)
+
+alsaconfigdir = @ALSA_CONFIG_DIR@
+alsadir = $(alsaconfigdir)
+alsa_DATA = $(cfg_files)
diff --git a/usr/share/alsa/alsa.conf b/src/conf/alsa.conf
similarity index 100%
rename from usr/share/alsa/alsa.conf
rename to src/conf/alsa.conf
diff --git a/src/conf/alsa.conf.d/Makefile.am b/src/conf/alsa.conf.d/Makefile.am
new file mode 100644
index 0000000..c91661e
--- /dev/null
+++ b/src/conf/alsa.conf.d/Makefile.am
@@ -0,0 +1,8 @@
+alsaconfigdir = @ALSA_CONFIG_DIR@
+alsadir = $(alsaconfigdir)/alsa.conf.d
+cfg_files = README
+
+alsa_DATA = $(cfg_files)
+
+EXTRA_DIST = \
+	$(cfg_files)
diff --git a/usr/share/alsa/alsa.conf.d/README b/src/conf/alsa.conf.d/README
similarity index 100%
rename from usr/share/alsa/alsa.conf.d/README
rename to src/conf/alsa.conf.d/README
diff --git a/usr/share/alsa/cards/AACI.conf b/src/conf/cards/AACI.conf
similarity index 100%
rename from usr/share/alsa/cards/AACI.conf
rename to src/conf/cards/AACI.conf
diff --git a/usr/share/alsa/cards/ATIIXP-MODEM.conf b/src/conf/cards/ATIIXP-MODEM.conf
similarity index 100%
rename from usr/share/alsa/cards/ATIIXP-MODEM.conf
rename to src/conf/cards/ATIIXP-MODEM.conf
diff --git a/usr/share/alsa/cards/ATIIXP-SPDMA.conf b/src/conf/cards/ATIIXP-SPDMA.conf
similarity index 100%
rename from usr/share/alsa/cards/ATIIXP-SPDMA.conf
rename to src/conf/cards/ATIIXP-SPDMA.conf
diff --git a/usr/share/alsa/cards/ATIIXP.conf b/src/conf/cards/ATIIXP.conf
similarity index 100%
rename from usr/share/alsa/cards/ATIIXP.conf
rename to src/conf/cards/ATIIXP.conf
diff --git a/usr/share/alsa/cards/AU8810.conf b/src/conf/cards/AU8810.conf
similarity index 100%
rename from usr/share/alsa/cards/AU8810.conf
rename to src/conf/cards/AU8810.conf
diff --git a/usr/share/alsa/cards/AU8820.conf b/src/conf/cards/AU8820.conf
similarity index 100%
rename from usr/share/alsa/cards/AU8820.conf
rename to src/conf/cards/AU8820.conf
diff --git a/usr/share/alsa/cards/AU8830.conf b/src/conf/cards/AU8830.conf
similarity index 100%
rename from usr/share/alsa/cards/AU8830.conf
rename to src/conf/cards/AU8830.conf
diff --git a/usr/share/alsa/cards/Audigy.conf b/src/conf/cards/Audigy.conf
similarity index 100%
rename from usr/share/alsa/cards/Audigy.conf
rename to src/conf/cards/Audigy.conf
diff --git a/usr/share/alsa/cards/Audigy2.conf b/src/conf/cards/Audigy2.conf
similarity index 100%
rename from usr/share/alsa/cards/Audigy2.conf
rename to src/conf/cards/Audigy2.conf
diff --git a/usr/share/alsa/cards/Aureon51.conf b/src/conf/cards/Aureon51.conf
similarity index 100%
rename from usr/share/alsa/cards/Aureon51.conf
rename to src/conf/cards/Aureon51.conf
diff --git a/usr/share/alsa/cards/Aureon71.conf b/src/conf/cards/Aureon71.conf
similarity index 100%
rename from usr/share/alsa/cards/Aureon71.conf
rename to src/conf/cards/Aureon71.conf
diff --git a/usr/share/alsa/cards/CA0106.conf b/src/conf/cards/CA0106.conf
similarity index 100%
rename from usr/share/alsa/cards/CA0106.conf
rename to src/conf/cards/CA0106.conf
diff --git a/usr/share/alsa/cards/CMI8338-SWIEC.conf b/src/conf/cards/CMI8338-SWIEC.conf
similarity index 100%
rename from usr/share/alsa/cards/CMI8338-SWIEC.conf
rename to src/conf/cards/CMI8338-SWIEC.conf
diff --git a/usr/share/alsa/cards/CMI8338.conf b/src/conf/cards/CMI8338.conf
similarity index 100%
rename from usr/share/alsa/cards/CMI8338.conf
rename to src/conf/cards/CMI8338.conf
diff --git a/usr/share/alsa/cards/CMI8738-MC6.conf b/src/conf/cards/CMI8738-MC6.conf
similarity index 100%
rename from usr/share/alsa/cards/CMI8738-MC6.conf
rename to src/conf/cards/CMI8738-MC6.conf
diff --git a/usr/share/alsa/cards/CMI8738-MC8.conf b/src/conf/cards/CMI8738-MC8.conf
similarity index 100%
rename from usr/share/alsa/cards/CMI8738-MC8.conf
rename to src/conf/cards/CMI8738-MC8.conf
diff --git a/usr/share/alsa/cards/CMI8788.conf b/src/conf/cards/CMI8788.conf
similarity index 100%
rename from usr/share/alsa/cards/CMI8788.conf
rename to src/conf/cards/CMI8788.conf
diff --git a/usr/share/alsa/cards/CS46xx.conf b/src/conf/cards/CS46xx.conf
similarity index 100%
rename from usr/share/alsa/cards/CS46xx.conf
rename to src/conf/cards/CS46xx.conf
diff --git a/usr/share/alsa/cards/EMU10K1.conf b/src/conf/cards/EMU10K1.conf
similarity index 100%
rename from usr/share/alsa/cards/EMU10K1.conf
rename to src/conf/cards/EMU10K1.conf
diff --git a/usr/share/alsa/cards/EMU10K1X.conf b/src/conf/cards/EMU10K1X.conf
similarity index 100%
rename from usr/share/alsa/cards/EMU10K1X.conf
rename to src/conf/cards/EMU10K1X.conf
diff --git a/usr/share/alsa/cards/ENS1370.conf b/src/conf/cards/ENS1370.conf
similarity index 100%
rename from usr/share/alsa/cards/ENS1370.conf
rename to src/conf/cards/ENS1370.conf
diff --git a/usr/share/alsa/cards/ENS1371.conf b/src/conf/cards/ENS1371.conf
similarity index 100%
rename from usr/share/alsa/cards/ENS1371.conf
rename to src/conf/cards/ENS1371.conf
diff --git a/usr/share/alsa/cards/ES1968.conf b/src/conf/cards/ES1968.conf
similarity index 100%
rename from usr/share/alsa/cards/ES1968.conf
rename to src/conf/cards/ES1968.conf
diff --git a/usr/share/alsa/cards/FM801.conf b/src/conf/cards/FM801.conf
similarity index 100%
rename from usr/share/alsa/cards/FM801.conf
rename to src/conf/cards/FM801.conf
diff --git a/usr/share/alsa/cards/FWSpeakers.conf b/src/conf/cards/FWSpeakers.conf
similarity index 100%
rename from usr/share/alsa/cards/FWSpeakers.conf
rename to src/conf/cards/FWSpeakers.conf
diff --git a/usr/share/alsa/cards/FireWave.conf b/src/conf/cards/FireWave.conf
similarity index 100%
rename from usr/share/alsa/cards/FireWave.conf
rename to src/conf/cards/FireWave.conf
diff --git a/usr/share/alsa/cards/GUS.conf b/src/conf/cards/GUS.conf
similarity index 100%
rename from usr/share/alsa/cards/GUS.conf
rename to src/conf/cards/GUS.conf
diff --git a/usr/share/alsa/cards/HDA-Intel.conf b/src/conf/cards/HDA-Intel.conf
similarity index 100%
rename from usr/share/alsa/cards/HDA-Intel.conf
rename to src/conf/cards/HDA-Intel.conf
diff --git a/usr/share/alsa/cards/ICE1712.conf b/src/conf/cards/ICE1712.conf
similarity index 100%
rename from usr/share/alsa/cards/ICE1712.conf
rename to src/conf/cards/ICE1712.conf
diff --git a/usr/share/alsa/cards/ICE1724.conf b/src/conf/cards/ICE1724.conf
similarity index 100%
rename from usr/share/alsa/cards/ICE1724.conf
rename to src/conf/cards/ICE1724.conf
diff --git a/usr/share/alsa/cards/ICH-MODEM.conf b/src/conf/cards/ICH-MODEM.conf
similarity index 100%
rename from usr/share/alsa/cards/ICH-MODEM.conf
rename to src/conf/cards/ICH-MODEM.conf
diff --git a/usr/share/alsa/cards/ICH.conf b/src/conf/cards/ICH.conf
similarity index 100%
rename from usr/share/alsa/cards/ICH.conf
rename to src/conf/cards/ICH.conf
diff --git a/usr/share/alsa/cards/ICH4.conf b/src/conf/cards/ICH4.conf
similarity index 100%
rename from usr/share/alsa/cards/ICH4.conf
rename to src/conf/cards/ICH4.conf
diff --git a/src/conf/cards/Loopback.conf b/src/conf/cards/Loopback.conf
new file mode 100644
index 0000000..5365fa1
--- /dev/null
+++ b/src/conf/cards/Loopback.conf
@@ -0,0 +1,74 @@
+#
+# Configuration for the Intel HD audio (ICH6/ICH7)
+#
+
+<confdir:pcm/front.conf>
+
+Loopback.pcm.front.0 {
+	@args [ CARD ]
+	@args.CARD {
+		type string
+	}
+	type softvol
+	slave.pcm {
+		type hw
+		card $CARD
+	}
+	control {
+		name "PCM Playback Volume"
+		card $CARD
+	}
+}	
+
+# default with dmix+softvol & dsnoop
+Loopback.pcm.default {
+	@args [ CARD ]
+	@args.CARD {
+		type string
+	}
+	type asym
+	playback.pcm {
+		type plug
+		slave.pcm {
+			type softvol
+			slave.pcm {
+				@func concat
+				strings [ "dmix:" $CARD ]
+			}
+			control {
+				name "PCM Playback Volume"
+				card $CARD
+			}
+		}
+	}
+	capture.pcm {
+		type plug
+		slave.pcm {
+			type softvol
+			slave.pcm {
+				@func concat
+				strings [ "dsnoop:" $CARD ]
+			}
+			control {
+				name "Digital Capture Volume"
+				card $CARD
+			}
+			min_dB -30.0
+			max_dB 30.0
+			resolution 121
+		}
+		# to avoid possible phase inversions with digital mics
+		route_policy copy
+	}
+	hint.device 0
+}
+
+<confdir:pcm/surround40.conf>
+<confdir:pcm/surround41.conf>
+<confdir:pcm/surround50.conf>
+<confdir:pcm/surround51.conf>
+<confdir:pcm/surround71.conf>
+
+Loopback.pcm.surround40.0 cards.Loopback.pcm.front.0
+Loopback.pcm.surround51.0 cards.Loopback.pcm.front.0
+Loopback.pcm.surround71.0 cards.Loopback.pcm.front.0
diff --git a/usr/share/alsa/cards/Maestro3.conf b/src/conf/cards/Maestro3.conf
similarity index 100%
rename from usr/share/alsa/cards/Maestro3.conf
rename to src/conf/cards/Maestro3.conf
diff --git a/src/conf/cards/Makefile.am b/src/conf/cards/Makefile.am
new file mode 100644
index 0000000..b7190e7
--- /dev/null
+++ b/src/conf/cards/Makefile.am
@@ -0,0 +1,76 @@
+alsaconfigdir = @ALSA_CONFIG_DIR@
+alsadir = $(alsaconfigdir)/cards
+cfg_files = aliases.conf \
+	AACI.conf \
+	ATIIXP.conf \
+	ATIIXP-SPDMA.conf \
+	ATIIXP-MODEM.conf \
+	AU8810.conf \
+	AU8820.conf \
+	AU8830.conf \
+	Audigy.conf \
+	Audigy2.conf \
+	Aureon51.conf \
+	Aureon71.conf \
+	CA0106.conf \
+	CMI8338.conf \
+	CMI8338-SWIEC.conf \
+	CMI8738-MC6.conf \
+	CMI8738-MC8.conf \
+	CMI8788.conf \
+	CS46xx.conf \
+	EMU10K1.conf \
+	EMU10K1X.conf \
+	ENS1370.conf \
+	ENS1371.conf \
+	ES1968.conf \
+	FM801.conf \
+	FWSpeakers.conf \
+	FireWave.conf \
+	GUS.conf \
+	HDA-Intel.conf \
+	ICE1712.conf \
+	ICE1724.conf \
+	ICH.conf \
+	ICH4.conf \
+	ICH-MODEM.conf \
+	Maestro3.conf \
+	NFORCE.conf \
+	PC-Speaker.conf \
+	PMac.conf \
+	PMacToonie.conf \
+	PS3.conf \
+	RME9636.conf \
+	RME9652.conf \
+	SI7018.conf \
+	SB-XFi.conf \
+	TRID4DWAVENX.conf \
+	USB-Audio.conf \
+	YMF744.conf \
+	VIA686A.conf \
+	VIA8233.conf \
+	VIA8233A.conf \
+	VIA8237.conf \
+	VX222.conf \
+	VXPocket.conf \
+	VXPocket440.conf
+
+if BUILD_ALISP
+cfg_files += aliases.alisp
+endif
+
+alsa_DATA = $(cfg_files)
+
+if BUILD_ALISP
+SI7018dir = $(alsaconfigdir)/cards/SI7018
+SI7018_files = \
+	SI7018/sndoc-mixer.alisp \
+	SI7018/sndop-mixer.alisp
+SI7018_DATA = $(SI7018_files)
+else
+SI7018_files=
+endif
+
+EXTRA_DIST = \
+	$(cfg_files) \
+	$(SI7018_files)
diff --git a/usr/share/alsa/cards/NFORCE.conf b/src/conf/cards/NFORCE.conf
similarity index 100%
rename from usr/share/alsa/cards/NFORCE.conf
rename to src/conf/cards/NFORCE.conf
diff --git a/usr/share/alsa/cards/PC-Speaker.conf b/src/conf/cards/PC-Speaker.conf
similarity index 100%
rename from usr/share/alsa/cards/PC-Speaker.conf
rename to src/conf/cards/PC-Speaker.conf
diff --git a/usr/share/alsa/cards/PMac.conf b/src/conf/cards/PMac.conf
similarity index 100%
rename from usr/share/alsa/cards/PMac.conf
rename to src/conf/cards/PMac.conf
diff --git a/usr/share/alsa/cards/PMacToonie.conf b/src/conf/cards/PMacToonie.conf
similarity index 100%
rename from usr/share/alsa/cards/PMacToonie.conf
rename to src/conf/cards/PMacToonie.conf
diff --git a/usr/share/alsa/cards/PS3.conf b/src/conf/cards/PS3.conf
similarity index 100%
rename from usr/share/alsa/cards/PS3.conf
rename to src/conf/cards/PS3.conf
diff --git a/usr/share/alsa/cards/RME9636.conf b/src/conf/cards/RME9636.conf
similarity index 100%
rename from usr/share/alsa/cards/RME9636.conf
rename to src/conf/cards/RME9636.conf
diff --git a/usr/share/alsa/cards/RME9652.conf b/src/conf/cards/RME9652.conf
similarity index 100%
rename from usr/share/alsa/cards/RME9652.conf
rename to src/conf/cards/RME9652.conf
diff --git a/usr/share/alsa/cards/SB-XFi.conf b/src/conf/cards/SB-XFi.conf
similarity index 100%
rename from usr/share/alsa/cards/SB-XFi.conf
rename to src/conf/cards/SB-XFi.conf
diff --git a/usr/share/alsa/cards/SI7018.conf b/src/conf/cards/SI7018.conf
similarity index 100%
rename from usr/share/alsa/cards/SI7018.conf
rename to src/conf/cards/SI7018.conf
diff --git a/src/conf/cards/SI7018/sndoc-mixer.alisp b/src/conf/cards/SI7018/sndoc-mixer.alisp
new file mode 100644
index 0000000..ade1ea3
--- /dev/null
+++ b/src/conf/cards/SI7018/sndoc-mixer.alisp
@@ -0,0 +1,11 @@
+;
+; SiS SI7018 mixer abstract layer
+;
+; Copyright (c) 2003 Jaroslav Kysela <perex@perex.cz>
+; License: GPL v2 (http://www.gnu.org/licenses/gpl.html)
+;
+
+(defun sndoc_mixer_open (hctl pcm)
+  (princ "sndoc_mixer_open: hctl=" hctl " pcm=" pcm "\n")
+  0
+)
diff --git a/src/conf/cards/SI7018/sndop-mixer.alisp b/src/conf/cards/SI7018/sndop-mixer.alisp
new file mode 100644
index 0000000..285e289
--- /dev/null
+++ b/src/conf/cards/SI7018/sndop-mixer.alisp
@@ -0,0 +1,11 @@
+;
+; SiS SI7018 mixer abstract layer
+;
+; Copyright (c) 2003 Jaroslav Kysela <perex@perex.cz>
+; License: GPL v2 (http://www.gnu.org/licenses/gpl.html)
+;
+
+(defun sndop_mixer_open (hctl pcm)
+  (princ "sndop_mixer_open: hctl=" hctl " pcm=" pcm "\n")
+  0
+)
diff --git a/usr/share/alsa/cards/TRID4DWAVENX.conf b/src/conf/cards/TRID4DWAVENX.conf
similarity index 100%
rename from usr/share/alsa/cards/TRID4DWAVENX.conf
rename to src/conf/cards/TRID4DWAVENX.conf
diff --git a/usr/share/alsa/cards/USB-Audio.conf b/src/conf/cards/USB-Audio.conf
similarity index 100%
rename from usr/share/alsa/cards/USB-Audio.conf
rename to src/conf/cards/USB-Audio.conf
diff --git a/usr/share/alsa/cards/VIA686A.conf b/src/conf/cards/VIA686A.conf
similarity index 100%
rename from usr/share/alsa/cards/VIA686A.conf
rename to src/conf/cards/VIA686A.conf
diff --git a/usr/share/alsa/cards/VIA8233.conf b/src/conf/cards/VIA8233.conf
similarity index 100%
rename from usr/share/alsa/cards/VIA8233.conf
rename to src/conf/cards/VIA8233.conf
diff --git a/usr/share/alsa/cards/VIA8233A.conf b/src/conf/cards/VIA8233A.conf
similarity index 100%
rename from usr/share/alsa/cards/VIA8233A.conf
rename to src/conf/cards/VIA8233A.conf
diff --git a/usr/share/alsa/cards/VIA8237.conf b/src/conf/cards/VIA8237.conf
similarity index 100%
rename from usr/share/alsa/cards/VIA8237.conf
rename to src/conf/cards/VIA8237.conf
diff --git a/usr/share/alsa/cards/VX222.conf b/src/conf/cards/VX222.conf
similarity index 100%
rename from usr/share/alsa/cards/VX222.conf
rename to src/conf/cards/VX222.conf
diff --git a/usr/share/alsa/cards/VXPocket.conf b/src/conf/cards/VXPocket.conf
similarity index 100%
rename from usr/share/alsa/cards/VXPocket.conf
rename to src/conf/cards/VXPocket.conf
diff --git a/usr/share/alsa/cards/VXPocket440.conf b/src/conf/cards/VXPocket440.conf
similarity index 100%
rename from usr/share/alsa/cards/VXPocket440.conf
rename to src/conf/cards/VXPocket440.conf
diff --git a/usr/share/alsa/cards/YMF744.conf b/src/conf/cards/YMF744.conf
similarity index 100%
rename from usr/share/alsa/cards/YMF744.conf
rename to src/conf/cards/YMF744.conf
diff --git a/src/conf/cards/aliases.alisp b/src/conf/cards/aliases.alisp
new file mode 100644
index 0000000..1661caa
--- /dev/null
+++ b/src/conf/cards/aliases.alisp
@@ -0,0 +1,29 @@
+(setq snd_card_aliases_array
+  (
+    ("YMF724" 		. "YMF744")
+    ("YMF724F"		. "YMF744")
+    ("YMF740"		. "YMF744")
+    ("YMF740C"		. "YMF744")
+    ("YMF754"		. "YMF744")
+    ("CMIPCI"		. "CMI8338")
+    ("CMI8738"		. "CMI8338")
+    ("CMI8738-MC4"	. "CMI8738-MC6")
+    ("E-mu APS"		. "EMU10K1")
+    ("GUS Max"		. "GUS")
+    ("GUS ACE" 		. "GUS")
+    ("GUS Extreme"	. "GUS")
+    ("AMD InterWave"	. "GUS")
+    ("Dynasonic 3-D"	. "GUS")
+    ("InterWave STB"	. "GUS")
+  )
+)
+
+(defun snd_card_alias (cardname)
+  (setq r (assq cardname snd_card_aliases_array))
+  (setq r (if (null r) cardname r))
+  (unsetq r)
+)
+
+(defun snd_card_alias_unset ()
+  (unsetq snd_card_aliases_array snd_card_alias)
+)
diff --git a/usr/share/alsa/cards/aliases.conf b/src/conf/cards/aliases.conf
similarity index 100%
rename from usr/share/alsa/cards/aliases.conf
rename to src/conf/cards/aliases.conf
diff --git a/src/conf/pcm/Makefile.am b/src/conf/pcm/Makefile.am
new file mode 100644
index 0000000..cc3286e
--- /dev/null
+++ b/src/conf/pcm/Makefile.am
@@ -0,0 +1,12 @@
+cfg_files = default.conf front.conf rear.conf center_lfe.conf side.conf\
+	    surround40.conf surround41.conf \
+	    surround50.conf surround51.conf \
+	    surround71.conf iec958.conf hdmi.conf modem.conf \
+	    dmix.conf dsnoop.conf \
+	    dpl.conf
+
+EXTRA_DIST = $(cfg_files)
+
+alsaconfigdir = @ALSA_CONFIG_DIR@
+alsadir = $(alsaconfigdir)/pcm
+alsa_DATA = $(cfg_files)
diff --git a/usr/share/alsa/pcm/center_lfe.conf b/src/conf/pcm/center_lfe.conf
similarity index 100%
rename from usr/share/alsa/pcm/center_lfe.conf
rename to src/conf/pcm/center_lfe.conf
diff --git a/usr/share/alsa/pcm/default.conf b/src/conf/pcm/default.conf
similarity index 100%
rename from usr/share/alsa/pcm/default.conf
rename to src/conf/pcm/default.conf
diff --git a/usr/share/alsa/pcm/dmix.conf b/src/conf/pcm/dmix.conf
similarity index 100%
rename from usr/share/alsa/pcm/dmix.conf
rename to src/conf/pcm/dmix.conf
diff --git a/usr/share/alsa/pcm/dpl.conf b/src/conf/pcm/dpl.conf
similarity index 100%
rename from usr/share/alsa/pcm/dpl.conf
rename to src/conf/pcm/dpl.conf
diff --git a/usr/share/alsa/pcm/dsnoop.conf b/src/conf/pcm/dsnoop.conf
similarity index 100%
rename from usr/share/alsa/pcm/dsnoop.conf
rename to src/conf/pcm/dsnoop.conf
diff --git a/usr/share/alsa/pcm/front.conf b/src/conf/pcm/front.conf
similarity index 100%
rename from usr/share/alsa/pcm/front.conf
rename to src/conf/pcm/front.conf
diff --git a/usr/share/alsa/pcm/hdmi.conf b/src/conf/pcm/hdmi.conf
similarity index 100%
rename from usr/share/alsa/pcm/hdmi.conf
rename to src/conf/pcm/hdmi.conf
diff --git a/usr/share/alsa/pcm/iec958.conf b/src/conf/pcm/iec958.conf
similarity index 100%
rename from usr/share/alsa/pcm/iec958.conf
rename to src/conf/pcm/iec958.conf
diff --git a/usr/share/alsa/pcm/modem.conf b/src/conf/pcm/modem.conf
similarity index 100%
rename from usr/share/alsa/pcm/modem.conf
rename to src/conf/pcm/modem.conf
diff --git a/usr/share/alsa/pcm/rear.conf b/src/conf/pcm/rear.conf
similarity index 100%
rename from usr/share/alsa/pcm/rear.conf
rename to src/conf/pcm/rear.conf
diff --git a/usr/share/alsa/pcm/side.conf b/src/conf/pcm/side.conf
similarity index 100%
rename from usr/share/alsa/pcm/side.conf
rename to src/conf/pcm/side.conf
diff --git a/usr/share/alsa/pcm/surround40.conf b/src/conf/pcm/surround40.conf
similarity index 100%
rename from usr/share/alsa/pcm/surround40.conf
rename to src/conf/pcm/surround40.conf
diff --git a/usr/share/alsa/pcm/surround41.conf b/src/conf/pcm/surround41.conf
similarity index 100%
rename from usr/share/alsa/pcm/surround41.conf
rename to src/conf/pcm/surround41.conf
diff --git a/usr/share/alsa/pcm/surround50.conf b/src/conf/pcm/surround50.conf
similarity index 100%
rename from usr/share/alsa/pcm/surround50.conf
rename to src/conf/pcm/surround50.conf
diff --git a/usr/share/alsa/pcm/surround51.conf b/src/conf/pcm/surround51.conf
similarity index 100%
rename from usr/share/alsa/pcm/surround51.conf
rename to src/conf/pcm/surround51.conf
diff --git a/usr/share/alsa/pcm/surround71.conf b/src/conf/pcm/surround71.conf
similarity index 100%
rename from usr/share/alsa/pcm/surround71.conf
rename to src/conf/pcm/surround71.conf
diff --git a/usr/share/alsa/smixer.conf b/src/conf/smixer.conf
similarity index 100%
rename from usr/share/alsa/smixer.conf
rename to src/conf/smixer.conf
diff --git a/src/conf/sndo-mixer.alisp b/src/conf/sndo-mixer.alisp
new file mode 100644
index 0000000..c8b03f0
--- /dev/null
+++ b/src/conf/sndo-mixer.alisp
@@ -0,0 +1,115 @@
+;
+; Toplevel configuration for the ALSA Ordinary Mixer Interface
+;
+; Copyright (c) 2003 Jaroslav Kysela <perex@perex.cz>
+; License: GPL v2 (http://www.gnu.org/licenses/gpl.html)
+;
+
+(defun sndo_include (hctl stream)
+  (setq info (Acall "ctl_card_info" (Acall "hctl_ctl" hctl)))
+  (if (= (Aerror info) 0)
+    (progn
+      (setq info (Aresult info))
+      (setq driver (cdr (assq "driver" (unsetq info))))
+      (setq file (concat (path "data") "/alsa/cards/" (snd_card_alias driver) "/sndo" stream "-mixer.alisp"))
+      (setq r (include file))
+      (when (= r -2) (Asyserr "unable to find file " file))
+    )
+    (setq r (Aerror info))
+  )
+  (unsetq info driver file r)
+)
+
+(defun sndo_mixer_open_fcn (hctl stream pcm)
+  (setq fcn (concat "sndo" stream "_mixer_open"))
+  (setq r (if (exfun fcn) (funcall fcn hctl pcm) 0))
+  (when (= r 0)
+    (setq hctls (if hctls (cons hctls (cons hctl)) hctl))
+  )
+  (unsetq fcn r)
+)
+
+(defun sndo_mixer_open_hctl (name stream pcm)
+  (setq hctl (Acall "hctl_open" name nil))
+  (setq r (Aerror hctl))
+  (when (= r 0)
+    (setq hctl (Aresult hctl))
+    (setq r (sndo_include hctl stream))
+    (if (= r 0)
+       (setq r (sndo_mixer_open_fcn hctl stream pcm))
+       (Acall "hctl_close" hctl)
+    )
+  )
+  (unsetq hctl r)
+)
+
+(defun sndo_mixer_open_virtual (name stream pcm)
+  (setq file (concat (path "data") "/alsa/virtual/" name "/sndo" stream "-mixer.alisp"))
+  (setq r (include file))
+  (when (= r -2) (Asyserr "unable to find file " file))
+  (when (= r 0) (setq r (sndo_mixer_open_fcn nil stream pcm)))
+  (unsetq file r)
+)
+
+(defun sndo_mixer_open1 (name stream)
+  (if (compare-strings name 0 2 "hw:" 0 2)
+    (sndo_mixer_open_hctl name stream nil)
+    (sndo_mixer_open_virtual name stream nil)
+  )
+)
+
+(defun sndo_mixer_open (pname cname)
+  (setq r (sndo_mixer_open1 pname "p"))
+  (when (= r 0) (setq r (sndo_mixer_open1 cname "c")))
+  (when (!= r 0) (sndo_mixer_close))
+  (unsetq sndo_mixer_open
+	  sndo_mixer_open_pcm sndo_mixer_open_pcm1
+          sndo_mixer_open_virtual sndo_mixer_open_fcn
+	  sndo_include r)
+)
+
+(defun sndo_mixer_open_pcm1 (pcm stream)
+  (setq info (Acall "pcm_info" pcm))
+  (setq r (Aerror info))
+  (when (= r 0)
+    (setq info (Aresult info))
+    (setq card (cdr (assq "card" info)))
+    (setq r
+      (if (< card 0)
+	(sndo_mixer_open_virtual (Acall "pcm_name" pcm) stream pcm)
+        (sndo_mixer_open_hctl (format "hw:%i" card) stream pcm)
+      )
+    )
+  )
+  (unsetq info card r)
+)
+
+(defun sndo_mixer_open_pcm (ppcm cpcm)
+  (setq r (sndo_mixer_open_pcm1 ppcm "p"))
+  (when (= r 0) (setq r (sndo_mixer_open_pcm1 cpcm "c")))
+  (when (!= r 0) (sndo_mixer_close))
+  (unsetq sndo_mixer_open
+	  sndo_mixer_open_pcm sndo_mixer_open_pcm1
+          sndo_mixer_open_virtual sndo_mixer_open_fcn
+	  sndo_include r)
+)
+
+(defun sndo_mixer_close1 (hctl stream)
+  (when hctl
+    (progn
+      (setq fcn (concat "sndo" stream "_mixer_close"))
+      (when (exfun fcn) (funcall fcn hctl))
+      (unsetq fcn)
+      (Acall "hctl_close" hctl)
+    )
+  )
+)
+
+(defun sndo_mixer_close nil
+  (sndo_mixer_close1 (nth 1 hctls) "c")
+  (sndo_mixer_close1 (nth 0 hctls) "p")
+  (snd_card_alias_unset)
+  (unsetq hctls)
+)
+
+(include (concat (path "data") "/alsa/cards/aliases.alisp"))
diff --git a/src/confmisc.c b/src/confmisc.c
new file mode 100644
index 0000000..80b0027
--- /dev/null
+++ b/src/confmisc.c
@@ -0,0 +1,1304 @@
+/**
+ * \file confmisc.c
+ * \ingroup Configuration
+ * \brief Configuration helper functions
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \date 2000-2001
+ * 
+ * Configuration helper functions.
+ *
+ * See the \ref conffunc page for more details.
+ */
+/*
+ *  Miscellaneous configuration helper functions
+ *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>,
+ *			  Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+/*! \page conffunc
+
+\section conffunc_ref Function reference
+
+<UL>
+  <LI>The getenv function - snd_func_getenv() - obtains
+      an environment value. The result is a string.
+  <LI>The igetenv function - snd_func_igetenv() - obtains
+      an environment value. The result is an integer.
+  <LI>The concat function - snd_func_concat() - merges all specified
+      strings. The result is a string.
+  <LI>The iadd function - snd_func_iadd() - sum all specified integers.
+      The result is an integer.
+  <LI>The imul function - snd_func_imul() - multiply all specified integers.
+      The result is an integer.
+  <LI>The datadir function - snd_func_datadir() - returns the
+      ALSA data directory. The result is a string.
+  <LI>The refer function - snd_func_refer() - copies the referred
+      configuration. The result has the same type as the referred node.
+  <LI>The card_inum function - snd_func_card_inum() - returns
+      a card number (integers).
+  <LI>The card_driver function - snd_func_card_driver() - returns
+      a driver identification. The result is a string.
+  <LI>The card_id function - snd_func_card_id() - returns
+      a card identification. The result is a string.
+  <LI>The card_name function - snd_func_card_name() - returns
+      a card's name. The result is a string.
+  <LI>The pcm_id function - snd_func_pcm_id() - returns
+      a pcm identification. The result is a string.
+  <LI>The private_string function - snd_func_private_string() - returns the
+      string from the private_data node.
+  <LI>The private_card_driver function - snd_func_private_card_driver() -
+      returns the driver identification from the private_data node.
+      The result is a string.
+  <LI>The private_pcm_subdevice function - snd_func_private_pcm_subdevice() -
+      returns the PCM subdevice number from the private_data node.
+      The result is a string.
+</UL>
+
+*/
+
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include "local.h"
+
+/**
+ * \brief Gets the boolean value from the given ASCII string.
+ * \param ascii The string to be parsed.
+ * \return 0 or 1 if successful, otherwise a negative error code.
+ */
+int snd_config_get_bool_ascii(const char *ascii)
+{
+	unsigned int k;
+	static const struct {
+		const char str[8];
+		int val;
+	} b[] = {
+		{ "0", 0 },
+		{ "1", 1 },
+		{ "false", 0 },
+		{ "true", 1 },
+		{ "no", 0 },
+		{ "yes", 1 },
+		{ "off", 0 },
+		{ "on", 1 },
+	};
+	for (k = 0; k < sizeof(b) / sizeof(*b); k++) {
+		if (strcasecmp(b[k].str, ascii) == 0)
+			return b[k].val;
+	}
+	return -EINVAL;
+}
+
+/**
+ * \brief Gets the boolean value from a configuration node.
+ * \param conf Handle to the configuration node to be parsed.
+ * \return 0 or 1 if successful, otherwise a negative error code.
+ */
+int snd_config_get_bool(const snd_config_t *conf)
+{
+	long v;
+	const char *str, *id;
+	int err;
+
+	err = snd_config_get_id(conf, &id);
+	if (err < 0)
+		return err;
+	err = snd_config_get_integer(conf, &v);
+	if (err >= 0) {
+		if (v < 0 || v > 1) {
+		_invalid_value:
+			SNDERR("Invalid value for %s", id);
+			return -EINVAL;
+		}
+		return v;
+	}
+	err = snd_config_get_string(conf, &str);
+	if (err < 0) {
+		SNDERR("Invalid type for %s", id);
+		return -EINVAL;
+	}
+	err = snd_config_get_bool_ascii(str);
+	if (err < 0)
+		goto _invalid_value;
+	return err;
+}
+
+/**
+ * \brief Gets the control interface index from the given ASCII string.
+ * \param ascii The string to be parsed.
+ * \return The control interface index if successful, otherwise a negative error code.
+ */ 
+int snd_config_get_ctl_iface_ascii(const char *ascii)
+{
+	long v;
+	snd_ctl_elem_iface_t idx;
+	if (isdigit(ascii[0])) {
+		if (safe_strtol(ascii, &v) >= 0) {
+			if (v < 0 || v > SND_CTL_ELEM_IFACE_LAST)
+				return -EINVAL;
+			return v;
+		}
+	}
+	for (idx = 0; idx <= SND_CTL_ELEM_IFACE_LAST; idx++) {
+		if (strcasecmp(snd_ctl_elem_iface_name(idx), ascii) == 0)
+			return idx;
+	}
+	return -EINVAL;
+}
+
+/**
+ * \brief Gets the control interface index from a configuration node.
+ * \param conf Handle to the configuration node to be parsed.
+ * \return The control interface index if successful, otherwise a negative error code.
+ */ 
+int snd_config_get_ctl_iface(const snd_config_t *conf)
+{
+	long v;
+	const char *str, *id;
+	int err;
+
+	err = snd_config_get_id(conf, &id);
+	if (err < 0)
+		return err;
+	err = snd_config_get_integer(conf, &v);
+	if (err >= 0) {
+		if (v < 0 || v > SND_CTL_ELEM_IFACE_LAST) {
+		_invalid_value:
+			SNDERR("Invalid value for %s", id);
+			return -EINVAL;
+		}
+		return v;
+	}
+	err = snd_config_get_string(conf, &str);
+	if (err < 0) {
+		SNDERR("Invalid type for %s", id);
+		return -EINVAL;
+	}
+	err = snd_config_get_ctl_iface_ascii(str);
+	if (err < 0)
+		goto _invalid_value;
+	return err;
+}
+
+/*
+ *  Helper functions for the configuration file
+ */
+
+/**
+ * \brief Returns an environment value.
+ * \param dst The function puts the handle to the result configuration node
+ *            (with type string) at the address specified by \p dst.
+ * \param root Handle to the root source node.
+ * \param src Handle to the source node, with definitions for \c vars and
+ *            \c default.
+ * \param private_data Handle to the \c private_data node.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * Example:
+\code
+	{
+		@func getenv
+		vars [ MY_CARD CARD C ]
+		default 0
+	}
+\endcode
+ */ 
+int snd_func_getenv(snd_config_t **dst, snd_config_t *root, snd_config_t *src,
+		    snd_config_t *private_data)
+{
+	snd_config_t *n, *d;
+	snd_config_iterator_t i, next;
+	const char *res, *id;
+	char *def = NULL;
+	int idx = 0, err, hit;
+	
+	err = snd_config_search(src, "vars", &n);
+	if (err < 0) {
+		SNDERR("field vars not found");
+		goto __error;
+	}
+	err = snd_config_evaluate(n, root, private_data, NULL);
+	if (err < 0) {
+		SNDERR("error evaluating vars");
+		goto __error;
+	}
+	err = snd_config_search(src, "default", &d);
+	if (err < 0) {
+		SNDERR("field default not found");
+		goto __error;
+	}
+	err = snd_config_evaluate(d, root, private_data, NULL);
+	if (err < 0) {
+		SNDERR("error evaluating default");
+		goto __error;
+	}
+	err = snd_config_get_ascii(d, &def);
+	if (err < 0) {
+		SNDERR("error getting field default");
+		goto __error;
+	}
+	do {
+		hit = 0;
+		snd_config_for_each(i, next, n) {
+			snd_config_t *n = snd_config_iterator_entry(i);
+			const char *ptr;
+			long i;
+			if (snd_config_get_id(n, &id) < 0)
+				continue;
+			if (snd_config_get_type(n) != SND_CONFIG_TYPE_STRING) {
+				SNDERR("field %s is not a string", id);
+				err = -EINVAL;
+				goto __error;
+			}
+			err = safe_strtol(id, &i);
+			if (err < 0) {
+				SNDERR("id of field %s is not an integer", id);
+				err = -EINVAL;
+				goto __error;
+			}
+			if (i == idx) {
+				idx++;
+				err = snd_config_get_string(n, &ptr);
+				if (err < 0) {
+					SNDERR("invalid string for id %s", id);
+					err = -EINVAL;
+					goto __error;
+				}
+				res = getenv(ptr);
+				if (res != NULL && *res != '\0')
+					goto __ok;
+				hit = 1;
+			}
+		}
+	} while (hit);
+	res = def;
+      __ok:
+	err = snd_config_get_id(src, &id);
+	if (err >= 0)
+		err = snd_config_imake_string(dst, id, res);
+      __error:
+	free(def);
+	return err;
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(snd_func_getenv, SND_CONFIG_DLSYM_VERSION_EVALUATE);
+#endif
+
+/**
+ * \brief Returns an integer environment value.
+ * \param dst The function puts the handle to the result configuration node
+ *            (with type integer) at the address specified by \p dst.
+ * \param root Handle to the root source node.
+ * \param src Handle to the source node, with definitions for \c vars and
+ *            \c default.
+ * \param private_data Handle to the \c private_data node.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * Example:
+\code
+	{
+		@func igetenv
+		vars [ MY_DEVICE DEVICE D ]
+		default 0
+	}
+\endcode
+ */ 
+int snd_func_igetenv(snd_config_t **dst, snd_config_t *root, snd_config_t *src,
+		     snd_config_t *private_data)
+{
+	snd_config_t *d;
+	const char *str, *id;
+	int err;
+	long v;
+
+	err = snd_func_getenv(&d, root, src, private_data);
+	if (err < 0)
+		return err;
+	err = snd_config_get_string(d, &str);
+	if (err < 0) {
+		snd_config_delete(d);
+		return err;
+	}
+	err = safe_strtol(str, &v);
+	if (err < 0) {
+		snd_config_delete(d);
+		return err;
+	}
+	snd_config_delete(d);
+	err = snd_config_get_id(src, &id);
+	if (err < 0)
+		return err;
+	err = snd_config_imake_integer(dst, id, v);
+	if (err < 0)
+		return err;
+	return 0;
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(snd_func_igetenv, SND_CONFIG_DLSYM_VERSION_EVALUATE);
+#endif
+		
+/**
+ * \brief Merges the given strings.
+ * \param dst The function puts the handle to the result configuration node
+ *            (with type string) at the address specified by \p dst.
+ * \param root Handle to the root source node.
+ * \param src Handle to the source node, with a definition for \c strings.
+ * \param private_data Handle to the \c private_data node.
+ * \return A non-negative value if successful, otherwise a negative error code.
+ *
+ * Example (result is "a1b2c3"):
+\code
+	{
+		@func concat
+		strings [ "a1" "b2" "c3" ]
+	}
+\endcode
+ */ 
+int snd_func_concat(snd_config_t **dst, snd_config_t *root, snd_config_t *src,
+		    snd_config_t *private_data)
+{
+	snd_config_t *n;
+	snd_config_iterator_t i, next;
+	const char *id;
+	char *res = NULL, *tmp;
+	int idx = 0, len = 0, len1, err, hit;
+	
+	err = snd_config_search(src, "strings", &n);
+	if (err < 0) {
+		SNDERR("field strings not found");
+		goto __error;
+	}
+	err = snd_config_evaluate(n, root, private_data, NULL);
+	if (err < 0) {
+		SNDERR("error evaluating strings");
+		goto __error;
+	}
+	do {
+		hit = 0;
+		snd_config_for_each(i, next, n) {
+			snd_config_t *n = snd_config_iterator_entry(i);
+			char *ptr;
+			const char *id;
+			long i;
+			if (snd_config_get_id(n, &id) < 0)
+				continue;
+			err = safe_strtol(id, &i);
+			if (err < 0) {
+				SNDERR("id of field %s is not an integer", id);
+				err = -EINVAL;
+				goto __error;
+			}
+			if (i == idx) {
+				idx++;
+				err = snd_config_get_ascii(n, &ptr);
+				if (err < 0) {
+					SNDERR("invalid ascii string for id %s", id);
+					err = -EINVAL;
+					goto __error;
+				}
+				len1 = strlen(ptr);
+				tmp = realloc(res, len + len1 + 1);
+				if (tmp == NULL) {
+					free(ptr);
+					free(res);
+					err = -ENOMEM;
+					goto __error;
+				}
+				memcpy(tmp + len, ptr, len1);
+				free(ptr);
+				len += len1;
+				tmp[len] = '\0';
+				res = tmp;
+				hit = 1;
+			}
+		}
+	} while (hit);
+	if (res == NULL) {
+		SNDERR("empty string is not accepted");
+		err = -EINVAL;
+		goto __error;
+	}
+	err = snd_config_get_id(src, &id);
+	if (err >= 0)
+		err = snd_config_imake_string(dst, id, res);
+	free(res);
+      __error:
+	return err;
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(snd_func_concat, SND_CONFIG_DLSYM_VERSION_EVALUATE);
+#endif
+
+
+static int snd_func_iops(snd_config_t **dst,
+			 snd_config_t *root,
+			 snd_config_t *src,
+			 snd_config_t *private_data,
+			 int op)
+{
+	snd_config_t *n;
+	snd_config_iterator_t i, next;
+	const char *id;
+	char *res = NULL;
+	long result = 0, val;
+	int idx = 0, err, hit;
+	
+	err = snd_config_search(src, "integers", &n);
+	if (err < 0) {
+		SNDERR("field integers not found");
+		goto __error;
+	}
+	err = snd_config_evaluate(n, root, private_data, NULL);
+	if (err < 0) {
+		SNDERR("error evaluating integers");
+		goto __error;
+	}
+	do {
+		hit = 0;
+		snd_config_for_each(i, next, n) {
+			snd_config_t *n = snd_config_iterator_entry(i);
+			const char *id;
+			long i;
+			if (snd_config_get_id(n, &id) < 0)
+				continue;
+			err = safe_strtol(id, &i);
+			if (err < 0) {
+				SNDERR("id of field %s is not an integer", id);
+				err = -EINVAL;
+				goto __error;
+			}
+			if (i == idx) {
+				idx++;
+				err = snd_config_get_integer(n, &val);
+				if (err < 0) {
+					SNDERR("invalid integer for id %s", id);
+					err = -EINVAL;
+					goto __error;
+				}
+				switch (op) {
+				case 0: result += val; break;
+				case 1: result *= val; break;
+				}
+				hit = 1;
+			}
+		}
+	} while (hit);
+	err = snd_config_get_id(src, &id);
+	if (err >= 0)
+		err = snd_config_imake_integer(dst, id, result);
+	free(res);
+      __error:
+	return err;
+}
+
+
+/**
+ * \brief Sum the given integers.
+ * \param dst The function puts the handle to the result configuration node
+ *            (with type integer) at the address specified by \p dst.
+ * \param root Handle to the root source node.
+ * \param src Handle to the source node, with a definition for \c integers.
+ * \param private_data Handle to the \c private_data node.
+ * \return A non-negative value if successful, otherwise a negative error code.
+ *
+ * Example (result is 10):
+\code
+	{
+		@func iadd
+		integers [ 2 3 5 ]
+	}
+\endcode
+ */ 
+int snd_func_iadd(snd_config_t **dst, snd_config_t *root,
+	          snd_config_t *src, snd_config_t *private_data)
+{
+	return snd_func_iops(dst, root, src, private_data, 0);
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(snd_func_iadd, SND_CONFIG_DLSYM_VERSION_EVALUATE);
+#endif
+
+/**
+ * \brief Multiply the given integers.
+ * \param dst The function puts the handle to the result configuration node
+ *            (with type integer) at the address specified by \p dst.
+ * \param root Handle to the root source node.
+ * \param src Handle to the source node, with a definition for \c integers.
+ * \param private_data Handle to the \c private_data node.
+ * \return A non-negative value if successful, otherwise a negative error code.
+ *
+ * Example (result is 12):
+\code
+	{
+		@func imul
+		integers [ 2 3 2 ]
+	}
+\endcode
+ */ 
+int snd_func_imul(snd_config_t **dst, snd_config_t *root,
+		  snd_config_t *src, snd_config_t *private_data)
+{
+	return snd_func_iops(dst, root, src, private_data, 1);
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(snd_func_imul, SND_CONFIG_DLSYM_VERSION_EVALUATE);
+#endif
+
+/**
+ * \brief Returns the ALSA data directory.
+ * \param dst The function puts the handle to the result configuration node
+ *            (with type string) at the address specified by \p dst.
+ * \param root Handle to the root source node.
+ * \param src Handle to the source node.
+ * \param private_data Handle to the \c private_data node. Not used.
+ * \return A non-negative value if successful, otherwise a negative error code.
+ *
+ * Example (result is "/usr/share/alsa" using the default paths):
+\code
+	{
+		@func datadir
+	}
+\endcode
+ */ 
+int snd_func_datadir(snd_config_t **dst, snd_config_t *root ATTRIBUTE_UNUSED,
+		     snd_config_t *src, snd_config_t *private_data ATTRIBUTE_UNUSED)
+{
+	int err;
+	const char *id;
+	
+	err = snd_config_get_id(src, &id);
+	if (err < 0)
+		return err;
+	return snd_config_imake_string(dst, id, ALSA_CONFIG_DIR);
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(snd_func_datadir, SND_CONFIG_DLSYM_VERSION_EVALUATE);
+#endif
+
+static int open_ctl(long card, snd_ctl_t **ctl)
+{
+	char name[16];
+	snprintf(name, sizeof(name), "hw:%li", card);
+	name[sizeof(name)-1] = '\0';
+	return snd_ctl_open(ctl, name, 0);
+}
+
+#if 0
+static int string_from_integer(char **dst, long v)
+{
+	char str[32];
+	char *res;
+	sprintf(str, "%li", v);
+	res = strdup(str);
+	if (res == NULL)
+		return -ENOMEM;
+	*dst = res;
+	return 0;
+}
+#endif
+
+/**
+ * \brief Returns the string from \c private_data.
+ * \param dst The function puts the handle to the result configuration node
+ *            (with type string) at the address specified by \p dst.
+ * \param root Handle to the root source node.
+ * \param src Handle to the source node.
+ * \param private_data Handle to the \c private_data node (type string,
+ *                     id "string").
+ * \return A non-negative value if successful, otherwise a negative error code.
+ *
+ * Example:
+\code
+	{
+		@func private_string
+	}
+\endcode
+ */ 
+int snd_func_private_string(snd_config_t **dst, snd_config_t *root ATTRIBUTE_UNUSED,
+			    snd_config_t *src, snd_config_t *private_data)
+{
+	int err;
+	const char *str, *id;
+
+	if (private_data == NULL)
+		return snd_config_copy(dst, src);
+	err = snd_config_test_id(private_data, "string");
+	if (err) {
+		SNDERR("field string not found");
+		return -EINVAL;
+	}
+	err = snd_config_get_string(private_data, &str);
+	if (err < 0) {
+		SNDERR("field string is not a string");
+		return err;
+	}
+	err = snd_config_get_id(src, &id);
+	if (err >= 0)
+		err = snd_config_imake_string(dst, id, str);
+	return err;
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(snd_func_private_string, SND_CONFIG_DLSYM_VERSION_EVALUATE);
+#endif
+
+#ifndef DOC_HIDDEN
+int snd_determine_driver(int card, char **driver)
+{
+	snd_ctl_t *ctl = NULL;
+	snd_ctl_card_info_t *info;
+	char *res = NULL;
+	int err;
+
+	assert(card >= 0 && card <= 32);
+	err = open_ctl(card, &ctl);
+	if (err < 0) {
+		SNDERR("could not open control for card %i", card);
+		goto __error;
+	}
+	snd_ctl_card_info_alloca(&info);
+	err = snd_ctl_card_info(ctl, info);
+	if (err < 0) {
+		SNDERR("snd_ctl_card_info error: %s", snd_strerror(err));
+		goto __error;
+	}
+	res = strdup(snd_ctl_card_info_get_driver(info));
+	if (res == NULL)
+		err = -ENOMEM;
+	else {
+		*driver = res;
+		err = 0;
+	}
+      __error:
+	if (ctl)
+		snd_ctl_close(ctl);
+	return err;
+}
+#endif
+
+/**
+ * \brief Returns the driver identification from \c private_data.
+ * \param dst The function puts the handle to the result configuration node
+ *            (with type string) at the address specified by \p dst.
+ * \param root Handle to the root source node.
+ * \param src Handle to the source node.
+ * \param private_data Handle to the \c private_data node (type integer,
+ *                     id "card").
+ * \return A non-negative value if successful, otherwise a negative error code.
+ *
+ * Example:
+\code
+	{
+		@func private_card_driver
+	}
+\endcode
+ */ 
+int snd_func_private_card_driver(snd_config_t **dst, snd_config_t *root ATTRIBUTE_UNUSED, snd_config_t *src,
+				 snd_config_t *private_data)
+{
+	char *driver;
+	const char *id;
+	int err;
+	long card;
+
+	err = snd_config_test_id(private_data, "card");
+	if (err) {
+		SNDERR("field card not found");
+		return -EINVAL;
+	}
+	err = snd_config_get_integer(private_data, &card);
+	if (err < 0) {
+		SNDERR("field card is not an integer");
+		return err;
+	}
+	if ((err = snd_determine_driver(card, &driver)) < 0)
+		return err;
+	err = snd_config_get_id(src, &id);
+	if (err >= 0)
+		err = snd_config_imake_string(dst, id, driver);
+	free(driver);
+	return err;
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(snd_func_private_card_driver, SND_CONFIG_DLSYM_VERSION_EVALUATE);
+#endif
+
+static int parse_card(snd_config_t *root, snd_config_t *src,
+		      snd_config_t *private_data)
+{
+	snd_config_t *n;
+	char *str;
+	int card, err;
+	
+	err = snd_config_search(src, "card", &n);
+	if (err < 0) {
+		SNDERR("field card not found");
+		return err;
+	}
+	err = snd_config_evaluate(n, root, private_data, NULL);
+	if (err < 0) {
+		SNDERR("error evaluating card");
+		return err;
+	}
+	err = snd_config_get_ascii(n, &str);
+	if (err < 0) {
+		SNDERR("field card is not an integer or a string");
+		return err;
+	}
+	card = snd_card_get_index(str);
+	if (card < 0)
+		SNDERR("cannot find card '%s'", str);
+	free(str);
+	return card;
+}
+
+/**
+ * \brief Returns the card number as integer.
+ * \param dst The function puts the handle to the result configuration node
+ *            (with type string) at the address specified by \p dst.
+ * \param root Handle to the root source node.
+ * \param src Handle to the source node, with a \c card definition.
+ * \param private_data Handle to the \c private_data node.
+ * \return A non-negative value if successful, otherwise a negative error code.
+ *
+ * Example:
+\code
+	{
+		@func card_inum
+		card '0'
+	}
+\endcode
+ */ 
+int snd_func_card_inum(snd_config_t **dst, snd_config_t *root, snd_config_t *src,
+		       snd_config_t *private_data)
+{
+	const char *id;
+	int card, err;
+	
+	card = parse_card(root, src, private_data);
+	if (card < 0)
+		return card;
+	err = snd_config_get_id(src, &id);
+	if (err >= 0)
+		err = snd_config_imake_integer(dst, id, card);
+	return err;
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(snd_func_card_inum, SND_CONFIG_DLSYM_VERSION_EVALUATE);
+#endif
+
+/**
+ * \brief Returns the driver identification for a card.
+ * \param dst The function puts the handle to the result configuration node
+ *            (with type string) at the address specified by \p dst.
+ * \param root Handle to the root source node.
+ * \param src Handle to the source node, with a \c card definition.
+ * \param private_data Handle to the \c private_data node.
+ * \return A non-negative value if successful, otherwise a negative error code.
+ *
+ * Example:
+\code
+	{
+		@func card_driver
+		card 0
+	}
+\endcode
+ */ 
+int snd_func_card_driver(snd_config_t **dst, snd_config_t *root, snd_config_t *src,
+			 snd_config_t *private_data)
+{
+	snd_config_t *val;
+	int card, err;
+	
+	card = parse_card(root, src, private_data);
+	if (card < 0)
+		return card;
+	err = snd_config_imake_integer(&val, "card", card);
+	if (err < 0)
+		return err;
+	err = snd_func_private_card_driver(dst, root, src, val);
+	snd_config_delete(val);
+	return err;
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(snd_func_card_driver, SND_CONFIG_DLSYM_VERSION_EVALUATE);
+#endif
+
+/**
+ * \brief Returns the identification of a card.
+ * \param dst The function puts the handle to the result configuration node
+ *            (with type string) at the address specified by \p dst.
+ * \param root Handle to the root source node.
+ * \param src Handle to the source node, with a \c card definition.
+ * \param private_data Handle to the \c private_data node.
+ * \return A non-negative value if successful, otherwise a negative error code.
+ *
+ * Example:
+\code
+	{
+		@func card_id
+		card 0
+	}
+\endcode
+ */ 
+int snd_func_card_id(snd_config_t **dst, snd_config_t *root, snd_config_t *src,
+		     snd_config_t *private_data)
+{
+	snd_ctl_t *ctl = NULL;
+	snd_ctl_card_info_t *info;
+	const char *id;
+	int card, err;
+	
+	card = parse_card(root, src, private_data);
+	if (card < 0)
+		return card;
+	err = open_ctl(card, &ctl);
+	if (err < 0) {
+		SNDERR("could not open control for card %i", card);
+		goto __error;
+	}
+	snd_ctl_card_info_alloca(&info);
+	err = snd_ctl_card_info(ctl, info);
+	if (err < 0) {
+		SNDERR("snd_ctl_card_info error: %s", snd_strerror(err));
+		goto __error;
+	}
+	err = snd_config_get_id(src, &id);
+	if (err >= 0)
+		err = snd_config_imake_string(dst, id,
+					      snd_ctl_card_info_get_id(info));
+      __error:
+      	if (ctl)
+      		snd_ctl_close(ctl);
+	return err;
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(snd_func_card_id, SND_CONFIG_DLSYM_VERSION_EVALUATE);
+#endif
+
+/**
+ * \brief Returns the name of a card.
+ * \param dst The function puts the handle to the result configuration node
+ *            (with type string) at the address specified by \p dst.
+ * \param root Handle to the root source node.
+ * \param src Handle to the source node, with a \c card definition.
+ * \param private_data Handle to the \c private_data node.
+ * \return A non-negative value if successful, otherwise a negative error code.
+ *
+ * Example:
+\code
+	{
+		@func card_name
+		card 0
+	}
+\endcode
+ */ 
+int snd_func_card_name(snd_config_t **dst, snd_config_t *root,
+		       snd_config_t *src, snd_config_t *private_data)
+{
+	snd_ctl_t *ctl = NULL;
+	snd_ctl_card_info_t *info;
+	const char *id;
+	int card, err;
+	
+	card = parse_card(root, src, private_data);
+	if (card < 0)
+		return card;
+	err = open_ctl(card, &ctl);
+	if (err < 0) {
+		SNDERR("could not open control for card %i", card);
+		goto __error;
+	}
+	snd_ctl_card_info_alloca(&info);
+	err = snd_ctl_card_info(ctl, info);
+	if (err < 0) {
+		SNDERR("snd_ctl_card_info error: %s", snd_strerror(err));
+		goto __error;
+	}
+	err = snd_config_get_id(src, &id);
+	if (err >= 0)
+		err = snd_config_imake_string(dst, id,
+					      snd_ctl_card_info_get_name(info));
+      __error:
+      	if (ctl)
+      		snd_ctl_close(ctl);
+	return err;
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(snd_func_card_name, SND_CONFIG_DLSYM_VERSION_EVALUATE);
+#endif
+
+#ifdef BUILD_PCM
+
+/**
+ * \brief Returns the pcm identification of a device.
+ * \param dst The function puts the handle to the result configuration node
+ *            (with type string) at the address specified by \p dst.
+ * \param root Handle to the root source node.
+ * \param src Handle to the source node, with definitions for \c card,
+ *            \c device and (optionally) \c subdevice.
+ * \param private_data Handle to the \c private_data node.
+ * \return A non-negative value if successful, otherwise a negative error code.
+ *
+ * Example:
+\code
+	{
+		@func pcm_id
+		card 0
+		device 0
+		subdevice 0	# optional
+	}
+\endcode
+ */ 
+int snd_func_pcm_id(snd_config_t **dst, snd_config_t *root, snd_config_t *src, void *private_data)
+{
+	snd_config_t *n;
+	snd_ctl_t *ctl = NULL;
+	snd_pcm_info_t *info;
+	const char *id;
+	long card, device, subdevice = 0;
+	int err;
+	
+	card = parse_card(root, src, private_data);
+	if (card < 0)
+		return card;
+	err = snd_config_search(src, "device", &n);
+	if (err < 0) {
+		SNDERR("field device not found");
+		goto __error;
+	}
+	err = snd_config_evaluate(n, root, private_data, NULL);
+	if (err < 0) {
+		SNDERR("error evaluating device");
+		goto __error;
+	}
+	err = snd_config_get_integer(n, &device);
+	if (err < 0) {
+		SNDERR("field device is not an integer");
+		goto __error;
+	}
+	if (snd_config_search(src, "subdevice", &n) >= 0) {
+		err = snd_config_evaluate(n, root, private_data, NULL);
+		if (err < 0) {
+			SNDERR("error evaluating subdevice");
+			goto __error;
+		}
+		err = snd_config_get_integer(n, &subdevice);
+		if (err < 0) {
+			SNDERR("field subdevice is not an integer");
+			goto __error;
+		}
+	}
+	err = open_ctl(card, &ctl);
+	if (err < 0) {
+		SNDERR("could not open control for card %li", card);
+		goto __error;
+	}
+	snd_pcm_info_alloca(&info);
+	snd_pcm_info_set_device(info, device);
+	snd_pcm_info_set_subdevice(info, subdevice);
+	err = snd_ctl_pcm_info(ctl, info);
+	if (err < 0) {
+		SNDERR("snd_ctl_pcm_info error: %s", snd_strerror(err));
+		goto __error;
+	}
+	err = snd_config_get_id(src, &id);
+	if (err >= 0)
+		err = snd_config_imake_string(dst, id, snd_pcm_info_get_id(info));
+      __error:
+      	if (ctl)
+      		snd_ctl_close(ctl);
+	return err;
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(snd_func_pcm_id, SND_CONFIG_DLSYM_VERSION_EVALUATE);
+#endif
+
+/**
+ * \brief Returns the pcm card and device arguments (in form CARD=N,DEV=M)
+ *                for pcm specified by class and index.
+ * \param dst The function puts the handle to the result configuration node
+ *            (with type string) at the address specified by \p dst.
+ * \param root Handle to the root source node.
+ * \param src Handle to the source node, with definitions for \c class
+ *            and \c index.
+ * \param private_data Handle to the \c private_data node.
+ * \return A non-negative value if successful, otherwise a negative error code.
+ *
+ * Example:
+\code
+	{
+		@func pcm_args_by_class
+		class 0
+		index 0
+	}
+\endcode
+ */ 
+int snd_func_pcm_args_by_class(snd_config_t **dst, snd_config_t *root, snd_config_t *src, void *private_data)
+{
+	snd_config_t *n;
+	snd_ctl_t *ctl = NULL;
+	snd_pcm_info_t *info;
+	const char *id;
+	int card = -1, dev;
+	long class, index;
+	int idx = 0;
+	int err;
+
+	err = snd_config_search(src, "class", &n);
+	if (err < 0) {
+		SNDERR("field class not found");
+		goto __out;
+	}
+	err = snd_config_evaluate(n, root, private_data, NULL);
+	if (err < 0) {
+		SNDERR("error evaluating class");
+		goto __out;
+	}
+	err = snd_config_get_integer(n, &class);
+	if (err < 0) {
+		SNDERR("field class is not an integer");
+		goto __out;
+	}
+	err = snd_config_search(src, "index", &n);
+	if (err < 0) {
+		SNDERR("field index not found");
+		goto __out;
+	}
+	err = snd_config_evaluate(n, root, private_data, NULL);
+	if (err < 0) {
+		SNDERR("error evaluating index");
+		goto __out;
+	}
+	err = snd_config_get_integer(n, &index);
+	if (err < 0) {
+		SNDERR("field index is not an integer");
+		goto __out;
+	}
+
+	snd_pcm_info_alloca(&info);
+	while(1) {
+		err = snd_card_next(&card);
+		if (err < 0) {
+			SNDERR("could not get next card");
+			goto __out;
+		}
+		if (card < 0)
+			break;
+		err = open_ctl(card, &ctl);
+		if (err < 0) {
+			SNDERR("could not open control for card %i", card);
+			goto __out;
+		}
+		dev = -1;
+		memset(info, 0, snd_pcm_info_sizeof());
+		while(1) {
+			err = snd_ctl_pcm_next_device(ctl, &dev);
+			if (err < 0) {
+				SNDERR("could not get next pcm for card %i", card);
+				goto __out;
+			}
+			if (dev < 0)
+				break;
+			snd_pcm_info_set_device(info, dev);
+			err = snd_ctl_pcm_info(ctl, info);
+			if (err < 0)
+				continue;
+			if (snd_pcm_info_get_class(info) == (snd_pcm_class_t)class &&
+					index == idx++)
+				goto __out;
+		}
+      		snd_ctl_close(ctl);
+		ctl = NULL;
+	}
+	err = -ENODEV;
+
+      __out:
+      	if (ctl)
+      		snd_ctl_close(ctl);
+	if (err < 0)
+		return err;
+	if((err = snd_config_get_id(src, &id)) >= 0) {
+		char name[32];
+		snprintf(name, sizeof(name), "CARD=%i,DEV=%i", card, dev);
+		err = snd_config_imake_string(dst, id, name);
+	}
+	return err;
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(snd_func_pcm_args_by_class, SND_CONFIG_DLSYM_VERSION_EVALUATE);
+#endif
+
+/**
+ * \brief Returns the PCM subdevice from \c private_data.
+ * \param dst The function puts the handle to the result configuration node
+ *            (with type integer) at the address specified by \p dst.
+ * \param root Handle to the root source node.
+ * \param src Handle to the source node.
+ * \param private_data Handle to the \c private_data node (type pointer,
+ *                     id "pcm_handle").
+ * \return A non-negative value if successful, otherwise a negative error code.
+ *
+ * Example:
+\code
+	{
+		@func private_pcm_subdevice
+	}
+\endcode
+ */ 
+int snd_func_private_pcm_subdevice(snd_config_t **dst, snd_config_t *root ATTRIBUTE_UNUSED,
+				   snd_config_t *src, snd_config_t *private_data)
+{
+	snd_pcm_info_t *info;
+	const char *id;
+	const void *data;
+	snd_pcm_t *pcm;
+	int err;
+
+	if (private_data == NULL)
+		return snd_config_copy(dst, src);
+	err = snd_config_test_id(private_data, "pcm_handle");
+	if (err) {
+		SNDERR("field pcm_handle not found");
+		return -EINVAL;
+	}
+	err = snd_config_get_pointer(private_data, &data);
+	pcm = (snd_pcm_t *)data;
+	if (err < 0) {
+		SNDERR("field pcm_handle is not a pointer");
+		return err;
+	}
+	snd_pcm_info_alloca(&info);
+	err = snd_pcm_info(pcm, info);
+	if (err < 0) {
+		SNDERR("snd_ctl_pcm_info error: %s", snd_strerror(err));
+		return err;
+	}
+	err = snd_config_get_id(src, &id);
+	if (err >= 0)
+		err = snd_config_imake_integer(dst, id, snd_pcm_info_get_subdevice(info));
+	return err;
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(snd_func_private_pcm_subdevice, SND_CONFIG_DLSYM_VERSION_EVALUATE);
+#endif
+
+#endif /* BUILD_PCM */
+
+/**
+ * \brief Copies the specified configuration node.
+ * \param dst The function puts the handle to the result configuration node
+ *            (with the same type as the specified node) at the address
+ *            specified by \p dst.
+ * \param root Handle to the root source node.
+ * \param src Handle to the source node, with definitions for \c name and
+ *            (optionally) \c file.
+ * \param private_data Handle to the \c private_data node.
+ * \return A non-negative value if successful, otherwise a negative error code.
+ * \note The root source node can be modified!
+ *
+ * Example:
+\code
+	{
+		@func refer
+		file "/etc/myconf.conf"		# optional
+		name "id1.id2.id3"
+	}
+\endcode
+ */ 
+int snd_func_refer(snd_config_t **dst, snd_config_t *root, snd_config_t *src,
+		   snd_config_t *private_data)
+{
+	snd_config_t *n;
+	const char *file = NULL, *name = NULL;
+	int err;
+	
+	err = snd_config_search(src, "file", &n);
+	if (err >= 0) {
+		err = snd_config_evaluate(n, root, private_data, NULL);
+		if (err < 0) {
+			SNDERR("error evaluating file");
+			goto _end;
+		}
+		err = snd_config_get_string(n, &file);
+		if (err < 0) {
+			SNDERR("file is not a string");
+			goto _end;
+		}
+	}
+	err = snd_config_search(src, "name", &n);
+	if (err >= 0) {
+		err = snd_config_evaluate(n, root, private_data, NULL);
+		if (err < 0) {
+			SNDERR("error evaluating name");
+			goto _end;
+		}
+		err = snd_config_get_string(n, &name);
+		if (err < 0) {
+			SNDERR("name is not a string");
+			goto _end;
+		}
+	}
+	if (!name) {
+		err = -EINVAL;
+		SNDERR("name is not specified");
+		goto _end;
+	}
+	if (file) {
+		snd_input_t *input;
+		err = snd_input_stdio_open(&input, file, "r");
+		if (err < 0) {
+			SNDERR("Unable to open file %s: %s", file, snd_strerror(err));
+			goto _end;
+		}
+		err = snd_config_load(root, input);
+		snd_input_close(input);
+		if (err < 0)
+			goto _end;
+	}
+	err = snd_config_search_definition(root, NULL, name, dst);
+	if (err >= 0) {
+		const char *id;
+		err = snd_config_get_id(src, &id);
+		if (err >= 0)
+			err = snd_config_set_id(*dst, id);
+	} else {
+		err = snd_config_search(src, "default", &n);
+		if (err < 0)
+			SNDERR("Unable to find definition '%s'", name);
+		else {
+			const char *id;
+			err = snd_config_evaluate(n, root, private_data, NULL);
+			if (err < 0)
+				return err;
+			if ((err = snd_config_copy(dst, n)) >= 0) {
+				if ((err = snd_config_get_id(src, &id)) < 0 ||
+				    (err = snd_config_set_id(*dst, id)) < 0)
+					snd_config_delete(*dst);
+			}
+		}
+	}
+ _end:
+	return err;
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(snd_func_refer, SND_CONFIG_DLSYM_VERSION_EVALUATE);
+#endif
diff --git a/src/control/Makefile.am b/src/control/Makefile.am
new file mode 100644
index 0000000..8076c73
--- /dev/null
+++ b/src/control/Makefile.am
@@ -0,0 +1,18 @@
+EXTRA_LTLIBRARIES = libcontrol.la
+
+libcontrol_la_SOURCES = cards.c tlv.c namehint.c hcontrol.c \
+                        control.c control_hw.c setup.c ctlparse.c \
+                        control_symbols.c
+if BUILD_CTL_PLUGIN_SHM
+libcontrol_la_SOURCES += control_shm.c
+endif
+if BUILD_CTL_PLUGIN_EXT
+libcontrol_la_SOURCES += control_ext.c
+endif
+
+noinst_HEADERS = control_local.h
+
+all: libcontrol.la
+
+
+INCLUDES=-I$(top_srcdir)/include
diff --git a/src/control/cards.c b/src/control/cards.c
new file mode 100644
index 0000000..b528e33
--- /dev/null
+++ b/src/control/cards.c
@@ -0,0 +1,222 @@
+/**
+ * \file control/cards.c
+ * \brief Basic Soundcard Operations
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \date 1998-2001
+ */
+/*
+ *  Soundcard Operations - main file
+ *  Copyright (c) 1998 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include "control_local.h"
+
+#ifndef DOC_HIDDEN
+#define SND_FILE_CONTROL	ALSA_DEVICE_DIRECTORY "controlC%i"
+#define SND_FILE_LOAD		ALOAD_DEVICE_DIRECTORY "aloadC%i"
+#endif
+
+static int snd_card_load2(const char *control)
+{
+	int open_dev;
+	snd_ctl_card_info_t info;
+
+	open_dev = snd_open_device(control, O_RDONLY);
+	if (open_dev >= 0) {
+		if (ioctl(open_dev, SNDRV_CTL_IOCTL_CARD_INFO, &info) < 0) {
+			int err = -errno;
+			close(open_dev);
+			return err;
+		}
+		close(open_dev);
+		return info.card;
+	} else {
+		return -errno;
+	}
+}
+
+static int snd_card_load1(int card)
+{
+	int res;
+	char control[sizeof(SND_FILE_CONTROL) + 10];
+
+	sprintf(control, SND_FILE_CONTROL, card);
+	res = snd_card_load2(control);
+#ifdef SUPPORT_ALOAD
+	if (res < 0) {
+		char aload[sizeof(SND_FILE_LOAD) + 10];
+		sprintf(aload, SND_FILE_LOAD, card);
+		res = snd_card_load2(aload);
+	}
+#endif
+	return res;
+}
+
+/**
+ * \brief Try to load the driver for a card.
+ * \param card Card number.
+ * \return 1 if driver is present, zero if driver is not present
+ */
+int snd_card_load(int card)
+{
+	return !!(snd_card_load1(card) >= 0);
+}
+
+/**
+ * \brief Try to determine the next card.
+ * \param rcard pointer to card number
+ * \result zero if success, otherwise a negative error code
+ *
+ * Tries to determine the next card from given card number.
+ * If card number is -1, then the first available card is
+ * returned. If the result card number is -1, no more cards
+ * are available.
+ */
+int snd_card_next(int *rcard)
+{
+	int card;
+	
+	if (rcard == NULL)
+		return -EINVAL;
+	card = *rcard;
+	card = card < 0 ? 0 : card + 1;
+	for (; card < 32; card++) {
+		if (snd_card_load(card)) {
+			*rcard = card;
+			return 0;
+		}
+	}
+	*rcard = -1;
+	return 0;
+}
+
+/**
+ * \brief Convert card string to an integer value.
+ * \param string String containing card identifier
+ * \return zero if success, otherwise a negative error code
+ *
+ * The accepted format is an integer value in ASCII representation
+ * or the card identifier (the id parameter for sound-card drivers).
+ * The control device name like /dev/snd/controlC0 is accepted, too.
+ */
+int snd_card_get_index(const char *string)
+{
+	int card, err;
+	snd_ctl_t *handle;
+	snd_ctl_card_info_t info;
+
+	if (!string || *string == '\0')
+		return -EINVAL;
+	if ((isdigit(*string) && *(string + 1) == 0) ||
+	    (isdigit(*string) && isdigit(*(string + 1)) && *(string + 2) == 0)) {
+		if (sscanf(string, "%i", &card) != 1)
+			return -EINVAL;
+		if (card < 0 || card > 31)
+			return -EINVAL;
+		err = snd_card_load1(card);
+		if (err >= 0)
+			return card;
+		return err;
+	}
+	if (string[0] == '/')	/* device name */
+		return snd_card_load2(string);
+	for (card = 0; card < 32; card++) {
+#ifdef SUPPORT_ALOAD
+		if (! snd_card_load(card))
+			continue;
+#endif
+		if (snd_ctl_hw_open(&handle, NULL, card, 0) < 0)
+			continue;
+		if (snd_ctl_card_info(handle, &info) < 0) {
+			snd_ctl_close(handle);
+			continue;
+		}
+		snd_ctl_close(handle);
+		if (!strcmp((const char *)info.id, string))
+			return card;
+	}
+	return -ENODEV;
+}
+
+/**
+ * \brief Obtain the card name.
+ * \param card Card number
+ * \param name Result - card name corresponding to card number
+ * \result zero if success, otherwise a negative error code
+ *
+ * The value returned in name is allocated with strdup and should be
+ * freed when no longer used.
+ */
+int snd_card_get_name(int card, char **name)
+{
+	snd_ctl_t *handle;
+	snd_ctl_card_info_t info;
+	int err;
+	
+	if (name == NULL)
+		return -EINVAL;
+	if ((err = snd_ctl_hw_open(&handle, NULL, card, 0)) < 0)
+		return err;
+	if ((err = snd_ctl_card_info(handle, &info)) < 0) {
+		snd_ctl_close(handle);
+		return err;
+	}
+	snd_ctl_close(handle);
+	*name = strdup((const char *)info.name);
+	if (*name == NULL)
+		return -ENOMEM;
+	return 0;
+}
+
+/**
+ * \brief Obtain the card long name.
+ * \param card Card number
+ * \param name Result - card long name corresponding to card number
+ * \result zero if success, otherwise a negative error code
+ *
+ * The value returned in name is allocated with strdup and should be
+ * freed when no longer used.
+ */
+int snd_card_get_longname(int card, char **name)
+{
+	snd_ctl_t *handle;
+	snd_ctl_card_info_t info;
+	int err;
+	
+	if (name == NULL)
+		return -EINVAL;
+	if ((err = snd_ctl_hw_open(&handle, NULL, card, 0)) < 0)
+		return err;
+	if ((err = snd_ctl_card_info(handle, &info)) < 0) {
+		snd_ctl_close(handle);
+		return err;
+	}
+	snd_ctl_close(handle);
+	*name = strdup((const char *)info.longname);
+	if (*name == NULL)
+		return -ENOMEM;
+	return 0;
+}
diff --git a/src/control/control.c b/src/control/control.c
new file mode 100644
index 0000000..a0965c6
--- /dev/null
+++ b/src/control/control.c
@@ -0,0 +1,2661 @@
+/**
+ * \file control/control.c
+ * \brief CTL interface - primitive controls
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \date 2000
+ *
+ * CTL interface is designed to access primitive controls.
+ * See \ref control page for more details.
+ */
+/*
+ *  Control Interface - main file
+ *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+/*! \page control Control interface
+
+<P>Control interface is designed to access primitive controls. There is
+also interface notifying about control and structure changes.
+
+\section control_general_overview General overview
+
+The primitive controls can be integer, boolean, enumerators, bytes
+and IEC958 structure.
+
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/poll.h>
+#include "control_local.h"
+
+/**
+ * \brief get identifier of CTL handle
+ * \param ctl CTL handle
+ * \return ascii identifier of CTL handle
+ *
+ * Returns the ASCII identifier of given CTL handle. It's the same
+ * identifier specified in snd_ctl_open().
+ */
+const char *snd_ctl_name(snd_ctl_t *ctl)
+{
+	assert(ctl);
+	return ctl->name;
+}
+
+/**
+ * \brief get type of CTL handle
+ * \param ctl CTL handle
+ * \return type of CTL handle
+ *
+ * Returns the type #snd_ctl_type_t of given CTL handle.
+ */
+snd_ctl_type_t snd_ctl_type(snd_ctl_t *ctl)
+{
+	assert(ctl);
+	return ctl->type;
+}
+
+/**
+ * \brief close CTL handle
+ * \param ctl CTL handle
+ * \return 0 on success otherwise a negative error code
+ *
+ * Closes the specified CTL handle and frees all associated
+ * resources.
+ */
+int snd_ctl_close(snd_ctl_t *ctl)
+{
+	int err;
+	while (!list_empty(&ctl->async_handlers)) {
+		snd_async_handler_t *h = list_entry(&ctl->async_handlers.next, snd_async_handler_t, hlist);
+		snd_async_del_handler(h);
+	}
+	err = ctl->ops->close(ctl);
+	free(ctl->name);
+	snd_dlobj_cache_put(ctl->open_func);
+	free(ctl);
+	return err;
+}
+
+/**
+ * \brief set nonblock mode
+ * \param ctl CTL handle
+ * \param nonblock 0 = block, 1 = nonblock mode
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_ctl_nonblock(snd_ctl_t *ctl, int nonblock)
+{
+	int err;
+	assert(ctl);
+	err = ctl->ops->nonblock(ctl, nonblock);
+	if (err < 0)
+		return err;
+	ctl->nonblock = nonblock;
+	return 0;
+}
+
+#ifndef DOC_HIDDEN
+int snd_ctl_new(snd_ctl_t **ctlp, snd_ctl_type_t type, const char *name)
+{
+	snd_ctl_t *ctl;
+	ctl = calloc(1, sizeof(*ctl));
+	if (!ctl)
+		return -ENOMEM;
+	ctl->type = type;
+	if (name)
+		ctl->name = strdup(name);
+	INIT_LIST_HEAD(&ctl->async_handlers);
+	*ctlp = ctl;
+	return 0;
+}
+	
+
+/**
+ * \brief set async mode
+ * \param ctl CTL handle
+ * \param sig Signal to raise: < 0 disable, 0 default (SIGIO)
+ * \param pid Process ID to signal: 0 current
+ * \return 0 on success otherwise a negative error code
+ *
+ * A signal is raised when a change happens.
+ */
+int snd_ctl_async(snd_ctl_t *ctl, int sig, pid_t pid)
+{
+	assert(ctl);
+	if (sig == 0)
+		sig = SIGIO;
+	if (pid == 0)
+		pid = getpid();
+	return ctl->ops->async(ctl, sig, pid);
+}
+#endif
+
+/**
+ * \brief get count of poll descriptors for CTL handle
+ * \param ctl CTL handle
+ * \return count of poll descriptors
+ */
+int snd_ctl_poll_descriptors_count(snd_ctl_t *ctl)
+{
+	assert(ctl);
+	if (ctl->ops->poll_descriptors_count)
+		return ctl->ops->poll_descriptors_count(ctl);
+	if (ctl->poll_fd < 0)
+		return 0;
+	return 1;
+}
+
+/**
+ * \brief get poll descriptors
+ * \param ctl CTL handle
+ * \param pfds array of poll descriptors
+ * \param space space in the poll descriptor array
+ * \return count of filled descriptors
+ */
+int snd_ctl_poll_descriptors(snd_ctl_t *ctl, struct pollfd *pfds, unsigned int space)
+{
+	assert(ctl && pfds);
+	if (ctl->ops->poll_descriptors)
+		return ctl->ops->poll_descriptors(ctl, pfds, space);
+	if (ctl->poll_fd < 0)
+		return 0;
+	if (space > 0) {
+		pfds->fd = ctl->poll_fd;
+		pfds->events = POLLIN|POLLERR|POLLNVAL;
+		return 1;
+	}
+	return 0;
+}
+
+/**
+ * \brief get returned events from poll descriptors
+ * \param ctl CTL handle
+ * \param pfds array of poll descriptors
+ * \param nfds count of poll descriptors
+ * \param revents returned events
+ * \return zero if success, otherwise a negative error code
+ */
+int snd_ctl_poll_descriptors_revents(snd_ctl_t *ctl, struct pollfd *pfds, unsigned int nfds, unsigned short *revents)
+{
+	assert(ctl && pfds && revents);
+	if (ctl->ops->poll_revents)
+		return ctl->ops->poll_revents(ctl, pfds, nfds, revents);
+	if (nfds == 1) {
+		*revents = pfds->revents;
+                return 0;
+	}
+	return -EINVAL;
+}
+
+/**
+ * \brief Ask to be informed about events (poll, #snd_async_add_ctl_handler, #snd_ctl_read)
+ * \param ctl CTL handle
+ * \param subscribe 0 = unsubscribe, 1 = subscribe
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_ctl_subscribe_events(snd_ctl_t *ctl, int subscribe)
+{
+	assert(ctl);
+	return ctl->ops->subscribe_events(ctl, subscribe);
+}
+
+
+/**
+ * \brief Get card related information
+ * \param ctl CTL handle
+ * \param info Card info pointer
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_ctl_card_info(snd_ctl_t *ctl, snd_ctl_card_info_t *info)
+{
+	assert(ctl && info);
+	return ctl->ops->card_info(ctl, info);
+}
+
+/**
+ * \brief Get a list of element identifiers
+ * \param ctl CTL handle
+ * \param list CTL element identifiers list pointer
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_ctl_elem_list(snd_ctl_t *ctl, snd_ctl_elem_list_t *list)
+{
+	assert(ctl && list);
+	assert(list->space == 0 || list->pids);
+	return ctl->ops->element_list(ctl, list);
+}
+
+/**
+ * \brief Get CTL element information
+ * \param ctl CTL handle
+ * \param info CTL element id/information pointer
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_ctl_elem_info(snd_ctl_t *ctl, snd_ctl_elem_info_t *info)
+{
+	assert(ctl && info && (info->id.name[0] || info->id.numid));
+	return ctl->ops->element_info(ctl, info);
+}
+
+/**
+ * \brief Create and add an user INTEGER CTL element
+ * \param ctl CTL handle
+ * \param id CTL element id to add
+ * \param count number of elements
+ * \param min minimum value
+ * \param max maximum value
+ * \param step value step
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_ctl_elem_add_integer(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
+			     unsigned int count, long min, long max, long step)
+{
+	snd_ctl_elem_info_t *info;
+	snd_ctl_elem_value_t *val;
+	unsigned int i;
+	int err;
+
+	assert(ctl && id && id->name[0]);
+	snd_ctl_elem_info_alloca(&info);
+	info->id = *id;
+	info->type = SND_CTL_ELEM_TYPE_INTEGER;
+	info->access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+		SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE;
+	info->count = count;
+	info->value.integer.min = min;
+	info->value.integer.max = max;
+	info->value.integer.step = step;
+	err = ctl->ops->element_add(ctl, info);
+	if (err < 0)
+		return err;
+	snd_ctl_elem_value_alloca(&val);
+	val->id = *id;
+	for (i = 0; i < count; i++)
+		val->value.integer.value[i] = min;
+	err = ctl->ops->element_write(ctl, val);
+	return err;
+}
+
+/**
+ * \brief Create and add an user INTEGER64 CTL element
+ * \param ctl CTL handle
+ * \param id CTL element id to add
+ * \param count number of elements
+ * \param min minimum value
+ * \param max maximum value
+ * \param step value step
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_ctl_elem_add_integer64(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
+			       unsigned int count, long long min, long long max,
+			       long long step)
+{
+	snd_ctl_elem_info_t *info;
+	snd_ctl_elem_value_t *val;
+	unsigned int i;
+	int err;
+
+	assert(ctl && id && id->name[0]);
+	snd_ctl_elem_info_alloca(&info);
+	info->id = *id;
+	info->type = SND_CTL_ELEM_TYPE_INTEGER64;
+	info->count = count;
+	info->value.integer64.min = min;
+	info->value.integer64.max = max;
+	info->value.integer64.step = step;
+	err = ctl->ops->element_add(ctl, info);
+	if (err < 0)
+		return err;
+	snd_ctl_elem_value_alloca(&val);
+	val->id = *id;
+	for (i = 0; i < count; i++)
+		val->value.integer64.value[i] = min;
+	err = ctl->ops->element_write(ctl, val);
+	return err;
+}
+
+/**
+ * \brief Create and add an user BOOLEAN CTL element
+ * \param ctl CTL handle
+ * \param id CTL element id to add
+ * \param count number of elements
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_ctl_elem_add_boolean(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
+			     unsigned int count)
+{
+	snd_ctl_elem_info_t *info;
+
+	assert(ctl && id && id->name[0]);
+	snd_ctl_elem_info_alloca(&info);
+	info->id = *id;
+	info->type = SND_CTL_ELEM_TYPE_BOOLEAN;
+	info->count = count;
+	info->value.integer.min = 0;
+	info->value.integer.max = 1;
+	return ctl->ops->element_add(ctl, info);
+}
+
+/**
+ * \brief Create and add a user-defined control element of type enumerated.
+ * \param[in] ctl Control device handle.
+ * \param[in] id ID of the new control element.
+ * \param[in] count Number of element values.
+ * \param[in] items Range of possible values (0 ... \a items - 1).
+ * \param[in] names An array containing \a items strings.
+ * \return Zero on success, otherwise a negative error code.
+ *
+ * This function creates a user element, i.e., a control element that is not
+ * controlled by the control device's driver but that is just stored together
+ * with the other elements of \a ctl.
+ *
+ * The fields of \a id, except numid, must be set to unique values that
+ * identify the new element.
+ *
+ * The new element is locked; its value is initialized as zero.
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-EBUSY<dd>A control element with ID \a id already exists.
+ * <dt>-EINVAL<dd>\a count is not at least one or greater than 128, or \a items
+ * 	is not at least one, or a string in \a names is empty or longer than 63
+ * 	bytes, or the strings in \a names require more than 64 KB storage.
+ * <dt>-ENOMEM<dd>Out of memory, or there are too many user control elements.
+ * <dt>-ENXIO<dd>This driver does not support (enumerated) user controls.
+ * <dt>-ENODEV<dd>Device unplugged.
+ * </dl>
+ *
+ * \par Compatibility:
+ * snd_ctl_elem_add_enumerated() was introduced in ALSA 1.0.25.
+ */
+int snd_ctl_elem_add_enumerated(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
+				unsigned int count, unsigned int items,
+				const char *const names[])
+{
+	snd_ctl_elem_info_t *info;
+	unsigned int i, bytes;
+	char *buf, *p;
+	int err;
+
+	assert(ctl && id && id->name[0] && names);
+
+	snd_ctl_elem_info_alloca(&info);
+	info->id = *id;
+	info->type = SND_CTL_ELEM_TYPE_ENUMERATED;
+	info->count = count;
+	info->value.enumerated.items = items;
+
+	bytes = 0;
+	for (i = 0; i < items; ++i)
+		bytes += strlen(names[i]) + 1;
+	buf = malloc(bytes);
+	if (!buf)
+		return -ENOMEM;
+	info->value.enumerated.names_ptr = (uintptr_t)buf;
+	info->value.enumerated.names_length = bytes;
+	p = buf;
+	for (i = 0; i < items; ++i)
+		p = stpcpy(p, names[i]) + 1;
+
+	err = ctl->ops->element_add(ctl, info);
+
+	free(buf);
+
+	return err;
+}
+
+/**
+ * \brief Create and add an user IEC958 CTL element
+ * \param ctl CTL handle
+ * \param id CTL element info to add
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_ctl_elem_add_iec958(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id)
+{
+	snd_ctl_elem_info_t *info;
+
+	assert(ctl && id && id->name[0]);
+	snd_ctl_elem_info_alloca(&info);
+	info->id = *id;
+	info->type = SND_CTL_ELEM_TYPE_IEC958;
+	info->count = 1;
+	return ctl->ops->element_add(ctl, info);
+}
+
+/**
+ * \brief Remove an user CTL element
+ * \param ctl CTL handle
+ * \param id CTL element identification
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_ctl_elem_remove(snd_ctl_t *ctl, snd_ctl_elem_id_t *id)
+{
+	assert(ctl && id && (id->name[0] || id->numid));
+	return ctl->ops->element_remove(ctl, id);
+}
+
+/**
+ * \brief Get CTL element value
+ * \param ctl CTL handle
+ * \param control CTL element id/value pointer
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_ctl_elem_read(snd_ctl_t *ctl, snd_ctl_elem_value_t *control)
+{
+	assert(ctl && control && (control->id.name[0] || control->id.numid));
+	return ctl->ops->element_read(ctl, control);
+}
+
+/**
+ * \brief Set CTL element value
+ * \param ctl CTL handle
+ * \param control CTL element id/value pointer
+ * \retval 0 on success
+ * \retval >0 on success when value was changed
+ * \retval <0 a negative error code
+ */
+int snd_ctl_elem_write(snd_ctl_t *ctl, snd_ctl_elem_value_t *control)
+{
+	assert(ctl && control && (control->id.name[0] || control->id.numid));
+	return ctl->ops->element_write(ctl, control);
+}
+
+static int snd_ctl_tlv_do(snd_ctl_t *ctl, int op_flag,
+			  const snd_ctl_elem_id_t *id,
+		          unsigned int *tlv, unsigned int tlv_size)
+{
+	snd_ctl_elem_info_t *info = NULL;
+	int err;
+
+	if (id->numid == 0) {
+		info = calloc(1, sizeof(*info));
+		if (info == NULL)
+			return -ENOMEM;
+		info->id = *id;
+		id = &info->id;
+		err = snd_ctl_elem_info(ctl, info);
+		if (err < 0)
+			goto __err;
+		if (id->numid == 0) {
+			err = -ENOENT;
+			goto __err;
+		}
+	}
+	err = ctl->ops->element_tlv(ctl, op_flag, id->numid, tlv, tlv_size);
+      __err:
+      	if (info)
+      		free(info);
+	return err;
+}
+
+
+
+/**
+ * \brief Get CTL element TLV value
+ * \param ctl CTL handle
+ * \param id CTL element id pointer
+ * \param tlv TLV array pointer to store 
+ * \param tlv_size TLV array size in bytes
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_ctl_elem_tlv_read(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
+			  unsigned int *tlv, unsigned int tlv_size)
+{
+	int err;
+	assert(ctl && id && (id->name[0] || id->numid) && tlv);
+	if (tlv_size < 2 * sizeof(int))
+		return -EINVAL;
+	/* 1.0.12 driver doesn't return the error even if the user TLV
+	 * is empty.  So, initialize TLV here with an invalid type
+	 * and compare the returned value after ioctl for checking
+	 * the validity of TLV.
+	 */
+	tlv[0] = -1;
+	tlv[1] = 0;
+	err = snd_ctl_tlv_do(ctl, 0, id, tlv, tlv_size);
+	if (err >= 0 && tlv[0] == (unsigned int)-1)
+		err = -ENXIO;
+	return err;
+}
+
+/**
+ * \brief Set CTL element TLV value
+ * \param ctl CTL handle
+ * \param id CTL element id pointer
+ * \param tlv TLV array pointer to store 
+ * \retval 0 on success
+ * \retval >0 on success when value was changed
+ * \retval <0 a negative error code
+ */
+int snd_ctl_elem_tlv_write(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
+			   const unsigned int *tlv)
+{
+	assert(ctl && id && (id->name[0] || id->numid) && tlv);
+	return snd_ctl_tlv_do(ctl, 1, id, (unsigned int *)tlv, tlv[1] + 2 * sizeof(unsigned int));
+}
+
+/**
+ * \brief Process CTL element TLV command
+ * \param ctl CTL handle
+ * \param id CTL element id pointer
+ * \param tlv TLV array pointer to process
+ * \retval 0 on success
+ * \retval >0 on success when value was changed
+ * \retval <0 a negative error code
+ */
+int snd_ctl_elem_tlv_command(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
+			     const unsigned int *tlv)
+{
+	assert(ctl && id && (id->name[0] || id->numid) && tlv);
+	return snd_ctl_tlv_do(ctl, -1, id, (unsigned int *)tlv, tlv[1] + 2 * sizeof(unsigned int));
+}
+
+/**
+ * \brief Lock CTL element
+ * \param ctl CTL handle
+ * \param id CTL element id pointer
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_ctl_elem_lock(snd_ctl_t *ctl, snd_ctl_elem_id_t *id)
+{
+	assert(ctl && id);
+	return ctl->ops->element_lock(ctl, id);
+}
+
+/**
+ * \brief Unlock CTL element
+ * \param ctl CTL handle
+ * \param id CTL element id pointer
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_ctl_elem_unlock(snd_ctl_t *ctl, snd_ctl_elem_id_t *id)
+{
+	assert(ctl && id);
+	return ctl->ops->element_unlock(ctl, id);
+}
+
+/**
+ * \brief Get next hardware dependent device number
+ * \param ctl CTL handle
+ * \param device current device on entry and next device on return
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_ctl_hwdep_next_device(snd_ctl_t *ctl, int *device)
+{
+	assert(ctl && device);
+	return ctl->ops->hwdep_next_device(ctl, device);
+}
+
+/**
+ * \brief Get info about a hardware dependent device
+ * \param ctl CTL handle
+ * \param info Hardware dependent device id/info pointer
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_ctl_hwdep_info(snd_ctl_t *ctl, snd_hwdep_info_t * info)
+{
+	assert(ctl && info);
+	return ctl->ops->hwdep_info(ctl, info);
+}
+
+/**
+ * \brief Get next PCM device number
+ * \param ctl CTL handle
+ * \param device current device on entry and next device on return
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_ctl_pcm_next_device(snd_ctl_t *ctl, int * device)
+{
+	assert(ctl && device);
+	return ctl->ops->pcm_next_device(ctl, device);
+}
+
+/**
+ * \brief Get info about a PCM device
+ * \param ctl CTL handle
+ * \param info PCM device id/info pointer
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_ctl_pcm_info(snd_ctl_t *ctl, snd_pcm_info_t * info)
+{
+	assert(ctl && info);
+	return ctl->ops->pcm_info(ctl, info);
+}
+
+/**
+ * \brief Set preferred PCM subdevice number of successive PCM open
+ * \param ctl CTL handle
+ * \param subdev Preferred PCM subdevice number
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_ctl_pcm_prefer_subdevice(snd_ctl_t *ctl, int subdev)
+{
+	assert(ctl);
+	return ctl->ops->pcm_prefer_subdevice(ctl, subdev);
+}
+
+/**
+ * \brief Get next RawMidi device number
+ * \param ctl CTL handle
+ * \param device current device on entry and next device on return
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_ctl_rawmidi_next_device(snd_ctl_t *ctl, int * device)
+{
+	assert(ctl && device);
+	return ctl->ops->rawmidi_next_device(ctl, device);
+}
+
+/**
+ * \brief Get info about a RawMidi device
+ * \param ctl CTL handle
+ * \param info RawMidi device id/info pointer
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_ctl_rawmidi_info(snd_ctl_t *ctl, snd_rawmidi_info_t * info)
+{
+	assert(ctl && info);
+	return ctl->ops->rawmidi_info(ctl, info);
+}
+
+/**
+ * \brief Set preferred RawMidi subdevice number of successive RawMidi open
+ * \param ctl CTL handle
+ * \param subdev Preferred RawMidi subdevice number
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_ctl_rawmidi_prefer_subdevice(snd_ctl_t *ctl, int subdev)
+{
+	assert(ctl);
+	return ctl->ops->rawmidi_prefer_subdevice(ctl, subdev);
+}
+
+/**
+ * \brief Set Power State to given SND_CTL_POWER_* value and do the power management
+ * \param ctl CTL handle
+ * \param state Desired Power State
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_ctl_set_power_state(snd_ctl_t *ctl, unsigned int state)
+{
+	assert(ctl);
+	if (ctl->ops->set_power_state)
+		return ctl->ops->set_power_state(ctl, state);
+	return -ENXIO;
+}
+
+/**
+ * \brief Get actual Power State
+ * \param ctl CTL handle
+ * \param state Destination value
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_ctl_get_power_state(snd_ctl_t *ctl, unsigned int *state)
+{
+	assert(ctl);
+	if (ctl->ops->get_power_state)
+		return ctl->ops->get_power_state(ctl, state);
+	return -ENXIO;
+}
+
+/**
+ * \brief Read an event
+ * \param ctl CTL handle
+ * \param event Event pointer
+ * \return number of events read otherwise a negative error code on failure
+ */
+int snd_ctl_read(snd_ctl_t *ctl, snd_ctl_event_t *event)
+{
+	assert(ctl && event);
+	return (ctl->ops->read)(ctl, event);
+}
+
+/**
+ * \brief Wait for a CTL to become ready (i.e. at least one event pending)
+ * \param ctl CTL handle
+ * \param timeout maximum time in milliseconds to wait
+ * \return 0 otherwise a negative error code on failure
+ */
+int snd_ctl_wait(snd_ctl_t *ctl, int timeout)
+{
+	struct pollfd *pfd;
+	unsigned short revents;
+	int npfds, err, err_poll;
+
+	npfds = snd_ctl_poll_descriptors_count(ctl);
+	if (npfds <= 0 || npfds >= 16) {
+		SNDERR("Invalid poll_fds %d\n", npfds);
+		return -EIO;
+	}
+	pfd = alloca(sizeof(*pfd) * npfds);
+	err = snd_ctl_poll_descriptors(ctl, pfd, npfds);
+	if (err < 0)
+		return err;
+	if (err != npfds) {
+		SNDMSG("invalid poll descriptors %d\n", err);
+		return -EIO;
+	}
+	for (;;) {
+		err_poll = poll(pfd, npfds, timeout);
+		if (err_poll < 0)
+			return -errno;
+		if (! err_poll)
+			return 0;
+		err = snd_ctl_poll_descriptors_revents(ctl, pfd, npfds, &revents);
+		if (err < 0)
+			return err;
+		if (revents & (POLLERR | POLLNVAL))
+			return -EIO;
+		if (revents & (POLLIN | POLLOUT))
+			return 1;
+	}
+}
+
+/**
+ * \brief Add an async handler for a CTL
+ * \param handler Returned handler handle
+ * \param ctl CTL handle
+ * \param callback Callback function
+ * \param private_data Callback private data
+ * \return 0 otherwise a negative error code on failure
+ */
+int snd_async_add_ctl_handler(snd_async_handler_t **handler, snd_ctl_t *ctl, 
+			      snd_async_callback_t callback, void *private_data)
+{
+	int err;
+	int was_empty;
+	snd_async_handler_t *h;
+	err = snd_async_add_handler(&h, _snd_ctl_async_descriptor(ctl),
+				    callback, private_data);
+	if (err < 0)
+		return err;
+	h->type = SND_ASYNC_HANDLER_CTL;
+	h->u.ctl = ctl;
+	was_empty = list_empty(&ctl->async_handlers);
+	list_add_tail(&h->hlist, &ctl->async_handlers);
+	if (was_empty) {
+		err = snd_ctl_async(ctl, snd_async_handler_get_signo(h), getpid());
+		if (err < 0) {
+			snd_async_del_handler(h);
+			return err;
+		}
+	}
+	*handler = h;
+	return 0;
+}
+
+/**
+ * \brief Return CTL handle related to an async handler
+ * \param handler Async handler handle
+ * \return CTL handle
+ */
+snd_ctl_t *snd_async_handler_get_ctl(snd_async_handler_t *handler)
+{
+	assert(handler->type == SND_ASYNC_HANDLER_CTL);
+	return handler->u.ctl;
+}
+
+static const char *const build_in_ctls[] = {
+	"hw", "shm", NULL
+};
+
+static int snd_ctl_open_conf(snd_ctl_t **ctlp, const char *name,
+			     snd_config_t *ctl_root, snd_config_t *ctl_conf, int mode)
+{
+	const char *str;
+	char *buf = NULL, *buf1 = NULL;
+	int err;
+	snd_config_t *conf, *type_conf = NULL;
+	snd_config_iterator_t i, next;
+	const char *lib = NULL, *open_name = NULL;
+	const char *id;
+	int (*open_func)(snd_ctl_t **, const char *, snd_config_t *, snd_config_t *, int) = NULL;
+#ifndef PIC
+	extern void *snd_control_open_symbols(void);
+#endif
+	if (snd_config_get_type(ctl_conf) != SND_CONFIG_TYPE_COMPOUND) {
+		if (name)
+			SNDERR("Invalid type for CTL %s definition", name);
+		else
+			SNDERR("Invalid type for CTL definition");
+		return -EINVAL;
+	}
+	err = snd_config_search(ctl_conf, "type", &conf);
+	if (err < 0) {
+		SNDERR("type is not defined");
+		return err;
+	}
+	err = snd_config_get_id(conf, &id);
+	if (err < 0) {
+		SNDERR("unable to get id");
+		return err;
+	}
+	err = snd_config_get_string(conf, &str);
+	if (err < 0) {
+		SNDERR("Invalid type for %s", id);
+		return err;
+	}
+	err = snd_config_search_definition(ctl_root, "ctl_type", str, &type_conf);
+	if (err >= 0) {
+		if (snd_config_get_type(type_conf) != SND_CONFIG_TYPE_COMPOUND) {
+			SNDERR("Invalid type for CTL type %s definition", str);
+			goto _err;
+		}
+		snd_config_for_each(i, next, type_conf) {
+			snd_config_t *n = snd_config_iterator_entry(i);
+			const char *id;
+			if (snd_config_get_id(n, &id) < 0)
+				continue;
+			if (strcmp(id, "comment") == 0)
+				continue;
+			if (strcmp(id, "lib") == 0) {
+				err = snd_config_get_string(n, &lib);
+				if (err < 0) {
+					SNDERR("Invalid type for %s", id);
+					goto _err;
+				}
+				continue;
+			}
+			if (strcmp(id, "open") == 0) {
+				err = snd_config_get_string(n, &open_name);
+				if (err < 0) {
+					SNDERR("Invalid type for %s", id);
+					goto _err;
+				}
+				continue;
+			}
+			SNDERR("Unknown field %s", id);
+			err = -EINVAL;
+			goto _err;
+		}
+	}
+	if (!open_name) {
+		buf = malloc(strlen(str) + 32);
+		if (buf == NULL) {
+			err = -ENOMEM;
+			goto _err;
+		}
+		open_name = buf;
+		sprintf(buf, "_snd_ctl_%s_open", str);
+	}
+	if (!lib) {
+		const char *const *build_in = build_in_ctls;
+		while (*build_in) {
+			if (!strcmp(*build_in, str))
+				break;
+			build_in++;
+		}
+		if (*build_in == NULL) {
+			buf1 = malloc(strlen(str) + sizeof(ALSA_PLUGIN_DIR) + 32);
+			if (buf1 == NULL) {
+				err = -ENOMEM;
+				goto _err;
+			}
+			lib = buf1;
+			sprintf(buf1, "%s/libasound_module_ctl_%s.so", ALSA_PLUGIN_DIR, str);
+		}
+	}
+#ifndef PIC
+	snd_control_open_symbols();
+#endif
+	open_func = snd_dlobj_cache_get(lib, open_name,
+			SND_DLSYM_VERSION(SND_CONTROL_DLSYM_VERSION), 1);
+	if (open_func) {
+		err = open_func(ctlp, name, ctl_root, ctl_conf, mode);
+		if (err >= 0) {
+			(*ctlp)->open_func = open_func;
+			err = 0;
+		} else {
+			snd_dlobj_cache_put(open_func);
+		}
+	} else {
+		err = -ENXIO;
+	}
+       _err:
+	if (type_conf)
+		snd_config_delete(type_conf);
+	free(buf);
+	free(buf1);
+	return err;
+}
+
+static int snd_ctl_open_noupdate(snd_ctl_t **ctlp, snd_config_t *root, const char *name, int mode)
+{
+	int err;
+	snd_config_t *ctl_conf;
+	err = snd_config_search_definition(root, "ctl", name, &ctl_conf);
+	if (err < 0) {
+		SNDERR("Invalid CTL %s", name);
+		return err;
+	}
+	err = snd_ctl_open_conf(ctlp, name, root, ctl_conf, mode);
+	snd_config_delete(ctl_conf);
+	return err;
+}
+
+/**
+ * \brief Opens a CTL
+ * \param ctlp Returned CTL handle
+ * \param name ASCII identifier of the CTL handle
+ * \param mode Open mode (see #SND_CTL_NONBLOCK, #SND_CTL_ASYNC)
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_ctl_open(snd_ctl_t **ctlp, const char *name, int mode)
+{
+	int err;
+	assert(ctlp && name);
+	err = snd_config_update();
+	if (err < 0)
+		return err;
+	return snd_ctl_open_noupdate(ctlp, snd_config, name, mode);
+}
+
+/**
+ * \brief Opens a CTL using local configuration
+ * \param ctlp Returned CTL handle
+ * \param name ASCII identifier of the CTL handle
+ * \param mode Open mode (see #SND_CTL_NONBLOCK, #SND_CTL_ASYNC)
+ * \param lconf Local configuration
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_ctl_open_lconf(snd_ctl_t **ctlp, const char *name,
+		       int mode, snd_config_t *lconf)
+{
+	assert(ctlp && name && lconf);
+	return snd_ctl_open_noupdate(ctlp, lconf, name, mode);
+}
+
+/**
+ * \brief Opens a fallback CTL
+ * \param ctlp Returned CTL handle
+ * \param root Configuration root
+ * \param name ASCII identifier of the CTL handle used as fallback
+ * \param orig_name The original ASCII name
+ * \param mode Open mode (see #SND_CTL_NONBLOCK, #SND_CTL_ASYNC)
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_ctl_open_fallback(snd_ctl_t **ctlp, snd_config_t *root,
+			  const char *name, const char *orig_name, int mode)
+{
+	int err;
+	assert(ctlp && name && root);
+	err = snd_ctl_open_noupdate(ctlp, root, name, mode);
+	if (err >= 0) {
+		free((*ctlp)->name);
+		(*ctlp)->name = orig_name ? strdup(orig_name) : NULL;
+	}
+	return err;
+}
+
+#ifndef DOC_HIDDEN
+#define TYPE(v) [SND_CTL_ELEM_TYPE_##v] = #v
+#define IFACE(v) [SND_CTL_ELEM_IFACE_##v] = #v
+#define IFACE1(v, n) [SND_CTL_ELEM_IFACE_##v] = #n
+#define EVENT(v) [SND_CTL_EVENT_##v] = #v
+
+static const char *const snd_ctl_elem_type_names[] = {
+	TYPE(NONE),
+	TYPE(BOOLEAN),
+	TYPE(INTEGER),
+	TYPE(ENUMERATED),
+	TYPE(BYTES),
+	TYPE(IEC958),
+	TYPE(INTEGER64),
+};
+
+static const char *const snd_ctl_elem_iface_names[] = {
+	IFACE(CARD),
+	IFACE(HWDEP),
+	IFACE(MIXER),
+	IFACE(PCM),
+	IFACE(RAWMIDI),
+	IFACE(TIMER),
+	IFACE(SEQUENCER),
+};
+
+static const char *const snd_ctl_event_type_names[] = {
+	EVENT(ELEM),
+};
+#endif
+
+/**
+ * \brief get name of a CTL element type
+ * \param type CTL element type
+ * \return ascii name of CTL element type
+ */
+const char *snd_ctl_elem_type_name(snd_ctl_elem_type_t type)
+{
+	assert(type <= SND_CTL_ELEM_TYPE_LAST);
+	return snd_ctl_elem_type_names[type];
+}
+
+/**
+ * \brief get name of a CTL element related interface
+ * \param iface CTL element related interface
+ * \return ascii name of CTL element related interface
+ */
+const char *snd_ctl_elem_iface_name(snd_ctl_elem_iface_t iface)
+{
+	assert(iface <= SND_CTL_ELEM_IFACE_LAST);
+	return snd_ctl_elem_iface_names[iface];
+}
+
+/**
+ * \brief get name of a CTL event type
+ * \param type CTL event type
+ * \return ascii name of CTL event type
+ */
+const char *snd_ctl_event_type_name(snd_ctl_event_type_t type)
+{
+	assert(type <= SND_CTL_EVENT_LAST);
+	return snd_ctl_event_type_names[type];
+}
+
+/**
+ * \brief allocate space for CTL element identifiers list
+ * \param obj CTL element identifiers list
+ * \param entries Entries to allocate
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_ctl_elem_list_alloc_space(snd_ctl_elem_list_t *obj, unsigned int entries)
+{
+	free(obj->pids);
+	obj->pids = calloc(entries, sizeof(*obj->pids));
+	if (!obj->pids) {
+		obj->space = 0;
+		return -ENOMEM;
+	}
+	obj->space = entries;
+	return 0;
+}  
+
+/**
+ * \brief free previously allocated space for CTL element identifiers list
+ * \param obj CTL element identifiers list
+ */
+void snd_ctl_elem_list_free_space(snd_ctl_elem_list_t *obj)
+{
+	free(obj->pids);
+	obj->pids = NULL;
+	obj->space = 0;
+}
+
+/**
+ * \brief Get event mask for an element related event
+ * \param obj CTL event
+ * \return event mask for element related event
+ */
+unsigned int snd_ctl_event_elem_get_mask(const snd_ctl_event_t *obj)
+{
+	assert(obj);
+	assert(obj->type == SND_CTL_EVENT_ELEM);
+	return obj->data.elem.mask;
+}
+
+/**
+ * \brief Get CTL element identifier for an element related event
+ * \param obj CTL event
+ * \param ptr Pointer to returned CTL element identifier
+ */
+void snd_ctl_event_elem_get_id(const snd_ctl_event_t *obj, snd_ctl_elem_id_t *ptr)
+{
+	assert(obj && ptr);
+	assert(obj->type == SND_CTL_EVENT_ELEM);
+	*ptr = obj->data.elem.id;
+}
+
+/**
+ * \brief Get element numeric identifier for an element related event
+ * \param obj CTL event
+ * \return element numeric identifier
+ */
+unsigned int snd_ctl_event_elem_get_numid(const snd_ctl_event_t *obj)
+{
+	assert(obj);
+	assert(obj->type == SND_CTL_EVENT_ELEM);
+	return obj->data.elem.id.numid;
+}
+
+/**
+ * \brief Get interface part of CTL element identifier for an element related event
+ * \param obj CTL event
+ * \return interface part of element identifier
+ */
+snd_ctl_elem_iface_t snd_ctl_event_elem_get_interface(const snd_ctl_event_t *obj)
+{
+	assert(obj);
+	assert(obj->type == SND_CTL_EVENT_ELEM);
+	return obj->data.elem.id.iface;
+}
+
+/**
+ * \brief Get device part of CTL element identifier for an element related event
+ * \param obj CTL event
+ * \return device part of element identifier
+ */
+unsigned int snd_ctl_event_elem_get_device(const snd_ctl_event_t *obj)
+{
+	assert(obj);
+	assert(obj->type == SND_CTL_EVENT_ELEM);
+	return obj->data.elem.id.device;
+}
+
+/**
+ * \brief Get subdevice part of CTL element identifier for an element related event
+ * \param obj CTL event
+ * \return subdevice part of element identifier
+ */
+unsigned int snd_ctl_event_elem_get_subdevice(const snd_ctl_event_t *obj)
+{
+	assert(obj);
+	assert(obj->type == SND_CTL_EVENT_ELEM);
+	return obj->data.elem.id.subdevice;
+}
+
+/**
+ * \brief Get name part of CTL element identifier for an element related event
+ * \param obj CTL event
+ * \return name part of element identifier
+ */
+const char *snd_ctl_event_elem_get_name(const snd_ctl_event_t *obj)
+{
+	assert(obj);
+	assert(obj->type == SND_CTL_EVENT_ELEM);
+	return (const char *)obj->data.elem.id.name;
+}
+
+/**
+ * \brief Get index part of CTL element identifier for an element related event
+ * \param obj CTL event
+ * \return index part of element identifier
+ */
+unsigned int snd_ctl_event_elem_get_index(const snd_ctl_event_t *obj)
+{
+	assert(obj);
+	assert(obj->type == SND_CTL_EVENT_ELEM);
+	return obj->data.elem.id.index;
+}
+
+#ifndef DOC_HIDDEN
+int _snd_ctl_poll_descriptor(snd_ctl_t *ctl)
+{
+	assert(ctl);
+	return ctl->poll_fd;
+}
+#endif
+
+/**
+ * \brief get size of #snd_ctl_elem_id_t
+ * \return size in bytes
+ */
+size_t snd_ctl_elem_id_sizeof()
+{
+	return sizeof(snd_ctl_elem_id_t);
+}
+
+/**
+ * \brief allocate an invalid #snd_ctl_elem_id_t using standard malloc
+ * \param ptr returned pointer
+ * \return 0 on success otherwise negative error code
+ */
+int snd_ctl_elem_id_malloc(snd_ctl_elem_id_t **ptr)
+{
+	assert(ptr);
+	*ptr = calloc(1, sizeof(snd_ctl_elem_id_t));
+	if (!*ptr)
+		return -ENOMEM;
+	return 0;
+}
+
+/**
+ * \brief frees a previously allocated #snd_ctl_elem_id_t
+ * \param obj pointer to object to free
+ */
+void snd_ctl_elem_id_free(snd_ctl_elem_id_t *obj)
+{
+	free(obj);
+}
+
+/**
+ * \brief clear given #snd_ctl_elem_id_t object
+ * \param obj pointer to object to clear
+ */
+void snd_ctl_elem_id_clear(snd_ctl_elem_id_t *obj)
+{
+	memset(obj, 0, sizeof(snd_ctl_elem_id_t));
+}
+
+/**
+ * \brief copy one #snd_ctl_elem_id_t to another
+ * \param dst pointer to destination
+ * \param src pointer to source
+ */
+void snd_ctl_elem_id_copy(snd_ctl_elem_id_t *dst, const snd_ctl_elem_id_t *src)
+{
+	assert(dst && src);
+	*dst = *src;
+}
+
+/**
+ * \brief Get numeric identifier from a CTL element identifier
+ * \param obj CTL element identifier
+ * \return CTL element numeric identifier
+ */
+unsigned int snd_ctl_elem_id_get_numid(const snd_ctl_elem_id_t *obj)
+{
+	assert(obj);
+	return obj->numid;
+}
+
+/**
+ * \brief Get interface part of a CTL element identifier
+ * \param obj CTL element identifier
+ * \return CTL element related interface
+ */
+snd_ctl_elem_iface_t snd_ctl_elem_id_get_interface(const snd_ctl_elem_id_t *obj)
+{
+	assert(obj);
+	return obj->iface;
+}
+
+/**
+ * \brief Get device part of a CTL element identifier
+ * \param obj CTL element identifier
+ * \return CTL element related device
+ */
+unsigned int snd_ctl_elem_id_get_device(const snd_ctl_elem_id_t *obj)
+{
+	assert(obj);
+	return obj->device;
+}
+
+/**
+ * \brief Get subdevice part of a CTL element identifier
+ * \param obj CTL element identifier
+ * \return CTL element related subdevice
+ */
+unsigned int snd_ctl_elem_id_get_subdevice(const snd_ctl_elem_id_t *obj)
+{
+	assert(obj);
+	return obj->subdevice;
+}
+
+/**
+ * \brief Get name part of a CTL element identifier
+ * \param obj CTL element identifier
+ * \return CTL element name
+ */
+const char *snd_ctl_elem_id_get_name(const snd_ctl_elem_id_t *obj)
+{
+	assert(obj);
+	return (const char *)obj->name;
+}
+
+/**
+ * \brief Get index part of a CTL element identifier
+ * \param obj CTL element identifier
+ * \return CTL element index
+ */
+unsigned int snd_ctl_elem_id_get_index(const snd_ctl_elem_id_t *obj)
+{
+	assert(obj);
+	return obj->index;
+}
+
+/**
+ * \brief Set numeric identifier for a CTL element identifier
+ * \param obj CTL element identifier
+ * \param val CTL element numeric identifier
+ */
+void snd_ctl_elem_id_set_numid(snd_ctl_elem_id_t *obj, unsigned int val)
+{
+	assert(obj);
+	obj->numid = val;
+}
+
+/**
+ * \brief Set interface part for a CTL element identifier
+ * \param obj CTL element identifier
+ * \param val CTL element related interface
+ */
+void snd_ctl_elem_id_set_interface(snd_ctl_elem_id_t *obj, snd_ctl_elem_iface_t val)
+{
+	assert(obj);
+	obj->iface = val;
+}
+
+/**
+ * \brief Set device part for a CTL element identifier
+ * \param obj CTL element identifier
+ * \param val CTL element related device
+ */
+void snd_ctl_elem_id_set_device(snd_ctl_elem_id_t *obj, unsigned int val)
+{
+	assert(obj);
+	obj->device = val;
+}
+
+/**
+ * \brief Set subdevice part for a CTL element identifier
+ * \param obj CTL element identifier
+ * \param val CTL element related subdevice
+ */
+void snd_ctl_elem_id_set_subdevice(snd_ctl_elem_id_t *obj, unsigned int val)
+{
+	assert(obj);
+	obj->subdevice = val;
+}
+
+/**
+ * \brief Set name part for a CTL element identifier
+ * \param obj CTL element identifier
+ * \param val CTL element name
+ */
+void snd_ctl_elem_id_set_name(snd_ctl_elem_id_t *obj, const char *val)
+{
+	assert(obj);
+	strncpy((char *)obj->name, val, sizeof(obj->name));
+}
+
+/**
+ * \brief Set index part for a CTL element identifier
+ * \param obj CTL element identifier
+ * \param val CTL element index
+ */
+void snd_ctl_elem_id_set_index(snd_ctl_elem_id_t *obj, unsigned int val)
+{
+	assert(obj);
+	obj->index = val;
+}
+
+/**
+ * \brief get size of #snd_ctl_card_info_t
+ * \return size in bytes
+ */
+size_t snd_ctl_card_info_sizeof()
+{
+	return sizeof(snd_ctl_card_info_t);
+}
+
+/**
+ * \brief allocate an invalid #snd_ctl_card_info_t using standard malloc
+ * \param ptr returned pointer
+ * \return 0 on success otherwise negative error code
+ */
+int snd_ctl_card_info_malloc(snd_ctl_card_info_t **ptr)
+{
+	assert(ptr);
+	*ptr = calloc(1, sizeof(snd_ctl_card_info_t));
+	if (!*ptr)
+		return -ENOMEM;
+	return 0;
+}
+
+/**
+ * \brief frees a previously allocated #snd_ctl_card_info_t
+ * \param obj pointer to object to free
+ */
+void snd_ctl_card_info_free(snd_ctl_card_info_t *obj)
+{
+	free(obj);
+}
+
+/**
+ * \brief clear given #snd_ctl_card_info_t object
+ * \param obj pointer to object to clear
+ */
+void snd_ctl_card_info_clear(snd_ctl_card_info_t *obj)
+{
+	memset(obj, 0, sizeof(snd_ctl_card_info_t));
+}
+
+/**
+ * \brief copy one #snd_ctl_card_info_t to another
+ * \param dst pointer to destination
+ * \param src pointer to source
+ */
+void snd_ctl_card_info_copy(snd_ctl_card_info_t *dst, const snd_ctl_card_info_t *src)
+{
+	assert(dst && src);
+	*dst = *src;
+}
+
+/**
+ * \brief Get card number from a CTL card info
+ * \param obj CTL card info
+ * \return card number
+ */
+int snd_ctl_card_info_get_card(const snd_ctl_card_info_t *obj)
+{
+	assert(obj);
+	return obj->card;
+}
+
+/**
+ * \brief Get card identifier from a CTL card info
+ * \param obj CTL card info
+ * \return card identifier
+ */
+const char *snd_ctl_card_info_get_id(const snd_ctl_card_info_t *obj)
+{
+	assert(obj);
+	return (const char *)obj->id;
+}
+
+/**
+ * \brief Get card driver name from a CTL card info
+ * \param obj CTL card info
+ * \return card driver name
+ */
+const char *snd_ctl_card_info_get_driver(const snd_ctl_card_info_t *obj)
+{
+	assert(obj);
+	return (const char *)obj->driver;
+}
+
+/**
+ * \brief Get card name from a CTL card info
+ * \param obj CTL card info
+ * \return card name
+ */
+const char *snd_ctl_card_info_get_name(const snd_ctl_card_info_t *obj)
+{
+	assert(obj);
+	return (const char *)obj->name;
+}
+
+/**
+ * \brief Get card long name from a CTL card info
+ * \param obj CTL card info
+ * \return card long name
+ */
+const char *snd_ctl_card_info_get_longname(const snd_ctl_card_info_t *obj)
+{
+	assert(obj);
+	return (const char *)obj->longname;
+}
+
+/**
+ * \brief Get card mixer name from a CTL card info
+ * \param obj CTL card info
+ * \return card mixer name
+ */
+const char *snd_ctl_card_info_get_mixername(const snd_ctl_card_info_t *obj)
+{
+	assert(obj);
+	return (const char *)obj->mixername;
+}
+
+/**
+ * \brief Get card component list from a CTL card info
+ * \param obj CTL card info
+ * \return card mixer identifier
+ */
+const char *snd_ctl_card_info_get_components(const snd_ctl_card_info_t *obj)
+{
+	assert(obj);
+	return (const char *)obj->components;
+}
+
+/**
+ * \brief get size of #snd_ctl_event_t
+ * \return size in bytes
+ */
+size_t snd_ctl_event_sizeof()
+{
+	return sizeof(snd_ctl_event_t);
+}
+
+/**
+ * \brief allocate an invalid #snd_ctl_event_t using standard malloc
+ * \param ptr returned pointer
+ * \return 0 on success otherwise negative error code
+ */
+int snd_ctl_event_malloc(snd_ctl_event_t **ptr)
+{
+	assert(ptr);
+	*ptr = calloc(1, sizeof(snd_ctl_event_t));
+	if (!*ptr)
+		return -ENOMEM;
+	return 0;
+}
+
+/**
+ * \brief frees a previously allocated #snd_ctl_event_t
+ * \param obj pointer to object to free
+ */
+void snd_ctl_event_free(snd_ctl_event_t *obj)
+{
+	free(obj);
+}
+
+/**
+ * \brief clear given #snd_ctl_event_t object
+ * \param obj pointer to object to clear
+ */
+void snd_ctl_event_clear(snd_ctl_event_t *obj)
+{
+	memset(obj, 0, sizeof(snd_ctl_event_t));
+}
+
+/**
+ * \brief copy one #snd_ctl_event_t to another
+ * \param dst pointer to destination
+ * \param src pointer to source
+ */
+void snd_ctl_event_copy(snd_ctl_event_t *dst, const snd_ctl_event_t *src)
+{
+	assert(dst && src);
+	*dst = *src;
+}
+
+/**
+ * \brief Get type of a CTL event
+ * \param obj CTL event
+ * \return CTL event type
+ */
+snd_ctl_event_type_t snd_ctl_event_get_type(const snd_ctl_event_t *obj)
+{
+	assert(obj);
+	return obj->type;
+}
+
+/**
+ * \brief get size of #snd_ctl_elem_list_t
+ * \return size in bytes
+ */
+size_t snd_ctl_elem_list_sizeof()
+{
+	return sizeof(snd_ctl_elem_list_t);
+}
+
+/**
+ * \brief allocate an invalid #snd_ctl_elem_list_t using standard malloc
+ * \param ptr returned pointer
+ * \return 0 on success otherwise negative error code
+ */
+int snd_ctl_elem_list_malloc(snd_ctl_elem_list_t **ptr)
+{
+	assert(ptr);
+	*ptr = calloc(1, sizeof(snd_ctl_elem_list_t));
+	if (!*ptr)
+		return -ENOMEM;
+	return 0;
+}
+
+/**
+ * \brief frees a previously allocated #snd_ctl_elem_list_t
+ * \param obj pointer to object to free
+ */
+void snd_ctl_elem_list_free(snd_ctl_elem_list_t *obj)
+{
+	free(obj);
+}
+
+/**
+ * \brief clear given #snd_ctl_elem_list_t object
+ * \param obj pointer to object to clear
+ */
+void snd_ctl_elem_list_clear(snd_ctl_elem_list_t *obj)
+{
+	memset(obj, 0, sizeof(snd_ctl_elem_list_t));
+}
+
+/**
+ * \brief copy one #snd_ctl_elem_list_t to another
+ * \param dst pointer to destination
+ * \param src pointer to source
+ */
+void snd_ctl_elem_list_copy(snd_ctl_elem_list_t *dst, const snd_ctl_elem_list_t *src)
+{
+	assert(dst && src);
+	*dst = *src;
+}
+
+/**
+ * \brief Set index of first wanted CTL element identifier in a CTL element identifiers list
+ * \param obj CTL element identifiers list
+ * \param val index of CTL element to put at position 0 of list
+ */
+void snd_ctl_elem_list_set_offset(snd_ctl_elem_list_t *obj, unsigned int val)
+{
+	assert(obj);
+	obj->offset = val;
+}
+
+/**
+ * \brief Get number of used entries in CTL element identifiers list
+ * \param obj CTL element identifier list
+ * \return number of used entries
+ */
+unsigned int snd_ctl_elem_list_get_used(const snd_ctl_elem_list_t *obj)
+{
+	assert(obj);
+	return obj->used;
+}
+
+/**
+ * \brief Get total count of elements present in CTL device (information present in every filled CTL element identifiers list)
+ * \param obj CTL element identifier list
+ * \return total number of elements
+ */
+unsigned int snd_ctl_elem_list_get_count(const snd_ctl_elem_list_t *obj)
+{
+	assert(obj);
+	return obj->count;
+}
+
+/**
+ * \brief Get CTL element identifier for an entry of a CTL element identifiers list
+ * \param obj CTL element identifier list
+ * \param idx Index of entry
+ * \param ptr Pointer to returned CTL element identifier
+ */
+void snd_ctl_elem_list_get_id(const snd_ctl_elem_list_t *obj, unsigned int idx, snd_ctl_elem_id_t *ptr)
+{
+	assert(obj && ptr);
+	assert(idx < obj->used);
+	*ptr = obj->pids[idx];
+}
+
+/**
+ * \brief Get CTL element numeric identifier for an entry of a CTL element identifiers list
+ * \param obj CTL element identifier list
+ * \param idx Index of entry
+ * \return CTL element numeric identifier
+ */
+unsigned int snd_ctl_elem_list_get_numid(const snd_ctl_elem_list_t *obj, unsigned int idx)
+{
+	assert(obj);
+	assert(idx < obj->used);
+	return obj->pids[idx].numid;
+}
+
+/**
+ * \brief Get interface part of CTL element identifier for an entry of a CTL element identifiers list
+ * \param obj CTL element identifier list
+ * \param idx Index of entry
+ * \return CTL element related interface
+ */
+snd_ctl_elem_iface_t snd_ctl_elem_list_get_interface(const snd_ctl_elem_list_t *obj, unsigned int idx)
+{
+	assert(obj);
+	assert(idx < obj->used);
+	return obj->pids[idx].iface;
+}
+
+/**
+ * \brief Get device part of CTL element identifier for an entry of a CTL element identifiers list
+ * \param obj CTL element identifier list
+ * \param idx Index of entry
+ * \return CTL element related device
+ */
+unsigned int snd_ctl_elem_list_get_device(const snd_ctl_elem_list_t *obj, unsigned int idx)
+{
+	assert(obj);
+	assert(idx < obj->used);
+	return obj->pids[idx].device;
+}
+
+/**
+ * \brief Get subdevice part of CTL element identifier for an entry of a CTL element identifiers list
+ * \param obj CTL element identifier list
+ * \param idx Index of entry
+ * \return CTL element related subdevice
+ */
+unsigned int snd_ctl_elem_list_get_subdevice(const snd_ctl_elem_list_t *obj, unsigned int idx)
+{
+	assert(obj);
+	assert(idx < obj->used);
+	return obj->pids[idx].subdevice;
+}
+
+/**
+ * \brief Get name part of CTL element identifier for an entry of a CTL element identifiers list
+ * \param obj CTL element identifier list
+ * \param idx Index of entry
+ * \return CTL element name
+ */
+const char *snd_ctl_elem_list_get_name(const snd_ctl_elem_list_t *obj, unsigned int idx)
+{
+	assert(obj);
+	assert(idx < obj->used);
+	return (const char *)obj->pids[idx].name;
+}
+
+/**
+ * \brief Get index part of CTL element identifier for an entry of a CTL element identifiers list
+ * \param obj CTL element identifier list
+ * \param idx Index of entry
+ * \return CTL element index
+ */
+unsigned int snd_ctl_elem_list_get_index(const snd_ctl_elem_list_t *obj, unsigned int idx)
+{
+	assert(obj);
+	assert(idx < obj->used);
+	return obj->pids[idx].index;
+}
+
+/**
+ * \brief get size of #snd_ctl_elem_info_t
+ * \return size in bytes
+ */
+size_t snd_ctl_elem_info_sizeof()
+{
+	return sizeof(snd_ctl_elem_info_t);
+}
+
+/**
+ * \brief allocate an invalid #snd_ctl_elem_info_t using standard malloc
+ * \param ptr returned pointer
+ * \return 0 on success otherwise negative error code
+ */
+int snd_ctl_elem_info_malloc(snd_ctl_elem_info_t **ptr)
+{
+	assert(ptr);
+	*ptr = calloc(1, sizeof(snd_ctl_elem_info_t));
+	if (!*ptr)
+		return -ENOMEM;
+	return 0;
+}
+
+/**
+ * \brief frees a previously allocated #snd_ctl_elem_info_t
+ * \param obj pointer to object to free
+ */
+void snd_ctl_elem_info_free(snd_ctl_elem_info_t *obj)
+{
+	free(obj);
+}
+
+/**
+ * \brief clear given #snd_ctl_elem_info_t object
+ * \param obj pointer to object to clear
+ */
+void snd_ctl_elem_info_clear(snd_ctl_elem_info_t *obj)
+{
+	memset(obj, 0, sizeof(snd_ctl_elem_info_t));
+}
+
+/**
+ * \brief copy one #snd_ctl_elem_info_t to another
+ * \param dst pointer to destination
+ * \param src pointer to source
+ */
+void snd_ctl_elem_info_copy(snd_ctl_elem_info_t *dst, const snd_ctl_elem_info_t *src)
+{
+	assert(dst && src);
+	*dst = *src;
+}
+
+/**
+ * \brief Get type from a CTL element id/info
+ * \param obj CTL element id/info
+ * \return CTL element content type
+ */
+snd_ctl_elem_type_t snd_ctl_elem_info_get_type(const snd_ctl_elem_info_t *obj)
+{
+	assert(obj);
+	return obj->type;
+}
+
+/**
+ * \brief Get info about readability from a CTL element id/info
+ * \param obj CTL element id/info
+ * \return 0 if element is not readable, 1 if element is readable
+ */
+int snd_ctl_elem_info_is_readable(const snd_ctl_elem_info_t *obj)
+{
+	assert(obj);
+	return !!(obj->access & SNDRV_CTL_ELEM_ACCESS_READ);
+}
+
+/**
+ * \brief Get info about writability from a CTL element id/info
+ * \param obj CTL element id/info
+ * \return 0 if element is not writable, 1 if element is not writable
+ */
+int snd_ctl_elem_info_is_writable(const snd_ctl_elem_info_t *obj)
+{
+	assert(obj);
+	return !!(obj->access & SNDRV_CTL_ELEM_ACCESS_WRITE);
+}
+
+/**
+ * \brief Get info about notification feasibility from a CTL element id/info
+ * \param obj CTL element id/info
+ * \return 0 if all element value changes are notified to subscribed applications, 1 otherwise
+ */
+int snd_ctl_elem_info_is_volatile(const snd_ctl_elem_info_t *obj)
+{
+	assert(obj);
+	return !!(obj->access & SNDRV_CTL_ELEM_ACCESS_VOLATILE);
+}
+
+/**
+ * \brief Get info about status from a CTL element id/info
+ * \param obj CTL element id/info
+ * \return 0 if element value is not active, 1 if is active
+ */
+int snd_ctl_elem_info_is_inactive(const snd_ctl_elem_info_t *obj)
+{
+	assert(obj);
+	return !!(obj->access & SNDRV_CTL_ELEM_ACCESS_INACTIVE);
+}
+
+/**
+ * \brief Get info whether an element is locked
+ * \param obj CTL element id/info
+ * \return 0 if element value is currently changeable, 1 if it's locked by another application
+ */
+int snd_ctl_elem_info_is_locked(const snd_ctl_elem_info_t *obj)
+{
+	assert(obj);
+	return !!(obj->access & SNDRV_CTL_ELEM_ACCESS_LOCK);
+}
+
+/**
+ * \brief Get info if I own an element
+ * \param obj CTL element id/info
+ * \return 0 if element value is currently changeable, 1 if it's locked by another application
+ */
+int snd_ctl_elem_info_is_owner(const snd_ctl_elem_info_t *obj)
+{
+	assert(obj);
+	return !!(obj->access & SNDRV_CTL_ELEM_ACCESS_OWNER);
+}
+
+/**
+ * \brief Get info if it's a user element
+ * \param obj CTL element id/info
+ * \return 0 if element value is a system element, 1 if it's a user-created element
+ */
+int snd_ctl_elem_info_is_user(const snd_ctl_elem_info_t *obj)
+{
+	assert(obj);
+	return !!(obj->access & SNDRV_CTL_ELEM_ACCESS_USER);
+}
+
+/**
+ * \brief Get info about TLV readability from a CTL element id/info
+ * \param obj CTL element id/info
+ * \return 0 if element's TLV is not readable, 1 if element's TLV is readable
+ */
+int snd_ctl_elem_info_is_tlv_readable(const snd_ctl_elem_info_t *obj)
+{
+	assert(obj);
+	return !!(obj->access & SNDRV_CTL_ELEM_ACCESS_TLV_READ);
+}
+
+/**
+ * \brief Get info about TLV writeability from a CTL element id/info
+ * \param obj CTL element id/info
+ * \return 0 if element's TLV is not writable, 1 if element's TLV is writable
+ */
+int snd_ctl_elem_info_is_tlv_writable(const snd_ctl_elem_info_t *obj)
+{
+	assert(obj);
+	return !!(obj->access & SNDRV_CTL_ELEM_ACCESS_TLV_WRITE);
+}
+
+/**
+ * \brief Get info about TLV command possibility from a CTL element id/info
+ * \param obj CTL element id/info
+ * \return 0 if element's TLV command is not possible, 1 if element's TLV command is supported
+ */
+int snd_ctl_elem_info_is_tlv_commandable(const snd_ctl_elem_info_t *obj)
+{
+	assert(obj);
+	return !!(obj->access & SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND);
+}
+
+/**
+ * \brief (DEPRECATED) Get info about values passing policy from a CTL element value
+ * \param obj CTL element id/info
+ * \return 0 if element value need to be passed by contents, 1 if need to be passed with a pointer
+ */
+int snd_ctl_elem_info_is_indirect(const snd_ctl_elem_info_t *obj)
+{
+	assert(obj);
+	return 0;
+}
+link_warning(snd_ctl_elem_info_is_indirect, "Warning: snd_ctl_elem_info_is_indirect is deprecated, do not use it");
+
+/**
+ * \brief Get owner of a locked element
+ * \param obj CTL element id/info
+ * \return value entries count
+ */
+pid_t snd_ctl_elem_info_get_owner(const snd_ctl_elem_info_t *obj)
+{
+	assert(obj);
+	return obj->owner;
+}
+
+/**
+ * \brief Get number of value entries from a CTL element id/info
+ * \param obj CTL element id/info
+ * \return value entries count
+ */
+unsigned int snd_ctl_elem_info_get_count(const snd_ctl_elem_info_t *obj)
+{
+	assert(obj);
+	return obj->count;
+}
+
+/**
+ * \brief Get minimum value from a #SND_CTL_ELEM_TYPE_INTEGER CTL element id/info
+ * \param obj CTL element id/info
+ * \return Minimum value
+ */
+long snd_ctl_elem_info_get_min(const snd_ctl_elem_info_t *obj)
+{
+	assert(obj);
+	assert(obj->type == SND_CTL_ELEM_TYPE_INTEGER);
+	return obj->value.integer.min;
+}
+
+/**
+ * \brief Get maximum value from a #SND_CTL_ELEM_TYPE_INTEGER CTL element id/info
+ * \param obj CTL element id/info
+ * \return Maximum value
+ */
+long snd_ctl_elem_info_get_max(const snd_ctl_elem_info_t *obj)
+{
+	assert(obj);
+	assert(obj->type == SND_CTL_ELEM_TYPE_INTEGER);
+	return obj->value.integer.max;
+}
+
+/**
+ * \brief Get value step from a #SND_CTL_ELEM_TYPE_INTEGER CTL element id/info
+ * \param obj CTL element id/info
+ * \return Step
+ */
+long snd_ctl_elem_info_get_step(const snd_ctl_elem_info_t *obj)
+{
+	assert(obj);
+	assert(obj->type == SND_CTL_ELEM_TYPE_INTEGER);
+	return obj->value.integer.step;
+}
+
+/**
+ * \brief Get minimum value from a #SND_CTL_ELEM_TYPE_INTEGER64 CTL element id/info
+ * \param obj CTL element id/info
+ * \return Minimum value
+ */
+long long snd_ctl_elem_info_get_min64(const snd_ctl_elem_info_t *obj)
+{
+	assert(obj);
+	assert(obj->type == SND_CTL_ELEM_TYPE_INTEGER64);
+	return obj->value.integer64.min;
+}
+
+/**
+ * \brief Get maximum value from a #SND_CTL_ELEM_TYPE_INTEGER64 CTL element id/info
+ * \param obj CTL element id/info
+ * \return Maximum value
+ */
+long long snd_ctl_elem_info_get_max64(const snd_ctl_elem_info_t *obj)
+{
+	assert(obj);
+	assert(obj->type == SND_CTL_ELEM_TYPE_INTEGER64);
+	return obj->value.integer64.max;
+}
+
+/**
+ * \brief Get value step from a #SND_CTL_ELEM_TYPE_INTEGER64 CTL element id/info
+ * \param obj CTL element id/info
+ * \return Step
+ */
+long long snd_ctl_elem_info_get_step64(const snd_ctl_elem_info_t *obj)
+{
+	assert(obj);
+	assert(obj->type == SND_CTL_ELEM_TYPE_INTEGER64);
+	return obj->value.integer64.step;
+}
+
+/**
+ * \brief Get number of items available from a #SND_CTL_ELEM_TYPE_ENUMERATED CTL element id/info
+ * \param obj CTL element id/info
+ * \return items count
+ */
+unsigned int snd_ctl_elem_info_get_items(const snd_ctl_elem_info_t *obj)
+{
+	assert(obj);
+	assert(obj->type == SND_CTL_ELEM_TYPE_ENUMERATED);
+	return obj->value.enumerated.items;
+}
+
+/**
+ * \brief Select item in a #SND_CTL_ELEM_TYPE_ENUMERATED CTL element id/info
+ * \param obj CTL element id/info
+ * \param val item number
+ */
+void snd_ctl_elem_info_set_item(snd_ctl_elem_info_t *obj, unsigned int val)
+{
+	assert(obj);
+	obj->value.enumerated.item = val;
+}
+
+/**
+ * \brief Get name for selected item in a #SND_CTL_ELEM_TYPE_ENUMERATED CTL element id/info
+ * \param obj CTL element id/info
+ * \return name of chosen item
+ */
+const char *snd_ctl_elem_info_get_item_name(const snd_ctl_elem_info_t *obj)
+{
+	assert(obj);
+	assert(obj->type == SND_CTL_ELEM_TYPE_ENUMERATED);
+	return obj->value.enumerated.name;
+}
+
+/**
+ * \brief Get count of dimensions for given element
+ * \param obj CTL element id/info
+ * \return zero value if no dimensions are defined, otherwise positive value with count of dimensions
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_ctl_elem_info_get_dimensions)(const snd_ctl_elem_info_t *obj)
+#else
+int snd_ctl_elem_info_get_dimensions(const snd_ctl_elem_info_t *obj)
+#endif
+{
+	int i;
+
+	assert(obj);
+	for (i = 3; i >= 0; i--)
+		if (obj->dimen.d[i])
+			break;
+	return i + 1;
+}
+use_default_symbol_version(__snd_ctl_elem_info_get_dimensions, snd_ctl_elem_info_get_dimensions, ALSA_0.9.3);
+
+/**
+ * \brief Get specified of dimension width for given element
+ * \param obj CTL element id/info
+ * \param idx The dimension index
+ * \return zero value if no dimension width is defined, otherwise positive value with with of specified dimension
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_ctl_elem_info_get_dimension)(const snd_ctl_elem_info_t *obj, unsigned int idx)
+#else
+int snd_ctl_elem_info_get_dimension(const snd_ctl_elem_info_t *obj, unsigned int idx)
+#endif
+{
+	assert(obj);
+	if (idx >= 3)
+		return 0;
+	return obj->dimen.d[idx];
+}
+use_default_symbol_version(__snd_ctl_elem_info_get_dimension, snd_ctl_elem_info_get_dimension, ALSA_0.9.3);
+
+/**
+ * \brief Get CTL element identifier of a CTL element id/info
+ * \param obj CTL element id/info
+ * \param ptr Pointer to returned CTL element identifier
+ */
+void snd_ctl_elem_info_get_id(const snd_ctl_elem_info_t *obj, snd_ctl_elem_id_t *ptr)
+{
+	assert(obj && ptr);
+	*ptr = obj->id;
+}
+
+/**
+ * \brief Get element numeric identifier of a CTL element id/info
+ * \param obj CTL element id/info
+ * \return element numeric identifier
+ */
+unsigned int snd_ctl_elem_info_get_numid(const snd_ctl_elem_info_t *obj)
+{
+	assert(obj);
+	return obj->id.numid;
+}
+
+/**
+ * \brief Get interface part of CTL element identifier of a CTL element id/info
+ * \param obj CTL element id/info
+ * \return interface part of element identifier
+ */
+snd_ctl_elem_iface_t snd_ctl_elem_info_get_interface(const snd_ctl_elem_info_t *obj)
+{
+	assert(obj);
+	return obj->id.iface;
+}
+
+/**
+ * \brief Get device part of CTL element identifier of a CTL element id/info
+ * \param obj CTL element id/info
+ * \return device part of element identifier
+ */
+unsigned int snd_ctl_elem_info_get_device(const snd_ctl_elem_info_t *obj)
+{
+	assert(obj);
+	return obj->id.device;
+}
+
+/**
+ * \brief Get subdevice part of CTL element identifier of a CTL element id/info
+ * \param obj CTL element id/info
+ * \return subdevice part of element identifier
+ */
+unsigned int snd_ctl_elem_info_get_subdevice(const snd_ctl_elem_info_t *obj)
+{
+	assert(obj);
+	return obj->id.subdevice;
+}
+
+/**
+ * \brief Get name part of CTL element identifier of a CTL element id/info
+ * \param obj CTL element id/info
+ * \return name part of element identifier
+ */
+const char *snd_ctl_elem_info_get_name(const snd_ctl_elem_info_t *obj)
+{
+	assert(obj);
+	return (const char *)obj->id.name;
+}
+
+/**
+ * \brief Get index part of CTL element identifier of a CTL element id/info
+ * \param obj CTL element id/info
+ * \return index part of element identifier
+ */
+unsigned int snd_ctl_elem_info_get_index(const snd_ctl_elem_info_t *obj)
+{
+	assert(obj);
+	return obj->id.index;
+}
+
+/**
+ * \brief Set CTL element identifier of a CTL element id/info
+ * \param obj CTL element id/info
+ * \param ptr CTL element identifier
+ */
+void snd_ctl_elem_info_set_id(snd_ctl_elem_info_t *obj, const snd_ctl_elem_id_t *ptr)
+{
+	assert(obj && ptr);
+	obj->id = *ptr;
+}
+
+/**
+ * \brief Set element numeric identifier of a CTL element id/info
+ * \param obj CTL element id/info
+ * \param val element numeric identifier
+ */
+void snd_ctl_elem_info_set_numid(snd_ctl_elem_info_t *obj, unsigned int val)
+{
+	assert(obj);
+	obj->id.numid = val;
+}
+
+/**
+ * \brief Set interface part of CTL element identifier of a CTL element id/info
+ * \param obj CTL element id/info
+ * \param val interface part of element identifier
+ */
+void snd_ctl_elem_info_set_interface(snd_ctl_elem_info_t *obj, snd_ctl_elem_iface_t val)
+{
+	assert(obj);
+	obj->id.iface = val;
+}
+
+/**
+ * \brief Set device part of CTL element identifier of a CTL element id/info
+ * \param obj CTL element id/info
+ * \param val device part of element identifier
+ */
+void snd_ctl_elem_info_set_device(snd_ctl_elem_info_t *obj, unsigned int val)
+{
+	assert(obj);
+	obj->id.device = val;
+}
+
+/**
+ * \brief Set subdevice part of CTL element identifier of a CTL element id/info
+ * \param obj CTL element id/info
+ * \param val subdevice part of element identifier
+ */
+void snd_ctl_elem_info_set_subdevice(snd_ctl_elem_info_t *obj, unsigned int val)
+{
+	assert(obj);
+	obj->id.subdevice = val;
+}
+
+/**
+ * \brief Set name part of CTL element identifier of a CTL element id/info
+ * \param obj CTL element id/info
+ * \param val name part of element identifier
+ */
+void snd_ctl_elem_info_set_name(snd_ctl_elem_info_t *obj, const char *val)
+{
+	assert(obj);
+	strncpy((char *)obj->id.name, val, sizeof(obj->id.name));
+}
+
+/**
+ * \brief Set index part of CTL element identifier of a CTL element id/info
+ * \param obj CTL element id/info
+ * \param val index part of element identifier
+ */
+void snd_ctl_elem_info_set_index(snd_ctl_elem_info_t *obj, unsigned int val)
+{
+	assert(obj);
+	obj->id.index = val;
+}
+
+/**
+ * \brief get size of #snd_ctl_elem_value_t
+ * \return size in bytes
+ */
+size_t snd_ctl_elem_value_sizeof()
+{
+	return sizeof(snd_ctl_elem_value_t);
+}
+
+/**
+ * \brief allocate an invalid #snd_ctl_elem_value_t using standard malloc
+ * \param ptr returned pointer
+ * \return 0 on success otherwise negative error code
+ */
+int snd_ctl_elem_value_malloc(snd_ctl_elem_value_t **ptr)
+{
+	assert(ptr);
+	*ptr = calloc(1, sizeof(snd_ctl_elem_value_t));
+	if (!*ptr)
+		return -ENOMEM;
+	return 0;
+}
+
+/**
+ * \brief frees a previously allocated #snd_ctl_elem_value_t
+ * \param obj pointer to object to free
+ */
+void snd_ctl_elem_value_free(snd_ctl_elem_value_t *obj)
+{
+	free(obj);
+}
+
+/**
+ * \brief clear given #snd_ctl_elem_value_t object
+ * \param obj pointer to object to clear
+ */
+void snd_ctl_elem_value_clear(snd_ctl_elem_value_t *obj)
+{
+	memset(obj, 0, sizeof(snd_ctl_elem_value_t));
+}
+
+/**
+ * \brief copy one #snd_ctl_elem_value_t to another
+ * \param dst pointer to destination
+ * \param src pointer to source
+ */
+void snd_ctl_elem_value_copy(snd_ctl_elem_value_t *dst, const snd_ctl_elem_value_t *src)
+{
+	assert(dst && src);
+	*dst = *src;
+}
+
+/**
+ * \brief compare one #snd_ctl_elem_value_t to another
+ * \param dst pointer to destination
+ * \param src pointer to source
+ * \return 0 on match, less than or greater than otherwise, see memcmp
+ */
+int snd_ctl_elem_value_compare(snd_ctl_elem_value_t *left, const snd_ctl_elem_value_t *right)
+{
+	assert(left && right);
+	return memcmp(left, right, sizeof(*left));
+}
+
+/**
+ * \brief Get CTL element identifier of a CTL element id/value
+ * \param obj CTL element id/value
+ * \param ptr Pointer to returned CTL element identifier
+ */
+void snd_ctl_elem_value_get_id(const snd_ctl_elem_value_t *obj, snd_ctl_elem_id_t *ptr)
+{
+	assert(obj && ptr);
+	*ptr = obj->id;
+}
+
+/**
+ * \brief Get element numeric identifier of a CTL element id/value
+ * \param obj CTL element id/value
+ * \return element numeric identifier
+ */
+unsigned int snd_ctl_elem_value_get_numid(const snd_ctl_elem_value_t *obj)
+{
+	assert(obj);
+	return obj->id.numid;
+}
+
+/**
+ * \brief Get interface part of CTL element identifier of a CTL element id/value
+ * \param obj CTL element id/value
+ * \return interface part of element identifier
+ */
+snd_ctl_elem_iface_t snd_ctl_elem_value_get_interface(const snd_ctl_elem_value_t *obj)
+{
+	assert(obj);
+	return obj->id.iface;
+}
+
+/**
+ * \brief Get device part of CTL element identifier of a CTL element id/value
+ * \param obj CTL element id/value
+ * \return device part of element identifier
+ */
+unsigned int snd_ctl_elem_value_get_device(const snd_ctl_elem_value_t *obj)
+{
+	assert(obj);
+	return obj->id.device;
+}
+
+/**
+ * \brief Get subdevice part of CTL element identifier of a CTL element id/value
+ * \param obj CTL element id/value
+ * \return subdevice part of element identifier
+ */
+unsigned int snd_ctl_elem_value_get_subdevice(const snd_ctl_elem_value_t *obj)
+{
+	assert(obj);
+	return obj->id.subdevice;
+}
+
+/**
+ * \brief Get name part of CTL element identifier of a CTL element id/value
+ * \param obj CTL element id/value
+ * \return name part of element identifier
+ */
+const char *snd_ctl_elem_value_get_name(const snd_ctl_elem_value_t *obj)
+{
+	assert(obj);
+	return (const char *)obj->id.name;
+}
+
+/**
+ * \brief Get index part of CTL element identifier of a CTL element id/value
+ * \param obj CTL element id/value
+ * \return index part of element identifier
+ */
+unsigned int snd_ctl_elem_value_get_index(const snd_ctl_elem_value_t *obj)
+{
+	assert(obj);
+	return obj->id.index;
+}
+
+/**
+ * \brief Set CTL element identifier of a CTL element id/value
+ * \param obj CTL element id/value
+ * \param ptr CTL element identifier
+ */
+void snd_ctl_elem_value_set_id(snd_ctl_elem_value_t *obj, const snd_ctl_elem_id_t *ptr)
+{
+	assert(obj && ptr);
+	obj->id = *ptr;
+}
+
+/**
+ * \brief Set element numeric identifier of a CTL element id/value
+ * \param obj CTL element id/value
+ * \param val element numeric identifier
+ */
+void snd_ctl_elem_value_set_numid(snd_ctl_elem_value_t *obj, unsigned int val)
+{
+	assert(obj);
+	obj->id.numid = val;
+}
+
+/**
+ * \brief Set interface part of CTL element identifier of a CTL element id/value
+ * \param obj CTL element id/value
+ * \param val interface part of element identifier
+ */
+void snd_ctl_elem_value_set_interface(snd_ctl_elem_value_t *obj, snd_ctl_elem_iface_t val)
+{
+	assert(obj);
+	obj->id.iface = val;
+}
+
+/**
+ * \brief Set device part of CTL element identifier of a CTL element id/value
+ * \param obj CTL element id/value
+ * \param val device part of element identifier
+ */
+void snd_ctl_elem_value_set_device(snd_ctl_elem_value_t *obj, unsigned int val)
+{
+	assert(obj);
+	obj->id.device = val;
+}
+
+/**
+ * \brief Set subdevice part of CTL element identifier of a CTL element id/value
+ * \param obj CTL element id/value
+ * \param val subdevice part of element identifier
+ */
+void snd_ctl_elem_value_set_subdevice(snd_ctl_elem_value_t *obj, unsigned int val)
+{
+	assert(obj);
+	obj->id.subdevice = val;
+}
+
+/**
+ * \brief Set name part of CTL element identifier of a CTL element id/value
+ * \param obj CTL element id/value
+ * \param val name part of element identifier
+ */
+void snd_ctl_elem_value_set_name(snd_ctl_elem_value_t *obj, const char *val)
+{
+	assert(obj);
+	strncpy((char *)obj->id.name, val, sizeof(obj->id.name));
+}
+
+/**
+ * \brief Set index part of CTL element identifier of a CTL element id/value
+ * \param obj CTL element id/value
+ * \param val index part of element identifier
+ */
+void snd_ctl_elem_value_set_index(snd_ctl_elem_value_t *obj, unsigned int val)
+{
+	assert(obj);
+	obj->id.index = val;
+}
+
+/**
+ * \brief Get value for an entry of a #SND_CTL_ELEM_TYPE_BOOLEAN CTL element id/value 
+ * \param obj CTL element id/value
+ * \param idx Entry index
+ * \return value for the entry
+ */ 
+int snd_ctl_elem_value_get_boolean(const snd_ctl_elem_value_t *obj, unsigned int idx)
+{
+	assert(obj);
+	assert(idx < sizeof(obj->value.integer.value) / sizeof(obj->value.integer.value[0]));
+	return obj->value.integer.value[idx];
+}
+
+/**
+ * \brief Get value for an entry of a #SND_CTL_ELEM_TYPE_INTEGER CTL element id/value 
+ * \param obj CTL element id/value
+ * \param idx Entry index
+ * \return value for the entry
+ */ 
+long snd_ctl_elem_value_get_integer(const snd_ctl_elem_value_t *obj, unsigned int idx)
+{
+	assert(obj);
+	assert(idx < sizeof(obj->value.integer.value) / sizeof(obj->value.integer.value[0]));
+	return obj->value.integer.value[idx];
+}
+
+/**
+ * \brief Get value for an entry of a #SND_CTL_ELEM_TYPE_INTEGER64 CTL element id/value 
+ * \param obj CTL element id/value
+ * \param idx Entry index
+ * \return value for the entry
+ */ 
+long long snd_ctl_elem_value_get_integer64(const snd_ctl_elem_value_t *obj, unsigned int idx)
+{
+	assert(obj);
+	assert(idx < sizeof(obj->value.integer64.value) / sizeof(obj->value.integer64.value[0]));
+	return obj->value.integer64.value[idx];
+}
+
+/**
+ * \brief Get value for an entry of a #SND_CTL_ELEM_TYPE_ENUMERATED CTL element id/value 
+ * \param obj CTL element id/value
+ * \param idx Entry index
+ * \return value for the entry
+ */ 
+unsigned int snd_ctl_elem_value_get_enumerated(const snd_ctl_elem_value_t *obj, unsigned int idx)
+{
+	assert(obj);
+	assert(idx < sizeof(obj->value.enumerated.item) / sizeof(obj->value.enumerated.item[0]));
+	return obj->value.enumerated.item[idx];
+}
+
+/**
+ * \brief Get value for an entry of a #SND_CTL_ELEM_TYPE_BYTES CTL element id/value 
+ * \param obj CTL element id/value
+ * \param idx Entry index
+ * \return value for the entry
+ */ 
+unsigned char snd_ctl_elem_value_get_byte(const snd_ctl_elem_value_t *obj, unsigned int idx)
+{
+	assert(obj);
+	assert(idx < sizeof(obj->value.bytes.data));
+	return obj->value.bytes.data[idx];
+}
+
+/**
+ * \brief Set value for an entry of a #SND_CTL_ELEM_TYPE_BOOLEAN CTL element id/value 
+ * \param obj CTL element id/value
+ * \param idx Entry index
+ * \param val value for the entry
+ */ 
+void snd_ctl_elem_value_set_boolean(snd_ctl_elem_value_t *obj, unsigned int idx, long val)
+{
+	assert(obj);
+	obj->value.integer.value[idx] = val;
+}
+
+/**
+ * \brief Set value for an entry of a #SND_CTL_ELEM_TYPE_INTEGER CTL element id/value 
+ * \param obj CTL element id/value
+ * \param idx Entry index
+ * \param val value for the entry
+ */ 
+void snd_ctl_elem_value_set_integer(snd_ctl_elem_value_t *obj, unsigned int idx, long val)
+{
+	assert(obj);
+	obj->value.integer.value[idx] = val;
+}
+
+/**
+ * \brief Set value for an entry of a #SND_CTL_ELEM_TYPE_INTEGER64 CTL element id/value 
+ * \param obj CTL element id/value
+ * \param idx Entry index
+ * \param val value for the entry
+ */ 
+void snd_ctl_elem_value_set_integer64(snd_ctl_elem_value_t *obj, unsigned int idx, long long val)
+{
+	assert(obj);
+	obj->value.integer64.value[idx] = val;
+}
+
+/**
+ * \brief Set value for an entry of a #SND_CTL_ELEM_TYPE_ENUMERATED CTL element id/value 
+ * \param obj CTL element id/value
+ * \param idx Entry index
+ * \param val value for the entry
+ */ 
+void snd_ctl_elem_value_set_enumerated(snd_ctl_elem_value_t *obj, unsigned int idx, unsigned int val)
+{
+	assert(obj);
+	obj->value.enumerated.item[idx] = val;
+}
+
+/**
+ * \brief Set value for an entry of a #SND_CTL_ELEM_TYPE_BYTES CTL element id/value 
+ * \param obj CTL element id/value
+ * \param idx Entry index
+ * \param val value for the entry
+ */ 
+void snd_ctl_elem_value_set_byte(snd_ctl_elem_value_t *obj, unsigned int idx, unsigned char val)
+{
+	assert(obj);
+	obj->value.bytes.data[idx] = val;
+}
+
+/**
+ * \brief Set CTL element #SND_CTL_ELEM_TYPE_BYTES value
+ * \param obj CTL handle
+ * \param data Bytes value
+ * \param size Size in bytes
+ */
+void snd_ctl_elem_set_bytes(snd_ctl_elem_value_t *obj, void *data, size_t size)
+{
+	assert(obj);
+	if (size >= sizeof(obj->value.bytes.data)) {
+		assert(0);
+		return;
+	}
+	memcpy(obj->value.bytes.data, data, size);
+}
+
+/**
+ * \brief Get value for a #SND_CTL_ELEM_TYPE_BYTES CTL element id/value 
+ * \param obj CTL element id/value
+ * \return Pointer to CTL element value
+ */ 
+const void * snd_ctl_elem_value_get_bytes(const snd_ctl_elem_value_t *obj)
+{
+	assert(obj);
+	return obj->value.bytes.data;
+}
+
+/**
+ * \brief Get value for a #SND_CTL_ELEM_TYPE_IEC958 CTL element id/value 
+ * \param obj CTL element id/value
+ * \param ptr Pointer to returned CTL element value
+ */ 
+void snd_ctl_elem_value_get_iec958(const snd_ctl_elem_value_t *obj, snd_aes_iec958_t *ptr)
+{
+	assert(obj && ptr);
+	memcpy(ptr, &obj->value.iec958, sizeof(*ptr));
+}
+
+/**
+ * \brief Set value for a #SND_CTL_ELEM_TYPE_IEC958 CTL element id/value 
+ * \param obj CTL element id/value
+ * \param ptr Pointer to CTL element value
+ */ 
+void snd_ctl_elem_value_set_iec958(snd_ctl_elem_value_t *obj, const snd_aes_iec958_t *ptr)
+{
+	assert(obj && ptr);
+	memcpy(&obj->value.iec958, ptr, sizeof(obj->value.iec958));
+}
+
diff --git a/src/control/control_ext.c b/src/control/control_ext.c
new file mode 100644
index 0000000..e20d4f3
--- /dev/null
+++ b/src/control/control_ext.c
@@ -0,0 +1,691 @@
+/**
+ * \file control/control_ext.c
+ * \ingroup CtlPlugin_SDK
+ * \brief External Control Plugin SDK
+ * \author Takashi Iwai <tiwai@suse.de>
+ * \date 2005
+ */
+/*
+ *  Control Interface - External Control Plugin SDK
+ *
+ *  Copyright (c) 2005 Takashi Iwai <tiwai@suse.de>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include "control_local.h"
+#include "control_external.h"
+
+#ifndef PIC
+/* entry for static linking */
+const char *_snd_module_control_ext = "";
+#endif
+
+static int snd_ctl_ext_close(snd_ctl_t *handle)
+{
+	snd_ctl_ext_t *ext = handle->private_data;
+	
+	if (ext->callback->close)
+		ext->callback->close(ext);
+	return 0;
+}
+
+static int snd_ctl_ext_nonblock(snd_ctl_t *handle, int nonblock)
+{
+	snd_ctl_ext_t *ext = handle->private_data;
+
+	ext->nonblock = nonblock;
+	return 0;
+}
+
+static int snd_ctl_ext_async(snd_ctl_t *ctl ATTRIBUTE_UNUSED,
+			     int sig ATTRIBUTE_UNUSED,
+			     pid_t pid ATTRIBUTE_UNUSED)
+{
+	return -ENOSYS;
+}
+
+static int snd_ctl_ext_subscribe_events(snd_ctl_t *handle, int subscribe)
+{
+	snd_ctl_ext_t *ext = handle->private_data;
+
+	if (subscribe < 0)
+		return ext->subscribed;
+	ext->subscribed = !!subscribe;
+	if (ext->callback->subscribe_events)
+		ext->callback->subscribe_events(ext, subscribe);
+	return 0;
+}
+
+static int snd_ctl_ext_card_info(snd_ctl_t *handle, snd_ctl_card_info_t *info)
+{
+	snd_ctl_ext_t *ext = handle->private_data;
+
+	memset(info, 0, sizeof(*info));
+	info->card = ext->card_idx;
+	memcpy(info->id, ext->id, sizeof(info->id));
+	memcpy(info->driver, ext->driver, sizeof(info->driver));
+	memcpy(info->name, ext->name, sizeof(info->name));
+	memcpy(info->longname, ext->longname, sizeof(info->longname));
+	memcpy(info->mixername, ext->mixername, sizeof(info->mixername));
+	return 0;
+}
+
+static int snd_ctl_ext_elem_list(snd_ctl_t *handle, snd_ctl_elem_list_t *list)
+{
+	snd_ctl_ext_t *ext = handle->private_data;
+	int ret;
+	unsigned int i, offset;
+	snd_ctl_elem_id_t *ids;
+
+	list->count = ext->callback->elem_count(ext);
+	list->used = 0;
+	ids = list->pids;
+	offset = list->offset;
+	for (i = 0; i < list->space; i++) {
+		if (offset >= list->count)
+			break;
+		snd_ctl_elem_id_clear(ids);
+		ret = ext->callback->elem_list(ext, offset, ids);
+		if (ret < 0)
+			return ret;
+		ids->numid = offset + 1; /* fake number */
+		list->used++;
+		offset++;
+		ids++;
+	}
+	return 0;
+}
+
+static snd_ctl_ext_key_t get_elem(snd_ctl_ext_t *ext, snd_ctl_elem_id_t *id)
+{
+	int numid = id->numid;
+	if (numid > 0) {
+		ext->callback->elem_list(ext, numid - 1, id);
+		id->numid = numid;
+	} else
+		id->numid = 0;
+	return ext->callback->find_elem(ext, id);
+}
+
+static int snd_ctl_ext_elem_info(snd_ctl_t *handle, snd_ctl_elem_info_t *info)
+{
+	snd_ctl_ext_t *ext = handle->private_data;
+	snd_ctl_ext_key_t key;
+	int type, ret;
+
+	key = get_elem(ext, &info->id);
+	if (key == SND_CTL_EXT_KEY_NOT_FOUND)
+		return -ENOENT;
+	ret = ext->callback->get_attribute(ext, key, &type, &info->access, &info->count);
+	if (ret < 0)
+		goto err;
+	info->type = type;
+	ret = -EINVAL;
+	switch (info->type) {
+	case SND_CTL_ELEM_TYPE_BOOLEAN:
+		info->value.integer.min = 0;
+		info->value.integer.max = 1;
+		ret = 0;
+		break;
+	case SND_CTL_ELEM_TYPE_INTEGER:
+		if (! ext->callback->get_integer_info)
+			goto err;
+		ret = ext->callback->get_integer_info(ext, key, &info->value.integer.min,
+						      &info->value.integer.max,
+						      &info->value.integer.step);
+		break;
+	case SND_CTL_ELEM_TYPE_INTEGER64:
+		if (! ext->callback->get_integer64_info)
+			goto err;
+		{
+			int64_t xmin, xmax, xstep;
+			ret = ext->callback->get_integer64_info(ext, key,
+								&xmin,
+								&xmax,
+								&xstep);
+			info->value.integer64.min = xmin;
+			info->value.integer64.max = xmax;
+			info->value.integer64.step = xstep;
+		}
+		break;
+	case SND_CTL_ELEM_TYPE_ENUMERATED:
+		if (! ext->callback->get_enumerated_info)
+			goto err;
+		ret = ext->callback->get_enumerated_info(ext, key, &info->value.enumerated.items);
+		ext->callback->get_enumerated_name(ext, key, info->value.enumerated.item,
+						   info->value.enumerated.name,
+						   sizeof(info->value.enumerated.name));
+		break;
+	default:
+		ret = 0;
+		break;
+	}
+
+ err:
+	if (ext->callback->free_key)
+		ext->callback->free_key(ext, key);
+
+	return ret;
+}
+
+static int snd_ctl_ext_elem_add(snd_ctl_t *handle ATTRIBUTE_UNUSED,
+				snd_ctl_elem_info_t *info ATTRIBUTE_UNUSED)
+{
+	return -ENXIO;
+}
+
+static int snd_ctl_ext_elem_replace(snd_ctl_t *handle ATTRIBUTE_UNUSED,
+				    snd_ctl_elem_info_t *info ATTRIBUTE_UNUSED)
+{
+	return -ENXIO;
+}
+
+static int snd_ctl_ext_elem_remove(snd_ctl_t *handle ATTRIBUTE_UNUSED,
+				   snd_ctl_elem_id_t *id ATTRIBUTE_UNUSED)
+{
+	return -ENXIO;
+}
+
+static int snd_ctl_ext_elem_read(snd_ctl_t *handle, snd_ctl_elem_value_t *control)
+{
+	snd_ctl_ext_t *ext = handle->private_data;
+	snd_ctl_ext_key_t key;
+	int type, ret;
+	unsigned int access, count;
+
+	key = get_elem(ext, &control->id);
+	if (key == SND_CTL_EXT_KEY_NOT_FOUND)
+		return -ENOENT;
+	ret = ext->callback->get_attribute(ext, key, &type, &access, &count);
+	if (ret < 0)
+		goto err;
+	ret = -EINVAL;
+	switch (type) {
+	case SND_CTL_ELEM_TYPE_BOOLEAN:
+	case SND_CTL_ELEM_TYPE_INTEGER:
+		if (! ext->callback->read_integer)
+			goto err;
+		ret = ext->callback->read_integer(ext, key, control->value.integer.value);
+		break;
+	case SND_CTL_ELEM_TYPE_INTEGER64:
+		if (! ext->callback->read_integer64)
+			goto err;
+		ret = ext->callback->read_integer64(ext, key,
+						    (int64_t*)control->value.integer64.value);
+		break;
+	case SND_CTL_ELEM_TYPE_ENUMERATED:
+		if (! ext->callback->read_enumerated)
+			goto err;
+		ret = ext->callback->read_enumerated(ext, key, control->value.enumerated.item);
+		break;
+	case SND_CTL_ELEM_TYPE_BYTES:
+		if (! ext->callback->read_bytes)
+			goto err;
+		ret = ext->callback->read_bytes(ext, key, control->value.bytes.data,
+						sizeof(control->value.bytes.data));
+		break;
+	case SND_CTL_ELEM_TYPE_IEC958:
+		if (! ext->callback->read_iec958)
+			goto err;
+		ret = ext->callback->read_iec958(ext, key, (snd_aes_iec958_t *)&control->value.iec958);
+		break;
+	default:
+		break;
+	}
+
+ err:
+	if (ext->callback->free_key)
+		ext->callback->free_key(ext, key);
+
+	return ret;
+}
+
+static int snd_ctl_ext_elem_write(snd_ctl_t *handle, snd_ctl_elem_value_t *control)
+{
+	snd_ctl_ext_t *ext = handle->private_data;
+	snd_ctl_ext_key_t key;
+	int type, ret;
+	unsigned int access, count;
+
+	key = get_elem(ext, &control->id);
+	if (key == SND_CTL_EXT_KEY_NOT_FOUND)
+		return -ENOENT;
+	ret = ext->callback->get_attribute(ext, key, &type, &access, &count);
+	if (ret < 0)
+		goto err;
+	ret = -EINVAL;
+	switch (type) {
+	case SND_CTL_ELEM_TYPE_BOOLEAN:
+	case SND_CTL_ELEM_TYPE_INTEGER:
+		if (! ext->callback->write_integer)
+			goto err;
+		ret = ext->callback->write_integer(ext, key, control->value.integer.value);
+		break;
+	case SND_CTL_ELEM_TYPE_INTEGER64:
+		if (! ext->callback->write_integer64)
+			goto err;
+		ret = ext->callback->write_integer64(ext, key, (int64_t *)control->value.integer64.value);
+		break;
+	case SND_CTL_ELEM_TYPE_ENUMERATED:
+		if (! ext->callback->write_enumerated)
+			goto err;
+		ret = ext->callback->write_enumerated(ext, key, control->value.enumerated.item);
+		break;
+	case SND_CTL_ELEM_TYPE_BYTES:
+		if (! ext->callback->write_bytes)
+			goto err;
+		ret = ext->callback->write_bytes(ext, key, control->value.bytes.data,
+						sizeof(control->value.bytes.data));
+		break;
+	case SND_CTL_ELEM_TYPE_IEC958:
+		if (! ext->callback->write_iec958)
+			goto err;
+		ret = ext->callback->write_iec958(ext, key, (snd_aes_iec958_t *)&control->value.iec958);
+		break;
+	default:
+		break;
+	}
+
+ err:
+	if (ext->callback->free_key)
+		ext->callback->free_key(ext, key);
+
+	return ret;
+}
+
+static int snd_ctl_ext_elem_lock(snd_ctl_t *handle ATTRIBUTE_UNUSED,
+				 snd_ctl_elem_id_t *id ATTRIBUTE_UNUSED)
+{
+	return -ENXIO;
+}
+
+static int snd_ctl_ext_elem_unlock(snd_ctl_t *handle ATTRIBUTE_UNUSED,
+				   snd_ctl_elem_id_t *id ATTRIBUTE_UNUSED)
+{
+	return -ENXIO;
+}
+
+static int snd_ctl_ext_next_device(snd_ctl_t *handle ATTRIBUTE_UNUSED,
+				   int *device ATTRIBUTE_UNUSED)
+{
+	return -ENXIO;
+}
+
+static int snd_ctl_ext_prefer_subdevice(snd_ctl_t *handle ATTRIBUTE_UNUSED,
+					int subdev ATTRIBUTE_UNUSED)
+{
+	return -ENXIO;
+}
+
+static int snd_ctl_ext_hwdep_info(snd_ctl_t *handle ATTRIBUTE_UNUSED,
+				  snd_hwdep_info_t *info ATTRIBUTE_UNUSED)
+{
+	return -ENXIO;
+}
+
+static int snd_ctl_ext_pcm_info(snd_ctl_t *handle ATTRIBUTE_UNUSED,
+				snd_pcm_info_t *info ATTRIBUTE_UNUSED)
+{
+	return -ENXIO;
+}
+
+static int snd_ctl_ext_rawmidi_info(snd_ctl_t *handle ATTRIBUTE_UNUSED,
+				    snd_rawmidi_info_t *info ATTRIBUTE_UNUSED)
+{
+	return -ENXIO;
+}
+
+static int snd_ctl_ext_set_power_state(snd_ctl_t *handle ATTRIBUTE_UNUSED,
+				       unsigned int state ATTRIBUTE_UNUSED)
+{
+	return 0;
+}
+
+static int snd_ctl_ext_get_power_state(snd_ctl_t *handle ATTRIBUTE_UNUSED,
+				       unsigned int *state ATTRIBUTE_UNUSED)
+{
+	return 0;
+}
+
+static int snd_ctl_ext_read(snd_ctl_t *handle, snd_ctl_event_t *event)
+{
+	snd_ctl_ext_t *ext = handle->private_data;
+
+	memset(event, 0, sizeof(*event));
+	return ext->callback->read_event(ext, &event->data.elem.id, &event->data.elem.mask);
+}
+
+static int snd_ctl_ext_poll_descriptors_count(snd_ctl_t *handle)
+{
+	snd_ctl_ext_t *ext = handle->private_data;
+
+	if (ext->callback->poll_descriptors_count)
+		return ext->callback->poll_descriptors_count(ext);
+	if (ext->poll_fd >= 0)
+		return 1;
+	return 0;
+}
+
+static int snd_ctl_ext_poll_descriptors(snd_ctl_t *handle, struct pollfd *pfds, unsigned int space)
+{
+	snd_ctl_ext_t *ext = handle->private_data;
+
+	if (ext->callback->poll_descriptors)
+		return ext->callback->poll_descriptors(ext, pfds, space);
+	if (ext->poll_fd < 0)
+		return 0;
+	if (space > 0) {
+		pfds->fd = ext->poll_fd;
+		pfds->events = POLLIN|POLLERR|POLLNVAL;
+		return 1;
+	}
+	return 0;
+}
+
+static int snd_ctl_ext_poll_revents(snd_ctl_t *handle, struct pollfd *pfds, unsigned int nfds, unsigned short *revents)
+{
+	snd_ctl_ext_t *ext = handle->private_data;
+
+	if (ext->callback->poll_revents)
+		return ext->callback->poll_revents(ext, pfds, nfds, revents);
+	if (nfds == 1) {
+		*revents = pfds->revents;
+                return 0;
+	}
+	return -EINVAL;
+}
+
+static const snd_ctl_ops_t snd_ctl_ext_ops = {
+	.close = snd_ctl_ext_close,
+	.nonblock = snd_ctl_ext_nonblock,
+	.async = snd_ctl_ext_async,
+	.subscribe_events = snd_ctl_ext_subscribe_events,
+	.card_info = snd_ctl_ext_card_info,
+	.element_list = snd_ctl_ext_elem_list,
+	.element_info = snd_ctl_ext_elem_info,
+	.element_add = snd_ctl_ext_elem_add,
+	.element_replace = snd_ctl_ext_elem_replace,
+	.element_remove = snd_ctl_ext_elem_remove,
+	.element_read = snd_ctl_ext_elem_read,
+	.element_write = snd_ctl_ext_elem_write,
+	.element_lock = snd_ctl_ext_elem_lock,
+	.element_unlock = snd_ctl_ext_elem_unlock,
+	.hwdep_next_device = snd_ctl_ext_next_device,
+	.hwdep_info = snd_ctl_ext_hwdep_info,
+	.pcm_next_device = snd_ctl_ext_next_device,
+	.pcm_info = snd_ctl_ext_pcm_info,
+	.pcm_prefer_subdevice = snd_ctl_ext_prefer_subdevice,
+	.rawmidi_next_device = snd_ctl_rawmidi_next_device,
+	.rawmidi_info = snd_ctl_ext_rawmidi_info,
+	.rawmidi_prefer_subdevice = snd_ctl_ext_prefer_subdevice,
+	.set_power_state = snd_ctl_ext_set_power_state,
+	.get_power_state = snd_ctl_ext_get_power_state,
+	.read = snd_ctl_ext_read,
+	.poll_descriptors_count = snd_ctl_ext_poll_descriptors_count,
+	.poll_descriptors = snd_ctl_ext_poll_descriptors,
+	.poll_revents = snd_ctl_ext_poll_revents,
+};
+
+/*
+ * Exported functions
+ */
+
+/*! \page ctl_external_plugins External Control Plugin SDK
+
+\section ctl_externals External Control Plugins
+
+The external plugins are implemented in a shared object file located
+at /usr/lib/alsa-lib (the exact location depends on the build option
+and asoundrc configuration).  It has to be the file like
+libasound_module_ctl_MYPLUGIN.so, where MYPLUGIN corresponds to your
+own plugin name.
+
+The entry point of the plugin is defined via
+#SND_CTL_PLUGIN_DEFINE_FUNC() macro.  This macro defines the function
+with a proper name to be referred from alsa-lib.  The function takes
+the following 5 arguments:
+\code
+int (snd_ctl_t **phandle, const char *name, snd_config_t *root,
+	snd_config_t *conf, int mode)
+\endcode
+The first argument, phandle, is the pointer to store the resultant control
+handle.  The arguments name, root and mode are the parameters
+to be passed to the plugin constructor.  The conf is the configuration
+tree for the plugin.  The arguments above are defined in the macro
+itself, so don't use variables with the same names to shadow
+parameters.
+
+After parsing the configuration parameters in the given conf tree,
+usually you will call the external plugin API function
+#snd_ctl_ext_create().
+The control handle must be filled *phandle in return.
+Then this function must return either a value 0 when succeeded, or a
+negative value as the error code. 
+
+Finally, add #SND_CTL_PLUGIN_SYMBOL() with the name of your
+plugin as the argument at the end.  This defines the proper versioned
+symbol as the reference.
+
+The typical code would look like below:
+\code
+struct myctl_info {
+	snd_ctl_ext_t ext;
+	int my_own_data;
+	...
+};
+
+SND_CTL_PLUGIN_DEFINE_FUNC(myctl)
+{
+	snd_config_iterator_t i, next;
+	struct myctl_info *myctl;
+	int err;
+
+	snd_config_for_each(i, next, conf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (strcmp(id, "comment") == 0 || strcmp(id, "type") == 0)
+			continue;
+		if (strcmp(id, "my_own_parameter") == 0) {
+			....
+			continue;
+		}
+		SNDERR("Unknown field %s", id);
+		return -EINVAL;
+	}
+
+	myctl = calloc(1, sizeof(*myctl));
+	if (myctl == NULL)
+		return -ENOMEM;
+
+	myctl->ext.version = SND_CTL_EXT_VERSION;
+	myctl->ext.card_idx = 0;
+	strcpy(myctl->ext.id, "Myctl");
+	strcpy(myctl->ext.name, "My Control");
+	strcpy(myctl->ext.longname, "My External Control for Foobar");
+	strcpy(myctl->ext.mixername, "My Control");
+	myctl->ext.callback = &my_own_callback;
+	myctl->ext.private_data = myctl;
+	....
+
+	err = snd_pcm_extplug_create(&myctl->ext, name, mode);
+	if (err < 0) {
+		myctl_free(myctl);
+		return err;
+	}
+
+	*phandle = myctl->ext.handle;
+	return 0;
+}
+
+SND_CTL_PLUGIN_SYMBOL(myctl);
+\endcode
+
+Read the codes in alsa-plugins package for the real examples.
+
+
+\section ctl_ext_impl Implementation of External Control Plugins
+
+The following fields have to be filled in external control record before calling
+#snd_ctl_ext_create() : version, card_idx, id, name, longname, mixername, poll_fd and callback.
+Otherfields are optional and should be initialized with zero.
+
+The constant #SND_CTL_EXT_VERSION must be passed to the version
+field for the version check in alsa-lib.  The card_idx field specifies the card
+index of this control.  [FIXME: solve confliction of card index in alsa-lib?]
+
+The id, name, longname and mixername fields are the strings shown in the card_info
+inqurirys.  They are the char arrays, so you have to <i>copy</i> strings to these
+fields.
+
+The callback field contains the  table of callback functions for this plugin (defined as
+#snd_ctl_ext_callback_t).
+The poll_fd can be used to specify the poll file descriptor for this control.
+Set -1 if not available.  Alternatively, you can define poll_descriptors_count and
+poll_descriptors callbacks in the callback table for handling the poll descriptor(s)
+dynamically after the creation of plugin instance.
+
+The driver can set an arbitrary value (pointer) to private_data
+field to refer its own data in the callbacks.
+
+The rest fields are filled by #snd_ctl_ext_create().  The handle field
+is the resultant PCM handle.  The others are the current status of the
+PCM.
+
+\section ctl_ext_impl Callback Functions of External Control Plugins
+
+The callback functions in #snd_ctl_ext_callback_t define the real
+behavior of the driver.  There are many callbacks but many of them are optional. 
+
+The close callback is called when the PCM is closed.  If the plugin
+allocates private resources, this is the place to release them
+again.  This callback is optional.
+
+The elem_count and elem_list callbacks are mandatory.  The elem_count returns the
+total number of control elements.  The elem_list returns the control element ID
+of the corresponding element offset (the offset is from 0 to elem_count - 1).
+The id field is initialized to all zero in prior to elem_list callback.  The callback
+has to fill the necessary field (typically iface, name and index) in return via the
+standard control API functions like #snd_ctl_elem_id_set_interface,
+#snd_ctl_elem_id_set_name and #snd_ctl_elem_id_set_index, etc.  The callbacks should
+return 0 if successful, or a negative error code.
+
+The find_elem callback is used to convert the given control element ID to the
+certain key value for the faster access to get, read and write callbacks.
+The key type is alias of unsigned long, so you can assign some static number
+(e.g. index of the array) to this value of the corresponding element, or
+assign the pointer (cast to #snd_ctl_ext_key_t).  When no key is defined or found,
+return #SND_CTL_EXT_KEY_NOT_FOUND.  This callback is (very likely) required
+if you use get, read and write callbacks as follows.
+If you need to create a record dynamically (e.g. via malloc) at each find_elem call,
+the allocated record can be released with the optional free_key callback.
+
+The get_attribute is a mandatory callback, which returns the attribute of the 
+control element given via a key value (converted with find_elem callback).
+It must fill the control element type (#snd_ctl_elem_type_t), the access type
+(#snd_ctl_ext_access_t), and the count (element array size).  The callback returns
+0 if successful, or a negative error code, as usual.
+
+The get_integer_info, get_integetr64_info and get_enumerated_info callbacks are called
+to return the information of the given control element for each element type.
+For integer and integer64 types, the callbacks need to fill the minimal (imin),
+maximal (imax) and the step (istep) values of the control.  For the enumerated type,
+the number of enum items must be filled.  Additionally, the enum control has to define
+get_enumerated_name callback to store the name of the enumerated item of the given control
+element.  All functions return 0 if successful, or a negative error code.
+
+For reading the current values of a control element, read_integer, read_integer64,
+read_enumerated, read_bytes and read_iec958 callbacks are called depending on the
+element type.  These callbacks have to fill the current values of the element in return.
+Note that a control element can be an array.  If it contains more than one values
+(i.e. the count value in get_attribute callback is more than 1), <i>all</i> values
+must be filled on the given value pointer as an array.  Also, note that the boolean type
+is handled as integer here (although boolean type doesn't need to define the corresponding
+info callback since it's obvious).  These callbacks return 0 if successful, or
+a negative error code.
+
+For writing the current values, write_integer, write_integer64, write_bytes, and
+write_iec958 callbacks are called as well as for read.  The callbacks should check the
+current values and compare with the given values.  If they are identical, the callbacks
+should do nothing and return 0.  If they differ, update the current values and return 1,
+instead.  For any errors, return a negative error code.
+
+The subscribe_events callback is called when the application subscribes or cancels
+the event notifications (e.g. through mixer API).  The current value of event
+subscription is kept in the subscribed field.
+The read_event callback is called for reading a pending notification event.
+The callback needs to fill the event_mask value, a bit-field defined as SND_CTL_EVENT_MASK_XXX.
+If no event is pending, return -EAGAIN.  These two callbacks are optional.
+
+The poll_descriptors_count and poll_descriptors callbacks are used to return
+the poll descriptor(s) via callbacks.  As already mentioned, if the callback cannot
+set the static poll_fd, you can define these callbacks to return dynamically.
+Also, when multiple poll descriptors are required, use these callbacks.
+The poll_revents callback is used for handle poll revents.
+
+*/
+
+/**
+ * \brief Create an external control plugin instance
+ * \param ext the plugin handle
+ * \param name name of control
+ * \param mode control open mode
+ * \return 0 if successful, or a negative error code
+ *
+ * Creates the external control instance.
+ *
+ */
+int snd_ctl_ext_create(snd_ctl_ext_t *ext, const char *name, int mode)
+{
+	snd_ctl_t *ctl;
+	int err;
+
+	if (ext->version != SND_CTL_EXT_VERSION) {
+		SNDERR("ctl_ext: Plugin version mismatch\n");
+		return -ENXIO;
+	}
+
+	err = snd_ctl_new(&ctl, SND_CTL_TYPE_EXT, name);
+	if (err < 0)
+		return err;
+
+	ext->handle = ctl;
+
+	ctl->ops = &snd_ctl_ext_ops;
+	ctl->private_data = ext;
+	ctl->poll_fd = ext->poll_fd;
+	if (mode & SND_CTL_NONBLOCK)
+		ext->nonblock = 1;
+
+	return 0;
+}
+
+/**
+ * \brief Delete the external control plugin
+ * \param ext the plugin handle
+ * \return 0 if successful, or a negative error code
+ */
+int snd_ctl_ext_delete(snd_ctl_ext_t *ext)
+{
+	return snd_ctl_close(ext->handle);
+}
diff --git a/src/control/control_hw.c b/src/control/control_hw.c
new file mode 100644
index 0000000..1920c99
--- /dev/null
+++ b/src/control/control_hw.c
@@ -0,0 +1,469 @@
+/*
+ *  Control Interface - Hardware
+ *  Copyright (c) 1998,1999,2000 by Jaroslav Kysela <perex@perex.cz>
+ *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include "control_local.h"
+
+#ifndef PIC
+/* entry for static linking */
+const char *_snd_module_control_hw = "";
+#endif
+
+#ifndef F_SETSIG
+#define F_SETSIG 10
+#endif
+
+#ifndef DOC_HIDDEN
+#define SNDRV_FILE_CONTROL	ALSA_DEVICE_DIRECTORY "controlC%i"
+#define SNDRV_CTL_VERSION_MAX	SNDRV_PROTOCOL_VERSION(2, 0, 4)
+
+typedef struct {
+	int card;
+	int fd;
+	unsigned int protocol;
+} snd_ctl_hw_t;
+#endif /* DOC_HIDDEN */
+
+static int snd_ctl_hw_close(snd_ctl_t *handle)
+{
+	snd_ctl_hw_t *hw = handle->private_data;
+	int res;
+	res = close(hw->fd) < 0 ? -errno : 0;
+	free(hw);
+	return res;
+}
+
+static int snd_ctl_hw_nonblock(snd_ctl_t *handle, int nonblock)
+{
+	snd_ctl_hw_t *hw = handle->private_data;
+	long flags;
+	int fd = hw->fd;
+	if ((flags = fcntl(fd, F_GETFL)) < 0) {
+		SYSERR("F_GETFL failed");
+		return -errno;
+	}
+	if (nonblock)
+		flags |= O_NONBLOCK;
+	else
+		flags &= ~O_NONBLOCK;
+	if (fcntl(fd, F_SETFL, flags) < 0) {
+		SYSERR("F_SETFL for O_NONBLOCK failed");
+		return -errno;
+	}
+	return 0;
+}
+
+static int snd_ctl_hw_async(snd_ctl_t *ctl, int sig, pid_t pid)
+{
+	long flags;
+	snd_ctl_hw_t *hw = ctl->private_data;
+	int fd = hw->fd;
+
+	if ((flags = fcntl(fd, F_GETFL)) < 0) {
+		SYSERR("F_GETFL failed");
+		return -errno;
+	}
+	if (sig >= 0)
+		flags |= O_ASYNC;
+	else
+		flags &= ~O_ASYNC;
+	if (fcntl(fd, F_SETFL, flags) < 0) {
+		SYSERR("F_SETFL for O_ASYNC failed");
+		return -errno;
+	}
+	if (sig < 0)
+		return 0;
+	if (fcntl(fd, F_SETSIG, (long)sig) < 0) {
+		SYSERR("F_SETSIG failed");
+		return -errno;
+	}
+	if (fcntl(fd, F_SETOWN, (long)pid) < 0) {
+		SYSERR("F_SETOWN failed");
+		return -errno;
+	}
+	return 0;
+}
+
+static int snd_ctl_hw_subscribe_events(snd_ctl_t *handle, int subscribe)
+{
+	snd_ctl_hw_t *hw = handle->private_data;
+	if (ioctl(hw->fd, SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS, &subscribe) < 0) {
+		SYSERR("SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS failed");
+		return -errno;
+	}
+	return 0;
+}
+
+static int snd_ctl_hw_card_info(snd_ctl_t *handle, snd_ctl_card_info_t *info)
+{
+	snd_ctl_hw_t *hw = handle->private_data;
+	if (ioctl(hw->fd, SNDRV_CTL_IOCTL_CARD_INFO, info) < 0) {
+		SYSERR("SNDRV_CTL_IOCTL_CARD_INFO failed");
+		return -errno;
+	}
+	return 0;
+}
+
+static int snd_ctl_hw_elem_list(snd_ctl_t *handle, snd_ctl_elem_list_t *list)
+{
+	snd_ctl_hw_t *hw = handle->private_data;
+	if (ioctl(hw->fd, SNDRV_CTL_IOCTL_ELEM_LIST, list) < 0)
+		return -errno;
+	return 0;
+}
+
+static int snd_ctl_hw_elem_info(snd_ctl_t *handle, snd_ctl_elem_info_t *info)
+{
+	snd_ctl_hw_t *hw = handle->private_data;
+	if (ioctl(hw->fd, SNDRV_CTL_IOCTL_ELEM_INFO, info) < 0)
+		return -errno;
+	return 0;
+}
+
+static int snd_ctl_hw_elem_add(snd_ctl_t *handle, snd_ctl_elem_info_t *info)
+{
+	snd_ctl_hw_t *hw = handle->private_data;
+
+	if (info->type == SNDRV_CTL_ELEM_TYPE_ENUMERATED &&
+	    hw->protocol < SNDRV_PROTOCOL_VERSION(2, 0, 7))
+		return -ENXIO;
+
+	if (ioctl(hw->fd, SNDRV_CTL_IOCTL_ELEM_ADD, info) < 0)
+		return -errno;
+	return 0;
+}
+
+static int snd_ctl_hw_elem_replace(snd_ctl_t *handle, snd_ctl_elem_info_t *info)
+{
+	snd_ctl_hw_t *hw = handle->private_data;
+
+	if (info->type == SNDRV_CTL_ELEM_TYPE_ENUMERATED &&
+	    hw->protocol < SNDRV_PROTOCOL_VERSION(2, 0, 7))
+		return -ENXIO;
+
+	if (ioctl(hw->fd, SNDRV_CTL_IOCTL_ELEM_REPLACE, info) < 0)
+		return -errno;
+	return 0;
+}
+
+static int snd_ctl_hw_elem_remove(snd_ctl_t *handle, snd_ctl_elem_id_t *id)
+{
+	snd_ctl_hw_t *hw = handle->private_data;
+	if (ioctl(hw->fd, SNDRV_CTL_IOCTL_ELEM_REMOVE, id) < 0)
+		return -errno;
+	return 0;
+}
+
+static int snd_ctl_hw_elem_read(snd_ctl_t *handle, snd_ctl_elem_value_t *control)
+{
+	snd_ctl_hw_t *hw = handle->private_data;
+	if (ioctl(hw->fd, SNDRV_CTL_IOCTL_ELEM_READ, control) < 0)
+		return -errno;
+	return 0;
+}
+
+static int snd_ctl_hw_elem_write(snd_ctl_t *handle, snd_ctl_elem_value_t *control)
+{
+	snd_ctl_hw_t *hw = handle->private_data;
+	if (ioctl(hw->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, control) < 0)
+		return -errno;
+	return 0;
+}
+
+static int snd_ctl_hw_elem_lock(snd_ctl_t *handle, snd_ctl_elem_id_t *id)
+{
+	snd_ctl_hw_t *hw = handle->private_data;
+	if (ioctl(hw->fd, SNDRV_CTL_IOCTL_ELEM_LOCK, id) < 0)
+		return -errno;
+	return 0;
+}
+
+static int snd_ctl_hw_elem_unlock(snd_ctl_t *handle, snd_ctl_elem_id_t *id)
+{
+	snd_ctl_hw_t *hw = handle->private_data;
+	if (ioctl(hw->fd, SNDRV_CTL_IOCTL_ELEM_UNLOCK, id) < 0)
+		return -errno;
+	return 0;
+}
+
+static int snd_ctl_hw_elem_tlv(snd_ctl_t *handle, int op_flag,
+			       unsigned int numid,
+			       unsigned int *tlv, unsigned int tlv_size)
+{
+	int inum;
+	snd_ctl_hw_t *hw = handle->private_data;
+	struct sndrv_ctl_tlv *xtlv;
+	
+	/* we don't support TLV on protocol ver 2.0.3 or earlier */
+	if (hw->protocol < SNDRV_PROTOCOL_VERSION(2, 0, 4))
+		return -ENXIO;
+
+	switch (op_flag) {
+	case -1: inum = SNDRV_CTL_IOCTL_TLV_COMMAND; break;
+ 	case 0:	inum = SNDRV_CTL_IOCTL_TLV_READ; break;
+	case 1:	inum = SNDRV_CTL_IOCTL_TLV_WRITE; break;
+	default: return -EINVAL;
+	}
+	xtlv = malloc(sizeof(struct sndrv_ctl_tlv) + tlv_size);
+	if (xtlv == NULL)
+		return -ENOMEM; 
+	xtlv->numid = numid;
+	xtlv->length = tlv_size;
+	memcpy(xtlv->tlv, tlv, tlv_size);
+	if (ioctl(hw->fd, inum, xtlv) < 0) {
+		free(xtlv);
+		return -errno;
+	}
+	if (op_flag == 0) {
+		if (xtlv->tlv[1] + 2 * sizeof(unsigned int) > tlv_size)
+			return -EFAULT;
+		memcpy(tlv, xtlv->tlv, xtlv->tlv[1] + 2 * sizeof(unsigned int));
+	}
+	free(xtlv);
+	return 0;
+}
+
+static int snd_ctl_hw_hwdep_next_device(snd_ctl_t *handle, int * device)
+{
+	snd_ctl_hw_t *hw = handle->private_data;
+	if (ioctl(hw->fd, SNDRV_CTL_IOCTL_HWDEP_NEXT_DEVICE, device) < 0)
+		return -errno;
+	return 0;
+}
+
+static int snd_ctl_hw_hwdep_info(snd_ctl_t *handle, snd_hwdep_info_t * info)
+{
+	snd_ctl_hw_t *hw = handle->private_data;
+	if (ioctl(hw->fd, SNDRV_CTL_IOCTL_HWDEP_INFO, info) < 0)
+		return -errno;
+	return 0;
+}
+
+static int snd_ctl_hw_pcm_next_device(snd_ctl_t *handle, int * device)
+{
+	snd_ctl_hw_t *hw = handle->private_data;
+	if (ioctl(hw->fd, SNDRV_CTL_IOCTL_PCM_NEXT_DEVICE, device) < 0)
+		return -errno;
+	return 0;
+}
+
+static int snd_ctl_hw_pcm_info(snd_ctl_t *handle, snd_pcm_info_t * info)
+{
+	snd_ctl_hw_t *hw = handle->private_data;
+	if (ioctl(hw->fd, SNDRV_CTL_IOCTL_PCM_INFO, info) < 0)
+		return -errno;
+	return 0;
+}
+
+static int snd_ctl_hw_pcm_prefer_subdevice(snd_ctl_t *handle, int subdev)
+{
+	snd_ctl_hw_t *hw = handle->private_data;
+	if (ioctl(hw->fd, SNDRV_CTL_IOCTL_PCM_PREFER_SUBDEVICE, &subdev) < 0)
+		return -errno;
+	return 0;
+}
+
+static int snd_ctl_hw_rawmidi_next_device(snd_ctl_t *handle, int * device)
+{
+	snd_ctl_hw_t *hw = handle->private_data;
+	if (ioctl(hw->fd, SNDRV_CTL_IOCTL_RAWMIDI_NEXT_DEVICE, device) < 0)
+		return -errno;
+	return 0;
+}
+
+static int snd_ctl_hw_rawmidi_info(snd_ctl_t *handle, snd_rawmidi_info_t * info)
+{
+	snd_ctl_hw_t *hw = handle->private_data;
+	if (ioctl(hw->fd, SNDRV_CTL_IOCTL_RAWMIDI_INFO, info) < 0)
+		return -errno;
+	return 0;
+}
+
+static int snd_ctl_hw_rawmidi_prefer_subdevice(snd_ctl_t *handle, int subdev)
+{
+	snd_ctl_hw_t *hw = handle->private_data;
+	if (ioctl(hw->fd, SNDRV_CTL_IOCTL_RAWMIDI_PREFER_SUBDEVICE, &subdev) < 0)
+		return -errno;
+	return 0;
+}
+
+static int snd_ctl_hw_set_power_state(snd_ctl_t *handle, unsigned int state)
+{
+	snd_ctl_hw_t *hw = handle->private_data;
+	if (ioctl(hw->fd, SNDRV_CTL_IOCTL_POWER, &state) < 0)
+		return -errno;
+	return 0;
+}
+
+static int snd_ctl_hw_get_power_state(snd_ctl_t *handle, unsigned int *state)
+{
+	snd_ctl_hw_t *hw = handle->private_data;
+	if (ioctl(hw->fd, SNDRV_CTL_IOCTL_POWER_STATE, state) < 0)
+		return -errno;
+	return 0;
+}
+
+static int snd_ctl_hw_read(snd_ctl_t *handle, snd_ctl_event_t *event)
+{
+	snd_ctl_hw_t *hw = handle->private_data;
+	ssize_t res = read(hw->fd, event, sizeof(*event));
+	if (res <= 0)
+		return -errno;
+	if (CHECK_SANITY(res != sizeof(*event))) {
+		SNDMSG("snd_ctl_hw_read: read size error (req:%d, got:%d)\n",
+		       sizeof(*event), res);
+		return -EINVAL;
+	}
+	return 1;
+}
+
+static const snd_ctl_ops_t snd_ctl_hw_ops = {
+	.close = snd_ctl_hw_close,
+	.nonblock = snd_ctl_hw_nonblock,
+	.async = snd_ctl_hw_async,
+	.subscribe_events = snd_ctl_hw_subscribe_events,
+	.card_info = snd_ctl_hw_card_info,
+	.element_list = snd_ctl_hw_elem_list,
+	.element_info = snd_ctl_hw_elem_info,
+	.element_add = snd_ctl_hw_elem_add,
+	.element_replace = snd_ctl_hw_elem_replace,
+	.element_remove = snd_ctl_hw_elem_remove,
+	.element_read = snd_ctl_hw_elem_read,
+	.element_write = snd_ctl_hw_elem_write,
+	.element_lock = snd_ctl_hw_elem_lock,
+	.element_unlock = snd_ctl_hw_elem_unlock,
+	.element_tlv = snd_ctl_hw_elem_tlv,
+	.hwdep_next_device = snd_ctl_hw_hwdep_next_device,
+	.hwdep_info = snd_ctl_hw_hwdep_info,
+	.pcm_next_device = snd_ctl_hw_pcm_next_device,
+	.pcm_info = snd_ctl_hw_pcm_info,
+	.pcm_prefer_subdevice = snd_ctl_hw_pcm_prefer_subdevice,
+	.rawmidi_next_device = snd_ctl_hw_rawmidi_next_device,
+	.rawmidi_info = snd_ctl_hw_rawmidi_info,
+	.rawmidi_prefer_subdevice = snd_ctl_hw_rawmidi_prefer_subdevice,
+	.set_power_state = snd_ctl_hw_set_power_state,
+	.get_power_state = snd_ctl_hw_get_power_state,
+	.read = snd_ctl_hw_read,
+};
+
+int snd_ctl_hw_open(snd_ctl_t **handle, const char *name, int card, int mode)
+{
+	int fd, ver;
+	char filename[sizeof(SNDRV_FILE_CONTROL) + 10];
+	int fmode;
+	snd_ctl_t *ctl;
+	snd_ctl_hw_t *hw;
+	int err;
+
+	*handle = NULL;	
+
+	if (CHECK_SANITY(card < 0 || card >= 32)) {
+		SNDMSG("Invalid card index %d", card);
+		return -EINVAL;
+	}
+	sprintf(filename, SNDRV_FILE_CONTROL, card);
+	if (mode & SND_CTL_READONLY)
+		fmode = O_RDONLY;
+	else
+		fmode = O_RDWR;
+	if (mode & SND_CTL_NONBLOCK)
+		fmode |= O_NONBLOCK;
+	if (mode & SND_CTL_ASYNC)
+		fmode |= O_ASYNC;
+	fd = snd_open_device(filename, fmode);
+	if (fd < 0) {
+		snd_card_load(card);
+		fd = snd_open_device(filename, fmode);
+		if (fd < 0)
+			return -errno;
+	}
+	if (ioctl(fd, SNDRV_CTL_IOCTL_PVERSION, &ver) < 0) {
+		err = -errno;
+		close(fd);
+		return err;
+	}
+	if (SNDRV_PROTOCOL_INCOMPATIBLE(ver, SNDRV_CTL_VERSION_MAX)) {
+		close(fd);
+		return -SND_ERROR_INCOMPATIBLE_VERSION;
+	}
+	hw = calloc(1, sizeof(snd_ctl_hw_t));
+	if (hw == NULL) {
+		close(fd);
+		return -ENOMEM;
+	}
+	hw->card = card;
+	hw->fd = fd;
+	hw->protocol = ver;
+
+	err = snd_ctl_new(&ctl, SND_CTL_TYPE_HW, name);
+	if (err < 0) {
+		close(fd);
+		free(hw);
+		return err;
+	}
+	ctl->ops = &snd_ctl_hw_ops;
+	ctl->private_data = hw;
+	ctl->poll_fd = fd;
+	*handle = ctl;
+	return 0;
+}
+
+int _snd_ctl_hw_open(snd_ctl_t **handlep, char *name, snd_config_t *root ATTRIBUTE_UNUSED, snd_config_t *conf, int mode)
+{
+	snd_config_iterator_t i, next;
+	long card = -1;
+	const char *str;
+	int err;
+	snd_config_for_each(i, next, conf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (strcmp(id, "comment") == 0)
+			continue;
+		if (strcmp(id, "type") == 0)
+			continue;
+		if (strcmp(id, "card") == 0) {
+			err = snd_config_get_integer(n, &card);
+			if (err < 0) {
+				err = snd_config_get_string(n, &str);
+				if (err < 0)
+					return -EINVAL;
+				card = snd_card_get_index(str);
+				if (card < 0)
+					return card;
+			}
+			continue;
+		}
+		return -EINVAL;
+	}
+	if (card < 0)
+		return -EINVAL;
+	return snd_ctl_hw_open(handlep, name, card, mode);
+}
+SND_DLSYM_BUILD_VERSION(_snd_ctl_hw_open, SND_CONTROL_DLSYM_VERSION);
diff --git a/src/control/control_local.h b/src/control/control_local.h
new file mode 100644
index 0000000..49150d8
--- /dev/null
+++ b/src/control/control_local.h
@@ -0,0 +1,100 @@
+/*
+ *  Control Interface - local header file
+ *  Copyright (c) 2000 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include "local.h"
+
+typedef struct _snd_ctl_ops {
+	int (*close)(snd_ctl_t *handle);
+	int (*nonblock)(snd_ctl_t *handle, int nonblock);
+	int (*async)(snd_ctl_t *handle, int sig, pid_t pid);
+	int (*subscribe_events)(snd_ctl_t *handle, int subscribe);
+	int (*card_info)(snd_ctl_t *handle, snd_ctl_card_info_t *info);
+	int (*element_list)(snd_ctl_t *handle, snd_ctl_elem_list_t *list);
+	int (*element_info)(snd_ctl_t *handle, snd_ctl_elem_info_t *info);
+	int (*element_add)(snd_ctl_t *handle, snd_ctl_elem_info_t *info);
+	int (*element_replace)(snd_ctl_t *handle, snd_ctl_elem_info_t *info);
+	int (*element_remove)(snd_ctl_t *handle, snd_ctl_elem_id_t *id);
+	int (*element_read)(snd_ctl_t *handle, snd_ctl_elem_value_t *control);
+	int (*element_write)(snd_ctl_t *handle, snd_ctl_elem_value_t *control);
+	int (*element_lock)(snd_ctl_t *handle, snd_ctl_elem_id_t *lock);
+	int (*element_unlock)(snd_ctl_t *handle, snd_ctl_elem_id_t *unlock);
+	int (*element_tlv)(snd_ctl_t *handle, int op_flag, unsigned int numid,
+			   unsigned int *tlv, unsigned int tlv_size);
+	int (*hwdep_next_device)(snd_ctl_t *handle, int *device);
+	int (*hwdep_info)(snd_ctl_t *handle, snd_hwdep_info_t * info);
+	int (*pcm_next_device)(snd_ctl_t *handle, int *device);
+	int (*pcm_info)(snd_ctl_t *handle, snd_pcm_info_t * info);
+	int (*pcm_prefer_subdevice)(snd_ctl_t *handle, int subdev);
+	int (*rawmidi_next_device)(snd_ctl_t *handle, int *device);
+	int (*rawmidi_info)(snd_ctl_t *handle, snd_rawmidi_info_t * info);
+	int (*rawmidi_prefer_subdevice)(snd_ctl_t *handle, int subdev);
+	int (*set_power_state)(snd_ctl_t *handle, unsigned int state);
+	int (*get_power_state)(snd_ctl_t *handle, unsigned int *state);
+	int (*read)(snd_ctl_t *handle, snd_ctl_event_t *event);
+	int (*poll_descriptors_count)(snd_ctl_t *handle);
+	int (*poll_descriptors)(snd_ctl_t *handle, struct pollfd *pfds, unsigned int space);
+	int (*poll_revents)(snd_ctl_t *handle, struct pollfd *pfds, unsigned int nfds, unsigned short *revents);
+} snd_ctl_ops_t;
+
+
+struct _snd_ctl {
+	void *open_func;
+	char *name;
+	snd_ctl_type_t type;
+	const snd_ctl_ops_t *ops;
+	void *private_data;
+	int nonblock;
+	int poll_fd;
+	struct list_head async_handlers;
+};
+
+struct _snd_hctl_elem {
+	snd_ctl_elem_id_t id; 		/* must be always on top */
+	struct list_head list;		/* links for list of all helems */
+	int compare_weight;		/* compare weight (reversed) */
+	/* event callback */
+	snd_hctl_elem_callback_t callback;
+	void *callback_private;
+	/* links */
+	snd_hctl_t *hctl;		/* associated handle */
+};
+
+struct _snd_hctl {
+	snd_ctl_t *ctl;
+	struct list_head elems;		/* list of all controls */
+	unsigned int alloc;	
+	unsigned int count;
+	snd_hctl_elem_t **pelems;
+	snd_hctl_compare_t compare;
+	snd_hctl_callback_t callback;
+	void *callback_private;
+};
+
+
+/* make local functions really local */
+#define snd_ctl_new	snd1_ctl_new
+
+int snd_ctl_new(snd_ctl_t **ctlp, snd_ctl_type_t type, const char *name);
+int _snd_ctl_poll_descriptor(snd_ctl_t *ctl);
+#define _snd_ctl_async_descriptor _snd_ctl_poll_descriptor
+int snd_ctl_hw_open(snd_ctl_t **handle, const char *name, int card, int mode);
+int snd_ctl_shm_open(snd_ctl_t **handlep, const char *name, const char *sockname, const char *sname, int mode);
+int snd_ctl_async(snd_ctl_t *ctl, int sig, pid_t pid);
diff --git a/src/control/control_shm.c b/src/control/control_shm.c
new file mode 100644
index 0000000..abab398
--- /dev/null
+++ b/src/control/control_shm.c
@@ -0,0 +1,679 @@
+/*
+ *  Control - SHM Client
+ *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/shm.h>
+#include <sys/socket.h>
+#include <sys/poll.h>
+#include <sys/un.h>
+#include <sys/uio.h>
+#include <sys/mman.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include "aserver.h"
+
+#ifndef PIC
+/* entry for static linking */
+const char *_snd_module_control_shm = "";
+#endif
+
+#ifndef DOC_HIDDEN
+typedef struct {
+	int socket;
+	volatile snd_ctl_shm_ctrl_t *ctrl;
+} snd_ctl_shm_t;
+#endif
+
+static int snd_ctl_shm_action(snd_ctl_t *ctl)
+{
+	snd_ctl_shm_t *shm = ctl->private_data;
+	int err;
+	char buf[1];
+	volatile snd_ctl_shm_ctrl_t *ctrl = shm->ctrl;
+	err = write(shm->socket, buf, 1);
+	if (err != 1)
+		return -EBADFD;
+	err = read(shm->socket, buf, 1);
+	if (err != 1)
+		return -EBADFD;
+	if (ctrl->cmd) {
+		SNDERR("Server has not done the cmd");
+		return -EBADFD;
+	}
+	return ctrl->result;
+}
+
+static int snd_ctl_shm_action_fd(snd_ctl_t *ctl, int *fd)
+{
+	snd_ctl_shm_t *shm = ctl->private_data;
+	int err;
+	char buf[1];
+	volatile snd_ctl_shm_ctrl_t *ctrl = shm->ctrl;
+	err = write(shm->socket, buf, 1);
+	if (err != 1)
+		return -EBADFD;
+	err = snd_receive_fd(shm->socket, buf, 1, fd);
+	if (err != 1)
+		return -EBADFD;
+	if (ctrl->cmd) {
+		SNDERR("Server has not done the cmd");
+		return -EBADFD;
+	}
+	return ctrl->result;
+}
+
+static int snd_ctl_shm_close(snd_ctl_t *ctl)
+{
+	snd_ctl_shm_t *shm = ctl->private_data;
+	volatile snd_ctl_shm_ctrl_t *ctrl = shm->ctrl;
+	int result;
+	ctrl->cmd = SND_CTL_IOCTL_CLOSE;
+	result = snd_ctl_shm_action(ctl);
+	shmdt((void *)ctrl);
+	close(shm->socket);
+	free(shm);
+	return result;
+}
+
+static int snd_ctl_shm_nonblock(snd_ctl_t *handle ATTRIBUTE_UNUSED, int nonblock ATTRIBUTE_UNUSED)
+{
+	return 0;
+}
+
+static int snd_ctl_shm_async(snd_ctl_t *ctl, int sig, pid_t pid)
+{
+	snd_ctl_shm_t *shm = ctl->private_data;
+	volatile snd_ctl_shm_ctrl_t *ctrl = shm->ctrl;
+	ctrl->cmd = SND_CTL_IOCTL_ASYNC;
+	ctrl->u.async.sig = sig;
+	if (pid == 0)
+		pid = getpid();
+	ctrl->u.async.pid = pid;
+	return snd_ctl_shm_action(ctl);
+}
+
+static int snd_ctl_shm_poll_descriptor(snd_ctl_t *ctl)
+{
+	snd_ctl_shm_t *shm = ctl->private_data;
+	volatile snd_ctl_shm_ctrl_t *ctrl = shm->ctrl;
+	int fd, err;
+	ctrl->cmd = SND_CTL_IOCTL_POLL_DESCRIPTOR;
+	err = snd_ctl_shm_action_fd(ctl, &fd);
+	if (err < 0)
+		return err;
+	return fd;
+}
+
+static int snd_ctl_shm_subscribe_events(snd_ctl_t *ctl, int subscribe)
+{
+	snd_ctl_shm_t *shm = ctl->private_data;
+	volatile snd_ctl_shm_ctrl_t *ctrl = shm->ctrl;
+	ctrl->cmd = SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS;
+	ctrl->u.subscribe_events = subscribe;
+	return snd_ctl_shm_action(ctl);
+}
+
+static int snd_ctl_shm_card_info(snd_ctl_t *ctl, snd_ctl_card_info_t *info)
+{
+	snd_ctl_shm_t *shm = ctl->private_data;
+	volatile snd_ctl_shm_ctrl_t *ctrl = shm->ctrl;
+	int err;
+//	ctrl->u.card_info = *info;
+	ctrl->cmd = SNDRV_CTL_IOCTL_CARD_INFO;
+	err = snd_ctl_shm_action(ctl);
+	if (err < 0)
+		return err;
+	*info = ctrl->u.card_info;
+	return err;
+}
+
+static int snd_ctl_shm_elem_list(snd_ctl_t *ctl, snd_ctl_elem_list_t *list)
+{
+	snd_ctl_shm_t *shm = ctl->private_data;
+	volatile snd_ctl_shm_ctrl_t *ctrl = shm->ctrl;
+	size_t maxsize = CTL_SHM_DATA_MAXLEN;
+	size_t bytes = list->space * sizeof(*list->pids);
+	int err;
+	snd_ctl_elem_id_t *pids = list->pids;
+	if (bytes > maxsize)
+		return -EINVAL;
+	ctrl->u.element_list = *list;
+	ctrl->cmd = SNDRV_CTL_IOCTL_ELEM_LIST;
+	err = snd_ctl_shm_action(ctl);
+	if (err < 0)
+		return err;
+	*list = ctrl->u.element_list;
+	list->pids = pids;
+	bytes = list->used * sizeof(*list->pids);
+	memcpy(pids, (void *)ctrl->data, bytes);
+	return err;
+}
+
+static int snd_ctl_shm_elem_info(snd_ctl_t *ctl, snd_ctl_elem_info_t *info)
+{
+	snd_ctl_shm_t *shm = ctl->private_data;
+	volatile snd_ctl_shm_ctrl_t *ctrl = shm->ctrl;
+	int err;
+	ctrl->u.element_info = *info;
+	ctrl->cmd = SNDRV_CTL_IOCTL_ELEM_INFO;
+	err = snd_ctl_shm_action(ctl);
+	if (err < 0)
+		return err;
+	*info = ctrl->u.element_info;
+	return err;
+}
+
+static int snd_ctl_shm_elem_read(snd_ctl_t *ctl, snd_ctl_elem_value_t *control)
+{
+	snd_ctl_shm_t *shm = ctl->private_data;
+	volatile snd_ctl_shm_ctrl_t *ctrl = shm->ctrl;
+	int err;
+	ctrl->u.element_read = *control;
+	ctrl->cmd = SNDRV_CTL_IOCTL_ELEM_READ;
+	err = snd_ctl_shm_action(ctl);
+	if (err < 0)
+		return err;
+	*control = ctrl->u.element_read;
+	return err;
+}
+
+static int snd_ctl_shm_elem_write(snd_ctl_t *ctl, snd_ctl_elem_value_t *control)
+{
+	snd_ctl_shm_t *shm = ctl->private_data;
+	volatile snd_ctl_shm_ctrl_t *ctrl = shm->ctrl;
+	int err;
+	ctrl->u.element_write = *control;
+	ctrl->cmd = SNDRV_CTL_IOCTL_ELEM_WRITE;
+	err = snd_ctl_shm_action(ctl);
+	if (err < 0)
+		return err;
+	*control = ctrl->u.element_write;
+	return err;
+}
+
+static int snd_ctl_shm_elem_lock(snd_ctl_t *ctl, snd_ctl_elem_id_t *id)
+{
+	snd_ctl_shm_t *shm = ctl->private_data;
+	volatile snd_ctl_shm_ctrl_t *ctrl = shm->ctrl;
+	int err;
+	ctrl->u.element_lock = *id;
+	ctrl->cmd = SNDRV_CTL_IOCTL_ELEM_LOCK;
+	err = snd_ctl_shm_action(ctl);
+	if (err < 0)
+		return err;
+	*id = ctrl->u.element_lock;
+	return err;
+}
+
+static int snd_ctl_shm_elem_unlock(snd_ctl_t *ctl, snd_ctl_elem_id_t *id)
+{
+	snd_ctl_shm_t *shm = ctl->private_data;
+	volatile snd_ctl_shm_ctrl_t *ctrl = shm->ctrl;
+	int err;
+	ctrl->u.element_unlock = *id;
+	ctrl->cmd = SNDRV_CTL_IOCTL_ELEM_UNLOCK;
+	err = snd_ctl_shm_action(ctl);
+	if (err < 0)
+		return err;
+	*id = ctrl->u.element_unlock;
+	return err;
+}
+
+static int snd_ctl_shm_hwdep_next_device(snd_ctl_t *ctl, int * device)
+{
+	snd_ctl_shm_t *shm = ctl->private_data;
+	volatile snd_ctl_shm_ctrl_t *ctrl = shm->ctrl;
+	int err;
+	ctrl->u.device = *device;
+	ctrl->cmd = SNDRV_CTL_IOCTL_HWDEP_NEXT_DEVICE;
+	err = snd_ctl_shm_action(ctl);
+	if (err < 0)
+		return err;
+	*device = ctrl->u.device;
+	return err;
+}
+
+static int snd_ctl_shm_hwdep_info(snd_ctl_t *ctl, snd_hwdep_info_t * info)
+{
+	snd_ctl_shm_t *shm = ctl->private_data;
+	volatile snd_ctl_shm_ctrl_t *ctrl = shm->ctrl;
+	int err;
+	ctrl->u.hwdep_info = *info;
+	ctrl->cmd = SNDRV_CTL_IOCTL_HWDEP_INFO;
+	err = snd_ctl_shm_action(ctl);
+	if (err < 0)
+		return err;
+	*info = ctrl->u.hwdep_info;
+	return err;
+}
+
+static int snd_ctl_shm_pcm_next_device(snd_ctl_t *ctl, int * device)
+{
+	snd_ctl_shm_t *shm = ctl->private_data;
+	volatile snd_ctl_shm_ctrl_t *ctrl = shm->ctrl;
+	int err;
+	ctrl->u.device = *device;
+	ctrl->cmd = SNDRV_CTL_IOCTL_PCM_NEXT_DEVICE;
+	err = snd_ctl_shm_action(ctl);
+	if (err < 0)
+		return err;
+	*device = ctrl->u.device;
+	return err;
+}
+
+static int snd_ctl_shm_pcm_info(snd_ctl_t *ctl, snd_pcm_info_t * info)
+{
+	snd_ctl_shm_t *shm = ctl->private_data;
+	volatile snd_ctl_shm_ctrl_t *ctrl = shm->ctrl;
+	int err;
+	ctrl->u.pcm_info = *info;
+	ctrl->cmd = SNDRV_CTL_IOCTL_PCM_INFO;
+	err = snd_ctl_shm_action(ctl);
+	if (err < 0)
+		return err;
+	*info = ctrl->u.pcm_info;
+	return err;
+}
+
+static int snd_ctl_shm_pcm_prefer_subdevice(snd_ctl_t *ctl, int subdev)
+{
+	snd_ctl_shm_t *shm = ctl->private_data;
+	volatile snd_ctl_shm_ctrl_t *ctrl = shm->ctrl;
+	int err;
+	ctrl->u.pcm_prefer_subdevice = subdev;
+	ctrl->cmd = SNDRV_CTL_IOCTL_PCM_PREFER_SUBDEVICE;
+	err = snd_ctl_shm_action(ctl);
+	if (err < 0)
+		return err;
+	return err;
+}
+
+static int snd_ctl_shm_rawmidi_next_device(snd_ctl_t *ctl, int * device)
+{
+	snd_ctl_shm_t *shm = ctl->private_data;
+	volatile snd_ctl_shm_ctrl_t *ctrl = shm->ctrl;
+	int err;
+	ctrl->u.device = *device;
+	ctrl->cmd = SNDRV_CTL_IOCTL_RAWMIDI_NEXT_DEVICE;
+	err = snd_ctl_shm_action(ctl);
+	if (err < 0)
+		return err;
+	*device = ctrl->u.device;
+	return err;
+}
+
+static int snd_ctl_shm_rawmidi_info(snd_ctl_t *ctl, snd_rawmidi_info_t * info)
+{
+	snd_ctl_shm_t *shm = ctl->private_data;
+	volatile snd_ctl_shm_ctrl_t *ctrl = shm->ctrl;
+	int err;
+	ctrl->u.rawmidi_info = *info;
+	ctrl->cmd = SNDRV_CTL_IOCTL_RAWMIDI_INFO;
+	err = snd_ctl_shm_action(ctl);
+	if (err < 0)
+		return err;
+	*info = ctrl->u.rawmidi_info;
+	return err;
+}
+
+static int snd_ctl_shm_rawmidi_prefer_subdevice(snd_ctl_t *ctl, int subdev)
+{
+	snd_ctl_shm_t *shm = ctl->private_data;
+	volatile snd_ctl_shm_ctrl_t *ctrl = shm->ctrl;
+	int err;
+	ctrl->u.rawmidi_prefer_subdevice = subdev;
+	ctrl->cmd = SNDRV_CTL_IOCTL_RAWMIDI_PREFER_SUBDEVICE;
+	err = snd_ctl_shm_action(ctl);
+	if (err < 0)
+		return err;
+	return err;
+}
+
+static int snd_ctl_shm_set_power_state(snd_ctl_t *ctl, unsigned int state)
+{
+	snd_ctl_shm_t *shm = ctl->private_data;
+	volatile snd_ctl_shm_ctrl_t *ctrl = shm->ctrl;
+	int err;
+	ctrl->u.power_state = state;
+	ctrl->cmd = SNDRV_CTL_IOCTL_POWER;
+	err = snd_ctl_shm_action(ctl);
+	if (err < 0)
+		return err;
+	return err;
+}
+
+static int snd_ctl_shm_get_power_state(snd_ctl_t *ctl, unsigned int *state)
+{
+	snd_ctl_shm_t *shm = ctl->private_data;
+	volatile snd_ctl_shm_ctrl_t *ctrl = shm->ctrl;
+	int err;
+	ctrl->cmd = SNDRV_CTL_IOCTL_POWER_STATE;
+	err = snd_ctl_shm_action(ctl);
+	if (err < 0)
+		return err;
+	*state = ctrl->u.power_state;
+	return err;
+}
+
+static int snd_ctl_shm_read(snd_ctl_t *ctl, snd_ctl_event_t *event)
+{
+	snd_ctl_shm_t *shm;
+	volatile snd_ctl_shm_ctrl_t *ctrl;
+	int err;
+	err = snd_ctl_wait(ctl, -1);
+	if (err < 0)
+		return 0;
+	shm = ctl->private_data;
+	ctrl = shm->ctrl;
+	ctrl->u.read = *event;
+	ctrl->cmd = SND_CTL_IOCTL_READ;
+	err = snd_ctl_shm_action(ctl);
+	if (err < 0)
+		return err;
+	*event = ctrl->u.read;
+	return err;
+}
+
+static const snd_ctl_ops_t snd_ctl_shm_ops = {
+	.close = snd_ctl_shm_close,
+	.nonblock = snd_ctl_shm_nonblock,
+	.async = snd_ctl_shm_async,
+	.subscribe_events = snd_ctl_shm_subscribe_events,
+	.card_info = snd_ctl_shm_card_info,
+	.element_list = snd_ctl_shm_elem_list,
+	.element_info = snd_ctl_shm_elem_info,
+	.element_read = snd_ctl_shm_elem_read,
+	.element_write = snd_ctl_shm_elem_write,
+	.element_lock = snd_ctl_shm_elem_lock,
+	.element_unlock = snd_ctl_shm_elem_unlock,
+	.hwdep_next_device = snd_ctl_shm_hwdep_next_device,
+	.hwdep_info = snd_ctl_shm_hwdep_info,
+	.pcm_next_device = snd_ctl_shm_pcm_next_device,
+	.pcm_info = snd_ctl_shm_pcm_info,
+	.pcm_prefer_subdevice = snd_ctl_shm_pcm_prefer_subdevice,
+	.rawmidi_next_device = snd_ctl_shm_rawmidi_next_device,
+	.rawmidi_info = snd_ctl_shm_rawmidi_info,
+	.rawmidi_prefer_subdevice = snd_ctl_shm_rawmidi_prefer_subdevice,
+	.set_power_state = snd_ctl_shm_set_power_state,
+	.get_power_state = snd_ctl_shm_get_power_state,
+	.read = snd_ctl_shm_read,
+};
+
+static int make_local_socket(const char *filename)
+{
+	size_t l = strlen(filename);
+	size_t size = offsetof(struct sockaddr_un, sun_path) + l;
+	struct sockaddr_un *addr = alloca(size);
+	int sock;
+
+	sock = socket(PF_LOCAL, SOCK_STREAM, 0);
+	if (sock < 0)
+		return -errno;
+	
+	addr->sun_family = AF_LOCAL;
+	memcpy(addr->sun_path, filename, l);
+
+	if (connect(sock, (struct sockaddr *) addr, size) < 0)
+		return -errno;
+	return sock;
+}
+
+#if 0
+static int make_inet_socket(const char *host, int port)
+{
+	struct sockaddr_in addr;
+	int sock;
+	struct hostent *h = gethostbyname(host);
+	if (!h)
+		return -ENOENT;
+
+	sock = socket(PF_INET, SOCK_STREAM, 0);
+	if (sock < 0)
+		return -errno;
+	
+	addr.sin_family = AF_INET;
+	addr.sin_port = htons(port);
+	memcpy(&addr.sin_addr, h->h_addr_list[0], sizeof(struct in_addr));
+
+	if (connect(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0)
+		return -errno;
+	return sock;
+}
+#endif
+
+int snd_ctl_shm_open(snd_ctl_t **handlep, const char *name, const char *sockname, const char *sname, int mode)
+{
+	snd_ctl_t *ctl;
+	snd_ctl_shm_t *shm = NULL;
+	snd_client_open_request_t *req;
+	snd_client_open_answer_t ans;
+	size_t snamelen, reqlen;
+	int err;
+	int result;
+	int sock = -1;
+	snd_ctl_shm_ctrl_t *ctrl = NULL;
+	snamelen = strlen(sname);
+	if (snamelen > 255)
+		return -EINVAL;
+
+	result = make_local_socket(sockname);
+	if (result < 0) {
+		SNDERR("server for socket %s is not running", sockname);
+		goto _err;
+	}
+	sock = result;
+
+	reqlen = sizeof(*req) + snamelen;
+	req = alloca(reqlen);
+	memcpy(req->name, sname, snamelen);
+	req->dev_type = SND_DEV_TYPE_CONTROL;
+	req->transport_type = SND_TRANSPORT_TYPE_SHM;
+	req->stream = 0;
+	req->mode = mode;
+	req->namelen = snamelen;
+	err = write(sock, req, reqlen);
+	if (err < 0) {
+		SNDERR("write error");
+		result = -errno;
+		goto _err;
+	}
+	if ((size_t) err != reqlen) {
+		SNDERR("write size error");
+		result = -EINVAL;
+		goto _err;
+	}
+	err = read(sock, &ans, sizeof(ans));
+	if (err < 0) {
+		SNDERR("read error");
+		result = -errno;
+		goto _err;
+	}
+	if (err != sizeof(ans)) {
+		SNDERR("read size error");
+		result = -EINVAL;
+		goto _err;
+	}
+	result = ans.result;
+	if (result < 0)
+		goto _err;
+
+	ctrl = shmat(ans.cookie, 0, 0);
+	if (!ctrl) {
+		result = -errno;
+		goto _err;
+	}
+		
+	shm = calloc(1, sizeof(snd_ctl_shm_t));
+	if (!shm) {
+		result = -ENOMEM;
+		goto _err;
+	}
+
+	shm->socket = sock;
+	shm->ctrl = ctrl;
+
+	err = snd_ctl_new(&ctl, SND_CTL_TYPE_SHM, name);
+	if (err < 0) {
+		result = err;
+		goto _err;
+	}
+	ctl->ops = &snd_ctl_shm_ops;
+	ctl->private_data = shm;
+	err = snd_ctl_shm_poll_descriptor(ctl);
+	if (err < 0) {
+		snd_ctl_close(ctl);
+		return err;
+	}
+	ctl->poll_fd = err;
+	*handlep = ctl;
+	return 0;
+
+ _err:
+	close(sock);
+	if (ctrl)
+		shmdt(ctrl);
+	free(shm);
+	return result;
+}
+
+int _snd_ctl_shm_open(snd_ctl_t **handlep, char *name, snd_config_t *root, snd_config_t *conf, int mode)
+{
+	snd_config_iterator_t i, next;
+	const char *server = NULL;
+	const char *ctl_name = NULL;
+	snd_config_t *sconfig;
+	const char *host = NULL;
+	const char *sockname = NULL;
+	long port = -1;
+	int err;
+	int local;
+	struct hostent *h;
+	snd_config_for_each(i, next, conf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (strcmp(id, "comment") == 0)
+			continue;
+		if (strcmp(id, "type") == 0)
+			continue;
+		if (strcmp(id, "server") == 0) {
+			err = snd_config_get_string(n, &server);
+			if (err < 0) {
+				SNDERR("Invalid type for %s", id);
+				return -EINVAL;
+			}
+			continue;
+		}
+		if (strcmp(id, "ctl") == 0) {
+			err = snd_config_get_string(n, &ctl_name);
+			if (err < 0) {
+				SNDERR("Invalid type for %s", id);
+				return -EINVAL;
+			}
+			continue;
+		}
+		SNDERR("Unknown field %s", id);
+		return -EINVAL;
+	}
+	if (!ctl_name) {
+		SNDERR("ctl is not defined");
+		return -EINVAL;
+	}
+	if (!server) {
+		SNDERR("server is not defined");
+		return -EINVAL;
+	}
+	err = snd_config_search_definition(root, "server", server, &sconfig);
+	if (err < 0) {
+		SNDERR("Unknown server %s", server);
+		return -EINVAL;
+	}
+	if (snd_config_get_type(sconfig) != SND_CONFIG_TYPE_COMPOUND) {
+		SNDERR("Invalid type for server %s definition", server);
+		err = -EINVAL;
+		goto _err;
+	}
+	snd_config_for_each(i, next, sconfig) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (strcmp(id, "comment") == 0)
+			continue;
+		if (strcmp(id, "host") == 0) {
+			err = snd_config_get_string(n, &host);
+			if (err < 0) {
+				SNDERR("Invalid type for %s", id);
+				goto _err;
+			}
+			continue;
+		}
+		if (strcmp(id, "socket") == 0) {
+			err = snd_config_get_string(n, &sockname);
+			if (err < 0) {
+				SNDERR("Invalid type for %s", id);
+				goto _err;
+			}
+			continue;
+		}
+		if (strcmp(id, "port") == 0) {
+			err = snd_config_get_integer(n, &port);
+			if (err < 0) {
+				SNDERR("Invalid type for %s", id);
+				goto _err;
+			}
+			continue;
+		}
+		SNDERR("Unknown field %s", id);
+		err = -EINVAL;
+		goto _err;
+	}
+
+	if (!host) {
+		SNDERR("host is not defined");
+		goto _err;
+	}
+	if (!sockname) {
+		SNDERR("socket is not defined");
+		goto _err;
+	}
+	h = gethostbyname(host);
+	if (!h) {
+		SNDERR("Cannot resolve %s", host);
+		goto _err;
+	}
+	local = snd_is_local(h);
+	if (!local) {
+		SNDERR("%s is not the local host", host);
+		goto _err;
+	}
+	err = snd_ctl_shm_open(handlep, name, sockname, ctl_name, mode);
+       _err:
+	snd_config_delete(sconfig);
+	return err;
+}
+SND_DLSYM_BUILD_VERSION(_snd_ctl_shm_open, SND_CONTROL_DLSYM_VERSION);
diff --git a/src/control/control_symbols.c b/src/control/control_symbols.c
new file mode 100644
index 0000000..0cccade
--- /dev/null
+++ b/src/control/control_symbols.c
@@ -0,0 +1,37 @@
+/*
+ *  Control Symbols
+ *  Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#ifndef PIC
+
+extern const char *_snd_module_control_hw;
+extern const char *_snd_module_control_shm;
+extern const char *_snd_module_control_ext;
+
+static const char **snd_control_open_objects[] = {
+	&_snd_module_control_hw,
+#include "ctl_symbols_list.c"
+};
+	
+void *snd_control_open_symbols(void)
+{
+	return snd_control_open_objects;
+}
+
+#endif /* !PIC */
diff --git a/src/control/ctlparse.c b/src/control/ctlparse.c
new file mode 100644
index 0000000..a16f96a
--- /dev/null
+++ b/src/control/ctlparse.c
@@ -0,0 +1,360 @@
+/**
+ * \file control/control.c
+ * \brief CTL interface - parse ASCII identifiers and values
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \date 2010
+ */
+/*
+ *  Control Interface - ASCII parser
+ *  Copyright (c) 2010 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <math.h>
+#include "control_local.h"
+
+/* Function to convert from percentage to volume. val = percentage */
+
+#ifdef HAVE_SOFT_FLOAT
+static inline long int convert_prange1(long val, long min, long max)
+{
+	long temp = val * (max - min);
+	return temp / 100 + min + ((temp % 100) == 0 ? 0 : 1);
+}
+#else
+
+#define convert_prange1(val, min, max) \
+	ceil((val) * ((max) - (min)) * 0.01 + (min))
+#endif
+
+#define check_range(val, min, max) \
+	((val < min) ? (min) : ((val > max) ? (max) : (val)))
+
+static long get_integer(const char **ptr, long min, long max)
+{
+	long val = min;
+	char *p = (char *)*ptr, *s;
+
+	if (*p == ':')
+		p++;
+	if (*p == '\0' || (!isdigit(*p) && *p != '-'))
+		goto out;
+
+	s = p;
+	val = strtol(s, &p, 10);
+	if (*p == '.') {
+		p++;
+		strtol(p, &p, 10);
+	}
+	if (*p == '%') {
+		val = (long)convert_prange1(strtod(s, NULL), min, max);
+		p++;
+	}
+	val = check_range(val, min, max);
+	if (*p == ',')
+		p++;
+ out:
+	*ptr = p;
+	return val;
+}
+
+static long long get_integer64(const char **ptr, long long min, long long max)
+{
+	long long val = min;
+	char *p = (char *)*ptr, *s;
+
+	if (*p == ':')
+		p++;
+	if (*p == '\0' || (!isdigit(*p) && *p != '-'))
+		goto out;
+
+	s = p;
+	val = strtol(s, &p, 10);
+	if (*p == '.') {
+		p++;
+		strtol(p, &p, 10);
+	}
+	if (*p == '%') {
+		val = (long long)convert_prange1(strtod(s, NULL), min, max);
+		p++;
+	}
+	val = check_range(val, min, max);
+	if (*p == ',')
+		p++;
+ out:
+	*ptr = p;
+	return val;
+}
+
+/**
+ * \brief return ASCII CTL element identifier name
+ * \param id CTL identifier
+ * \return ascii identifier of CTL element
+ *
+ * The string is allocated using strdup().
+ */
+char *snd_ctl_ascii_elem_id_get(snd_ctl_elem_id_t *id)
+{
+	unsigned int index, device, subdevice;
+	char buf[256], buf1[32];
+
+	snprintf(buf, sizeof(buf), "numid=%u,iface=%s,name='%s'",
+				snd_ctl_elem_id_get_numid(id),
+				snd_ctl_elem_iface_name(
+					snd_ctl_elem_id_get_interface(id)),
+				snd_ctl_elem_id_get_name(id));
+	buf[sizeof(buf)-1] = '\0';
+	index = snd_ctl_elem_id_get_index(id);
+	device = snd_ctl_elem_id_get_device(id);
+	subdevice = snd_ctl_elem_id_get_subdevice(id);
+	if (index) {
+		snprintf(buf1, sizeof(buf1), ",index=%i", index);
+		if (strlen(buf) + strlen(buf1) < sizeof(buf))
+			strcat(buf, buf1);
+	}
+	if (device) {
+		snprintf(buf1, sizeof(buf1), ",device=%i", device);
+		if (strlen(buf) + strlen(buf1) < sizeof(buf))
+			strcat(buf, buf1);
+	}
+	if (subdevice) {
+		snprintf(buf1, sizeof(buf1), ",subdevice=%i", subdevice);
+		if (strlen(buf) + strlen(buf1) < sizeof(buf))
+			strcat(buf, buf1);
+	}
+	return strdup(buf);
+}
+
+/**
+ * \brief parse ASCII string as CTL element identifier
+ * \param dst destination CTL identifier
+ * \param str source ASCII string
+ * \return zero on success, otherwise a negative error code
+ */
+int snd_ctl_ascii_elem_id_parse(snd_ctl_elem_id_t *dst, const char *str)
+{
+	int c, size, numid;
+	char *ptr;
+
+	while (*str == ' ' || *str == '\t')
+		str++;
+	if (!(*str))
+		return -EINVAL;
+	snd_ctl_elem_id_set_interface(dst, SND_CTL_ELEM_IFACE_MIXER);	/* default */
+	while (*str) {
+		if (!strncasecmp(str, "numid=", 6)) {
+			str += 6;
+			numid = atoi(str);
+			if (numid <= 0) {
+				fprintf(stderr, "amixer: Invalid numid %d\n", numid);
+				return -EINVAL;
+			}
+			snd_ctl_elem_id_set_numid(dst, atoi(str));
+			while (isdigit(*str))
+				str++;
+		} else if (!strncasecmp(str, "iface=", 6)) {
+			str += 6;
+			if (!strncasecmp(str, "card", 4)) {
+				snd_ctl_elem_id_set_interface(dst, SND_CTL_ELEM_IFACE_CARD);
+				str += 4;
+			} else if (!strncasecmp(str, "mixer", 5)) {
+				snd_ctl_elem_id_set_interface(dst, SND_CTL_ELEM_IFACE_MIXER);
+				str += 5;
+			} else if (!strncasecmp(str, "pcm", 3)) {
+				snd_ctl_elem_id_set_interface(dst, SND_CTL_ELEM_IFACE_PCM);
+				str += 3;
+			} else if (!strncasecmp(str, "rawmidi", 7)) {
+				snd_ctl_elem_id_set_interface(dst, SND_CTL_ELEM_IFACE_RAWMIDI);
+				str += 7;
+			} else if (!strncasecmp(str, "timer", 5)) {
+				snd_ctl_elem_id_set_interface(dst, SND_CTL_ELEM_IFACE_TIMER);
+				str += 5;
+			} else if (!strncasecmp(str, "sequencer", 9)) {
+				snd_ctl_elem_id_set_interface(dst, SND_CTL_ELEM_IFACE_SEQUENCER);
+				str += 9;
+			} else {
+				return -EINVAL;
+			}
+		} else if (!strncasecmp(str, "name=", 5)) {
+			char buf[64];
+			str += 5;
+			ptr = buf;
+			size = 0;
+			if (*str == '\'' || *str == '\"') {
+				c = *str++;
+				while (*str && *str != c) {
+					if (size < (int)sizeof(buf)) {
+						*ptr++ = *str;
+						size++;
+					}
+					str++;
+				}
+				if (*str == c)
+					str++;
+			} else {
+				while (*str && *str != ',') {
+					if (size < (int)sizeof(buf)) {
+						*ptr++ = *str;
+						size++;
+					}
+					str++;
+				}
+			}
+			*ptr = '\0';
+			snd_ctl_elem_id_set_name(dst, buf);
+		} else if (!strncasecmp(str, "index=", 6)) {
+			str += 6;
+			snd_ctl_elem_id_set_index(dst, atoi(str));
+			while (isdigit(*str))
+				str++;
+		} else if (!strncasecmp(str, "device=", 7)) {
+			str += 7;
+			snd_ctl_elem_id_set_device(dst, atoi(str));
+			while (isdigit(*str))
+				str++;
+		} else if (!strncasecmp(str, "subdevice=", 10)) {
+			str += 10;
+			snd_ctl_elem_id_set_subdevice(dst, atoi(str));
+			while (isdigit(*str))
+				str++;
+		}
+		if (*str == ',') {
+			str++;
+		} else {
+			if (*str)
+				return -EINVAL;
+		}
+	}			
+	return 0;
+}
+
+static int get_ctl_enum_item_index(snd_ctl_t *handle,
+				   snd_ctl_elem_info_t *info,
+				   const char **ptrp)
+{ 
+	char *ptr = (char *)*ptrp;
+	int items, i, len;
+	const char *name;
+  
+	items = snd_ctl_elem_info_get_items(info);
+	if (items <= 0)
+		return -1;
+
+	for (i = 0; i < items; i++) {
+		snd_ctl_elem_info_set_item(info, i);
+		if (snd_ctl_elem_info(handle, info) < 0)
+			return -1;
+		name = snd_ctl_elem_info_get_item_name(info);
+		len = strlen(name);
+		if (! strncmp(name, ptr, len)) {
+			if (! ptr[len] || ptr[len] == ',' || ptr[len] == '\n') {
+				ptr += len;
+				*ptrp = ptr;
+				return i;
+			}
+		}
+	}
+	return -1;
+}
+
+/**
+ * \brief parse ASCII string as CTL element value
+ * \param dst destination CTL element value
+ * \param info CTL element info structure
+ * \param value source ASCII string
+ * \return zero on success, otherwise a negative error code
+ */
+int snd_ctl_ascii_value_parse(snd_ctl_t *handle,
+			      snd_ctl_elem_value_t *dst,
+			      snd_ctl_elem_info_t *info,
+			      const char *value)
+{
+	const char *ptr = value;
+	snd_ctl_elem_id_t *myid;
+	snd_ctl_elem_type_t type;
+	unsigned int idx, count;
+	long tmp;
+	long long tmp64;
+
+	snd_ctl_elem_id_alloca(&myid);
+	snd_ctl_elem_info_get_id(info, myid);
+	type = snd_ctl_elem_info_get_type(info);
+	count = snd_ctl_elem_info_get_count(info);
+	snd_ctl_elem_value_set_id(dst, myid);
+	
+	for (idx = 0; idx < count && idx < 128 && ptr && *ptr; idx++) {
+		switch (type) {
+		case SND_CTL_ELEM_TYPE_BOOLEAN:
+			tmp = 0;
+			if (!strncasecmp(ptr, "on", 2) ||
+			    !strncasecmp(ptr, "up", 2)) {
+				tmp = 1;
+				ptr += 2;
+			} else if (!strncasecmp(ptr, "yes", 3)) {
+				tmp = 1;
+				ptr += 3;
+			} else if (!strncasecmp(ptr, "toggle", 6)) {
+				tmp = snd_ctl_elem_value_get_boolean(dst, idx);
+				tmp = tmp > 0 ? 0 : 1;
+				ptr += 6;
+			} else if (isdigit(*ptr)) {
+				tmp = atoi(ptr) > 0 ? 1 : 0;
+				while (isdigit(*ptr))
+					ptr++;
+			} else {
+				while (*ptr && *ptr != ',')
+					ptr++;
+			}
+			snd_ctl_elem_value_set_boolean(dst, idx, tmp);
+			break;
+		case SND_CTL_ELEM_TYPE_INTEGER:
+			tmp = get_integer(&ptr,
+					  snd_ctl_elem_info_get_min(info),
+					  snd_ctl_elem_info_get_max(info));
+			snd_ctl_elem_value_set_integer(dst, idx, tmp);
+			break;
+		case SND_CTL_ELEM_TYPE_INTEGER64:
+			tmp64 = get_integer64(&ptr,
+					  snd_ctl_elem_info_get_min64(info),
+					  snd_ctl_elem_info_get_max64(info));
+			snd_ctl_elem_value_set_integer64(dst, idx, tmp64);
+			break;
+		case SND_CTL_ELEM_TYPE_ENUMERATED:
+			tmp = get_ctl_enum_item_index(handle, info, &ptr);
+			if (tmp < 0)
+				tmp = get_integer(&ptr, 0,
+					snd_ctl_elem_info_get_items(info) - 1);
+			snd_ctl_elem_value_set_enumerated(dst, idx, tmp);
+			break;
+		case SND_CTL_ELEM_TYPE_BYTES:
+			tmp = get_integer(&ptr, 0, 255);
+			snd_ctl_elem_value_set_byte(dst, idx, tmp);
+			break;
+		default:
+			break;
+		}
+		if (!strchr(value, ','))
+			ptr = value;
+		else if (*ptr == ',')
+			ptr++;
+	}
+	return 0;
+}
diff --git a/src/control/hcontrol.c b/src/control/hcontrol.c
new file mode 100644
index 0000000..8ffc434
--- /dev/null
+++ b/src/control/hcontrol.c
@@ -0,0 +1,1015 @@
+/**
+ * \file control/hcontrol.c
+ * \brief HCTL Interface - High Level CTL
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \date 2000
+ *
+ * HCTL interface is designed to access preloaded and sorted primitive controls.
+ * Callbacks may be used for event handling.
+ * See \ref hcontrol page for more details.
+ */
+/*
+ *  Control Interface - high level API
+ *  Copyright (c) 2000 by Jaroslav Kysela <perex@perex.cz>
+ *  Copyright (c) 2001 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+/*! \page hcontrol High level control interface
+
+<P> High level control interface is designed to access preloaded and sorted primitive controls.
+
+\section hcontrol_general_overview General overview
+
+<P> High level control interface caches the accesses to primitive controls
+to reduce overhead accessing the real controls in kernel drivers.
+
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include "control_local.h"
+#ifdef HAVE_LIBPTHREAD
+#include <pthread.h>
+#endif
+
+#ifndef DOC_HIDDEN
+#define NOT_FOUND 1000000000
+#endif
+
+static int snd_hctl_compare_default(const snd_hctl_elem_t *c1,
+				    const snd_hctl_elem_t *c2);
+
+/**
+ * \brief Opens an HCTL
+ * \param hctlp Returned HCTL handle
+ * \param name ASCII identifier of the underlying CTL handle
+ * \param mode Open mode (see #SND_CTL_NONBLOCK, #SND_CTL_ASYNC)
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_hctl_open(snd_hctl_t **hctlp, const char *name, int mode)
+{
+	snd_ctl_t *ctl;
+	int err;
+	
+	if ((err = snd_ctl_open(&ctl, name, mode)) < 0)
+		return err;
+	err = snd_hctl_open_ctl(hctlp, ctl);
+	if (err < 0)
+		snd_ctl_close(ctl);
+	return err;
+}
+
+/**
+ * \brief Opens an HCTL
+ * \param hctlp Returned HCTL handle
+ * \param ctl underlying CTL handle
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_hctl_open_ctl(snd_hctl_t **hctlp, snd_ctl_t *ctl)
+{
+	snd_hctl_t *hctl;
+
+	assert(hctlp);
+	*hctlp = NULL;
+	if ((hctl = (snd_hctl_t *)calloc(1, sizeof(snd_hctl_t))) == NULL)
+		return -ENOMEM;
+	INIT_LIST_HEAD(&hctl->elems);
+	hctl->ctl = ctl;
+	*hctlp = hctl;
+	return 0;
+}
+
+/**
+ * \brief close HCTL handle
+ * \param hctl HCTL handle
+ * \return 0 on success otherwise a negative error code
+ *
+ * Closes the specified HCTL handle and frees all associated
+ * resources.
+ */
+int snd_hctl_close(snd_hctl_t *hctl)
+{
+	int err;
+
+	assert(hctl);
+	err = snd_ctl_close(hctl->ctl);
+	snd_hctl_free(hctl);
+	free(hctl);
+	return err;
+}
+
+/**
+ * \brief get identifier of HCTL handle
+ * \param hctl HCTL handle
+ * \return ascii identifier of HCTL handle
+ *
+ * Returns the ASCII identifier of given HCTL handle. It's the same
+ * identifier specified in snd_hctl_open().
+ */
+const char *snd_hctl_name(snd_hctl_t *hctl)
+{
+	assert(hctl);
+	return snd_ctl_name(hctl->ctl);
+}
+
+/**
+ * \brief set nonblock mode
+ * \param hctl HCTL handle
+ * \param nonblock 0 = block, 1 = nonblock mode
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_hctl_nonblock(snd_hctl_t *hctl, int nonblock)
+{
+	assert(hctl);
+	return snd_ctl_nonblock(hctl->ctl, nonblock);
+}
+
+/**
+ * \brief set async mode
+ * \param hctl HCTL handle
+ * \param sig Signal to raise: < 0 disable, 0 default (SIGIO)
+ * \param pid Process ID to signal: 0 current
+ * \return 0 on success otherwise a negative error code
+ *
+ * A signal is raised when a change happens.
+ */
+int snd_hctl_async(snd_hctl_t *hctl, int sig, pid_t pid)
+{
+	assert(hctl);
+	return snd_ctl_async(hctl->ctl, sig, pid);
+}
+
+/**
+ * \brief get count of poll descriptors for HCTL handle
+ * \param hctl HCTL handle
+ * \return count of poll descriptors
+ */
+int snd_hctl_poll_descriptors_count(snd_hctl_t *hctl)
+{
+	assert(hctl);
+	return snd_ctl_poll_descriptors_count(hctl->ctl);
+}
+
+/**
+ * \brief get poll descriptors
+ * \param hctl HCTL handle
+ * \param pfds array of poll descriptors
+ * \param space space in the poll descriptor array
+ * \return count of filled descriptors
+ */
+int snd_hctl_poll_descriptors(snd_hctl_t *hctl, struct pollfd *pfds, unsigned int space)
+{
+	assert(hctl);
+	return snd_ctl_poll_descriptors(hctl->ctl, pfds, space);
+}
+
+/**
+ * \brief get returned events from poll descriptors
+ * \param hctl HCTL handle
+ * \param pfds array of poll descriptors
+ * \param nfds count of poll descriptors
+ * \param revents returned events
+ * \return zero if success, otherwise a negative error code
+ */
+int snd_hctl_poll_descriptors_revents(snd_hctl_t *hctl, struct pollfd *pfds, unsigned int nfds, unsigned short *revents)
+{
+	assert(hctl);
+	return snd_ctl_poll_descriptors_revents(hctl->ctl, pfds, nfds, revents);
+}
+
+static int snd_hctl_throw_event(snd_hctl_t *hctl, unsigned int mask,
+			 snd_hctl_elem_t *elem)
+{
+	if (hctl->callback)
+		return hctl->callback(hctl, mask, elem);
+	return 0;
+}
+
+static int snd_hctl_elem_throw_event(snd_hctl_elem_t *elem,
+			      unsigned int mask)
+{
+	if (elem->callback)
+		return elem->callback(elem, mask);
+	return 0;
+}
+
+static int snd_hctl_compare_mixer_priority_lookup(const char **name, const char * const *names, int coef)
+{
+	int res;
+
+	for (res = 0; *names; names++, res += coef) {
+		if (!strncmp(*name, *names, strlen(*names))) {
+			*name += strlen(*names);
+			if (**name == ' ')
+				(*name)++;
+			return res+1;
+		}
+	}
+	return NOT_FOUND;
+}
+
+static int get_compare_weight(const snd_ctl_elem_id_t *id)
+{
+	static const char *const names[] = {
+		"Master",
+		"Hardware Master",
+		"Headphone",
+		"Tone Control",
+		"3D Control",
+		"PCM",
+		"Front",
+		"Surround",
+		"Center",
+		"LFE",
+		"Synth",
+		"FM",
+		"Wave",
+		"Music",
+		"DSP",
+		"Line",
+		"CD",
+		"Mic",
+		"Phone",
+		"Video",
+		"Zoom Video",
+		"PC Speaker",
+		"Aux",
+		"Mono",
+		"ADC",
+		"Capture Source",
+		"Capture",
+		"Playback",
+		"Loopback",
+		"Analog Loopback",
+		"Digital Loopback",
+		"I2S",
+		"IEC958",
+		NULL
+	};
+	static const char *const names1[] = {
+		"Switch",
+		"Volume",
+		"Playback",
+		"Capture",
+		"Bypass",
+		"Mono",
+		"Front",
+		"Rear",
+		"Pan",
+		"Output",
+		"-",
+		NULL
+	};
+	static const char *const names2[] = {
+		"Switch",
+		"Volume",
+		"Bypass",
+		"Depth",
+		"Wide",
+		"Space",
+		"Level",
+		"Center",
+		NULL
+	};
+	const char *name = (char *)id->name, *name1;
+	int res, res1;
+	
+	if ((res = snd_hctl_compare_mixer_priority_lookup((const char **)&name, names, 1000000)) == NOT_FOUND)
+		return NOT_FOUND;
+	if (*name == '\0')
+		return res;
+	for (name1 = name; *name1 != '\0'; name1++);
+	for (name1--; name1 != name && *name1 != ' '; name1--);
+	while (name1 != name && *name1 == ' ')
+		name1--;
+	if (name1 != name) {
+		for (; name1 != name && *name1 != ' '; name1--);
+		name = name1;
+		if ((res1 = snd_hctl_compare_mixer_priority_lookup((const char **)&name, names1, 1000)) == NOT_FOUND)
+			return res;
+		res += res1;
+	} else {
+		name = name1;
+	}
+	if ((res1 = snd_hctl_compare_mixer_priority_lookup((const char **)&name, names2, 1)) == NOT_FOUND)
+		return res;
+	return res + res1;
+}
+
+static int _snd_hctl_find_elem(snd_hctl_t *hctl, const snd_ctl_elem_id_t *id, int *dir)
+{
+	unsigned int l, u;
+	snd_hctl_elem_t el;
+	int c = 0;
+	int idx = -1;
+	assert(hctl && id);
+	assert(hctl->compare);
+	el.id = *id;
+	el.compare_weight = get_compare_weight(id);
+	l = 0;
+	u = hctl->count;
+	while (l < u) {
+		idx = (l + u) / 2;
+		c = hctl->compare(&el, hctl->pelems[idx]);
+		if (c < 0)
+			u = idx;
+		else if (c > 0)
+			l = idx + 1;
+		else
+			break;
+	}
+	*dir = c;
+	return idx;
+}
+
+static int snd_hctl_elem_add(snd_hctl_t *hctl, snd_hctl_elem_t *elem)
+{
+	int dir;
+	int idx; 
+	elem->compare_weight = get_compare_weight(&elem->id);
+	if (hctl->count == hctl->alloc) {
+		snd_hctl_elem_t **h;
+		hctl->alloc += 32;
+		h = realloc(hctl->pelems, sizeof(*h) * hctl->alloc);
+		if (!h) {
+			hctl->alloc -= 32;
+			return -ENOMEM;
+		}
+		hctl->pelems = h;
+	}
+	if (hctl->count == 0) {
+		list_add_tail(&elem->list, &hctl->elems);
+		hctl->pelems[0] = elem;
+	} else {
+		idx = _snd_hctl_find_elem(hctl, &elem->id, &dir);
+		assert(dir != 0);
+		if (dir > 0) {
+			list_add(&elem->list, &hctl->pelems[idx]->list);
+			idx++;
+		} else {
+			list_add_tail(&elem->list, &hctl->pelems[idx]->list);
+		}
+		memmove(hctl->pelems + idx + 1,
+			hctl->pelems + idx,
+			(hctl->count - idx) * sizeof(snd_hctl_elem_t *));
+		hctl->pelems[idx] = elem;
+	}
+	hctl->count++;
+	return snd_hctl_throw_event(hctl, SNDRV_CTL_EVENT_MASK_ADD, elem);
+}
+
+static void snd_hctl_elem_remove(snd_hctl_t *hctl, unsigned int idx)
+{
+	snd_hctl_elem_t *elem = hctl->pelems[idx];
+	unsigned int m;
+	snd_hctl_elem_throw_event(elem, SNDRV_CTL_EVENT_MASK_REMOVE);
+	list_del(&elem->list);
+	free(elem);
+	hctl->count--;
+	m = hctl->count - idx;
+	if (m > 0)
+		memmove(hctl->pelems + idx,
+			hctl->pelems + idx + 1,
+			m * sizeof(snd_hctl_elem_t *));
+}
+
+/**
+ * \brief free HCTL loaded elements
+ * \param hctl HCTL handle
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_hctl_free(snd_hctl_t *hctl)
+{
+	while (hctl->count > 0)
+		snd_hctl_elem_remove(hctl, hctl->count - 1);
+	free(hctl->pelems);
+	hctl->pelems = 0;
+	hctl->alloc = 0;
+	INIT_LIST_HEAD(&hctl->elems);
+	return 0;
+}
+
+static snd_hctl_t *compare_hctl;
+static int hctl_compare(const void *a, const void *b) {
+	return compare_hctl->compare(*(const snd_hctl_elem_t * const *) a,
+			     *(const snd_hctl_elem_t * const *) b);
+}
+
+static void snd_hctl_sort(snd_hctl_t *hctl)
+{
+	unsigned int k;
+#ifdef HAVE_LIBPTHREAD
+	static pthread_mutex_t sync_lock = PTHREAD_MUTEX_INITIALIZER;
+#endif
+
+	assert(hctl);
+	assert(hctl->compare);
+	INIT_LIST_HEAD(&hctl->elems);
+
+#ifdef HAVE_LIBPTHREAD
+	pthread_mutex_lock(&sync_lock);
+#endif
+	compare_hctl = hctl;
+	qsort(hctl->pelems, hctl->count, sizeof(*hctl->pelems), hctl_compare);
+#ifdef HAVE_LIBPTHREAD
+	pthread_mutex_unlock(&sync_lock);
+#endif
+	for (k = 0; k < hctl->count; k++)
+		list_add_tail(&hctl->pelems[k]->list, &hctl->elems);
+}
+
+/**
+ * \brief Change HCTL compare function and reorder elements
+ * \param hctl HCTL handle
+ * \param compare Element compare function
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_hctl_set_compare(snd_hctl_t *hctl, snd_hctl_compare_t compare)
+{
+	assert(hctl);
+	hctl->compare = compare == NULL ? snd_hctl_compare_default : compare;
+	snd_hctl_sort(hctl);
+	return 0;
+}
+
+/**
+ * \brief A "don't care" fast compare functions that may be used with #snd_hctl_set_compare
+ * \param c1 First HCTL element
+ * \param c2 Second HCTL element
+ * \return -1 if c1 < c2, 0 if c1 == c2, 1 if c1 > c2
+ */
+int snd_hctl_compare_fast(const snd_hctl_elem_t *c1,
+			  const snd_hctl_elem_t *c2)
+{
+	return c1->id.numid - c2->id.numid;
+}
+
+static int snd_hctl_compare_default(const snd_hctl_elem_t *c1,
+				    const snd_hctl_elem_t *c2)
+{
+	int res, d;
+
+	d = c1->id.iface - c2->id.iface;
+	if (d != 0)
+		return d;
+	if (c1->id.iface == SNDRV_CTL_ELEM_IFACE_MIXER) {
+		d = c1->compare_weight - c2->compare_weight;
+		if (d != 0)
+			return d;
+	}
+	d = c1->id.device - c2->id.device;
+	if (d != 0)
+		return d;
+	d = c1->id.subdevice - c2->id.subdevice;
+	if (d != 0)
+		return d;
+	res = strcmp((const char *)c1->id.name, (const char *)c2->id.name);
+	if (res != 0)
+		return res;
+	return c1->id.index - c2->id.index;
+}
+
+/**
+ * \brief get first element for an HCTL
+ * \param hctl HCTL handle
+ * \return pointer to first element
+ */
+snd_hctl_elem_t *snd_hctl_first_elem(snd_hctl_t *hctl)
+{
+	assert(hctl);
+	if (list_empty(&hctl->elems))
+		return NULL;
+	return list_entry(hctl->elems.next, snd_hctl_elem_t, list);
+}
+
+/**
+ * \brief get last element for an HCTL
+ * \param hctl HCTL handle
+ * \return pointer to last element
+ */
+snd_hctl_elem_t *snd_hctl_last_elem(snd_hctl_t *hctl)
+{
+	assert(hctl);
+	if (list_empty(&hctl->elems))
+		return NULL;
+	return list_entry(hctl->elems.prev, snd_hctl_elem_t, list);
+}
+
+/**
+ * \brief get next HCTL element
+ * \param elem HCTL element
+ * \return pointer to next element
+ */
+snd_hctl_elem_t *snd_hctl_elem_next(snd_hctl_elem_t *elem)
+{
+	assert(elem);
+	if (elem->list.next == &elem->hctl->elems)
+		return NULL;
+	return list_entry(elem->list.next, snd_hctl_elem_t, list);
+}
+
+/**
+ * \brief get previous HCTL element
+ * \param elem HCTL element
+ * \return pointer to previous element
+ */
+snd_hctl_elem_t *snd_hctl_elem_prev(snd_hctl_elem_t *elem)
+{
+	assert(elem);
+	if (elem->list.prev == &elem->hctl->elems)
+		return NULL;
+	return list_entry(elem->list.prev, snd_hctl_elem_t, list);
+}
+
+/**
+ * \brief Search an HCTL element
+ * \param hctl HCTL handle
+ * \param id Element identifier
+ * \return pointer to found HCTL element or NULL if it does not exists
+ */
+snd_hctl_elem_t *snd_hctl_find_elem(snd_hctl_t *hctl, const snd_ctl_elem_id_t *id)
+{
+	int dir;
+	int res = _snd_hctl_find_elem(hctl, id, &dir);
+	if (res < 0 || dir != 0)
+		return NULL;
+	return hctl->pelems[res];
+}
+
+/**
+ * \brief Load an HCTL with all elements and sort them
+ * \param hctl HCTL handle
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_hctl_load(snd_hctl_t *hctl)
+{
+	snd_ctl_elem_list_t list;
+	int err = 0;
+	unsigned int idx;
+
+	assert(hctl);
+	assert(hctl->ctl);
+	assert(hctl->count == 0);
+	assert(list_empty(&hctl->elems));
+	memset(&list, 0, sizeof(list));
+	if ((err = snd_ctl_elem_list(hctl->ctl, &list)) < 0)
+		goto _end;
+	while (list.count != list.used) {
+		err = snd_ctl_elem_list_alloc_space(&list, list.count);
+		if (err < 0)
+			goto _end;
+		if ((err = snd_ctl_elem_list(hctl->ctl, &list)) < 0)
+			goto _end;
+	}
+	if (hctl->alloc < list.count) {
+		hctl->alloc = list.count;
+		free(hctl->pelems);
+		hctl->pelems = malloc(hctl->alloc * sizeof(*hctl->pelems));
+		if (!hctl->pelems) {
+			err = -ENOMEM;
+			goto _end;
+		}
+	}
+	for (idx = 0; idx < list.count; idx++) {
+		snd_hctl_elem_t *elem;
+		elem = calloc(1, sizeof(snd_hctl_elem_t));
+		if (elem == NULL) {
+			snd_hctl_free(hctl);
+			err = -ENOMEM;
+			goto _end;
+		}
+		elem->id = list.pids[idx];
+		elem->hctl = hctl;
+		elem->compare_weight = get_compare_weight(&elem->id);
+		hctl->pelems[idx] = elem;
+		list_add_tail(&elem->list, &hctl->elems);
+		hctl->count++;
+	}
+	if (!hctl->compare)
+		hctl->compare = snd_hctl_compare_default;
+	snd_hctl_sort(hctl);
+	for (idx = 0; idx < hctl->count; idx++) {
+		int res = snd_hctl_throw_event(hctl, SNDRV_CTL_EVENT_MASK_ADD,
+					       hctl->pelems[idx]);
+		if (res < 0)
+			return res;
+	}
+	err = snd_ctl_subscribe_events(hctl->ctl, 1);
+ _end:
+	free(list.pids);
+	return err;
+}
+
+/**
+ * \brief Set callback function for an HCTL
+ * \param hctl HCTL handle
+ * \param callback callback function
+ */
+void snd_hctl_set_callback(snd_hctl_t *hctl, snd_hctl_callback_t callback)
+{
+	assert(hctl);
+	hctl->callback = callback;
+}
+
+/**
+ * \brief Set callback private value for an HCTL
+ * \param hctl HCTL handle
+ * \param callback_private callback private value
+ */
+void snd_hctl_set_callback_private(snd_hctl_t *hctl, void *callback_private)
+{
+	assert(hctl);
+	hctl->callback_private = callback_private;
+}
+
+/**
+ * \brief Get callback private value for an HCTL
+ * \param hctl HCTL handle
+ * \return callback private value
+ */
+void *snd_hctl_get_callback_private(snd_hctl_t *hctl)
+{
+	assert(hctl);
+	return hctl->callback_private;
+}
+
+/**
+ * \brief Get number of loaded elements for an HCTL
+ * \param hctl HCTL handle
+ * \return elements count
+ */
+unsigned int snd_hctl_get_count(snd_hctl_t *hctl)
+{
+	return hctl->count;
+}
+
+/**
+ * \brief Wait for a HCTL to become ready (i.e. at least one event pending)
+ * \param hctl HCTL handle
+ * \param timeout maximum time in milliseconds to wait
+ * \return a positive value on success otherwise a negative error code
+ * \retval 0 timeout occurred
+ * \retval 1 an event is pending
+ */
+int snd_hctl_wait(snd_hctl_t *hctl, int timeout)
+{
+	struct pollfd *pfd;
+	unsigned short *revents;
+	int i, npfds, pollio, err, err_poll;
+	
+	npfds = snd_hctl_poll_descriptors_count(hctl);
+	if (npfds <= 0 || npfds >= 16) {
+		SNDERR("Invalid poll_fds %d\n", npfds);
+		return -EIO;
+	}
+	pfd = alloca(sizeof(*pfd) * npfds);
+	revents = alloca(sizeof(*revents) * npfds);
+	err = snd_hctl_poll_descriptors(hctl, pfd, npfds);
+	if (err < 0)
+		return err;
+	if (err != npfds) {
+		SNDMSG("invalid poll descriptors %d\n", err);
+		return -EIO;
+	}
+	do {
+		pollio = 0;
+		err_poll = poll(pfd, npfds, timeout);
+		if (err_poll < 0) {
+			if (errno == EINTR)
+				continue;
+			return -errno;
+		}
+		if (! err_poll)
+			break;
+		err = snd_hctl_poll_descriptors_revents(hctl, pfd, npfds, revents);
+		if (err < 0)
+			return err;
+		for (i = 0; i < npfds; i++) {
+			if (revents[i] & (POLLERR | POLLNVAL))
+				return -EIO;
+			if ((revents[i] & (POLLIN | POLLOUT)) == 0)
+				continue;
+			pollio++;
+		}
+	} while (! pollio);
+	return err_poll > 0 ? 1 : 0;
+}
+
+/**
+ * \brief Get a ctl handle associated to the given hctl handle
+ * \param hctl HCTL handle
+ * \return a ctl handle otherwise NULL
+ */
+snd_ctl_t *snd_hctl_ctl(snd_hctl_t *hctl)
+{
+	return hctl->ctl;
+}
+
+static int snd_hctl_handle_event(snd_hctl_t *hctl, snd_ctl_event_t *event)
+{
+	snd_hctl_elem_t *elem;
+	int res;
+
+	assert(hctl);
+	assert(hctl->ctl);
+	switch (event->type) {
+	case SND_CTL_EVENT_ELEM:
+		break;
+	default:
+		return 0;
+	}
+	if (event->data.elem.mask == SNDRV_CTL_EVENT_MASK_REMOVE) {
+		int dir;
+		res = _snd_hctl_find_elem(hctl, &event->data.elem.id, &dir);
+		assert(res >= 0 && dir == 0);
+		if (res < 0 || dir != 0)
+			return -ENOENT;
+		snd_hctl_elem_remove(hctl, (unsigned int) res);
+		return 0;
+	}
+	if (event->data.elem.mask & SNDRV_CTL_EVENT_MASK_ADD) {
+		elem = calloc(1, sizeof(snd_hctl_elem_t));
+		if (elem == NULL)
+			return -ENOMEM;
+		elem->id = event->data.elem.id;
+		elem->hctl = hctl;
+		res = snd_hctl_elem_add(hctl, elem);
+		if (res < 0)
+			return res;
+	}
+	if (event->data.elem.mask & (SNDRV_CTL_EVENT_MASK_VALUE |
+				     SNDRV_CTL_EVENT_MASK_INFO)) {
+		elem = snd_hctl_find_elem(hctl, &event->data.elem.id);
+		assert(elem);
+		if (!elem)
+			return -ENOENT;
+		res = snd_hctl_elem_throw_event(elem, event->data.elem.mask &
+						(SNDRV_CTL_EVENT_MASK_VALUE |
+						 SNDRV_CTL_EVENT_MASK_INFO));
+		if (res < 0)
+			return res;
+	}
+	return 0;
+}
+
+/**
+ * \brief Handle pending HCTL events invoking callbacks
+ * \param hctl HCTL handle
+ * \return 0 otherwise a negative error code on failure
+ */
+int snd_hctl_handle_events(snd_hctl_t *hctl)
+{
+	snd_ctl_event_t event;
+	int res;
+	unsigned int count = 0;
+	
+	assert(hctl);
+	assert(hctl->ctl);
+	while ((res = snd_ctl_read(hctl->ctl, &event)) != 0 &&
+	       res != -EAGAIN) {
+		if (res < 0)
+			return res;
+		res = snd_hctl_handle_event(hctl, &event);
+		if (res < 0)
+			return res;
+		count++;
+	}
+	return count;
+}
+
+/**
+ * \brief Get information for an HCTL element
+ * \param elem HCTL element
+ * \param info HCTL element information
+ * \return 0 otherwise a negative error code on failure
+ */
+int snd_hctl_elem_info(snd_hctl_elem_t *elem, snd_ctl_elem_info_t *info)
+{
+	assert(elem);
+	assert(elem->hctl);
+	assert(info);
+	info->id = elem->id;
+	return snd_ctl_elem_info(elem->hctl->ctl, info);
+}
+
+/**
+ * \brief Get value for an HCTL element
+ * \param elem HCTL element
+ * \param value HCTL element value
+ * \return 0 otherwise a negative error code on failure
+ */
+int snd_hctl_elem_read(snd_hctl_elem_t *elem, snd_ctl_elem_value_t * value)
+{
+	assert(elem);
+	assert(elem->hctl);
+	assert(value);
+	value->id = elem->id;
+	return snd_ctl_elem_read(elem->hctl->ctl, value);
+}
+
+/**
+ * \brief Set value for an HCTL element
+ * \param elem HCTL element
+ * \param value HCTL element value
+ * \retval 0 on success
+ * \retval >1 on success when value was changed
+ * \retval <0 a negative error code on failure
+ */
+int snd_hctl_elem_write(snd_hctl_elem_t *elem, snd_ctl_elem_value_t * value)
+{
+	assert(elem);
+	assert(elem->hctl);
+	assert(value);
+	value->id = elem->id;
+	return snd_ctl_elem_write(elem->hctl->ctl, value);
+}
+
+/**
+ * \brief Get TLV value for an HCTL element
+ * \param elem HCTL element
+ * \param tlv TLV array for value
+ * \param tlv_size size of TLV array in bytes
+ * \return 0 otherwise a negative error code on failure
+ */
+int snd_hctl_elem_tlv_read(snd_hctl_elem_t *elem, unsigned int *tlv, unsigned int tlv_size)
+{
+	assert(elem);
+	assert(tlv);
+	assert(tlv_size >= 12);
+	return snd_ctl_elem_tlv_read(elem->hctl->ctl, &elem->id, tlv, tlv_size);
+}
+
+/**
+ * \brief Set TLV value for an HCTL element
+ * \param elem HCTL element
+ * \param tlv TLV array for value
+ * \retval 0 on success
+ * \retval >1 on success when value was changed
+ * \retval <0 a negative error code on failure
+ */
+int snd_hctl_elem_tlv_write(snd_hctl_elem_t *elem, const unsigned int *tlv)
+{
+	assert(elem);
+	assert(tlv);
+	assert(tlv[1] >= 4);
+	return snd_ctl_elem_tlv_write(elem->hctl->ctl, &elem->id, tlv);
+}
+
+/**
+ * \brief Set TLV value for an HCTL element
+ * \param elem HCTL element
+ * \param tlv TLV array for value
+ * \retval 0 on success
+ * \retval >1 on success when value was changed
+ * \retval <0 a negative error code on failure
+ */
+int snd_hctl_elem_tlv_command(snd_hctl_elem_t *elem, const unsigned int *tlv)
+{
+	assert(elem);
+	assert(tlv);
+	assert(tlv[1] >= 4);
+	return snd_ctl_elem_tlv_command(elem->hctl->ctl, &elem->id, tlv);
+}
+
+/**
+ * \brief Get HCTL handle for an HCTL element
+ * \param elem HCTL element
+ * \return HCTL handle
+ */
+snd_hctl_t *snd_hctl_elem_get_hctl(snd_hctl_elem_t *elem)
+{
+	assert(elem);
+	return elem->hctl;
+}
+
+/**
+ * \brief Get CTL element identifier of a CTL element id/value
+ * \param obj CTL element id/value
+ * \param ptr Pointer to returned CTL element identifier
+ */
+void snd_hctl_elem_get_id(const snd_hctl_elem_t *obj, snd_ctl_elem_id_t *ptr)
+{
+	assert(obj && ptr);
+	*ptr = obj->id;
+}
+
+/**
+ * \brief Get element numeric identifier of a CTL element id/value
+ * \param obj CTL element id/value
+ * \return element numeric identifier
+ */
+unsigned int snd_hctl_elem_get_numid(const snd_hctl_elem_t *obj)
+{
+	assert(obj);
+	return obj->id.numid;
+}
+
+/**
+ * \brief Get interface part of CTL element identifier of a CTL element id/value
+ * \param obj CTL element id/value
+ * \return interface part of element identifier
+ */
+snd_ctl_elem_iface_t snd_hctl_elem_get_interface(const snd_hctl_elem_t *obj)
+{
+	assert(obj);
+	return obj->id.iface;
+}
+
+/**
+ * \brief Get device part of CTL element identifier of a CTL element id/value
+ * \param obj CTL element id/value
+ * \return device part of element identifier
+ */
+unsigned int snd_hctl_elem_get_device(const snd_hctl_elem_t *obj)
+{
+	assert(obj);
+	return obj->id.device;
+}
+
+/**
+ * \brief Get subdevice part of CTL element identifier of a CTL element id/value
+ * \param obj CTL element id/value
+ * \return subdevice part of element identifier
+ */
+unsigned int snd_hctl_elem_get_subdevice(const snd_hctl_elem_t *obj)
+{
+	assert(obj);
+	return obj->id.subdevice;
+}
+
+/**
+ * \brief Get name part of CTL element identifier of a CTL element id/value
+ * \param obj CTL element id/value
+ * \return name part of element identifier
+ */
+const char *snd_hctl_elem_get_name(const snd_hctl_elem_t *obj)
+{
+	assert(obj);
+	return (const char *)obj->id.name;
+}
+
+/**
+ * \brief Get index part of CTL element identifier of a CTL element id/value
+ * \param obj CTL element id/value
+ * \return index part of element identifier
+ */
+unsigned int snd_hctl_elem_get_index(const snd_hctl_elem_t *obj)
+{
+	assert(obj);
+	return obj->id.index;
+}
+
+/**
+ * \brief Set callback function for an HCTL element
+ * \param obj HCTL element
+ * \param val callback function
+ */
+void snd_hctl_elem_set_callback(snd_hctl_elem_t *obj, snd_hctl_elem_callback_t val)
+{
+	assert(obj);
+	obj->callback = val;
+}
+
+/**
+ * \brief Set callback private value for an HCTL element
+ * \param obj HCTL element
+ * \param val callback private value
+ */
+void snd_hctl_elem_set_callback_private(snd_hctl_elem_t *obj, void * val)
+{
+	assert(obj);
+	obj->callback_private = val;
+}
+
+/**
+ * \brief Get callback private value for an HCTL element
+ * \param obj HCTL element
+ * \return callback private value
+ */
+void * snd_hctl_elem_get_callback_private(const snd_hctl_elem_t *obj)
+{
+	assert(obj);
+	return obj->callback_private;
+}
+
diff --git a/src/control/namehint.c b/src/control/namehint.c
new file mode 100644
index 0000000..19352be
--- /dev/null
+++ b/src/control/namehint.c
@@ -0,0 +1,693 @@
+/**
+ * \file control/namehint.c
+ * \brief Give device name hints
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \date 2006
+ */
+/*
+ *  Give device name hints  - main file
+ *  Copyright (c) 2006 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include "local.h"
+
+#ifndef DOC_HIDDEN
+struct hint_list {
+	char **list;
+	unsigned int count;
+	unsigned int allocated;
+	const char *siface;
+	snd_ctl_elem_iface_t iface;
+	snd_ctl_t *ctl;
+	snd_ctl_card_info_t *info;	
+	int card;
+	int device;
+	long device_input;
+	long device_output;
+	int stream;
+	int show_all;
+	char *cardname;
+};
+#endif
+
+static int hint_list_add(struct hint_list *list,
+			 const char *name,
+			 const char *description)
+{
+	char *x;
+
+	if (list->count == list->allocated) {
+		char **n = realloc(list->list, (list->allocated + 10) * sizeof(char *));
+		if (n == NULL)
+			return -ENOMEM;
+		list->allocated += 10;
+		list->list = n;
+	}
+	if (name == NULL) {
+		x = NULL;
+	} else {
+		x = malloc(4 + strlen(name) + (description != NULL ? (4 + strlen(description) + 1) : 0) + 1);
+		if (x == NULL)
+			return -ENOMEM;
+		memcpy(x, "NAME", 4);
+		strcpy(x + 4, name);
+		if (description != NULL) {
+			strcat(x, "|DESC");
+			strcat(x, description);
+		}
+	}
+	list->list[list->count++] = x;
+	return 0;
+}
+
+static void zero_handler(const char *file ATTRIBUTE_UNUSED,
+			 int line ATTRIBUTE_UNUSED,
+			 const char *function ATTRIBUTE_UNUSED,
+			 int err ATTRIBUTE_UNUSED,
+			 const char *fmt ATTRIBUTE_UNUSED, ...)
+{
+}
+
+static int get_dev_name1(struct hint_list *list, char **res, int device,
+			 int stream)
+{
+	*res = NULL;
+	if (device < 0)
+		return 0;
+	switch (list->iface) {
+#ifdef BUILD_HWDEP
+	case SND_CTL_ELEM_IFACE_HWDEP:
+		{
+			snd_hwdep_info_t *info;
+			snd_hwdep_info_alloca(&info);
+			snd_hwdep_info_set_device(info, device);
+			if (snd_ctl_hwdep_info(list->ctl, info) < 0)
+				return 0;
+			*res = strdup(snd_hwdep_info_get_name(info));
+			return 0;
+		}
+#endif
+#ifdef BUILD_PCM
+	case SND_CTL_ELEM_IFACE_PCM:
+		{
+			snd_pcm_info_t *info;
+			snd_pcm_info_alloca(&info);
+			snd_pcm_info_set_device(info, device);
+			snd_pcm_info_set_stream(info, stream ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK);
+			if (snd_ctl_pcm_info(list->ctl, info) < 0)
+				return 0;
+			switch (snd_pcm_info_get_class(info)) {
+			case SND_PCM_CLASS_MODEM:
+			case SND_PCM_CLASS_DIGITIZER:
+				return -ENODEV;
+			default:
+				break;
+			}
+			*res = strdup(snd_pcm_info_get_name(info));
+			return 0;
+		}
+#endif
+#ifdef BUILD_RAWMIDI
+	case SND_CTL_ELEM_IFACE_RAWMIDI:
+		{
+			snd_rawmidi_info_t *info;
+			snd_rawmidi_info_alloca(&info);
+			snd_rawmidi_info_set_device(info, device);
+			snd_rawmidi_info_set_stream(info, stream ? SND_RAWMIDI_STREAM_INPUT : SND_RAWMIDI_STREAM_OUTPUT);
+			if (snd_ctl_rawmidi_info(list->ctl, info) < 0)
+				return 0;
+			*res = strdup(snd_rawmidi_info_get_name(info));
+			return 0;
+		}
+#endif
+	default:
+		return 0;
+	}
+}
+
+static char *get_dev_name(struct hint_list *list)
+{
+	char *str1, *str2, *res;
+	int device;
+	
+	device = list->device_input >= 0 ? list->device_input : list->device;
+	if (get_dev_name1(list, &str1, device, 1) < 0)
+		return NULL;
+	device = list->device_output >= 0 ? list->device_output : list->device;
+	if (get_dev_name1(list, &str2, device, 0) < 0) {
+		if (str1)
+			free(str1);
+		return NULL;
+	}
+	if (str1 != NULL || str2 != NULL) {
+		if (str1 != NULL && str2 != NULL) {
+			if (strcmp(str1, str2) == 0) {
+				res = malloc(strlen(list->cardname) + strlen(str2) + 3);
+				if (res != NULL) {
+					strcpy(res, list->cardname);
+					strcat(res, ", ");
+					strcat(res, str2);
+				}
+			} else {
+				res = malloc(strlen(list->cardname) + strlen(str2) + strlen(str1) + 6);
+				if (res != NULL) {
+					strcpy(res, list->cardname);
+					strcat(res, ", ");
+					strcat(res, str2);
+					strcat(res, " / ");
+					strcat(res, str1);
+				}
+			}
+			free(str2);
+			free(str1);
+			return res;
+		} else {
+			if (str1 != NULL) {
+				str2 = "Input";
+			} else {
+				str1 = str2;
+				str2 = "Output";
+			}
+			res = malloc(strlen(list->cardname) + strlen(str1) + 19);
+			if (res == NULL) {
+				free(str1);
+				return NULL;
+			}
+			strcpy(res, list->cardname);
+			strcat(res, ", ");
+			strcat(res, str1);
+			strcat(res, "|IOID");
+			strcat(res, str2);
+			free(str1);
+			return res;
+		}
+	}
+	/* if the specified device doesn't exist, skip this entry */
+	if (list->device >= 0 || list->device_input >= 0 || list->device_output >= 0)
+		return NULL;
+	return strdup(list->cardname);
+}
+
+#ifndef DOC_HIDDEN
+#define BUF_SIZE 128
+#endif
+
+static int try_config(struct hint_list *list,
+		      const char *base,
+		      const char *name)
+{
+	snd_lib_error_handler_t eh;
+	snd_config_t *res = NULL, *cfg, *cfg1, *n;
+	snd_config_iterator_t i, next;
+	char *buf, *buf1 = NULL, *buf2;
+	const char *str;
+	int err = 0, level;
+	long dev = list->device;
+	int cleanup_res = 0;
+
+	list->device_input = -1;
+	list->device_output = -1;
+	buf = malloc(BUF_SIZE);
+	if (buf == NULL)
+		return -ENOMEM;
+	sprintf(buf, "%s.%s", base, name);
+	/* look for redirection */
+	if (snd_config_search(snd_config, buf, &cfg) >= 0 &&
+	    snd_config_get_string(cfg, &str) >= 0 &&
+	    ((strncmp(base, str, strlen(base)) == 0 &&
+	     str[strlen(base)] == '.') || strchr(str, '.') == NULL))
+	     	goto __skip_add;
+	if (list->card >= 0 && list->device >= 0)
+		sprintf(buf, "%s:CARD=%s,DEV=%i", name, snd_ctl_card_info_get_id(list->info), list->device);
+	else if (list->card >= 0)
+		sprintf(buf, "%s:CARD=%s", name, snd_ctl_card_info_get_id(list->info));
+	else
+		strcpy(buf, name);
+	eh = snd_lib_error;
+	snd_lib_error_set_handler(&zero_handler);
+	err = snd_config_search_definition(snd_config, base, buf, &res);
+	snd_lib_error_set_handler(eh);
+	if (err < 0)
+		goto __skip_add;
+	cleanup_res = 1;
+	err = -EINVAL;
+	if (snd_config_get_type(res) != SND_CONFIG_TYPE_COMPOUND)
+		goto __cleanup;
+	if (snd_config_search(res, "type", NULL) < 0)
+		goto __cleanup;
+
+#if 0	/* for debug purposes */
+		{
+			snd_output_t *out;
+			fprintf(stderr, "********* PCM '%s':\n", buf);
+			snd_output_stdio_attach(&out, stderr, 0);
+			snd_config_save(res, out);
+			snd_output_close(out);
+			fprintf(stderr, "\n");
+		}
+#endif
+
+	cfg1 = res;
+	level = 0;
+      __hint:
+      	level++;
+	if (snd_config_search(cfg1, "type", &cfg) >= 0 &&
+	    snd_config_get_string(cfg, &str) >= 0 &&
+	    strcmp(str, "hw") == 0) {
+	    	dev = 0;
+		list->device_input = -1;
+		list->device_output = -1;
+		if (snd_config_search(cfg1, "device", &cfg) >= 0) {
+			if (snd_config_get_integer(cfg, &dev) < 0) {
+				SNDERR("(%s) device must be an integer", buf);
+				err = -EINVAL;
+				goto __cleanup;
+			}
+		}
+	}
+      	
+	if (snd_config_search(cfg1, "hint", &cfg) >= 0) {
+		if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
+			SNDERR("hint (%s) must be a compound", buf);
+			err = -EINVAL;
+			goto __cleanup;
+		}
+		if (level == 1 &&
+		    snd_config_search(cfg, "show", &n) >= 0 &&
+		    snd_config_get_bool(n) <= 0)
+		    	goto __skip_add;
+		if (buf1 == NULL &&
+		    snd_config_search(cfg, "description", &n) >= 0 &&
+		    snd_config_get_string(n, &str) >= 0) {
+			buf1 = strdup(str);
+			if (buf1 == NULL) {
+				err = -ENOMEM;
+				goto __cleanup;
+			}
+		}
+		if (snd_config_search(cfg, "device", &n) >= 0) {
+			if (snd_config_get_integer(n, &dev) < 0) {
+				SNDERR("(%s) device must be an integer", buf);
+				err = -EINVAL;
+				goto __cleanup;
+			}
+			list->device_input = dev;
+			list->device_output = dev;
+		}
+		if (snd_config_search(cfg, "device_input", &n) >= 0) {
+			if (snd_config_get_integer(n, &list->device_input) < 0) {
+				SNDERR("(%s) device_input must be an integer", buf);
+				err = -EINVAL;
+				goto __cleanup;
+			}
+			list->device_output = -1;
+		}
+		if (snd_config_search(cfg, "device_output", &n) >= 0) {
+			if (snd_config_get_integer(n, &list->device_output) < 0) {
+				SNDERR("(%s) device_output must be an integer", buf);
+				err = -EINVAL;
+				goto __cleanup;
+			}
+		}
+	} else if (level == 1 && !list->show_all)
+		goto __skip_add;
+	if (snd_config_search(cfg1, "slave", &cfg) >= 0 &&
+	    snd_config_search(cfg, base, &cfg1) >= 0)
+	    	goto __hint;
+	snd_config_delete(res);
+	res = NULL;
+	cleanup_res = 0;
+	if (strchr(buf, ':') != NULL)
+		goto __ok;
+	/* find, if all parameters have a default, */
+	/* otherwise filter this definition */
+	eh = snd_lib_error;
+	snd_lib_error_set_handler(&zero_handler);
+	err = snd_config_search_alias_hooks(snd_config, base, buf, &res);
+	snd_lib_error_set_handler(eh);
+	if (err < 0)
+		goto __cleanup;
+	if (snd_config_search(res, "@args", &cfg) >= 0) {
+		snd_config_for_each(i, next, cfg) {
+			if (snd_config_search(snd_config_iterator_entry(i),
+					      "default", NULL) < 0) {
+				err = -EINVAL;
+				goto __cleanup;
+			}
+		}
+	}
+      __ok:
+	err = 0;
+      __cleanup:
+      	if (err >= 0) {
+      		list->device = dev;
+ 		str = list->card >= 0 ? get_dev_name(list) : NULL;
+      		if (str != NULL) {
+      			level = (buf1 == NULL ? 0 : strlen(buf1)) + 1 + strlen(str);
+      			buf2 = realloc((char *)str, level + 1);
+      			if (buf2 != NULL) {
+      				if (buf1 != NULL) {
+      					str = strchr(buf2, '|');
+      					if (str != NULL)
+						memmove(buf2 + (level - strlen(str)), str, strlen(str));
+					else
+						str = buf2 + strlen(buf2);
+      					*(char *)str++ = '\n';
+	      				memcpy((char *)str, buf1, strlen(buf1));
+	      				buf2[level] = '\0';
+					free(buf1);
+				}
+				buf1 = buf2;
+			} else {
+				free((char *)str);
+			}
+      		} else if (list->device >= 0)
+      			goto __skip_add;
+	      	err = hint_list_add(list, buf, buf1);
+	}
+      __skip_add:
+	if (res && cleanup_res)
+	      	snd_config_delete(res);
+	if (buf1)
+		free(buf1);
+      	free(buf);
+	return err;
+}
+
+#ifndef DOC_HIDDEN
+#define IFACE(v, fcn) [SND_CTL_ELEM_IFACE_##v] = (next_devices_t)fcn
+
+typedef int (*next_devices_t)(snd_ctl_t *, int *);
+
+static const next_devices_t next_devices[] = {
+	IFACE(CARD, NULL),
+	IFACE(HWDEP, snd_ctl_hwdep_next_device),
+	IFACE(MIXER, NULL),
+	IFACE(PCM, snd_ctl_pcm_next_device),
+	IFACE(RAWMIDI, snd_ctl_rawmidi_next_device),
+	IFACE(TIMER, NULL),
+	IFACE(SEQUENCER, NULL)
+};
+#endif
+
+static int add_card(struct hint_list *list, int card)
+{
+	int err, ok;
+	snd_config_t *conf, *n;
+	snd_config_iterator_t i, next;
+	const char *str;
+	char ctl_name[16];
+	snd_ctl_card_info_t *info;
+	int device, max_device = 0;
+	
+	snd_ctl_card_info_alloca(&info);
+	list->info = info;
+	err = snd_config_search(snd_config, list->siface, &conf);
+	if (err < 0)
+		return err;
+	sprintf(ctl_name, "hw:%i", card);
+	err = snd_ctl_open(&list->ctl, ctl_name, 0);
+	if (err < 0)
+		return err;
+	err = snd_ctl_card_info(list->ctl, info);
+	if (err < 0)
+		goto __error;
+	snd_config_for_each(i, next, conf) {
+		n = snd_config_iterator_entry(i);
+		if (snd_config_get_id(n, &str) < 0)
+			continue;
+		
+		if (next_devices[list->iface] != NULL) {
+			list->card = card;
+			device = max_device = -1;
+			err = next_devices[list->iface](list->ctl, &device);
+			if (device < 0)
+				err = -EINVAL;
+			else
+				max_device = device;
+			while (err >= 0 && device >= 0) {
+				err = next_devices[list->iface](list->ctl, &device);
+				if (err >= 0 && device > max_device)
+					max_device = device;
+			}
+			ok = 0;
+			for (device = 0; err >= 0 && device <= max_device; device++) {
+				list->device = device;
+				err = try_config(list, list->siface, str);
+				if (err < 0)
+					break;
+				ok++;
+			}
+			if (ok)
+				continue;
+		} else {
+			err = -EINVAL;
+		}
+		if (err == -EXDEV)
+			continue;
+		if (err < 0) {
+			list->card = card;
+			list->device = -1;
+			err = try_config(list, list->siface, str);
+		}
+		if (err == -ENOMEM)
+			goto __error;
+	}
+	err = 0;
+      __error:
+      	snd_ctl_close(list->ctl);
+	return err;
+}
+
+static int get_card_name(struct hint_list *list, int card)
+{
+	char scard[16], *s;
+	int err;
+
+	free(list->cardname);
+	list->cardname = NULL;
+	err = snd_card_get_name(card, &list->cardname);
+	if (err <= 0)
+		return 0;
+	sprintf(scard, " #%i", card);
+	s = realloc(list->cardname, strlen(list->cardname) + strlen(scard) + 1);
+	if (s == NULL)
+		return -ENOMEM;
+	list->cardname = s;
+	return 0;
+}
+
+static int add_software_devices(struct hint_list *list)
+{
+	int err;
+	snd_config_t *conf, *n;
+	snd_config_iterator_t i, next;
+	const char *str;
+
+	err = snd_config_search(snd_config, list->siface, &conf);
+	if (err < 0)
+		return err;
+	snd_config_for_each(i, next, conf) {
+		n = snd_config_iterator_entry(i);
+		if (snd_config_get_id(n, &str) < 0)
+			continue;
+		list->card = -1;
+		list->device = -1;
+		err = try_config(list, list->siface, str);
+		if (err == -ENOMEM)
+			return -ENOMEM;
+	}
+	return 0;
+}
+
+/**
+ * \brief Get a set of device name hints
+ * \param card Card number or -1 (means all cards)
+ * \param iface Interface identification (like "pcm", "rawmidi", "timer", "seq")
+ * \param hints Result - array of device name hints
+ * \result zero if success, otherwise a negative error code
+ *
+ * hints will receive a NULL-terminated array of device name hints,
+ * which can be passed to #snd_device_name_get_hint to extract usable
+ * values. When no longer needed, hints should be passed to
+ * #snd_device_name_free_hint to release resources.
+ *
+ * User-defined hints are gathered from namehint.IFACE tree like:
+ *
+ * <code>
+ * namehint.pcm {<br>
+ *   myfile "file:FILE=/tmp/soundwave.raw|Save sound output to /tmp/soundwave.raw"<br>
+ *   myplug "plug:front:Do all conversions for front speakers"<br>
+ * }
+ * </code>
+ *
+ * Note: The device description is separated with '|' char.
+ *
+ * Special variables: defaults.namehint.showall specifies if all device
+ * definitions are accepted (boolean type).
+ */
+int snd_device_name_hint(int card, const char *iface, void ***hints)
+{
+	struct hint_list list;
+	char ehints[24];
+	const char *str;
+	snd_config_t *conf;
+	snd_config_iterator_t i, next;
+	int err;
+
+	if (hints == NULL)
+		return -EINVAL;
+	err = snd_config_update();
+	if (err < 0)
+		return err;
+	list.list = NULL;
+	list.count = list.allocated = 0;
+	list.siface = iface;
+	if (strcmp(iface, "card") == 0)
+		list.iface = SND_CTL_ELEM_IFACE_CARD;
+	else if (strcmp(iface, "pcm") == 0)
+		list.iface = SND_CTL_ELEM_IFACE_PCM;
+	else if (strcmp(iface, "rawmidi") == 0)
+		list.iface = SND_CTL_ELEM_IFACE_RAWMIDI;
+	else if (strcmp(iface, "timer") == 0)
+		list.iface = SND_CTL_ELEM_IFACE_TIMER;
+	else if (strcmp(iface, "seq") == 0)
+		list.iface = SND_CTL_ELEM_IFACE_SEQUENCER;
+	else if (strcmp(iface, "hwdep") == 0)
+		list.iface = SND_CTL_ELEM_IFACE_HWDEP;
+	else if (strcmp(iface, "ctl") == 0)
+		list.iface = SND_CTL_ELEM_IFACE_MIXER;
+	else
+		return -EINVAL;
+	list.show_all = 0;
+	list.cardname = NULL;
+	if (snd_config_search(snd_config, "defaults.namehint.showall", &conf) >= 0)
+		list.show_all = snd_config_get_bool(conf) > 0;
+	if (card >= 0) {
+		err = get_card_name(&list, card);
+		if (err >= 0)
+			err = add_card(&list, card);
+	} else {
+		add_software_devices(&list);
+		err = snd_card_next(&card);
+		if (err < 0)
+			goto __error;
+		while (card >= 0) {
+			err = get_card_name(&list, card);
+			if (err < 0)
+				goto __error;
+			err = add_card(&list, card);
+			if (err < 0)
+				goto __error;
+			err = snd_card_next(&card);
+			if (err < 0)
+				goto __error;
+		}
+	}
+	sprintf(ehints, "namehint.%s", list.siface);
+	err = snd_config_search(snd_config, ehints, &conf);
+	if (err >= 0) {
+		snd_config_for_each(i, next, conf) {
+			if (snd_config_get_string(snd_config_iterator_entry(i),
+						  &str) < 0)
+				continue;
+			err = hint_list_add(&list, str, NULL);
+			if (err < 0)
+				goto __error;
+		}
+	}
+	err = 0;
+      __error:
+      	if (err < 0) {
+      		snd_device_name_free_hint((void **)list.list);
+      		if (list.cardname)
+	      		free(list.cardname);
+      		return err;
+      	} else {
+      		err = hint_list_add(&list, NULL, NULL);
+      		if (err < 0)
+      			goto __error;
+      		*hints = (void **)list.list;
+      		if (list.cardname)
+	      		free(list.cardname);
+	}
+      	return 0;
+}
+
+/**
+ * \brief Free a list of device name hints.
+ * \param hints List to free
+ * \result zero if success, otherwise a negative error code
+ */
+int snd_device_name_free_hint(void **hints)
+{
+	char **h;
+
+	if (hints == NULL)
+		return 0;
+	h = (char **)hints;
+	while (*h) {
+		free(*h);
+		h++;
+	}
+	free(hints);
+	return 0;
+}
+
+/**
+ * \brief Extract a value from a hint
+ * \param hint A pointer to hint
+ * \param id Hint value to extract ("NAME", "DESC", or "IOID", see below)
+ * \result an allocated ASCII string if success, otherwise NULL
+ *
+ * List of valid IDs:
+ * NAME - name of device
+ * DESC - description of device
+ * IOID - input / output identification ("Input" or "Output"), NULL means both
+ *
+ * The return value should be freed when no longer needed.
+ */
+char *snd_device_name_get_hint(const void *hint, const char *id)
+{
+	const char *hint1 = (const char *)hint, *delim;
+	char *res;
+	unsigned size;
+
+	if (strlen(id) != 4)
+		return NULL;
+	while (*hint1 != '\0') {
+		delim = strchr(hint1, '|');
+		if (memcmp(id, hint1, 4) != 0) {
+			if (delim == NULL)
+				return NULL;
+			hint1 = delim + 1;
+			continue;
+		} 
+		if (delim == NULL)
+			return strdup(hint1 + 4);
+		size = delim - hint1 - 4;
+		res = malloc(size + 1);
+		if (res != NULL) {
+			memcpy(res, hint1 + 4, size);
+			res[size] = '\0';
+		}
+		return res;
+	}
+	return NULL;
+}
diff --git a/src/control/setup.c b/src/control/setup.c
new file mode 100644
index 0000000..eecda45
--- /dev/null
+++ b/src/control/setup.c
@@ -0,0 +1,661 @@
+/**
+ * \file control/setup.c
+ * \brief Routines to setup control primitives from configuration
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \date 2001
+ *
+ * Routines to setup control primitives from configuration
+ */
+/*
+ *  Control Interface - routines for setup from configuration
+ *  Copyright (c) 2001 by Abramo Bagnara <abramo@alsa-project.org>
+ *			  Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include "local.h"
+
+#ifndef DOC_HIDDEN
+typedef struct {
+	unsigned int lock: 1;
+	unsigned int preserve: 1;
+	snd_ctl_elem_id_t *id;
+	snd_ctl_elem_info_t *info;
+	snd_ctl_elem_value_t *val;
+	snd_ctl_elem_value_t *mask;
+	snd_ctl_elem_value_t *old;
+	struct list_head list;
+} snd_sctl_elem_t;
+
+struct _snd_sctl {
+	int mode;
+	snd_ctl_t *ctl;
+	struct list_head elems;
+};
+#endif /* DOC_HIDDEN */
+
+static int free_elems(snd_sctl_t *h)
+{
+	int err = 0;
+	while (!list_empty(&h->elems)) {
+		snd_sctl_elem_t *elem = list_entry(h->elems.next, snd_sctl_elem_t, list);
+		snd_ctl_elem_id_free(elem->id);
+		snd_ctl_elem_info_free(elem->info);
+		snd_ctl_elem_value_free(elem->val);
+		snd_ctl_elem_value_free(elem->mask);
+		snd_ctl_elem_value_free(elem->old);
+		list_del(&elem->list);
+		free(elem);
+	}
+	if ((h->mode & SND_SCTL_NOFREE) == 0)
+		err = snd_ctl_close(h->ctl);
+	free(h);
+	return err;
+}
+
+/**
+ * \brief Install given values to control elements
+ * \param h Setup control handle
+ * \result zero if success, otherwise a negative error code
+ */
+int snd_sctl_install(snd_sctl_t *h)
+{
+	struct list_head *pos;
+	int err;
+	unsigned int k;
+	assert(h);
+	list_for_each(pos, &h->elems) {
+		snd_sctl_elem_t *elem = list_entry(pos, snd_sctl_elem_t, list);
+		unsigned int count;
+		snd_ctl_elem_type_t type;
+		if (elem->lock) {
+			err = snd_ctl_elem_lock(h->ctl, elem->id);
+			if (err < 0) {
+				SNDERR("Cannot lock ctl elem");
+				return err;
+			}
+		}
+		err = snd_ctl_elem_read(h->ctl, elem->old);
+		if (err < 0) {
+			SNDERR("Cannot read ctl elem");
+			return err;
+		}
+		count = snd_ctl_elem_info_get_count(elem->info);
+		type = snd_ctl_elem_info_get_type(elem->info);
+		switch (type) {
+		case SND_CTL_ELEM_TYPE_BOOLEAN:
+			for (k = 0; k < count; ++k) {
+				int old, val, mask;
+				old = snd_ctl_elem_value_get_boolean(elem->old, k);
+				mask = snd_ctl_elem_value_get_boolean(elem->mask, k);
+				old &= ~mask;
+				if (old) {
+					val = snd_ctl_elem_value_get_boolean(elem->val, k);
+					val |= old;
+					snd_ctl_elem_value_set_boolean(elem->val, k, val);
+				}
+			}
+			break;
+		case SND_CTL_ELEM_TYPE_INTEGER:
+			for (k = 0; k < count; ++k) {
+				long old, val, mask;
+				old = snd_ctl_elem_value_get_integer(elem->old, k);
+				mask = snd_ctl_elem_value_get_integer(elem->mask, k);
+				old &= ~mask;
+				if (old) {
+					val = snd_ctl_elem_value_get_integer(elem->val, k);
+					val |= old;
+					snd_ctl_elem_value_set_integer(elem->val, k, val);
+				}
+			}
+			break;
+		case SND_CTL_ELEM_TYPE_ENUMERATED:
+			for (k = 0; k < count; ++k) {
+				unsigned int old, val, mask;
+				old = snd_ctl_elem_value_get_enumerated(elem->old, k);
+				mask = snd_ctl_elem_value_get_enumerated(elem->mask, k);
+				old &= ~mask;
+				if (old) {
+					val = snd_ctl_elem_value_get_enumerated(elem->val, k);
+					val |= old;
+					snd_ctl_elem_value_set_enumerated(elem->val, k, val);
+				}
+			}
+			break;
+		case SND_CTL_ELEM_TYPE_IEC958:
+			count = sizeof(snd_aes_iec958_t);
+			/* Fall through */
+		case SND_CTL_ELEM_TYPE_BYTES:
+			for (k = 0; k < count; ++k) {
+				unsigned char old, val, mask;
+				old = snd_ctl_elem_value_get_byte(elem->old, k);
+				mask = snd_ctl_elem_value_get_byte(elem->mask, k);
+				old &= ~mask;
+				if (old) {
+					val = snd_ctl_elem_value_get_byte(elem->val, k);
+					val |= old;
+					snd_ctl_elem_value_set_byte(elem->val, k, val);
+				}
+			}
+			break;
+		default:
+			assert(0);
+			break;
+		}
+		err = snd_ctl_elem_write(h->ctl, elem->val);
+		if (err < 0) {
+			SNDERR("Cannot write ctl elem");
+			return err;
+		}
+	}
+	return 0;
+}
+
+/**
+ * \brief Remove (restore) previous values from control elements
+ * \param h Setup control handle
+ * \result zero if success, otherwise a negative error code
+ */
+int snd_sctl_remove(snd_sctl_t *h)
+{
+	struct list_head *pos;
+	int err;
+	assert(h);
+	list_for_each(pos, &h->elems) {
+		snd_sctl_elem_t *elem = list_entry(pos, snd_sctl_elem_t, list);
+		if (elem->lock) {
+			err = snd_ctl_elem_unlock(h->ctl, elem->id);
+			if (err < 0) {
+				SNDERR("Cannot unlock ctl elem");
+				return err;
+			}
+		}
+		/* Only restore the old value if it differs from the requested
+		 * value, because if it has changed restoring the old value
+		 * overrides the change.  Take for example, a voice modem with
+		 * a .conf that sets preserve off-hook.  Start playback (on-hook
+		 * to off-hook), start record (off-hook to off-hook), stop
+		 * playback (off-hook to restore on-hook), stop record (on-hook
+		 * to restore off-hook), Clearly you don't want to leave the
+		 * modem "on the phone" now that there isn't any playback or
+		 * recording active.
+		 */
+		if (elem->preserve && snd_ctl_elem_value_compare(elem->val, elem->old)) {
+			err = snd_ctl_elem_write(h->ctl, elem->old);
+			if (err < 0) {
+				SNDERR("Cannot restore ctl elem");
+				return err;
+			}
+		}
+	}
+	return 0;
+}
+
+static int snd_config_get_ctl_elem_enumerated(snd_config_t *n, snd_ctl_t *ctl,
+					      snd_ctl_elem_info_t *info)
+{
+	const char *str;
+	long val;
+	unsigned int idx, items;
+	switch (snd_config_get_type(n)) {
+	case SND_CONFIG_TYPE_INTEGER:
+		snd_config_get_integer(n, &val);
+		return val;
+	case SND_CONFIG_TYPE_STRING:
+		snd_config_get_string(n, &str);
+		break;
+	default:
+		return -1;
+	}
+	items = snd_ctl_elem_info_get_items(info);
+	for (idx = 0; idx < items; idx++) {
+		int err;
+		snd_ctl_elem_info_set_item(info, idx);
+		err = snd_ctl_elem_info(ctl, info);
+		if (err < 0) {
+			SNDERR("Cannot obtain info for CTL elem");
+			return err;
+		}
+		if (strcmp(str, snd_ctl_elem_info_get_item_name(info)) == 0)
+			return idx;
+	}
+	return -1;
+}
+
+static int snd_config_get_ctl_elem_value(snd_config_t *conf,
+					 snd_ctl_t *ctl,
+					 snd_ctl_elem_value_t *val,
+					 snd_ctl_elem_value_t *mask,
+					 snd_ctl_elem_info_t *info)
+{
+	int err;
+	snd_config_iterator_t i, next;
+	snd_ctl_elem_id_t *id;
+	snd_ctl_elem_type_t type;
+	unsigned int count;
+	long v;
+	long idx;
+	snd_ctl_elem_id_alloca(&id);
+	snd_ctl_elem_value_get_id(val, id);
+	count = snd_ctl_elem_info_get_count(info);
+	type = snd_ctl_elem_info_get_type(info);
+	if (count == 1) {
+		switch (type) {
+		case SND_CTL_ELEM_TYPE_BOOLEAN:
+			v = snd_config_get_bool(conf);
+			if (v >= 0) {
+				snd_ctl_elem_value_set_boolean(val, 0, v);
+				if (mask)
+					snd_ctl_elem_value_set_boolean(mask, 0, 1);
+				return 0;
+			}
+			break;
+		case SND_CTL_ELEM_TYPE_INTEGER:
+			err = snd_config_get_integer(conf, &v);
+			if (err == 0) {
+				snd_ctl_elem_value_set_integer(val, 0, v);
+				if (mask)
+					snd_ctl_elem_value_set_integer(mask, 0, ~0L);
+				return 0;
+			}
+			break;
+		case SND_CTL_ELEM_TYPE_ENUMERATED:
+			v = snd_config_get_ctl_elem_enumerated(conf, ctl, info);
+			if (v >= 0) {
+				snd_ctl_elem_value_set_enumerated(val, 0, v);
+				if (mask)
+					snd_ctl_elem_value_set_enumerated(mask, 0, ~0);
+				return 0;
+			}
+			break;
+		case SND_CTL_ELEM_TYPE_BYTES:
+		case SND_CTL_ELEM_TYPE_IEC958:
+			break;
+		default:
+			SNDERR("Unknown control type: %d", type);
+			return -EINVAL;
+		}
+	}
+	switch (type) {
+	case SND_CTL_ELEM_TYPE_IEC958:
+		count = sizeof(snd_aes_iec958_t);
+		/* Fall through */
+	case SND_CTL_ELEM_TYPE_BYTES:
+	{
+		const char *buf;
+		err = snd_config_get_string(conf, &buf);
+		if (err >= 0) {
+			int c1 = 0;
+			unsigned int len = strlen(buf);
+			unsigned int idx = 0;
+			if (len % 2 != 0 || len > count * 2) {
+			_bad_content:
+				SNDERR("bad value content\n");
+				return -EINVAL;
+			}
+			while (*buf) {
+				int c = *buf++;
+				if (c >= '0' && c <= '9')
+					c -= '0';
+				else if (c >= 'a' && c <= 'f')
+					c = c - 'a' + 10;
+				else if (c >= 'A' && c <= 'F')
+					c = c - 'A' + 10;
+				else {
+					goto _bad_content;
+				}
+				if (idx % 2 == 1) {
+					snd_ctl_elem_value_set_byte(val, idx / 2, c1 << 4 | c);
+					if (mask)
+						snd_ctl_elem_value_set_byte(mask, idx / 2, 0xff);
+				} else
+					c1 = c;
+				idx++;
+			}
+			return 0;
+		}
+	}
+	default:
+		break;
+	}
+	if (snd_config_get_type(conf) != SND_CONFIG_TYPE_COMPOUND) {
+		SNDERR("bad value type");
+		return -EINVAL;
+	}
+
+	snd_config_for_each(i, next, conf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		err = safe_strtol(id, &idx);
+		if (err < 0 || idx < 0 || (unsigned int) idx >= count) {
+			SNDERR("bad value index");
+			return -EINVAL;
+		}
+		switch (type) {
+		case SND_CTL_ELEM_TYPE_BOOLEAN:
+			v = snd_config_get_bool(n);
+			if (v < 0)
+				goto _bad_content;
+			snd_ctl_elem_value_set_boolean(val, idx, v);
+			if (mask)
+				snd_ctl_elem_value_set_boolean(mask, idx, 1);
+			break;
+		case SND_CTL_ELEM_TYPE_INTEGER:
+			err = snd_config_get_integer(n, &v);
+			if (err < 0)
+				goto _bad_content;
+			snd_ctl_elem_value_set_integer(val, idx, v);
+			if (mask)
+				snd_ctl_elem_value_set_integer(mask, idx, ~0L);
+			break;
+		case SND_CTL_ELEM_TYPE_ENUMERATED:
+			v = snd_config_get_ctl_elem_enumerated(n, ctl, info);
+			if (v < 0)
+				goto _bad_content;
+			snd_ctl_elem_value_set_enumerated(val, idx, v);
+			if (mask)
+				snd_ctl_elem_value_set_enumerated(mask, idx, ~0);
+			break;
+		case SND_CTL_ELEM_TYPE_BYTES:
+		case SND_CTL_ELEM_TYPE_IEC958:
+			err = snd_config_get_integer(n, &v);
+			if (err < 0 || v < 0 || v > 255)
+				goto _bad_content;
+			snd_ctl_elem_value_set_byte(val, idx, v);
+			if (mask)
+				snd_ctl_elem_value_set_byte(mask, idx, 0xff);
+			break;
+		default:
+			break;
+		}
+	}
+	return 0;
+}
+
+static int add_elem(snd_sctl_t *h, snd_config_t *_conf, snd_config_t *private_data)
+{
+	snd_config_t *conf;
+	snd_config_iterator_t i, next;
+	char *tmp;
+	int iface = SND_CTL_ELEM_IFACE_MIXER;
+	const char *name = NULL;
+	long index = 0;
+	long device = -1;
+	long subdevice = -1;
+	int lock = 0;
+	int preserve = 0;
+	int optional = 0;
+	snd_config_t *value = NULL, *mask = NULL;
+	snd_sctl_elem_t *elem = NULL;
+	int err;
+	err = snd_config_expand(_conf, _conf, NULL, private_data, &conf);
+	if (err < 0)
+		return err;
+	snd_config_for_each(i, next, conf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (strcmp(id, "comment") == 0)
+			continue;
+		if (strcmp(id, "iface") == 0 || strcmp(id, "interface") == 0) {
+			const char *ptr;
+			if ((err = snd_config_get_string(n, &ptr)) < 0) {
+				SNDERR("field %s is not a string", id);
+				goto _err;
+			}
+			if ((err = snd_config_get_ctl_iface_ascii(ptr)) < 0) {
+				SNDERR("Invalid value for '%s'", id);
+				goto _err;
+			}
+			iface = err;
+			continue;
+		}
+		if (strcmp(id, "name") == 0) {
+			if ((err = snd_config_get_string(n, &name)) < 0) {
+				SNDERR("field %s is not a string", id);
+				goto _err;
+			}
+			continue;
+		}
+		if (strcmp(id, "index") == 0) {
+			if ((err = snd_config_get_integer(n, &index)) < 0) {
+				SNDERR("field %s is not an integer", id);
+				goto _err;
+			}
+			continue;
+		}
+		if (strcmp(id, "device") == 0) {
+			if ((err = snd_config_get_integer(n, &device)) < 0) {
+				SNDERR("field %s is not an integer", id);
+				goto _err;
+			}
+			continue;
+		}
+		if (strcmp(id, "subdevice") == 0) {
+			if ((err = snd_config_get_integer(n, &subdevice)) < 0) {
+				SNDERR("field %s is not an integer", id);
+				goto _err;
+			}
+			continue;
+		}
+		if (strcmp(id, "lock") == 0) {
+			if ((err = snd_config_get_ascii(n, &tmp)) < 0) {
+				SNDERR("field %s has an invalid type", id);
+				goto _err;
+			}
+			err = snd_config_get_bool_ascii(tmp);
+			if (err < 0) {
+				SNDERR("field %s is not a boolean", id);
+				free(tmp);
+				goto _err;
+			}
+			lock = err;
+			free(tmp);
+			continue;
+		}
+		if (strcmp(id, "preserve") == 0) {
+			if ((err = snd_config_get_ascii(n, &tmp)) < 0) {
+				SNDERR("field %s has an invalid type", id);
+				goto _err;
+			}
+			err = snd_config_get_bool_ascii(tmp);
+			if (err < 0) {
+				SNDERR("field %s is not a boolean", id);
+				free(tmp);
+				goto _err;
+			}
+			preserve = err;
+			free(tmp);
+			continue;
+		}
+		if (strcmp(id, "value") == 0) {
+			value = n;
+			continue;
+		}
+		if (strcmp(id, "mask") == 0) {
+			mask = n;
+			continue;
+		}
+		if (strcmp(id, "optional") == 0) {
+			if ((err = snd_config_get_ascii(n, &tmp)) < 0) {
+				SNDERR("field %s has an invalid type", id);
+				goto _err;
+			}
+			err = snd_config_get_bool_ascii(tmp);
+			if (err < 0) {
+				SNDERR("field %s is not a boolean", id);
+				free(tmp);
+				goto _err;
+			}
+			optional = err;
+			free(tmp);
+			continue;
+		}
+		SNDERR("Unknown field %s", id);
+		return -EINVAL;
+	}
+	if (name == NULL) {
+		SNDERR("Missing control name");
+		err = -EINVAL;
+		goto _err;
+	}
+	if (value == NULL) {
+		SNDERR("Missing control value");
+		err = -EINVAL;
+		goto _err;
+	}
+	if (device < 0)
+		device = 0;
+	if (subdevice < 0)
+		subdevice = 0;
+	elem = calloc(1, sizeof(*elem));
+	if (!elem)
+		return -ENOMEM;
+	err = snd_ctl_elem_id_malloc(&elem->id);
+	if (err < 0)
+		goto _err;
+	err = snd_ctl_elem_info_malloc(&elem->info);
+	if (err < 0)
+		goto _err;
+	err = snd_ctl_elem_value_malloc(&elem->val);
+	if (err < 0)
+		goto _err;
+	err = snd_ctl_elem_value_malloc(&elem->mask);
+	if (err < 0)
+		goto _err;
+	err = snd_ctl_elem_value_malloc(&elem->old);
+	if (err < 0)
+		goto _err;
+	elem->lock = lock;
+	elem->preserve = preserve;
+	snd_ctl_elem_id_set_interface(elem->id, iface);
+	snd_ctl_elem_id_set_name(elem->id, name);
+	snd_ctl_elem_id_set_index(elem->id, index);
+	snd_ctl_elem_id_set_device(elem->id, device);
+	snd_ctl_elem_id_set_subdevice(elem->id, subdevice);
+	snd_ctl_elem_info_set_id(elem->info, elem->id);
+	err = snd_ctl_elem_info(h->ctl, elem->info);
+	if (err < 0) {
+		if (! optional)
+			SNDERR("Cannot obtain info for CTL elem (%s,'%s',%li,%li,%li): %s", snd_ctl_elem_iface_name(iface), name, index, device, subdevice, snd_strerror(err));
+		goto _err;
+	}
+	snd_ctl_elem_value_set_id(elem->val, elem->id);
+	snd_ctl_elem_value_set_id(elem->old, elem->id);
+	if (mask) {
+		err = snd_config_get_ctl_elem_value(value, h->ctl, elem->val, NULL, elem->info);
+		if (err < 0)
+			goto _err;
+		err = snd_config_get_ctl_elem_value(mask, h->ctl, elem->mask, NULL, elem->info);
+		if (err < 0)
+			goto _err;
+	} else {
+		err = snd_config_get_ctl_elem_value(value, h->ctl, elem->val, elem->mask, elem->info);
+		if (err < 0)
+			goto _err;
+	}
+		
+	err = snd_config_get_ctl_elem_value(value, h->ctl, elem->val, elem->mask, elem->info);
+	if (err < 0)
+		goto _err;
+	list_add_tail(&elem->list, &h->elems);
+
+ _err:
+ 	if (err < 0 && elem) {
+		if (elem->id)
+			snd_ctl_elem_id_free(elem->id);
+		if (elem->info)
+			snd_ctl_elem_info_free(elem->info);
+		if (elem->val)
+			snd_ctl_elem_value_free(elem->val);
+		if (elem->mask)
+			snd_ctl_elem_value_free(elem->mask);
+		if (elem->old)
+			snd_ctl_elem_value_free(elem->old);
+		free(elem);
+		if (err != -ENOMEM && optional)
+			err = 0; /* ignore the error */
+	}
+	if (conf)
+		snd_config_delete(conf);
+	return err;
+}
+
+/**
+ * \brief Build setup control handle
+ * \param sctl Result - setup control handle
+ * \param handle Master control handle
+ * \param conf Setup configuration
+ * \param private_data Private data for runtime evaluation
+ * \param mode Build mode - SND_SCTL_xxxx
+ * \result zero if success, otherwise a negative error code
+ */
+int snd_sctl_build(snd_sctl_t **sctl, snd_ctl_t *handle, snd_config_t *conf, snd_config_t *private_data, int mode)
+{
+	snd_sctl_t *h;
+	snd_config_iterator_t i, next;
+	int err;
+
+	assert(sctl);
+	assert(handle);
+	assert(conf);
+	*sctl = NULL;
+	if (snd_config_get_type(conf) != SND_CONFIG_TYPE_COMPOUND)
+		return -EINVAL;
+	h = calloc(1, sizeof(*h));
+	if (!h) {
+		if (mode & SND_SCTL_NOFREE)
+			return -ENOMEM;
+		snd_ctl_close(handle);
+		return -ENOMEM;
+	}
+	h->mode = mode;
+	h->ctl = handle;
+	INIT_LIST_HEAD(&h->elems);
+	snd_config_for_each(i, next, conf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		err = add_elem(h, n, private_data);
+		if (err < 0) {
+			free_elems(h);
+			return err;
+		}
+	}
+	*sctl = h;
+	return 0;
+}
+
+/**
+ * \brief Free setup control handle
+ * \param sctl Setup control handle
+ * \result zero if success, otherwise a negative error code
+ */
+int snd_sctl_free(snd_sctl_t *sctl)
+{
+	assert(sctl);
+	return free_elems(sctl);
+}
diff --git a/src/control/tlv.c b/src/control/tlv.c
new file mode 100644
index 0000000..f7c9976
--- /dev/null
+++ b/src/control/tlv.c
@@ -0,0 +1,502 @@
+/**
+ * \file control/tlv.c
+ * \brief dB conversion functions from control TLV information
+ * \author Takashi Iwai <tiwai@suse.de>
+ * \date 2007
+ */
+/*
+ *  Control Interface - dB conversion functions from control TLV information
+ *
+ *  Copyright (c) 2007 Takashi Iwai <tiwai@suse.de>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#ifndef HAVE_SOFT_FLOAT
+#include <math.h>
+#endif
+#include "control_local.h"
+
+#ifndef DOC_HIDDEN
+/* convert to index of integer array */
+#define int_index(size)	(((size) + sizeof(int) - 1) / sizeof(int))
+/* max size of a TLV entry for dB information (including compound one) */
+#define MAX_TLV_RANGE_SIZE	256
+#endif
+
+/**
+ * \brief Parse TLV stream and retrieve dB information
+ * \param tlv the TLV source
+ * \param tlv_size the byte size of TLV source
+ * \param db_tlvp the pointer stored the dB TLV information
+ * \return the byte size of dB TLV information if found in the given
+ *   TLV source, or a negative error code.
+ *
+ * This function parses the given TLV source and stores the TLV start
+ * point if the TLV information regarding dB conversion is found.
+ * The stored TLV pointer can be passed to the convesion functions
+ * #snd_tlv_convert_to_dB(), #snd_tlv_convert_from_dB() and
+ * #snd_tlv_get_dB_range().
+ */
+int snd_tlv_parse_dB_info(unsigned int *tlv,
+			  unsigned int tlv_size,
+			  unsigned int **db_tlvp)
+{
+	unsigned int type;
+	unsigned int size;
+	int err;
+
+	*db_tlvp = NULL;
+	type = tlv[0];
+	size = tlv[1];
+	tlv_size -= 2 * sizeof(int);
+	if (size > tlv_size) {
+		SNDERR("TLV size error");
+		return -EINVAL;
+	}
+	switch (type) {
+	case SND_CTL_TLVT_CONTAINER:
+		size = int_index(size) * sizeof(int);
+		tlv += 2;
+		while (size > 0) {
+			unsigned int len;
+			err = snd_tlv_parse_dB_info(tlv, size, db_tlvp);
+			if (err < 0)
+				return err; /* error */
+			if (err > 0)
+				return err; /* found */
+			len = int_index(tlv[1]) + 2;
+			size -= len * sizeof(int);
+			tlv += len;
+		}
+		break;
+	case SND_CTL_TLVT_DB_SCALE:
+	case SND_CTL_TLVT_DB_MINMAX:
+	case SND_CTL_TLVT_DB_MINMAX_MUTE:
+#ifndef HAVE_SOFT_FLOAT
+	case SND_CTL_TLVT_DB_LINEAR:
+#endif
+	case SND_CTL_TLVT_DB_RANGE: {
+		unsigned int minsize;
+		if (type == SND_CTL_TLVT_DB_RANGE)
+			minsize = 4 * sizeof(int);
+		else
+			minsize = 2 * sizeof(int);
+		if (size < minsize) {
+			SNDERR("Invalid dB_scale TLV size");
+			return -EINVAL;
+		}
+		if (size > MAX_TLV_RANGE_SIZE) {
+			SNDERR("Too big dB_scale TLV size: %d", size);
+			return -EINVAL;
+		}
+		*db_tlvp = tlv;
+		return size + sizeof(int) * 2;
+	}
+	default:
+		break;
+	}
+	return -EINVAL; /* not found */
+}
+
+/**
+ * \brief Get the dB min/max values
+ * \param tlv the TLV source returned by #snd_tlv_parse_dB_info()
+ * \param rangemin the minimum value of the raw volume
+ * \param rangemax the maximum value of the raw volume
+ * \param min the pointer to store the minimum dB value (in 0.01dB unit)
+ * \param max the pointer to store the maximum dB value (in 0.01dB unit)
+ * \return 0 if successful, or a negative error code
+ */
+int snd_tlv_get_dB_range(unsigned int *tlv, long rangemin, long rangemax,
+			 long *min, long *max)
+{
+	int err;
+
+	switch (tlv[0]) {
+	case SND_CTL_TLVT_DB_RANGE: {
+		unsigned int pos, len;
+		len = int_index(tlv[1]);
+		if (len > MAX_TLV_RANGE_SIZE)
+			return -EINVAL;
+		pos = 2;
+		while (pos + 4 <= len) {
+			long rmin, rmax;
+			long submin, submax;
+			submin = (int)tlv[pos];
+			submax = (int)tlv[pos + 1];
+			if (rangemax < submax)
+				submax = rangemax;
+			err = snd_tlv_get_dB_range(tlv + pos + 2,
+						   submin, submax,
+						   &rmin, &rmax);
+			if (err < 0)
+				return err;
+			if (pos > 2) {
+				if (rmin < *min)
+					*min = rmin;
+				if (rmax > *max)
+					*max = rmax;
+			} else {
+				*min = rmin;
+				*max = rmax;
+			}
+			if (rangemax == submax)
+				return 0;
+			pos += int_index(tlv[pos + 3]) + 4;
+		}
+		return 0;
+	}
+	case SND_CTL_TLVT_DB_SCALE: {
+		int step;
+		if (tlv[3] & 0x10000)
+			*min = SND_CTL_TLV_DB_GAIN_MUTE;
+		else
+			*min = (int)tlv[2];
+		step = (tlv[3] & 0xffff);
+		*max = (int)tlv[2] + step * (rangemax - rangemin);
+		return 0;
+	}
+	case SND_CTL_TLVT_DB_MINMAX:
+	case SND_CTL_TLVT_DB_LINEAR:
+		*min = (int)tlv[2];
+		*max = (int)tlv[3];
+		return 0;
+	case SND_CTL_TLVT_DB_MINMAX_MUTE:
+		*min = SND_CTL_TLV_DB_GAIN_MUTE;
+		*max = (int)tlv[3];
+		return 0;
+	}
+	return -EINVAL;
+}
+
+/**
+ * \brief Convert the given raw volume value to a dB gain
+ * \param tlv the TLV source returned by #snd_tlv_parse_dB_info()
+ * \param rangemin the minimum value of the raw volume
+ * \param rangemax the maximum value of the raw volume
+ * \param volume the raw volume value to convert
+ * \param db_gain the dB gain (in 0.01dB unit)
+ * \return 0 if successful, or a negative error code
+ */
+int snd_tlv_convert_to_dB(unsigned int *tlv, long rangemin, long rangemax,
+			  long volume, long *db_gain)
+{
+	switch (tlv[0]) {
+	case SND_CTL_TLVT_DB_RANGE: {
+		unsigned int pos, len;
+		len = int_index(tlv[1]);
+		if (len > MAX_TLV_RANGE_SIZE)
+			return -EINVAL;
+		pos = 2;
+		while (pos + 4 <= len) {
+			rangemin = (int)tlv[pos];
+			rangemax = (int)tlv[pos + 1];
+			if (volume >= rangemin && volume <= rangemax)
+				return snd_tlv_convert_to_dB(tlv + pos + 2,
+							     rangemin, rangemax,
+							     volume, db_gain);
+			pos += int_index(tlv[pos + 3]) + 4;
+		}
+		return -EINVAL;
+	}
+	case SND_CTL_TLVT_DB_SCALE: {
+		int min, step, mute;
+		min = tlv[2];
+		step = (tlv[3] & 0xffff);
+		mute = (tlv[3] >> 16) & 1;
+		if (mute && volume <= rangemin)
+			*db_gain = SND_CTL_TLV_DB_GAIN_MUTE;
+		else
+			*db_gain = (volume - rangemin) * step + min;
+		return 0;
+	}
+	case SND_CTL_TLVT_DB_MINMAX:
+	case SND_CTL_TLVT_DB_MINMAX_MUTE: {
+		int mindb, maxdb;
+		mindb = tlv[2];
+		maxdb = tlv[3];
+		if (volume <= rangemin || rangemax <= rangemin) {
+			if (tlv[0] == SND_CTL_TLVT_DB_MINMAX_MUTE)
+				*db_gain = SND_CTL_TLV_DB_GAIN_MUTE;
+			else
+				*db_gain = mindb;
+		} else if (volume >= rangemax)
+			*db_gain = maxdb;
+		else
+			*db_gain = (maxdb - mindb) * (volume - rangemin) /
+				(rangemax - rangemin) + mindb;
+		return 0;
+	}
+#ifndef HAVE_SOFT_FLOAT
+	case SND_CTL_TLVT_DB_LINEAR: {
+		int mindb = tlv[2];
+		int maxdb = tlv[3];
+		if (volume <= rangemin || rangemax <= rangemin)
+			*db_gain = mindb;
+		else if (volume >= rangemax)
+			*db_gain = maxdb;
+		else {
+			double val = (double)(volume - rangemin) /
+				(double)(rangemax - rangemin);
+			if (mindb <= SND_CTL_TLV_DB_GAIN_MUTE)
+				*db_gain = (long)(100.0 * 20.0 * log10(val)) +
+					maxdb;
+			else {
+				/* FIXME: precalculate and cache these values */
+				double lmin = pow(10.0, mindb/2000.0);
+				double lmax = pow(10.0, maxdb/2000.0);
+				val = (lmax - lmin) * val + lmin;
+				*db_gain = (long)(100.0 * 20.0 * log10(val));
+			}
+		}
+		return 0;
+	}
+#endif
+	}
+	return -EINVAL;
+}
+
+/**
+ * \brief Convert from dB gain to the corresponding raw value
+ * \param tlv the TLV source returned by #snd_tlv_parse_dB_info()
+ * \param rangemin the minimum value of the raw volume
+ * \param rangemax the maximum value of the raw volume
+ * \param db_gain the dB gain to convert (in 0.01dB unit)
+ * \param value the pointer to store the converted raw volume value
+ * \param xdir the direction for round-up. The value is round up
+ *        when this is positive.
+ * \return 0 if successful, or a negative error code
+ */
+int snd_tlv_convert_from_dB(unsigned int *tlv, long rangemin, long rangemax,
+			    long db_gain, long *value, int xdir)
+{
+	switch (tlv[0]) {
+	case SND_CTL_TLVT_DB_RANGE: {
+		long dbmin, dbmax, prev_rangemax;
+		unsigned int pos, len;
+		len = int_index(tlv[1]);
+		if (len > MAX_TLV_RANGE_SIZE)
+			return -EINVAL;
+		if (snd_tlv_get_dB_range(tlv, rangemin, rangemax,
+					 &dbmin, &dbmax))
+			return -EINVAL;
+		if (db_gain <= dbmin) {
+			*value = rangemin;
+			return 0;
+		} else if (db_gain >= dbmax) {
+			*value = rangemax;
+			return 0;
+		}
+		pos = 2;
+		prev_rangemax = 0;
+		while (pos + 4 <= len) {
+			rangemin = (int)tlv[pos];
+			rangemax = (int)tlv[pos + 1];
+			if (!snd_tlv_get_dB_range(tlv + pos + 2,
+						  rangemin, rangemax,
+						  &dbmin, &dbmax) &&
+			    db_gain >= dbmin && db_gain <= dbmax)
+				return snd_tlv_convert_from_dB(tlv + pos + 2,
+							       rangemin, rangemax,
+							       db_gain, value, xdir);
+			else if (db_gain < dbmin) {
+				*value = xdir ? rangemin : prev_rangemax;
+				return 0;
+			}
+			prev_rangemax = rangemax;
+			pos += int_index(tlv[pos + 3]) + 4;
+		}
+		return -EINVAL;
+	}
+	case SND_CTL_TLVT_DB_SCALE: {
+		int min, step, max;
+		min = tlv[2];
+		step = (tlv[3] & 0xffff);
+		max = min + (int)(step * (rangemax - rangemin));
+		if (db_gain <= min)
+			if (db_gain > SND_CTL_TLV_DB_GAIN_MUTE && xdir > 0 &&
+			    (tlv[3] & 0x10000))
+				*value = rangemin + 1;
+			else
+				*value = rangemin;
+		else if (db_gain >= max)
+			*value = rangemax;
+		else {
+			long v = (db_gain - min) * (rangemax - rangemin);
+			if (xdir > 0)
+				v += (max - min) - 1;
+			v = v / (max - min) + rangemin;
+			*value = v;
+		}
+		return 0;
+	}
+	case SND_CTL_TLVT_DB_MINMAX:
+	case SND_CTL_TLVT_DB_MINMAX_MUTE: {
+		int min, max;
+		min = tlv[2];
+		max = tlv[3];
+		if (db_gain <= min)
+			if (db_gain > SND_CTL_TLV_DB_GAIN_MUTE && xdir > 0 &&
+			    tlv[0] == SND_CTL_TLVT_DB_MINMAX_MUTE)
+				*value = rangemin + 1;
+			else
+				*value = rangemin;
+		else if (db_gain >= max)
+			*value = rangemax;
+		else {
+			long v = (db_gain - min) * (rangemax - rangemin);
+			if (xdir > 0)
+				v += (max - min) - 1;
+			v = v / (max - min) + rangemin;
+			*value = v;
+		}
+		return 0;
+	}
+#ifndef HAVE_SOFT_FLOAT
+	case SND_CTL_TLVT_DB_LINEAR: {
+		int min, max;
+		min = tlv[2];
+		max = tlv[3];
+		if (db_gain <= min)
+			*value = rangemin;
+		else if (db_gain >= max)
+			*value = rangemax;
+		else {
+			/* FIXME: precalculate and cache vmin and vmax */
+			double vmin, vmax, v;
+			vmin = (min <= SND_CTL_TLV_DB_GAIN_MUTE) ? 0.0 :
+				pow(10.0,  (double)min / 2000.0);
+			vmax = !max ? 1.0 : pow(10.0,  (double)max / 2000.0);
+			v = pow(10.0, (double)db_gain / 2000.0);
+			v = (v - vmin) * (rangemax - rangemin) / (vmax - vmin);
+			if (xdir > 0)
+				v = ceil(v);
+			*value = (long)v + rangemin;
+		}
+		return 0;
+	}
+#endif
+	default:
+		break;
+	}
+	return -EINVAL;
+}
+
+#ifndef DOC_HIDDEN
+#define TEMP_TLV_SIZE		4096
+struct tlv_info {
+	long minval, maxval;
+	unsigned int *tlv;
+	unsigned int buf[TEMP_TLV_SIZE];
+};
+#endif
+
+static int get_tlv_info(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
+			struct tlv_info *rec)
+{
+	snd_ctl_elem_info_t *info;
+	int err;
+
+	snd_ctl_elem_info_alloca(&info);
+	snd_ctl_elem_info_set_id(info, id);
+	err = snd_ctl_elem_info(ctl, info);
+	if (err < 0)
+		return err;
+	if (!snd_ctl_elem_info_is_tlv_readable(info))
+		return -EINVAL;
+	if (snd_ctl_elem_info_get_type(info) != SND_CTL_ELEM_TYPE_INTEGER)
+		return -EINVAL;
+	rec->minval = snd_ctl_elem_info_get_min(info);
+	rec->maxval = snd_ctl_elem_info_get_max(info);
+	err = snd_ctl_elem_tlv_read(ctl, id, rec->buf, sizeof(rec->buf));
+	if (err < 0)
+		return err;
+	err = snd_tlv_parse_dB_info(rec->buf, sizeof(rec->buf), &rec->tlv);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+/**
+ * \brief Get the dB min/max values on the given control element
+ * \param ctl the control handler
+ * \param id the element id
+ * \param min the pointer to store the minimum dB value (in 0.01dB unit)
+ * \param max the pointer to store the maximum dB value (in 0.01dB unit)
+ * \return 0 if successful, or a negative error code
+ */
+int snd_ctl_get_dB_range(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
+			 long *min, long *max)
+{
+	struct tlv_info info;
+	int err;
+
+	err = get_tlv_info(ctl, id, &info);
+	if (err < 0)
+		return err;
+	return snd_tlv_get_dB_range(info.tlv, info.minval, info.maxval,
+				    min, max);
+}
+
+/**
+ * \brief Convert the volume value to dB on the given control element
+ * \param ctl the control handler
+ * \param id the element id
+ * \param volume the raw volume value to convert
+ * \param db_gain the dB gain (in 0.01dB unit)
+ * \return 0 if successful, or a negative error code
+ */
+int snd_ctl_convert_to_dB(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
+			  long volume, long *db_gain)
+{
+	struct tlv_info info;
+	int err;
+
+	err = get_tlv_info(ctl, id, &info);
+	if (err < 0)
+		return err;
+	return snd_tlv_convert_to_dB(info.tlv, info.minval, info.maxval,
+				     volume, db_gain);
+}
+
+/**
+ * \brief Convert from dB gain to the raw volume value on the given control element
+ * \param ctl the control handler
+ * \param id the element id
+ * \param db_gain the dB gain to convert (in 0.01dB unit)
+ * \param value the pointer to store the converted raw volume value
+ * \param xdir the direction for round-up. The value is round up
+ *        when this is positive.
+ * \return 0 if successful, or a negative error code
+ */
+int snd_ctl_convert_from_dB(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
+			    long db_gain, long *value, int xdir)
+{
+	struct tlv_info info;
+	int err;
+
+	err = get_tlv_info(ctl, id, &info);
+	if (err < 0)
+		return err;
+	return snd_tlv_convert_from_dB(info.tlv, info.minval, info.maxval,
+				       db_gain, value, xdir);
+}
diff --git a/src/dlmisc.c b/src/dlmisc.c
new file mode 100644
index 0000000..ecbbe8d
--- /dev/null
+++ b/src/dlmisc.c
@@ -0,0 +1,311 @@
+/**
+ * \file dlmisc.c
+ * \brief dynamic loader helpers
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \date 2001
+ *
+ * Dynamic loader helpers
+ */
+/*
+ *  Dynamic loader helpers
+ *  Copyright (c) 2000 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include "list.h"
+#include "local.h"
+#ifdef HAVE_LIBPTHREAD
+#include <pthread.h>
+#endif
+
+#ifndef DOC_HIDDEN
+#ifndef PIC
+struct snd_dlsym_link *snd_dlsym_start = NULL;
+#endif
+#endif
+
+/**
+ * \brief Opens a dynamic library - ALSA wrapper for \c dlopen.
+ * \param name name of the library, similar to \c dlopen.
+ * \param mode mode flags, similar to \c dlopen.
+ * \return Library handle if successful, otherwise \c NULL.
+ *
+ * This function can emulate dynamic linking for the static build of
+ * the alsa-lib library. In that case, \p name is set to \c NULL.
+ */
+void *snd_dlopen(const char *name, int mode)
+{
+#ifndef PIC
+	if (name == NULL)
+		return &snd_dlsym_start;
+#else
+#ifdef HAVE_LIBDL
+	if (name == NULL) {
+		static const char * self = NULL;
+		if (self == NULL) {
+			Dl_info dlinfo;
+			if (dladdr(snd_dlopen, &dlinfo) > 0)
+				self = dlinfo.dli_fname;
+		}
+		name = self;
+	}
+#endif
+#endif
+#ifdef HAVE_LIBDL
+	return dlopen(name, mode);
+#else
+	return NULL;
+#endif
+}
+
+/**
+ * \brief Closes a dynamic library - ALSA wrapper for \c dlclose.
+ * \param handle Library handle, similar to \c dlclose.
+ * \return Zero if successful, otherwise an error code.
+ *
+ * This function can emulate dynamic linking for the static build of
+ * the alsa-lib library.
+ */
+int snd_dlclose(void *handle)
+{
+#ifndef PIC
+	if (handle == &snd_dlsym_start)
+		return 0;
+#endif
+#ifdef HAVE_LIBDL
+	return dlclose(handle);
+#else
+	return 0;
+#endif
+}
+
+/**
+ * \brief Verifies a dynamically loaded symbol.
+ * \param handle Library handle, similar to \c dlsym.
+ * \param name Symbol name.
+ * \param version Version of the symbol.
+ * \return Zero is successful, otherwise a negative error code.
+ *
+ * This function checks that the symbol with the version appended to its name
+ * does exist in the library.
+ */
+static int snd_dlsym_verify(void *handle, const char *name, const char *version)
+{
+#ifdef HAVE_LIBDL
+	int res;
+	char *vname;
+	
+	if (handle == NULL)
+		return -EINVAL;
+	vname = alloca(1 + strlen(name) + strlen(version) + 1);
+	if (vname == NULL)
+		return -ENOMEM;
+	vname[0] = '_';
+	strcpy(vname + 1, name);
+	strcat(vname, version);
+	res = dlsym(handle, vname) == NULL ? -ENOENT : 0;
+	// printf("dlsym verify: %i, vname = '%s'\n", res, vname);
+	if (res < 0)
+		SNDERR("unable to verify version for symbol %s", name);
+	return res;
+#else
+	return 0;
+#endif
+}
+
+/**
+ * \brief Resolves a symbol from a dynamic library - ALSA wrapper for \c dlsym.
+ * \param handle Library handle, similar to \c dlsym.
+ * \param name Symbol name.
+ * \param version Version of the symbol.
+ *
+ * This function can emulate dynamic linking for the static build of
+ * the alsa-lib library.
+ *
+ * This special version of the \c dlsym function checks also the version
+ * of the symbol. A versioned symbol should be defined using the
+ * #SND_DLSYM_BUILD_VERSION macro.
+ */
+void *snd_dlsym(void *handle, const char *name, const char *version)
+{
+	int err;
+
+#ifndef PIC
+	if (handle == &snd_dlsym_start) {
+		/* it's the funny part: */
+		/* we are looking for a symbol in a static library */
+		struct snd_dlsym_link *link = snd_dlsym_start;
+		while (link) {
+			if (!strcmp(name, link->dlsym_name))
+				return (void *)link->dlsym_ptr;
+			link = link->next;
+		}
+		return NULL;
+	}
+#endif
+#ifdef HAVE_LIBDL
+	if (version) {
+		err = snd_dlsym_verify(handle, name, version);
+		if (err < 0)
+			return NULL;
+	}
+	return dlsym(handle, name);
+#else
+	return NULL;
+#endif
+}
+
+/*
+ * dlobj cache
+ */
+
+#ifndef DOC_HIDDEN
+struct dlobj_cache {
+	const char *lib;
+	const char *name;
+	void *dlobj;
+	void *func;
+	unsigned int refcnt;
+	struct list_head list;
+};
+
+#ifdef HAVE_LIBPTHREAD
+static pthread_mutex_t snd_dlobj_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+static inline void snd_dlobj_lock(void)
+{
+	pthread_mutex_lock(&snd_dlobj_mutex);
+}
+
+static inline void snd_dlobj_unlock(void)
+{
+	pthread_mutex_unlock(&snd_dlobj_mutex);
+}
+#else
+static inline void snd_dlobj_lock(void) {}
+static inline void snd_dlobj_unlock(void) {}
+#endif
+
+static LIST_HEAD(pcm_dlobj_list);
+
+void *snd_dlobj_cache_get(const char *lib, const char *name,
+			  const char *version, int verbose)
+{
+	struct list_head *p;
+	struct dlobj_cache *c;
+	void *func, *dlobj = NULL;
+	int dlobj_close = 0;
+
+	snd_dlobj_lock();
+	list_for_each(p, &pcm_dlobj_list) {
+		c = list_entry(p, struct dlobj_cache, list);
+		if (c->lib && lib && strcmp(c->lib, lib) != 0)
+			continue;
+		if (!c->lib && lib)
+			continue;
+		if (!lib && c->lib)
+			continue;
+		dlobj = c->dlobj;
+		if (strcmp(c->name, name) == 0) {
+			c->refcnt++;
+			func = c->func;
+			snd_dlobj_unlock();
+			return func;
+		}
+	}
+	if (dlobj == NULL) {
+		dlobj = snd_dlopen(lib, RTLD_NOW);
+		if (dlobj == NULL) {
+			if (verbose)
+				SNDERR("Cannot open shared library %s",
+						lib ? lib : "[builtin]");
+			snd_dlobj_unlock();
+			return NULL;
+		}
+		dlobj_close = 1;
+	}
+	func = snd_dlsym(dlobj, name, version);
+	if (func == NULL) {
+		if (verbose)
+			SNDERR("symbol %s is not defined inside %s",
+					name, lib ? lib : "[builtin]");
+		goto __err;
+	}
+	c = malloc(sizeof(*c));
+	if (! c)
+		goto __err;
+	c->refcnt = 1;
+	c->lib = lib ? strdup(lib) : NULL;
+	c->name = strdup(name);
+	if ((lib && ! c->lib) || ! c->name) {
+		free((void *)c->name);
+		free((void *)c->lib);
+		free(c);
+	      __err:
+		if (dlobj_close)
+			snd_dlclose(dlobj);
+		snd_dlobj_unlock();
+		return NULL;
+	}
+	c->dlobj = dlobj;
+	c->func = func;
+	list_add_tail(&c->list, &pcm_dlobj_list);
+	snd_dlobj_unlock();
+	return func;
+}
+
+int snd_dlobj_cache_put(void *func)
+{
+	struct list_head *p;
+	struct dlobj_cache *c;
+	unsigned int refcnt;
+
+	snd_dlobj_lock();
+	list_for_each(p, &pcm_dlobj_list) {
+		c = list_entry(p, struct dlobj_cache, list);
+		if (c->func == func) {
+			refcnt = c->refcnt;
+			if (c->refcnt > 0)
+				c->refcnt--;
+			snd_dlobj_unlock();
+			return refcnt == 1 ? 0 : -EINVAL;
+		}
+	}
+	snd_dlobj_unlock();
+	return -ENOENT;
+}
+
+void snd_dlobj_cache_cleanup(void)
+{
+	struct list_head *p, *npos;
+	struct dlobj_cache *c;
+
+	snd_dlobj_lock();
+	list_for_each_safe(p, npos, &pcm_dlobj_list) {
+		c = list_entry(p, struct dlobj_cache, list);
+		if (c->refcnt == 0) {
+			list_del(p);
+			snd_dlclose(c->dlobj);
+			free((void *)c->name); /* shut up gcc warning */
+			free((void *)c->lib); /* shut up gcc warning */
+			free(c);
+		}
+	}
+	snd_dlobj_unlock();
+}
+#endif
diff --git a/src/error.c b/src/error.c
new file mode 100644
index 0000000..7d5f509
--- /dev/null
+++ b/src/error.c
@@ -0,0 +1,150 @@
+/**
+ * \file error.c
+ * \brief Error code handling routines
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \date 1998-2001
+ *
+ * Error code handling routines.
+ */
+/*
+ *  Copyright (c) 1998 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *  snd_strerror routine needs to be recoded for the locale support
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include "local.h"
+
+/**
+ * Array of error codes in US ASCII.
+ */
+static const char *snd_error_codes[] =
+{
+	"Sound protocol is not compatible"
+};
+
+/**
+ * \brief Returns the message for an error code.
+ * \param errnum The error code number, which must be a system error code
+ *               or an ALSA error code.
+ * \return The ASCII description of the given numeric error code.
+ */
+const char *snd_strerror(int errnum)
+{
+	if (errnum < 0)
+		errnum = -errnum;
+	if (errnum < SND_ERROR_BEGIN)
+		return (const char *) strerror(errnum);
+	errnum -= SND_ERROR_BEGIN;
+	if ((unsigned int) errnum >= sizeof(snd_error_codes) / sizeof(const char *))
+		 return "Unknown error";
+	return snd_error_codes[errnum];
+}
+
+/**
+ * \brief The default error handler function.
+ * \param file The filename where the error was hit.
+ * \param line The line number.
+ * \param function The function name.
+ * \param err The error code.
+ * \param fmt The message (including the format characters).
+ * \param ... Optional arguments.
+ *
+ * Prints the error message including location to \c stderr.
+ */
+static void snd_lib_error_default(const char *file, int line, const char *function, int err, const char *fmt, ...)
+{
+	va_list arg;
+	va_start(arg, fmt);
+	fprintf(stderr, "ALSA lib %s:%i:(%s) ", file, line, function);
+	vfprintf(stderr, fmt, arg);
+	if (err)
+		fprintf(stderr, ": %s", snd_strerror(err));
+	putc('\n', stderr);
+	va_end(arg);
+}
+
+/**
+ * \ingroup Error
+ * Pointer to the error handler function.
+ * For internal use only.
+ */
+snd_lib_error_handler_t snd_lib_error = snd_lib_error_default;
+
+/**
+ * \brief Sets the error handler.
+ * \param handler The pointer to the new error handler function.
+ *
+ * This function sets a new error handler, or (if \c handler is \c NULL)
+ * the default one which prints the error messages to \c stderr.
+ */
+int snd_lib_error_set_handler(snd_lib_error_handler_t handler)
+{
+	snd_lib_error = handler == NULL ? snd_lib_error_default : handler;
+#ifndef NDEBUG
+	if (snd_lib_error != snd_lib_error_default)
+		snd_err_msg = snd_lib_error;
+#endif
+	return 0;
+}
+
+/**
+ * \brief Returns the ALSA sound library version in ASCII format
+ * \return The ASCII description of the used ALSA sound library.
+ */
+const char *snd_asoundlib_version(void)
+{
+	return SND_LIB_VERSION_STR;
+}
+
+#ifndef NDEBUG
+/*
+ * internal error handling
+ */
+static void snd_err_msg_default(const char *file, int line, const char *function, int err, const char *fmt, ...)
+{
+	va_list arg;
+	const char *verbose;
+	
+	verbose = getenv("LIBASOUND_DEBUG");
+	if (! verbose || ! *verbose)
+		return;
+	va_start(arg, fmt);
+	fprintf(stderr, "ALSA lib %s:%i:(%s) ", file, line, function);
+	vfprintf(stderr, fmt, arg);
+	if (err)
+		fprintf(stderr, ": %s", snd_strerror(err));
+	putc('\n', stderr);
+	va_end(arg);
+#ifdef ALSA_DEBUG_ASSERT
+	verbose = getenv("LIBASOUND_DEBUG_ASSERT");
+	if (verbose && *verbose)
+		assert(0);
+#endif
+}
+
+/**
+ * The ALSA error message handler
+ */
+snd_lib_error_handler_t snd_err_msg = snd_err_msg_default;
+
+#endif
diff --git a/src/hwdep/Makefile.am b/src/hwdep/Makefile.am
new file mode 100644
index 0000000..0b626b9
--- /dev/null
+++ b/src/hwdep/Makefile.am
@@ -0,0 +1,8 @@
+EXTRA_LTLIBRARIES=libhwdep.la
+
+libhwdep_la_SOURCES = hwdep.c hwdep_hw.c hwdep_symbols.c
+noinst_HEADERS = hwdep_local.h
+all: libhwdep.la
+
+
+INCLUDES=-I$(top_srcdir)/include
diff --git a/src/hwdep/hwdep.c b/src/hwdep/hwdep.c
new file mode 100644
index 0000000..5dc791c
--- /dev/null
+++ b/src/hwdep/hwdep.c
@@ -0,0 +1,761 @@
+/**
+ * \file hwdep/hwdep.c
+ * \brief HwDep Interface (hardware dependent)
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \date 2000-2001
+ *
+ * HwDep (hardware dependent) Interface is designed for individual hardware
+ * access. This interface does not cover any API specification.
+ */
+/*
+ *  Hardware dependent Interface - main file
+ *  Copyright (c) 2000 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include "hwdep_local.h"
+
+static int snd_hwdep_open_conf(snd_hwdep_t **hwdep,
+			       const char *name, snd_config_t *hwdep_root,
+			       snd_config_t *hwdep_conf, int mode)
+{
+	const char *str;
+	char buf[256];
+	int err;
+	snd_config_t *conf, *type_conf = NULL;
+	snd_config_iterator_t i, next;
+	const char *id;
+	const char *lib = NULL, *open_name = NULL;
+	int (*open_func)(snd_hwdep_t **, const char *, snd_config_t *, snd_config_t *, int) = NULL;
+#ifndef PIC
+	extern void *snd_hwdep_open_symbols(void);
+#endif
+	void *h = NULL;
+	if (snd_config_get_type(hwdep_conf) != SND_CONFIG_TYPE_COMPOUND) {
+		if (name)
+			SNDERR("Invalid type for HWDEP %s definition", name);
+		else
+			SNDERR("Invalid type for HWDEP definition");
+		return -EINVAL;
+	}
+	err = snd_config_search(hwdep_conf, "type", &conf);
+	if (err < 0) {
+		SNDERR("type is not defined");
+		return err;
+	}
+	err = snd_config_get_id(conf, &id);
+	if (err < 0) {
+		SNDERR("unable to get id");
+		return err;
+	}
+	err = snd_config_get_string(conf, &str);
+	if (err < 0) {
+		SNDERR("Invalid type for %s", id);
+		return err;
+	}
+	err = snd_config_search_definition(hwdep_root, "hwdep_type", str, &type_conf);
+	if (err >= 0) {
+		if (snd_config_get_type(type_conf) != SND_CONFIG_TYPE_COMPOUND) {
+			SNDERR("Invalid type for HWDEP type %s definition", str);
+			err = -EINVAL;
+			goto _err;
+		}
+		snd_config_for_each(i, next, type_conf) {
+			snd_config_t *n = snd_config_iterator_entry(i);
+			const char *id;
+			if (snd_config_get_id(n, &id) < 0)
+				continue;
+			if (strcmp(id, "comment") == 0)
+				continue;
+			if (strcmp(id, "lib") == 0) {
+				err = snd_config_get_string(n, &lib);
+				if (err < 0) {
+					SNDERR("Invalid type for %s", id);
+					goto _err;
+				}
+				continue;
+			}
+			if (strcmp(id, "open") == 0) {
+				err = snd_config_get_string(n, &open_name);
+				if (err < 0) {
+					SNDERR("Invalid type for %s", id);
+					goto _err;
+				}
+				continue;
+			}
+			SNDERR("Unknown field %s", id);
+			err = -EINVAL;
+			goto _err;
+		}
+	}
+	if (!open_name) {
+		open_name = buf;
+		snprintf(buf, sizeof(buf), "_snd_hwdep_%s_open", str);
+	}
+#ifndef PIC
+	snd_hwdep_open_symbols();
+#endif
+	h = snd_dlopen(lib, RTLD_NOW);
+	if (h)
+		open_func = snd_dlsym(h, open_name, SND_DLSYM_VERSION(SND_HWDEP_DLSYM_VERSION));
+	err = 0;
+	if (!h) {
+		SNDERR("Cannot open shared library %s", lib);
+		err = -ENOENT;
+	} else if (!open_func) {
+		SNDERR("symbol %s is not defined inside %s", open_name, lib);
+		snd_dlclose(h);
+		err = -ENXIO;
+	}
+       _err:
+	if (type_conf)
+		snd_config_delete(type_conf);
+	if (err >= 0) {
+		err = open_func(hwdep, name, hwdep_root, hwdep_conf, mode);
+		if (err >= 0) {
+			(*hwdep)->dl_handle = h;
+		} else {
+			snd_dlclose(h);
+		}
+	}
+	return err;
+}
+
+static int snd_hwdep_open_noupdate(snd_hwdep_t **hwdep, snd_config_t *root, const char *name, int mode)
+{
+	int err;
+	snd_config_t *hwdep_conf;
+	err = snd_config_search_definition(root, "hwdep", name, &hwdep_conf);
+	if (err < 0) {
+		SNDERR("Unknown HwDep %s", name);
+		return err;
+	}
+	err = snd_hwdep_open_conf(hwdep, name, root, hwdep_conf, mode);
+	snd_config_delete(hwdep_conf);
+	return err;
+}
+
+/**
+ * \brief Opens a new connection to the HwDep interface.
+ * \param hwdep Returned handle (NULL if not wanted)
+ * \param name ASCII identifier of the HwDep handle
+ * \param mode Open mode
+ * \return 0 on success otherwise a negative error code
+ *
+ * Opens a new connection to the HwDep interface specified with
+ * an ASCII identifier and mode.
+ */
+int snd_hwdep_open(snd_hwdep_t **hwdep, const char *name, int mode)
+{
+	int err;
+	assert(hwdep && name);
+	err = snd_config_update();
+	if (err < 0)
+		return err;
+	return snd_hwdep_open_noupdate(hwdep, snd_config, name, mode);
+}
+
+/**
+ * \brief Opens a new connection to the HwDep interface using local configuration
+ * \param hwdep Returned handle (NULL if not wanted)
+ * \param name ASCII identifier of the HwDep handle
+ * \param mode Open mode
+ * \param lconf The local configuration tree
+ * \return 0 on success otherwise a negative error code
+ *
+ * Opens a new connection to the HwDep interface specified with
+ * an ASCII identifier and mode.
+ */
+int snd_hwdep_open_lconf(snd_hwdep_t **hwdep, const char *name,
+			 int mode, snd_config_t *lconf)
+{
+	assert(hwdep && name && lconf);
+	return snd_hwdep_open_noupdate(hwdep, lconf, name, mode);
+}
+
+/**
+ * \brief close HwDep handle
+ * \param hwdep HwDep handle
+ * \return 0 on success otherwise a negative error code
+ *
+ * Closes the specified HwDep handle and frees all associated
+ * resources.
+ */
+int snd_hwdep_close(snd_hwdep_t *hwdep)
+{
+	int err;
+  	assert(hwdep);
+	err = hwdep->ops->close(hwdep);
+	if (hwdep->dl_handle)
+		snd_dlclose(hwdep->dl_handle);
+	free(hwdep->name);
+	free(hwdep);
+	return err;
+}
+
+/**
+ * \brief get identifier of HwDep handle
+ * \param hwdep a Hwdep handle
+ * \return ascii identifier of HwDep handle
+ *
+ * Returns the ASCII identifier of given HwDep handle. It's the same
+ * identifier specified in snd_hwdep_open().
+ */
+const char *snd_hwdep_name(snd_hwdep_t *hwdep)
+{
+	assert(hwdep);
+	return hwdep->name;
+}
+
+/**
+ * \brief get type of HwDep handle
+ * \param hwdep a HwDep handle
+ * \return type of HwDep handle
+ *
+ * Returns the type #snd_hwdep_type_t of given HwDep handle.
+ */
+snd_hwdep_type_t snd_hwdep_type(snd_hwdep_t *hwdep)
+{
+	assert(hwdep);
+	return hwdep->type;
+}
+
+/**
+ * \brief get count of poll descriptors for HwDep handle
+ * \param hwdep HwDep handle
+ * \return count of poll descriptors
+ */
+int snd_hwdep_poll_descriptors_count(snd_hwdep_t *hwdep)
+{
+	assert(hwdep);
+	return 1;
+}
+
+/**
+ * \brief get poll descriptors
+ * \param hwdep HwDep handle
+ * \param pfds array of poll descriptors
+ * \param space space in the poll descriptor array
+ * \return count of filled descriptors
+ */
+int snd_hwdep_poll_descriptors(snd_hwdep_t *hwdep, struct pollfd *pfds, unsigned int space)
+{
+	assert(hwdep);
+	if (space >= 1) {
+		pfds->fd = hwdep->poll_fd;
+		switch (hwdep->mode & O_ACCMODE) {
+		case O_WRONLY:
+			pfds->events = POLLOUT|POLLERR|POLLNVAL;
+			break;
+		case O_RDONLY:
+			pfds->events = POLLIN|POLLERR|POLLNVAL;
+			break;
+		case O_RDWR:
+			pfds->events = POLLOUT|POLLIN|POLLERR|POLLNVAL;
+			break;
+		default:
+			return -EIO;
+		}
+		return 1;
+	}
+	return 0;
+}
+
+/**
+ * \brief get returned events from poll descriptors
+ * \param hwdep HwDep  handle
+ * \param pfds array of poll descriptors
+ * \param nfds count of poll descriptors
+ * \param revents returned events
+ * \return zero if success, otherwise a negative error code
+ */
+int snd_hwdep_poll_descriptors_revents(snd_hwdep_t *hwdep, struct pollfd *pfds, unsigned int nfds, unsigned short *revents)
+{
+        assert(hwdep && pfds && revents);
+        if (nfds == 1) {
+                *revents = pfds->revents;
+                return 0;
+        }
+        return -EINVAL;
+}                                                                       
+                                                                       
+/**
+ * \brief set nonblock mode
+ * \param hwdep HwDep handle
+ * \param nonblock 0 = block, 1 = nonblock mode
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_hwdep_nonblock(snd_hwdep_t *hwdep, int nonblock)
+{
+	int err;
+	assert(hwdep);
+	if ((err = hwdep->ops->nonblock(hwdep, nonblock)) < 0)
+		return err;
+	if (nonblock)
+		hwdep->mode |= SND_HWDEP_OPEN_NONBLOCK;
+	else
+		hwdep->mode &= ~SND_HWDEP_OPEN_NONBLOCK;
+	return 0;
+}
+
+/**
+ * \brief get size of the snd_hwdep_info_t structure in bytes
+ * \return size of the snd_hwdep_info_t structure in bytes
+ */
+size_t snd_hwdep_info_sizeof()
+{
+	return sizeof(snd_hwdep_info_t);
+}
+
+/**
+ * \brief allocate a new snd_hwdep_info_t structure
+ * \param info returned pointer
+ * \return 0 on success otherwise a negative error code if fails
+ *
+ * Allocates a new snd_hwdep_info_t structure using the standard
+ * malloc C library function.
+ */
+int snd_hwdep_info_malloc(snd_hwdep_info_t **info)
+{
+	assert(info);
+	*info = calloc(1, sizeof(snd_hwdep_info_t));
+	if (!*info)
+		return -ENOMEM;
+	return 0;
+}
+
+/**
+ * \brief frees the snd_hwdep_info_t structure
+ * \param info pointer to the snd_hwdep_info_t structure to free
+ *
+ * Frees the given snd_hwdep_info_t structure using the standard
+ * free C library function.
+ */
+void snd_hwdep_info_free(snd_hwdep_info_t *info)
+{
+	assert(info);
+	free(info);
+}
+
+/**
+ * \brief copy one snd_hwdep_info_t structure to another
+ * \param dst destination snd_hwdep_info_t structure
+ * \param src source snd_hwdep_info_t structure
+ */
+void snd_hwdep_info_copy(snd_hwdep_info_t *dst, const snd_hwdep_info_t *src)
+{
+	assert(dst && src);
+	*dst = *src;
+}
+
+/**
+ * \brief get hwdep card number
+ * \param obj pointer to a snd_hwdep_info_t structure
+ * \return hwdep card number
+ */
+int snd_hwdep_info_get_card(const snd_hwdep_info_t *obj)
+{
+	assert(obj);
+	return obj->card;
+}
+
+/**
+ * \brief get hwdep device number
+ * \param info pointer to a snd_hwdep_info_t structure
+ * \return hwdep device number
+ */
+unsigned int snd_hwdep_info_get_device(const snd_hwdep_info_t *info)
+{
+	assert(info);
+	return info->device;
+}
+
+/**
+ * \brief get hwdep driver identifier
+ * \param obj pointer to a snd_hwdep_info_t structure
+ * \return hwdep driver identifier
+ */
+const char *snd_hwdep_info_get_id(const snd_hwdep_info_t *obj)
+{
+	assert(obj);
+	return (const char *)obj->id;
+}
+
+/**
+ * \brief get hwdep driver name
+ * \param obj pointer to a snd_hwdep_info_t structure
+ * \return hwdep driver name
+ */
+const char *snd_hwdep_info_get_name(const snd_hwdep_info_t *obj)
+{
+	assert(obj);
+	return (const char *)obj->name;
+}
+
+/**
+ * \brief get hwdep protocol interface
+ * \param obj pointer to a snd_hwdep_info_t structure
+ * \return hwdep protocol interface
+ */
+snd_hwdep_iface_t snd_hwdep_info_get_iface(const snd_hwdep_info_t *obj)
+{
+	assert(obj);
+	return obj->iface;
+}
+
+/**
+ * \brief set hwdep device number
+ * \param obj pointer to a snd_hwdep_info_t structure
+ * \param val hwdep device
+ */
+void snd_hwdep_info_set_device(snd_hwdep_info_t *obj, unsigned int val)
+{
+	assert(obj);
+	obj->device = val;
+}
+
+/**
+ * \brief get information about HwDep handle
+ * \param hwdep HwDep handle
+ * \param info pointer to a snd_hwdep_info_t structure to be filled
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_hwdep_info(snd_hwdep_t *hwdep, snd_hwdep_info_t * info)
+{
+	assert(hwdep);
+	assert(info);
+	return hwdep->ops->info(hwdep, info);
+}
+
+/**
+ * \brief do hardware dependent ioctl
+ * \param hwdep HwDep handle
+ * \param request ioctl command
+ * \param arg ioctl argument
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_hwdep_ioctl(snd_hwdep_t *hwdep, unsigned int request, void * arg)
+{
+	assert(hwdep);
+	return hwdep->ops->ioctl(hwdep, request, arg);
+}
+
+/**
+ * \brief write bytes using HwDep handle
+ * \param hwdep HwDep handle
+ * \param buffer buffer containing bytes to write
+ * \param size output buffer size in bytes
+ */
+ssize_t snd_hwdep_write(snd_hwdep_t *hwdep, const void *buffer, size_t size)
+{
+	assert(hwdep);
+	assert(((hwdep->mode & O_ACCMODE) == O_WRONLY) || ((hwdep->mode & O_ACCMODE) == O_RDWR));
+	assert(buffer || size == 0);
+	return hwdep->ops->write(hwdep, buffer, size);
+}
+
+/**
+ * \brief read bytes using HwDep handle
+ * \param hwdep HwDep handle
+ * \param buffer buffer to store the input bytes
+ * \param size input buffer size in bytes
+ */
+ssize_t snd_hwdep_read(snd_hwdep_t *hwdep, void *buffer, size_t size)
+{
+	assert(hwdep);
+	assert(((hwdep->mode & O_ACCMODE) == O_RDONLY) || ((hwdep->mode & O_ACCMODE) == O_RDWR));
+	assert(buffer || size == 0);
+	return (hwdep->ops->read)(hwdep, buffer, size);
+}
+
+/**
+ * \brief get the DSP status information
+ * \param hwdep HwDep handle
+ * \param info pointer to a snd_hwdep_dsp_status_t structure to be filled
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_hwdep_dsp_status(snd_hwdep_t *hwdep, snd_hwdep_dsp_status_t *info)
+{
+	assert(hwdep);
+	assert(info);
+	return hwdep->ops->ioctl(hwdep, SNDRV_HWDEP_IOCTL_DSP_STATUS, (void*)info);
+}
+
+/**
+ * \brief load the DSP block
+ * \param hwdep HwDep handle
+ * \param block pointer to a snd_hwdep_dsp_image_t structure to transfer
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_hwdep_dsp_load(snd_hwdep_t *hwdep, snd_hwdep_dsp_image_t *block)
+{
+	assert(hwdep);
+	assert(block);
+	return hwdep->ops->ioctl(hwdep, SNDRV_HWDEP_IOCTL_DSP_LOAD, (void*)block);
+}
+
+/**
+ * \brief get size of the snd_hwdep_dsp_status_t structure in bytes
+ * \return size of the snd_hwdep_dsp_status_t structure in bytes
+ */
+size_t snd_hwdep_dsp_status_sizeof()
+{
+	return sizeof(snd_hwdep_dsp_status_t);
+}
+
+/**
+ * \brief allocate a new snd_hwdep_dsp_status_t structure
+ * \param info returned pointer
+ * \return 0 on success otherwise a negative error code if fails
+ *
+ * Allocates a new snd_hwdep_dsp_status_t structure using the standard
+ * malloc C library function.
+ */
+int snd_hwdep_dsp_status_malloc(snd_hwdep_dsp_status_t **info)
+{
+	assert(info);
+	*info = calloc(1, sizeof(snd_hwdep_dsp_status_t));
+	if (!*info)
+		return -ENOMEM;
+	return 0;
+}
+
+/**
+ * \brief frees the snd_hwdep_dsp_status_t structure
+ * \param info pointer to the snd_hwdep_dsp_status_t structure to free
+ *
+ * Frees the given snd_hwdep_dsp_status_t structure using the standard
+ * free C library function.
+ */
+void snd_hwdep_dsp_status_free(snd_hwdep_dsp_status_t *info)
+{
+	assert(info);
+	free(info);
+}
+
+/**
+ * \brief copy one snd_hwdep_dsp_status_t structure to another
+ * \param dst destination snd_hwdep_dsp_status_t structure
+ * \param src source snd_hwdep_dsp_status_t structure
+ */
+void snd_hwdep_dsp_status_copy(snd_hwdep_dsp_status_t *dst, const snd_hwdep_dsp_status_t *src)
+{
+	assert(dst && src);
+	*dst = *src;
+}
+
+/**
+ * \brief get the driver version of dsp loader
+ * \param obj pointer to a snd_hwdep_dsp_status_t structure
+ * \return the driver version
+ */
+unsigned int snd_hwdep_dsp_status_get_version(const snd_hwdep_dsp_status_t *obj)
+{
+	assert(obj);
+	return obj->version;
+}
+
+/**
+ * \brief get the driver id of dsp loader
+ * \param obj pointer to a snd_hwdep_dsp_status_t structure
+ * \return the driver id string
+ */
+const char *snd_hwdep_dsp_status_get_id(const snd_hwdep_dsp_status_t *obj)
+{
+	assert(obj);
+	return (const char *)obj->id;
+}
+
+/**
+ * \brief get number of dsp blocks
+ * \param obj pointer to a snd_hwdep_dsp_status_t structure
+ * \return number of dsp blocks
+ */
+unsigned int snd_hwdep_dsp_status_get_num_dsps(const snd_hwdep_dsp_status_t *obj)
+{
+	assert(obj);
+	return obj->num_dsps;
+}
+
+/**
+ * \brief get the bit flags of the loaded dsp blocks
+ * \param info pointer to a snd_hwdep_dsp_status_t structure
+ * \return the big flags of the loaded dsp blocks
+ */
+unsigned int snd_hwdep_dsp_status_get_dsp_loaded(const snd_hwdep_dsp_status_t *info)
+{
+	assert(info);
+	return info->dsp_loaded;
+}
+
+/**
+ * \brief get the chip status of dsp loader
+ * \param obj pointer to a snd_hwdep_dsp_status_t structure
+ * \return non-zero if all DSP blocks are loaded and the chip is ready
+ */
+unsigned int snd_hwdep_dsp_status_get_chip_ready(const snd_hwdep_dsp_status_t *obj)
+{
+	assert(obj);
+	return obj->chip_ready;
+}
+
+/**
+ * \brief get size of the snd_hwdep_dsp_image_t structure in bytes
+ * \return size of the snd_hwdep_dsp_image_t structure in bytes
+ */
+size_t snd_hwdep_dsp_image_sizeof()
+{
+	return sizeof(snd_hwdep_dsp_image_t);
+}
+
+/**
+ * \brief allocate a new snd_hwdep_dsp_image_t structure
+ * \param info returned pointer
+ * \return 0 on success otherwise a negative error code if fails
+ *
+ * Allocates a new snd_hwdep_dsp_image_t structure using the standard
+ * malloc C library function.
+ */
+int snd_hwdep_dsp_image_malloc(snd_hwdep_dsp_image_t **info)
+{
+	assert(info);
+	*info = calloc(1, sizeof(snd_hwdep_dsp_image_t));
+	if (!*info)
+		return -ENOMEM;
+	return 0;
+}
+
+/**
+ * \brief frees the snd_hwdep_dsp_image_t structure
+ * \param info pointer to the snd_hwdep_dsp_image_t structure to free
+ *
+ * Frees the given snd_hwdep_dsp_image_t structure using the standard
+ * free C library function.
+ */
+void snd_hwdep_dsp_image_free(snd_hwdep_dsp_image_t *info)
+{
+	assert(info);
+	free(info);
+}
+
+/**
+ * \brief copy one snd_hwdep_dsp_image_t structure to another
+ * \param dst destination snd_hwdep_dsp_image_t structure
+ * \param src source snd_hwdep_dsp_image_t structure
+ */
+void snd_hwdep_dsp_image_copy(snd_hwdep_dsp_image_t *dst, const snd_hwdep_dsp_image_t *src)
+{
+	assert(dst && src);
+	*dst = *src;
+}
+
+/**
+ * \brief get the DSP block index
+ * \param obj pointer to a snd_hwdep_dsp_image_t structure
+ * \return the index of the DSP block
+ */
+unsigned int snd_hwdep_dsp_image_get_index(const snd_hwdep_dsp_image_t *obj)
+{
+	assert(obj);
+	return obj->index;
+}
+
+/**
+ * \brief get the name of the DSP block
+ * \param obj pointer to a snd_hwdep_dsp_image_t structure
+ * \return the name string of the DSP block
+ */
+const char *snd_hwdep_dsp_image_get_name(const snd_hwdep_dsp_image_t *obj)
+{
+	assert(obj);
+	return (const char *)obj->name;
+}
+
+/**
+ * \brief get the length of the DSP block
+ * \param obj pointer to a snd_hwdep_dsp_image_t structure
+ * \return the length of the DSP block in bytes
+ */
+size_t snd_hwdep_dsp_image_get_length(const snd_hwdep_dsp_image_t *obj)
+{
+	assert(obj);
+	return obj->length;
+}
+
+/**
+ * \brief get the image pointer of the DSP block
+ * \param obj pointer to a snd_hwdep_dsp_image_t structure
+ * \return the image pointer of the DSP block
+ */
+const void *snd_hwdep_dsp_image_get_image(const snd_hwdep_dsp_image_t *obj)
+{
+	assert(obj);
+	return obj->image;
+}
+
+/**
+ * \brief set the DSP block index
+ * \param obj pointer to a snd_hwdep_dsp_image_t structure
+ * \param index the index value to set
+ */
+void snd_hwdep_dsp_image_set_index(snd_hwdep_dsp_image_t *obj, unsigned int index)
+{
+	assert(obj);
+	obj->index = index;
+}
+
+/**
+ * \brief set the name of the DSP block
+ * \param obj pointer to a snd_hwdep_dsp_image_t structure
+ * \param name the name string
+ */
+void snd_hwdep_dsp_image_set_name(snd_hwdep_dsp_image_t *obj, const char *name)
+{
+	assert(obj && name);
+	strncpy((char *)obj->name, name, sizeof(obj->name));
+	obj->name[sizeof(obj->name)-1] = 0;
+}
+
+/**
+ * \brief set the DSP block length
+ * \param obj pointer to a snd_hwdep_dsp_image_t structure
+ * \param length the length of the DSP block
+ */
+void snd_hwdep_dsp_image_set_length(snd_hwdep_dsp_image_t *obj, size_t length)
+{
+	assert(obj);
+	obj->length = length;
+}
+
+/**
+ * \brief set the DSP block image pointer
+ * \param obj pointer to a snd_hwdep_dsp_image_t structure
+ * \param image the DSP image pointer
+ */
+void snd_hwdep_dsp_image_set_image(snd_hwdep_dsp_image_t *obj, void *image)
+{
+	assert(obj);
+	obj->image = image;
+}
+
diff --git a/src/hwdep/hwdep_hw.c b/src/hwdep/hwdep_hw.c
new file mode 100644
index 0000000..e4fcdc3
--- /dev/null
+++ b/src/hwdep/hwdep_hw.c
@@ -0,0 +1,190 @@
+/*
+ *  Hardware dependent Interface - main file for hardware access
+ *  Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include "hwdep_local.h"
+
+#ifndef PIC
+/* entry for static linking */
+const char *_snd_module_hwdep_hw = "";
+#endif
+
+#define SNDRV_FILE_HWDEP	ALSA_DEVICE_DIRECTORY "hwC%iD%i"
+#define SNDRV_HWDEP_VERSION_MAX	SNDRV_PROTOCOL_VERSION(1, 0, 1)
+
+static int snd_hwdep_hw_close(snd_hwdep_t *hwdep)
+{
+	int res;
+	assert(hwdep);
+	res = close(hwdep->poll_fd) < 0 ? -errno : 0;
+	return res;
+}
+
+static int snd_hwdep_hw_nonblock(snd_hwdep_t *hwdep, int nonblock)
+{
+	long flags;
+	assert(hwdep);
+	if ((flags = fcntl(hwdep->poll_fd, F_GETFL)) < 0)
+		return -errno;
+	if (nonblock)
+		flags |= O_NONBLOCK;
+	else
+		flags &= ~O_NONBLOCK;
+	if (fcntl(hwdep->poll_fd, F_SETFL, flags) < 0)
+		return -errno;
+	return 0;
+}
+
+static int snd_hwdep_hw_info(snd_hwdep_t *hwdep, snd_hwdep_info_t *info)
+{
+	assert(hwdep && info);
+	if (ioctl(hwdep->poll_fd, SNDRV_HWDEP_IOCTL_INFO, info) < 0)
+		return -errno;
+	return 0;
+}
+
+static int snd_hwdep_hw_ioctl(snd_hwdep_t *hwdep, unsigned int request, void * arg)
+{
+	assert(hwdep);
+	if (ioctl(hwdep->poll_fd, request, arg) < 0)
+		return -errno;
+	return 0;
+}
+
+static ssize_t snd_hwdep_hw_write(snd_hwdep_t *hwdep, const void *buffer, size_t size)
+{
+	ssize_t result;
+	assert(hwdep && (buffer || size == 0));
+	result = write(hwdep->poll_fd, buffer, size);
+	if (result < 0)
+		return -errno;
+	return result;
+}
+
+static ssize_t snd_hwdep_hw_read(snd_hwdep_t *hwdep, void *buffer, size_t size)
+{
+	ssize_t result;
+	assert(hwdep && (buffer || size == 0));
+	result = read(hwdep->poll_fd, buffer, size);
+	if (result < 0)
+		return -errno;
+	return result;
+}
+
+static const snd_hwdep_ops_t snd_hwdep_hw_ops = {
+	.close = snd_hwdep_hw_close,
+	.nonblock = snd_hwdep_hw_nonblock,
+	.info = snd_hwdep_hw_info,
+	.ioctl = snd_hwdep_hw_ioctl,
+	.write = snd_hwdep_hw_write,
+	.read = snd_hwdep_hw_read,
+};
+
+int snd_hwdep_hw_open(snd_hwdep_t **handle, const char *name, int card, int device, int mode)
+{
+	int fd, ver, ret;
+	char filename[sizeof(SNDRV_FILE_HWDEP) + 20];
+	snd_hwdep_t *hwdep;
+	assert(handle);
+
+	*handle = NULL;
+	
+	if (card < 0 || card >= 32)
+		return -EINVAL;
+	sprintf(filename, SNDRV_FILE_HWDEP, card, device);
+	fd = snd_open_device(filename, mode);
+	if (fd < 0) {
+		snd_card_load(card);
+		fd = snd_open_device(filename, mode);
+		if (fd < 0)
+			return -errno;
+	}
+	if (ioctl(fd, SNDRV_HWDEP_IOCTL_PVERSION, &ver) < 0) {
+		ret = -errno;
+		close(fd);
+		return ret;
+	}
+	if (SNDRV_PROTOCOL_INCOMPATIBLE(ver, SNDRV_HWDEP_VERSION_MAX)) {
+		close(fd);
+		return -SND_ERROR_INCOMPATIBLE_VERSION;
+	}
+	hwdep = (snd_hwdep_t *) calloc(1, sizeof(snd_hwdep_t));
+	if (hwdep == NULL) {
+		close(fd);
+		return -ENOMEM;
+	}
+	hwdep->name = strdup(name);
+	hwdep->poll_fd = fd;
+	hwdep->mode = mode;
+	hwdep->type = SND_HWDEP_TYPE_HW;
+	hwdep->ops = &snd_hwdep_hw_ops;
+	*handle = hwdep;
+	return 0;
+}
+
+int _snd_hwdep_hw_open(snd_hwdep_t **hwdep, char *name,
+		       snd_config_t *root ATTRIBUTE_UNUSED,
+		       snd_config_t *conf, int mode)
+{
+	snd_config_iterator_t i, next;
+	long card = -1, device = 0;
+	const char *str;
+	int err;
+	snd_config_for_each(i, next, conf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (strcmp(id, "comment") == 0)
+			continue;
+		if (strcmp(id, "type") == 0)
+			continue;
+		if (strcmp(id, "card") == 0) {
+			err = snd_config_get_integer(n, &card);
+			if (err < 0) {
+				err = snd_config_get_string(n, &str);
+				if (err < 0)
+					return -EINVAL;
+				card = snd_card_get_index(str);
+				if (card < 0)
+					return card;
+			}
+			continue;
+		}
+		if (strcmp(id, "device") == 0) {
+			err = snd_config_get_integer(n, &device);
+			if (err < 0)
+				return err;
+			continue;
+		}
+		SNDERR("Unexpected field %s", id);
+		return -EINVAL;
+	}
+	if (card < 0)
+		return -EINVAL;
+	return snd_hwdep_hw_open(hwdep, name, card, device, mode);
+}
+SND_DLSYM_BUILD_VERSION(_snd_hwdep_hw_open, SND_HWDEP_DLSYM_VERSION);
diff --git a/src/hwdep/hwdep_local.h b/src/hwdep/hwdep_local.h
new file mode 100644
index 0000000..47467f9
--- /dev/null
+++ b/src/hwdep/hwdep_local.h
@@ -0,0 +1,46 @@
+/*
+ *  HwDep interface - local header file
+ *  Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include "local.h"
+
+typedef struct {
+	int (*close)(snd_hwdep_t *hwdep);
+	int (*nonblock)(snd_hwdep_t *hwdep, int nonblock);
+	int (*info)(snd_hwdep_t *hwdep, snd_hwdep_info_t *info);
+	int (*ioctl)(snd_hwdep_t *hwdep, unsigned int request, void * arg);
+	ssize_t (*write)(snd_hwdep_t *hwdep, const void *buffer, size_t size);
+	ssize_t (*read)(snd_hwdep_t *hwdep, void *buffer, size_t size);
+} snd_hwdep_ops_t;
+
+struct _snd_hwdep {
+	void *dl_handle;
+	char *name;
+	snd_hwdep_type_t type;
+	int mode;
+	int poll_fd;
+	const snd_hwdep_ops_t *ops;
+	void *private_data;
+};
+
+int snd_hwdep_hw_open(snd_hwdep_t **handle, const char *name, int card, int device, int mode);
diff --git a/src/hwdep/hwdep_symbols.c b/src/hwdep/hwdep_symbols.c
new file mode 100644
index 0000000..aaa5667
--- /dev/null
+++ b/src/hwdep/hwdep_symbols.c
@@ -0,0 +1,34 @@
+/*
+ *  HwDep Symbols
+ *  Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#ifndef PIC
+
+extern const char *_snd_module_hwdep_hw;
+
+static const char **snd_hwdep_open_objects[] = {
+	&_snd_module_hwdep_hw
+};
+	
+void *snd_hwdep_open_symbols(void)
+{
+	return snd_hwdep_open_objects;
+}
+
+#endif
diff --git a/src/input.c b/src/input.c
new file mode 100644
index 0000000..7cfbe56
--- /dev/null
+++ b/src/input.c
@@ -0,0 +1,337 @@
+/**
+ * \file input.c
+ * \brief Generic stdio-like input interface
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \date 2000
+ *
+ * Generic stdio-like input interface
+ */
+/*
+ *  Input object
+ *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include "local.h"
+
+#ifndef DOC_HIDDEN
+
+typedef struct _snd_input_ops {
+	int (*close)(snd_input_t *input);
+	int (*scan)(snd_input_t *input, const char *format, va_list args);
+	char *(*(gets))(snd_input_t *input, char *str, size_t size);
+	int (*getch)(snd_input_t *input);
+	int (*ungetch)(snd_input_t *input, int c);
+} snd_input_ops_t;
+
+struct _snd_input {
+	snd_input_type_t type;
+	const snd_input_ops_t *ops;
+	void *private_data;
+};
+#endif
+
+/**
+ * \brief Closes an input handle.
+ * \param input The input handle to be closed.
+ * \return Zero if successful, otherwise a negative error code.
+ */
+int snd_input_close(snd_input_t *input)
+{
+	int err = input->ops->close(input);
+	free(input);
+	return err;
+}
+
+/**
+ * \brief Reads formatted input (like \c fscanf(3)) from an input handle.
+ * \param input The input handle.
+ * \param format Format string in \c fscanf format.
+ * \param ... Other \c fscanf arguments.
+ * \return The number of input items assigned, or \c EOF.
+ *
+ * \bug Reading from a memory buffer doesn't work.
+ */
+int snd_input_scanf(snd_input_t *input, const char *format, ...)
+{
+	int result;
+	va_list args;
+	va_start(args, format);
+	result = input->ops->scan(input, format, args);
+	va_end(args);
+	return result;
+}
+
+/**
+ * \brief Reads a line from an input handle (like \c fgets(3)).
+ * \param input The input handle.
+ * \param str Address of the destination buffer.
+ * \param size The size of the destination buffer.
+ * \return Pointer to the buffer if successful, otherwise \c NULL.
+ *
+ * Like \c fgets, the returned string is zero-terminated, and contains
+ * the new-line character \c '\\n' if the line fits into the buffer.
+ */
+char *snd_input_gets(snd_input_t *input, char *str, size_t size)
+{
+	return (input->ops->gets)(input, str, size);
+}
+			
+/**
+ * \brief Reads a character from an input handle (like \c fgetc(3)).
+ * \param input The input handle.
+ * \return The character read, or \c EOF on end of file or error.
+ */
+int snd_input_getc(snd_input_t *input)
+{
+	return input->ops->getch(input);
+}
+
+/**
+ * \brief Puts the last character read back to an input handle (like \c ungetc(3)).
+ * \param input The input handle.
+ * \param c The character to push back.
+ * \return The character pushed back, or \c EOF on error.
+ */
+int snd_input_ungetc(snd_input_t *input, int c)
+{
+	return input->ops->ungetch(input, c);
+}
+
+#ifndef DOC_HIDDEN
+typedef struct _snd_input_stdio {
+	int close;
+	FILE *fp;
+} snd_input_stdio_t;
+
+static int snd_input_stdio_close(snd_input_t *input ATTRIBUTE_UNUSED)
+{
+	snd_input_stdio_t *stdio = input->private_data;
+	if (stdio->close)
+		fclose(stdio->fp);
+	free(stdio);
+	return 0;
+}
+
+static int snd_input_stdio_scan(snd_input_t *input, const char *format, va_list args)
+{
+	snd_input_stdio_t *stdio = input->private_data;
+	extern int vfscanf(FILE *, const char *, va_list);
+	return vfscanf(stdio->fp, format, args);
+}
+
+static char *snd_input_stdio_gets(snd_input_t *input, char *str, size_t size)
+{
+	snd_input_stdio_t *stdio = input->private_data;
+	return fgets(str, (int) size, stdio->fp);
+}
+			
+static int snd_input_stdio_getc(snd_input_t *input)
+{
+	snd_input_stdio_t *stdio = input->private_data;
+	return getc(stdio->fp);
+}
+
+static int snd_input_stdio_ungetc(snd_input_t *input, int c)
+{
+	snd_input_stdio_t *stdio = input->private_data;
+	return ungetc(c, stdio->fp);
+}
+
+static const snd_input_ops_t snd_input_stdio_ops = {
+	.close		= snd_input_stdio_close,
+	.scan		= snd_input_stdio_scan,
+	.gets		= snd_input_stdio_gets,
+	.getch		= snd_input_stdio_getc,
+	.ungetch	= snd_input_stdio_ungetc,
+};
+#endif
+
+/**
+ * \brief Creates a new input object using an existing stdio \c FILE pointer.
+ * \param inputp The function puts the pointer to the new input object
+ *               at the address specified by \p inputp.
+ * \param fp The \c FILE pointer to read from.
+ *           Reading begins at the current file position.
+ * \param _close Close flag. Set this to 1 if #snd_input_close should close
+ *              \p fp by calling \c fclose.
+ * \return Zero if successful, otherwise a negative error code.
+ */
+int snd_input_stdio_attach(snd_input_t **inputp, FILE *fp, int _close)
+{
+	snd_input_t *input;
+	snd_input_stdio_t *stdio;
+	assert(inputp && fp);
+	stdio = calloc(1, sizeof(*stdio));
+	if (!stdio)
+		return -ENOMEM;
+	input = calloc(1, sizeof(*input));
+	if (!input) {
+		free(stdio);
+		return -ENOMEM;
+	}
+	stdio->fp = fp;
+	stdio->close = _close;
+	input->type = SND_INPUT_STDIO;
+	input->ops = &snd_input_stdio_ops;
+	input->private_data = stdio;
+	*inputp = input;
+	return 0;
+}
+	
+/**
+ * \brief Creates a new input object reading from a file.
+ * \param inputp The functions puts the pointer to the new input object
+ *               at the address specified by \p inputp.
+ * \param file The name of the file to read from.
+ * \param mode The open mode, like \c fopen(3).
+ * \return Zero if successful, otherwise a negative error code.
+ */
+int snd_input_stdio_open(snd_input_t **inputp, const char *file, const char *mode)
+{
+	int err;
+	FILE *fp = fopen(file, mode);
+	if (!fp) {
+		//SYSERR("fopen");
+		return -errno;
+	}
+	err = snd_input_stdio_attach(inputp, fp, 1);
+	if (err < 0)
+		fclose(fp);
+	return err;
+}
+
+#ifndef DOC_HIDDEN
+
+typedef struct _snd_input_buffer {
+	unsigned char *buf;
+	unsigned char *ptr;
+	size_t size;
+} snd_input_buffer_t;
+
+static int snd_input_buffer_close(snd_input_t *input)
+{
+	snd_input_buffer_t *buffer = input->private_data;
+	free(buffer->buf);
+	free(buffer);
+	return 0;
+}
+
+static int snd_input_buffer_scan(snd_input_t *input, const char *format, va_list args)
+{
+	snd_input_buffer_t *buffer = input->private_data;
+	extern int vsscanf(const char *, const char *, va_list);
+	/* FIXME: how can I obtain consumed chars count? */
+	assert(0);
+	return vsscanf((char *)buffer->ptr, format, args);
+}
+
+static char *snd_input_buffer_gets(snd_input_t *input, char *str, size_t size)
+{
+	snd_input_buffer_t *buffer = input->private_data;
+	size_t bsize = buffer->size;
+	while (--size > 0 && bsize > 0) {
+		unsigned char c = *buffer->ptr++;
+		bsize--;
+		*str++ = c;
+		if (c == '\n')
+			break;
+	}
+	if (bsize == buffer->size)
+		return NULL;
+	buffer->size = bsize;
+	*str = '\0';
+	return str;
+}
+			
+static int snd_input_buffer_getc(snd_input_t *input)
+{
+	snd_input_buffer_t *buffer = input->private_data;
+	if (buffer->size == 0)
+		return EOF;
+	buffer->size--;
+	return *buffer->ptr++;
+}
+
+static int snd_input_buffer_ungetc(snd_input_t *input, int c)
+{
+	snd_input_buffer_t *buffer = input->private_data;
+	if (buffer->ptr == buffer->buf)
+		return EOF;
+	buffer->ptr--;
+	assert(*buffer->ptr == (unsigned char) c);
+	buffer->size++;
+	return c;
+}
+
+static const snd_input_ops_t snd_input_buffer_ops = {
+	.close		= snd_input_buffer_close,
+	.scan		= snd_input_buffer_scan,
+	.gets		= snd_input_buffer_gets,
+	.getch		= snd_input_buffer_getc,
+	.ungetch	= snd_input_buffer_ungetc,
+};
+#endif
+
+/**
+ * \brief Creates a new input object from a memory buffer.
+ * \param inputp The function puts the pointer to the new input object
+ *               at the address specified by \p inputp.
+ * \param buf Address of the input buffer.
+ * \param size Size of the input buffer.
+ * \return Zero if successful, otherwise a negative error code.
+ *
+ * This functions creates a copy of the input buffer, so the application is
+ * not required to preserve the buffer after this function has been called.
+ */
+int snd_input_buffer_open(snd_input_t **inputp, const char *buf, ssize_t size)
+{
+	snd_input_t *input;
+	snd_input_buffer_t *buffer;
+	assert(inputp);
+	buffer = calloc(1, sizeof(*buffer));
+	if (!buffer)
+		return -ENOMEM;
+	input = calloc(1, sizeof(*input));
+	if (!input) {
+		free(buffer);
+		return -ENOMEM;
+	}
+	if (size < 0)
+		size = strlen(buf);
+	buffer->buf = malloc((size_t)size + 1);
+	if (!buffer->buf) {
+		free(input);
+		free(buffer);
+		return -ENOMEM;
+	}
+	memcpy(buffer->buf, buf, (size_t) size);
+	buffer->buf[size] = 0;
+	buffer->ptr = buffer->buf;
+	buffer->size = size;
+	input->type = SND_INPUT_BUFFER;
+	input->ops = &snd_input_buffer_ops;
+	input->private_data = buffer;
+	*inputp = input;
+	return 0;
+}
+	
diff --git a/src/mixer/Makefile.am b/src/mixer/Makefile.am
new file mode 100644
index 0000000..bb466ed
--- /dev/null
+++ b/src/mixer/Makefile.am
@@ -0,0 +1,10 @@
+EXTRA_LTLIBRARIES=libmixer.la
+
+libmixer_la_SOURCES = bag.c mixer.c simple.c simple_none.c simple_abst.c
+
+noinst_HEADERS = mixer_local.h mixer_simple.h
+
+all: libmixer.la
+
+
+INCLUDES=-I$(top_srcdir)/include
diff --git a/src/mixer/bag.c b/src/mixer/bag.c
new file mode 100644
index 0000000..d88a900
--- /dev/null
+++ b/src/mixer/bag.c
@@ -0,0 +1,73 @@
+/*
+ *  Bag of pointers
+ *  Copyright (c) 2001 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include "mixer_local.h"
+
+int bag_new(bag_t **bag)
+{
+	bag_t *b = malloc(sizeof(*b));
+	if (!b)
+		return -ENOMEM;
+	INIT_LIST_HEAD(b);
+	*bag = b;
+	return 0;
+}
+
+void bag_free(bag_t *bag)
+{
+	assert(list_empty(bag));
+	free(bag);
+}
+
+int bag_empty(bag_t *bag)
+{
+	return list_empty(bag);
+}
+
+int bag_add(bag_t *bag, void *ptr)
+{
+	bag1_t *b = malloc(sizeof(*b));
+	if (!b)
+		return -ENOMEM;
+	b->ptr = ptr;
+	list_add_tail(&b->list, bag);
+	return 0;
+}
+
+int bag_del(bag_t *bag, void *ptr)
+{
+	struct list_head *pos;
+	list_for_each(pos, bag) {
+		bag1_t *b = list_entry(pos, bag1_t, list);
+		if (b->ptr == ptr) {
+			list_del(&b->list);
+			free(b);
+			return 0;
+		}
+	}
+	return -ENOENT;
+}
+
+void bag_del_all(bag_t *bag)
+{
+	while (!list_empty(bag))
+		list_del(bag->next);
+}
diff --git a/src/mixer/mixer.c b/src/mixer/mixer.c
new file mode 100644
index 0000000..85d843f
--- /dev/null
+++ b/src/mixer/mixer.c
@@ -0,0 +1,1083 @@
+/**
+ * \file mixer/mixer.c
+ * \brief Mixer Interface
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \date 2001
+ *
+ * Mixer interface is designed to access mixer elements.
+ * Callbacks may be used for event handling.
+ */
+/*
+ *  Mixer Interface - main file
+ *  Copyright (c) 1998/1999/2000 by Jaroslav Kysela <perex@perex.cz>
+ *  Copyright (c) 2001 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+/*! \page mixer Mixer interface
+
+<P>Mixer interface is designed to access the abstracted mixer controls.
+This is an abstraction layer over the hcontrol layer.
+
+\section mixer_general_overview General overview
+
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include "mixer_local.h"
+
+#ifndef DOC_HIDDEN
+typedef struct _snd_mixer_slave {
+	snd_hctl_t *hctl;
+	struct list_head list;
+} snd_mixer_slave_t;
+
+#endif
+
+static int snd_mixer_compare_default(const snd_mixer_elem_t *c1,
+				     const snd_mixer_elem_t *c2);
+
+
+/**
+ * \brief Opens an empty mixer
+ * \param mixerp Returned mixer handle
+ * \param mode Open mode
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_mixer_open(snd_mixer_t **mixerp, int mode ATTRIBUTE_UNUSED)
+{
+	snd_mixer_t *mixer;
+	assert(mixerp);
+	mixer = calloc(1, sizeof(*mixer));
+	if (mixer == NULL)
+		return -ENOMEM;
+	INIT_LIST_HEAD(&mixer->slaves);
+	INIT_LIST_HEAD(&mixer->classes);
+	INIT_LIST_HEAD(&mixer->elems);
+	mixer->compare = snd_mixer_compare_default;
+	*mixerp = mixer;
+	return 0;
+}
+
+/**
+ * \brief Attach an HCTL element to a mixer element
+ * \param melem Mixer element
+ * \param helem HCTL element
+ * \return 0 on success otherwise a negative error code
+ *
+ * For use by mixer element class specific code.
+ */
+int snd_mixer_elem_attach(snd_mixer_elem_t *melem,
+			  snd_hctl_elem_t *helem)
+{
+	bag_t *bag = snd_hctl_elem_get_callback_private(helem);
+	int err;
+	err = bag_add(bag, melem);
+	if (err < 0)
+		return err;
+	return bag_add(&melem->helems, helem);
+}
+
+/**
+ * \brief Detach an HCTL element from a mixer element
+ * \param melem Mixer element
+ * \param helem HCTL element
+ * \return 0 on success otherwise a negative error code
+ *
+ * For use by mixer element class specific code.
+ */
+int snd_mixer_elem_detach(snd_mixer_elem_t *melem,
+			  snd_hctl_elem_t *helem)
+{
+	bag_t *bag = snd_hctl_elem_get_callback_private(helem);
+	int err;
+	err = bag_del(bag, melem);
+	assert(err >= 0);
+	err = bag_del(&melem->helems, helem);
+	assert(err >= 0);
+	return 0;
+}
+
+/**
+ * \brief Return true if a mixer element does not contain any HCTL elements
+ * \param melem Mixer element
+ * \return 0 if not empty, 1 if empty
+ *
+ * For use by mixer element class specific code.
+ */
+int snd_mixer_elem_empty(snd_mixer_elem_t *melem)
+{
+	return bag_empty(&melem->helems);
+}
+
+static int hctl_elem_event_handler(snd_hctl_elem_t *helem,
+				   unsigned int mask)
+{
+	bag_t *bag = snd_hctl_elem_get_callback_private(helem);
+	if (mask == SND_CTL_EVENT_MASK_REMOVE) {
+		int res = 0;
+		int err;
+		bag_iterator_t i, n;
+		bag_for_each_safe(i, n, bag) {
+			snd_mixer_elem_t *melem = bag_iterator_entry(i);
+			snd_mixer_class_t *class = melem->class;
+			err = class->event(class, mask, helem, melem);
+			if (err < 0)
+				res = err;
+		}
+		assert(bag_empty(bag));
+		bag_free(bag);
+		return res;
+	}
+	if (mask & (SND_CTL_EVENT_MASK_VALUE | SND_CTL_EVENT_MASK_INFO)) {
+		int err = 0;
+		bag_iterator_t i, n;
+		bag_for_each_safe(i, n, bag) {
+			snd_mixer_elem_t *melem = bag_iterator_entry(i);
+			snd_mixer_class_t *class = melem->class;
+			err = class->event(class, mask, helem, melem);
+			if (err < 0)
+				return err;
+		}
+	}
+	return 0;
+}
+
+static int hctl_event_handler(snd_hctl_t *hctl, unsigned int mask,
+			      snd_hctl_elem_t *elem)
+{
+	snd_mixer_t *mixer = snd_hctl_get_callback_private(hctl);
+	int res = 0;
+	if (mask & SND_CTL_EVENT_MASK_ADD) {
+		struct list_head *pos;
+		bag_t *bag;
+		int err = bag_new(&bag);
+		if (err < 0)
+			return err;
+		snd_hctl_elem_set_callback(elem, hctl_elem_event_handler);
+		snd_hctl_elem_set_callback_private(elem, bag);
+		list_for_each(pos, &mixer->classes) {
+			snd_mixer_class_t *c;
+			c = list_entry(pos, snd_mixer_class_t, list);
+			err = c->event(c, mask, elem, NULL);
+			if (err < 0)
+				res = err;
+		}
+	}
+	return res;
+}
+
+
+/**
+ * \brief Attach an HCTL specified with the CTL device name to an opened mixer
+ * \param mixer Mixer handle
+ * \param name HCTL name (see #snd_hctl_open)
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_mixer_attach(snd_mixer_t *mixer, const char *name)
+{
+	snd_hctl_t *hctl;
+	int err;
+
+	err = snd_hctl_open(&hctl, name, 0);
+	if (err < 0)
+		return err;
+	err = snd_mixer_attach_hctl(mixer, hctl);
+	if (err < 0) {
+		snd_hctl_close(hctl);
+		return err;
+	}
+	return 0;
+}
+
+/**
+ * \brief Attach an HCTL to an opened mixer
+ * \param mixer Mixer handle
+ * \param hctl the HCTL to be attached
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_mixer_attach_hctl(snd_mixer_t *mixer, snd_hctl_t *hctl)
+{
+	snd_mixer_slave_t *slave;
+	int err;
+
+	assert(hctl);
+	slave = calloc(1, sizeof(*slave));
+	if (slave == NULL)
+		return -ENOMEM;
+	err = snd_hctl_nonblock(hctl, 1);
+	if (err < 0) {
+		snd_hctl_close(hctl);
+		free(slave);
+		return err;
+	}
+	snd_hctl_set_callback(hctl, hctl_event_handler);
+	snd_hctl_set_callback_private(hctl, mixer);
+	slave->hctl = hctl;
+	list_add_tail(&slave->list, &mixer->slaves);
+	return 0;
+}
+
+/**
+ * \brief Detach a previously attached HCTL to an opened mixer freeing all related resources
+ * \param mixer Mixer handle
+ * \param name HCTL previously attached
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_mixer_detach(snd_mixer_t *mixer, const char *name)
+{
+	struct list_head *pos;
+	list_for_each(pos, &mixer->slaves) {
+		snd_mixer_slave_t *s;
+		s = list_entry(pos, snd_mixer_slave_t, list);
+		if (strcmp(name, snd_hctl_name(s->hctl)) == 0) {
+			snd_hctl_close(s->hctl);
+			list_del(pos);
+			free(s);
+			return 0;
+		}
+	}
+	return -ENOENT;
+}
+
+/**
+ * \brief Detach a previously attached HCTL to an opened mixer freeing all related resources
+ * \param mixer Mixer handle
+ * \param hctl HCTL previously attached
+ * \return 0 on success otherwise a negative error code
+ *
+ * Note: The hctl handle is not closed!
+ */
+int snd_mixer_detach_hctl(snd_mixer_t *mixer, snd_hctl_t *hctl)
+{
+	struct list_head *pos;
+	list_for_each(pos, &mixer->slaves) {
+		snd_mixer_slave_t *s;
+		s = list_entry(pos, snd_mixer_slave_t, list);
+		if (hctl == s->hctl) {
+			list_del(pos);
+			free(s);
+			return 0;
+		}
+	}
+	return -ENOENT;
+}
+
+/**
+ * \brief Obtain a HCTL pointer associated to given name
+ * \param mixer Mixer handle
+ * \param name HCTL previously attached
+ * \param hctl HCTL pointer
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_mixer_get_hctl(snd_mixer_t *mixer, const char *name, snd_hctl_t **hctl)
+{
+	struct list_head *pos;
+	list_for_each(pos, &mixer->slaves) {
+		snd_mixer_slave_t *s;
+		s = list_entry(pos, snd_mixer_slave_t, list);
+		if (strcmp(name, snd_hctl_name(s->hctl)) == 0) {
+			*hctl = s->hctl;
+			return 0;
+		}
+	}
+	return -ENOENT;
+}
+
+static int snd_mixer_throw_event(snd_mixer_t *mixer, unsigned int mask,
+			  snd_mixer_elem_t *elem)
+{
+	mixer->events++;
+	if (mixer->callback)
+		return mixer->callback(mixer, mask, elem);
+	return 0;
+}
+
+static int snd_mixer_elem_throw_event(snd_mixer_elem_t *elem, unsigned int mask)
+{
+	elem->class->mixer->events++;
+	if (elem->callback)
+		return elem->callback(elem, mask);
+	return 0;
+}
+
+static int _snd_mixer_find_elem(snd_mixer_t *mixer, snd_mixer_elem_t *elem, int *dir)
+{
+	unsigned int l, u;
+	int c = 0;
+	int idx = -1;
+	assert(mixer && elem);
+	assert(mixer->compare);
+	l = 0;
+	u = mixer->count;
+	while (l < u) {
+		idx = (l + u) / 2;
+		c = mixer->compare(elem, mixer->pelems[idx]);
+		if (c < 0)
+			u = idx;
+		else if (c > 0)
+			l = idx + 1;
+		else
+			break;
+	}
+	*dir = c;
+	return idx;
+}
+
+/**
+ * \brief Get private data associated to give mixer element
+ * \param elem Mixer element
+ * \return private data
+ *
+ * For use by mixer element class specific code.
+ */
+void *snd_mixer_elem_get_private(const snd_mixer_elem_t *elem)
+{
+	return elem->private_data;
+}
+
+/**
+ * \brief Allocate a new mixer element
+ * \param elem Returned mixer element
+ * \param type Mixer element type
+ * \param compare_weight Mixer element compare weight
+ * \param private_data Private data
+ * \param private_free Private data free callback
+ * \return 0 on success otherwise a negative error code
+ *
+ * For use by mixer element class specific code.
+ */
+int snd_mixer_elem_new(snd_mixer_elem_t **elem,
+		       snd_mixer_elem_type_t type,
+		       int compare_weight,
+		       void *private_data,
+		       void (*private_free)(snd_mixer_elem_t *elem))
+{
+	snd_mixer_elem_t *melem = calloc(1, sizeof(*melem));
+	if (melem == NULL)
+		return -ENOMEM;
+	melem->type = type;
+	melem->compare_weight = compare_weight;
+	melem->private_data = private_data;
+	melem->private_free = private_free;
+	INIT_LIST_HEAD(&melem->helems);
+	*elem = melem;
+	return 0;
+}
+
+/**
+ * \brief Add an element for a registered mixer element class
+ * \param elem Mixer element
+ * \param class Mixer element class
+ * \return 0 on success otherwise a negative error code
+ *
+ * For use by mixer element class specific code.
+ */
+int snd_mixer_elem_add(snd_mixer_elem_t *elem, snd_mixer_class_t *class)
+{
+	int dir, idx;
+	snd_mixer_t *mixer = class->mixer;
+	elem->class = class;
+
+	if (mixer->count == mixer->alloc) {
+		snd_mixer_elem_t **m;
+		mixer->alloc += 32;
+		m = realloc(mixer->pelems, sizeof(*m) * mixer->alloc);
+		if (!m) {
+			mixer->alloc -= 32;
+			return -ENOMEM;
+		}
+		mixer->pelems = m;
+	}
+	if (mixer->count == 0) {
+		list_add_tail(&elem->list, &mixer->elems);
+		mixer->pelems[0] = elem;
+	} else {
+		idx = _snd_mixer_find_elem(mixer, elem, &dir);
+		assert(dir != 0);
+		if (dir > 0) {
+			list_add(&elem->list, &mixer->pelems[idx]->list);
+			idx++;
+		} else {
+			list_add_tail(&elem->list, &mixer->pelems[idx]->list);
+		}
+		memmove(mixer->pelems + idx + 1,
+			mixer->pelems + idx,
+			(mixer->count - idx) * sizeof(snd_mixer_elem_t *));
+		mixer->pelems[idx] = elem;
+	}
+	mixer->count++;
+	return snd_mixer_throw_event(mixer, SND_CTL_EVENT_MASK_ADD, elem);
+}
+
+/**
+ * \brief Remove a mixer element
+ * \param elem Mixer element
+ * \return 0 on success otherwise a negative error code
+ *
+ * For use by mixer element class specific code.
+ */
+int snd_mixer_elem_remove(snd_mixer_elem_t *elem)
+{
+	snd_mixer_t *mixer = elem->class->mixer;
+	bag_iterator_t i, n;
+	int err, idx, dir;
+	unsigned int m;
+	assert(elem);
+	assert(mixer->count);
+	idx = _snd_mixer_find_elem(mixer, elem, &dir);
+	if (dir != 0)
+		return -EINVAL;
+	bag_for_each_safe(i, n, &elem->helems) {
+		snd_hctl_elem_t *helem = bag_iterator_entry(i);
+		snd_mixer_elem_detach(elem, helem);
+	}
+	err = snd_mixer_elem_throw_event(elem, SND_CTL_EVENT_MASK_REMOVE);
+	list_del(&elem->list);
+	snd_mixer_elem_free(elem);
+	mixer->count--;
+	m = mixer->count - idx;
+	if (m > 0)
+		memmove(mixer->pelems + idx,
+			mixer->pelems + idx + 1,
+			m * sizeof(snd_mixer_elem_t *));
+	return err;
+}
+
+/**
+ * \brief Free a mixer element
+ * \param elem Mixer element
+ * \return 0 on success otherwise a negative error code
+ *
+ * For use by mixer element class specific code.
+ */
+void snd_mixer_elem_free(snd_mixer_elem_t *elem)
+{
+	if (elem->private_free)
+		elem->private_free(elem);
+	free(elem);
+}
+
+/**
+ * \brief Mixer element informations are changed
+ * \param elem Mixer element
+ * \return 0 on success otherwise a negative error code
+ *
+ * For use by mixer element class specific code.
+ */
+int snd_mixer_elem_info(snd_mixer_elem_t *elem)
+{
+	return snd_mixer_elem_throw_event(elem, SND_CTL_EVENT_MASK_INFO);
+}
+
+/**
+ * \brief Mixer element values is changed
+ * \param elem Mixer element
+ * \return 0 on success otherwise a negative error code
+ *
+ * For use by mixer element class specific code.
+ */
+int snd_mixer_elem_value(snd_mixer_elem_t *elem)
+{
+	return snd_mixer_elem_throw_event(elem, SND_CTL_EVENT_MASK_VALUE);
+}
+
+/**
+ * \brief Register mixer element class
+ * \param class Mixer element class
+ * \param mixer Mixer handle
+ * \return 0 on success otherwise a negative error code
+ *
+ * For use by mixer element class specific code.
+ */
+int snd_mixer_class_register(snd_mixer_class_t *class, snd_mixer_t *mixer)
+{
+	struct list_head *pos;
+	class->mixer = mixer;
+	list_add_tail(&class->list, &mixer->classes);
+	if (!class->event)
+		return 0;
+	list_for_each(pos, &mixer->slaves) {
+		int err;
+		snd_mixer_slave_t *slave;
+		snd_hctl_elem_t *elem;
+		slave = list_entry(pos, snd_mixer_slave_t, list);
+		elem = snd_hctl_first_elem(slave->hctl);
+		while (elem) {
+			err = class->event(class, SND_CTL_EVENT_MASK_ADD, elem, NULL);
+			if (err < 0)
+				return err;
+			elem = snd_hctl_elem_next(elem);
+		}
+	}
+	return 0;
+}
+
+/**
+ * \brief Unregister mixer element class and remove all its elements
+ * \param class Mixer element class
+ * \return 0 on success otherwise a negative error code
+ *
+ * Note that the class structure is also deallocated!
+ */
+int snd_mixer_class_unregister(snd_mixer_class_t *class)
+{
+	unsigned int k;
+	snd_mixer_elem_t *e;
+	snd_mixer_t *mixer = class->mixer;
+	for (k = mixer->count; k > 0; k--) {
+		e = mixer->pelems[k-1];
+		if (e->class == class)
+			snd_mixer_elem_remove(e);
+	}
+	if (class->private_free)
+		class->private_free(class);
+	list_del(&class->list);
+	free(class);
+	return 0;
+}
+
+/**
+ * \brief Load a mixer elements
+ * \param mixer Mixer handle
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_mixer_load(snd_mixer_t *mixer)
+{
+	struct list_head *pos;
+	list_for_each(pos, &mixer->slaves) {
+		int err;
+		snd_mixer_slave_t *s;
+		s = list_entry(pos, snd_mixer_slave_t, list);
+		err = snd_hctl_load(s->hctl);
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+/**
+ * \brief Unload all mixer elements and free all related resources
+ * \param mixer Mixer handle
+ */
+void snd_mixer_free(snd_mixer_t *mixer)
+{
+	struct list_head *pos;
+	list_for_each(pos, &mixer->slaves) {
+		snd_mixer_slave_t *s;
+		s = list_entry(pos, snd_mixer_slave_t, list);
+		snd_hctl_free(s->hctl);
+	}
+}
+
+/**
+ * \brief Close a mixer and free all related resources
+ * \param mixer Mixer handle
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_mixer_close(snd_mixer_t *mixer)
+{
+	int res = 0;
+	assert(mixer);
+	while (!list_empty(&mixer->classes)) {
+		snd_mixer_class_t *c;
+		c = list_entry(mixer->classes.next, snd_mixer_class_t, list);
+		snd_mixer_class_unregister(c);
+	}
+	assert(list_empty(&mixer->elems));
+	assert(mixer->count == 0);
+	free(mixer->pelems);
+	mixer->pelems = NULL;
+	while (!list_empty(&mixer->slaves)) {
+		int err;
+		snd_mixer_slave_t *s;
+		s = list_entry(mixer->slaves.next, snd_mixer_slave_t, list);
+		err = snd_hctl_close(s->hctl);
+		if (err < 0)
+			res = err;
+		list_del(&s->list);
+		free(s);
+	}
+	free(mixer);
+	return res;
+}
+
+static int snd_mixer_compare_default(const snd_mixer_elem_t *c1,
+				     const snd_mixer_elem_t *c2)
+{
+	int d = c1->compare_weight - c2->compare_weight;
+	if (d)
+		return d;
+	assert(c1->class && c1->class->compare);
+	assert(c2->class && c2->class->compare);
+	assert(c1->class == c2->class);
+	return c1->class->compare(c1, c2);
+}
+
+static int mixer_compare(const void *a, const void *b)
+{
+	snd_mixer_t *mixer;
+
+	mixer = (*((const snd_mixer_elem_t * const *)a))->class->mixer;
+	return mixer->compare(*(const snd_mixer_elem_t * const *)a, *(const snd_mixer_elem_t * const *)b);
+}
+
+static int snd_mixer_sort(snd_mixer_t *mixer)
+{
+	unsigned int k;
+	assert(mixer);
+	assert(mixer->compare);
+	INIT_LIST_HEAD(&mixer->elems);
+	qsort(mixer->pelems, mixer->count, sizeof(snd_mixer_elem_t *), mixer_compare);
+	for (k = 0; k < mixer->count; k++)
+		list_add_tail(&mixer->pelems[k]->list, &mixer->elems);
+	return 0;
+}
+
+/**
+ * \brief Change mixer compare function and reorder elements
+ * \param mixer Mixer handle
+ * \param compare Element compare function
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_mixer_set_compare(snd_mixer_t *mixer, snd_mixer_compare_t compare)
+{
+	snd_mixer_compare_t compare_old;
+	int err;
+
+	assert(mixer);
+	compare_old = mixer->compare;
+	mixer->compare = compare == NULL ? snd_mixer_compare_default : compare;
+	if ((err = snd_mixer_sort(mixer)) < 0) {
+		mixer->compare = compare_old;
+		return err;
+	}
+	return 0;
+}
+
+/**
+ * \brief get count of poll descriptors for mixer handle
+ * \param mixer Mixer handle
+ * \return count of poll descriptors
+ */
+int snd_mixer_poll_descriptors_count(snd_mixer_t *mixer)
+{
+	struct list_head *pos;
+	unsigned int c = 0;
+	assert(mixer);
+	list_for_each(pos, &mixer->slaves) {
+		snd_mixer_slave_t *s;
+		int n;
+		s = list_entry(pos, snd_mixer_slave_t, list);
+		n = snd_hctl_poll_descriptors_count(s->hctl);
+		if (n < 0)
+			return n;
+		c += n;
+	}
+	return c;
+}
+
+/**
+ * \brief get poll descriptors
+ * \param mixer Mixer handle
+ * \param pfds array of poll descriptors
+ * \param space space in the poll descriptor array
+ * \return count of filled descriptors
+ */
+int snd_mixer_poll_descriptors(snd_mixer_t *mixer, struct pollfd *pfds, unsigned int space)
+{
+	struct list_head *pos;
+	unsigned int count = 0;
+	assert(mixer);
+	list_for_each(pos, &mixer->slaves) {
+		snd_mixer_slave_t *s;
+		int n;
+		s = list_entry(pos, snd_mixer_slave_t, list);
+		n = snd_hctl_poll_descriptors(s->hctl, pfds, space);
+		if (n < 0)
+			return n;
+		if (space >= (unsigned int) n) {
+			count += n;
+			space -= n;
+			pfds += n;
+		} else
+			space = 0;
+	}
+	return count;
+}
+
+/**
+ * \brief get returned events from poll descriptors
+ * \param mixer Mixer handle
+ * \param pfds array of poll descriptors
+ * \param nfds count of poll descriptors
+ * \param revents returned events
+ * \return zero if success, otherwise a negative error code
+ */
+int snd_mixer_poll_descriptors_revents(snd_mixer_t *mixer, struct pollfd *pfds, unsigned int nfds, unsigned short *revents)
+{
+	unsigned int idx;
+	unsigned short res;
+        assert(mixer && pfds && revents);
+	if (nfds == 0)
+		return -EINVAL;
+	res = 0;
+	for (idx = 0; idx < nfds; idx++)
+		res |= pfds->revents & (POLLIN|POLLERR|POLLNVAL);
+	*revents = res;
+	return 0;
+}
+
+/**
+ * \brief Wait for a mixer to become ready (i.e. at least one event pending)
+ * \param mixer Mixer handle
+ * \param timeout maximum time in milliseconds to wait
+ * \return 0 otherwise a negative error code on failure
+ */
+int snd_mixer_wait(snd_mixer_t *mixer, int timeout)
+{
+	struct pollfd spfds[16];
+	struct pollfd *pfds = spfds;
+	int err;
+	int count;
+	count = snd_mixer_poll_descriptors(mixer, pfds, sizeof(spfds) / sizeof(spfds[0]));
+	if (count < 0)
+		return count;
+	if ((unsigned int) count > sizeof(spfds) / sizeof(spfds[0])) {
+		pfds = malloc(count * sizeof(*pfds));
+		if (!pfds)
+			return -ENOMEM;
+		err = snd_mixer_poll_descriptors(mixer, pfds, 
+						 (unsigned int) count);
+		assert(err == count);
+	}
+	err = poll(pfds, (unsigned int) count, timeout);
+	if (err < 0)
+		return -errno;
+	return 0;
+}
+
+/**
+ * \brief get first element for a mixer
+ * \param mixer Mixer handle
+ * \return pointer to first element
+ */
+snd_mixer_elem_t *snd_mixer_first_elem(snd_mixer_t *mixer)
+{
+	assert(mixer);
+	if (list_empty(&mixer->elems))
+		return NULL;
+	return list_entry(mixer->elems.next, snd_mixer_elem_t, list);
+}
+
+/**
+ * \brief get last element for a mixer
+ * \param mixer Mixer handle
+ * \return pointer to last element
+ */
+snd_mixer_elem_t *snd_mixer_last_elem(snd_mixer_t *mixer)
+{
+	assert(mixer);
+	if (list_empty(&mixer->elems))
+		return NULL;
+	return list_entry(mixer->elems.prev, snd_mixer_elem_t, list);
+}
+
+/**
+ * \brief get next mixer element
+ * \param elem mixer element
+ * \return pointer to next element
+ */
+snd_mixer_elem_t *snd_mixer_elem_next(snd_mixer_elem_t *elem)
+{
+	assert(elem);
+	if (elem->list.next == &elem->class->mixer->elems)
+		return NULL;
+	return list_entry(elem->list.next, snd_mixer_elem_t, list);
+}
+
+/**
+ * \brief get previous mixer element
+ * \param elem mixer element
+ * \return pointer to previous element
+ */
+snd_mixer_elem_t *snd_mixer_elem_prev(snd_mixer_elem_t *elem)
+{
+	assert(elem);
+	if (elem->list.prev == &elem->class->mixer->elems)
+		return NULL;
+	return list_entry(elem->list.prev, snd_mixer_elem_t, list);
+}
+
+/**
+ * \brief Handle pending mixer events invoking callbacks
+ * \param mixer Mixer handle
+ * \return Number of events that occured on success, otherwise a negative error code on failure
+ */
+int snd_mixer_handle_events(snd_mixer_t *mixer)
+{
+	struct list_head *pos;
+	assert(mixer);
+	mixer->events = 0;
+	list_for_each(pos, &mixer->slaves) {
+		int err;
+		snd_mixer_slave_t *s;
+		s = list_entry(pos, snd_mixer_slave_t, list);
+		err = snd_hctl_handle_events(s->hctl);
+		if (err < 0)
+			return err;
+	}
+	return mixer->events;
+}
+
+/**
+ * \brief Set callback function for a mixer
+ * \param obj mixer handle
+ * \param val callback function
+ */
+void snd_mixer_set_callback(snd_mixer_t *obj, snd_mixer_callback_t val)
+{
+	assert(obj);
+	obj->callback = val;
+}
+
+/**
+ * \brief Set callback private value for a mixer
+ * \param mixer mixer handle
+ * \param val callback private value
+ */
+void snd_mixer_set_callback_private(snd_mixer_t *mixer, void * val)
+{
+	assert(mixer);
+	mixer->callback_private = val;
+}
+
+/**
+ * \brief Get callback private value for a mixer
+ * \param mixer mixer handle
+ * \return callback private value
+ */
+void * snd_mixer_get_callback_private(const snd_mixer_t *mixer)
+{
+	assert(mixer);
+	return mixer->callback_private;
+}
+
+/**
+ * \brief Get elements count for a mixer
+ * \param mixer mixer handle
+ * \return elements count
+ */
+unsigned int snd_mixer_get_count(const snd_mixer_t *mixer)
+{
+	assert(mixer);
+	return mixer->count;
+}
+
+/**
+ * \brief Set callback function for a mixer element
+ * \param mixer mixer element
+ * \param val callback function
+ */
+void snd_mixer_elem_set_callback(snd_mixer_elem_t *mixer, snd_mixer_elem_callback_t val)
+{
+	assert(mixer);
+	mixer->callback = val;
+}
+
+/**
+ * \brief Set callback private value for a mixer element
+ * \param mixer mixer element
+ * \param val callback private value
+ */
+void snd_mixer_elem_set_callback_private(snd_mixer_elem_t *mixer, void * val)
+{
+	assert(mixer);
+	mixer->callback_private = val;
+}
+
+/**
+ * \brief Get callback private value for a mixer element
+ * \param mixer mixer element
+ * \return callback private value
+ */
+void * snd_mixer_elem_get_callback_private(const snd_mixer_elem_t *mixer)
+{
+	assert(mixer);
+	return mixer->callback_private;
+}
+
+/**
+ * \brief Get type for a mixer element
+ * \param mixer mixer element
+ * \return mixer element type
+ */
+snd_mixer_elem_type_t snd_mixer_elem_get_type(const snd_mixer_elem_t *mixer)
+{
+	assert(mixer);
+	return mixer->type;
+}
+
+
+/**
+ * \brief get size of #snd_mixer_class_t
+ * \return size in bytes
+ */
+size_t snd_mixer_class_sizeof()
+{
+	return sizeof(snd_mixer_class_t);
+}
+
+/**
+ * \brief allocate an invalid #snd_mixer_class_t using standard malloc
+ * \param ptr returned pointer
+ * \return 0 on success otherwise negative error code
+ */
+int snd_mixer_class_malloc(snd_mixer_class_t **ptr)
+{
+	assert(ptr);
+	*ptr = calloc(1, sizeof(snd_mixer_class_t));
+	if (!*ptr)
+		return -ENOMEM;
+	return 0;
+}
+
+/**
+ * \brief frees a previously allocated #snd_mixer_class_t
+ * \param obj pointer to object to free
+ */
+void snd_mixer_class_free(snd_mixer_class_t *obj)
+{
+	if (obj->private_free)
+		obj->private_free(obj);
+	free(obj);
+}
+
+/**
+ * \brief copy one #snd_mixer_class_t to another
+ * \param dst pointer to destination
+ * \param src pointer to source
+ */
+void snd_mixer_class_copy(snd_mixer_class_t *dst, const snd_mixer_class_t *src)
+{
+	assert(dst && src);
+	*dst = *src;
+}
+
+/**
+ * \brief Get a mixer associated to given mixer class
+ * \param obj Mixer simple class identifier
+ * \return mixer pointer
+ */
+snd_mixer_t *snd_mixer_class_get_mixer(const snd_mixer_class_t *obj)
+{
+	assert(obj);
+	return obj->mixer;
+}
+
+/**
+ * \brief Get mixer event callback associated to given mixer class
+ * \param obj Mixer simple class identifier
+ * \return event callback pointer
+ */
+snd_mixer_event_t snd_mixer_class_get_event(const snd_mixer_class_t *obj)
+{
+	assert(obj);
+	return obj->event;
+}
+
+/**
+ * \brief Get mixer private data associated to given mixer class
+ * \param obj Mixer simple class identifier
+ * \return event callback pointer
+ */
+void *snd_mixer_class_get_private(const snd_mixer_class_t *obj)
+{
+	assert(obj);
+	return obj->private_data;
+}
+
+
+/**
+ * \brief Get mixer compare callback associated to given mixer class
+ * \param obj Mixer simple class identifier
+ * \return event callback pointer
+ */
+snd_mixer_compare_t snd_mixer_class_get_compare(const snd_mixer_class_t *obj)
+{
+	assert(obj);
+	return obj->compare;
+}
+
+/**
+ * \brief Set mixer event callback to given mixer class
+ * \param obj Mixer simple class identifier
+ * \param event Event callback
+ * \return zero if success, otherwise a negative error code
+ */
+int snd_mixer_class_set_event(snd_mixer_class_t *obj, snd_mixer_event_t event)
+{
+	assert(obj);
+	obj->event = event;
+	return 0;
+}
+
+/**
+ * \brief Set mixer private data to given mixer class
+ * \param obj Mixer simple class identifier
+ * \param private_data class private data
+ * \return zero if success, otherwise a negative error code
+ */
+int snd_mixer_class_set_private(snd_mixer_class_t *obj, void *private_data)
+{
+	assert(obj);
+	obj->private_data = private_data;
+	return 0;
+}
+
+/**
+ * \brief Set mixer private data free callback to given mixer class
+ * \param obj Mixer simple class identifier
+ * \param private_free Mixer class private data free callback
+ * \return zero if success, otherwise a negative error code
+ */
+int snd_mixer_class_set_private_free(snd_mixer_class_t *obj, void (*private_free)(snd_mixer_class_t *class))
+{
+	assert(obj);
+	obj->private_free = private_free;
+	return 0;
+}
+
+/**
+ * \brief Set mixer compare callback to given mixer class
+ * \param obj Mixer simple class identifier
+ * \param compare the compare callback to be used
+ * \return zero if success, otherwise a negative error code
+ */
+int snd_mixer_class_set_compare(snd_mixer_class_t *obj, snd_mixer_compare_t compare)
+{
+	assert(obj);
+	obj->compare = compare;
+	return 0;
+}
diff --git a/src/mixer/mixer_local.h b/src/mixer/mixer_local.h
new file mode 100644
index 0000000..27b4a3b
--- /dev/null
+++ b/src/mixer/mixer_local.h
@@ -0,0 +1,82 @@
+/*
+ *  Mixer Interface - local header file
+ *  Copyright (c) 2000 by Jaroslav Kysela <perex@perex.cz>
+ *  Copyright (c) 2001 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include "local.h"
+
+typedef struct _bag1 {
+	void *ptr;
+	struct list_head list;
+} bag1_t;
+
+typedef struct list_head bag_t;
+
+int bag_new(bag_t **bag);
+void bag_free(bag_t *bag);
+int bag_add(bag_t *bag, void *ptr);
+int bag_del(bag_t *bag, void *ptr);
+int bag_empty(bag_t *bag);
+void bag_del_all(bag_t *bag);
+
+typedef struct list_head *bag_iterator_t;
+
+#define bag_iterator_entry(i) (list_entry((i), bag1_t, list)->ptr)
+#define bag_for_each(pos, bag) list_for_each(pos, bag)
+#define bag_for_each_safe(pos, next, bag) list_for_each_safe(pos, next, bag)
+
+struct _snd_mixer_class {
+	struct list_head list;
+	snd_mixer_t *mixer;
+	snd_mixer_event_t event;
+	void *private_data;		
+	void (*private_free)(snd_mixer_class_t *class);
+	snd_mixer_compare_t compare;
+};
+
+struct _snd_mixer_elem {
+	snd_mixer_elem_type_t type;
+	struct list_head list;		/* links for list of all elems */
+	snd_mixer_class_t *class;
+	void *private_data;
+	void (*private_free)(snd_mixer_elem_t *elem);
+	snd_mixer_elem_callback_t callback;
+	void *callback_private;
+	bag_t helems;
+	int compare_weight;		/* compare weight (reversed) */
+};
+
+struct _snd_mixer {
+	struct list_head slaves;	/* list of all slaves */
+	struct list_head classes;	/* list of all elem classes */
+	struct list_head elems;		/* list of all elems */
+	snd_mixer_elem_t **pelems;	/* array of all elems */
+	unsigned int count;
+	unsigned int alloc;
+	unsigned int events;
+	snd_mixer_callback_t callback;
+	void *callback_private;
+	snd_mixer_compare_t compare;
+};
+
+struct _snd_mixer_selem_id {
+	char name[60];
+	unsigned int index;
+};
diff --git a/src/mixer/mixer_simple.h b/src/mixer/mixer_simple.h
new file mode 100644
index 0000000..e88b007
--- /dev/null
+++ b/src/mixer/mixer_simple.h
@@ -0,0 +1,31 @@
+/*
+ *  Mixer Simple Interface - local header file
+ *  Copyright (c) 2005 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include "mixer_abst.h"
+
+/* make local functions really local */
+#define snd_mixer_simple_none_register \
+	snd1_mixer_simple_none_register
+#define snd_mixer_simple_basic_register \
+	snd1_mixer_simple_basic_register
+
+int snd_mixer_simple_none_register(snd_mixer_t *mixer, struct snd_mixer_selem_regopt *options, snd_mixer_class_t **classp);
+int snd_mixer_simple_basic_register(snd_mixer_t *mixer, struct snd_mixer_selem_regopt *options, snd_mixer_class_t **classp);
diff --git a/src/mixer/simple.c b/src/mixer/simple.c
new file mode 100644
index 0000000..8079fe7
--- /dev/null
+++ b/src/mixer/simple.c
@@ -0,0 +1,1055 @@
+/**
+ * \file mixer/simple.c
+ * \brief Mixer Simple Element Class Interface
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \date 2001-2004
+ *
+ * Mixer simple element class interface.
+ */
+/*
+ *  Mixer Interface - simple controls
+ *  Copyright (c) 2000,2004 by Jaroslav Kysela <perex@perex.cz>
+ *  Copyright (c) 2001 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <math.h>
+#include "mixer_local.h"
+#include "mixer_simple.h"
+
+/**
+ * \brief Register mixer simple element class
+ * \param mixer Mixer handle
+ * \param options Options container
+ * \param classp Pointer to returned mixer simple element class handle (or NULL)
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_mixer_selem_register(snd_mixer_t *mixer,
+			     struct snd_mixer_selem_regopt *options,
+			     snd_mixer_class_t **classp)
+{
+	if (options && options->ver == 1) {
+		if (options->device != NULL &&
+		    (options->playback_pcm != NULL ||
+		     options->capture_pcm != NULL))
+			return -EINVAL;
+		if (options->device == NULL &&
+		    options->playback_pcm == NULL &&
+		    options->capture_pcm == NULL)
+			return -EINVAL;
+	}
+	if (options == NULL ||
+	    (options->ver == 1 && options->abstract == SND_MIXER_SABSTRACT_NONE)) {
+		int err = snd_mixer_simple_none_register(mixer, options, classp);
+		if (err < 0)
+			return err;
+		if (options != NULL) {
+			err = snd_mixer_attach(mixer, options->device);
+			if (err < 0)
+				return err;
+		}
+		return 0;
+	} else if (options->ver == 1) {
+		if (options->abstract == SND_MIXER_SABSTRACT_BASIC)
+			return snd_mixer_simple_basic_register(mixer, options, classp);
+	}
+	return -ENXIO;
+}
+
+#ifndef DOC_HIDDEN
+
+#define CHECK_BASIC(xelem) \
+{ \
+	assert(xelem); \
+	assert((xelem)->type == SND_MIXER_ELEM_SIMPLE); \
+}
+
+#define CHECK_DIR(xelem, xwhat) \
+{ \
+	unsigned int xcaps = ((sm_selem_t *)(elem)->private_data)->caps; \
+	if (! (xcaps & (xwhat))) \
+		return -EINVAL; \
+}
+
+#define CHECK_DIR_CHN(xelem, xwhat, xjoin, xchannel) \
+{ \
+	unsigned int xcaps = ((sm_selem_t *)(elem)->private_data)->caps; \
+	if (! (xcaps & (xwhat))) \
+		return -EINVAL; \
+	if (xcaps & (xjoin)) \
+		xchannel = 0; \
+}
+
+#define CHECK_ENUM(xelem) \
+	if (!(((sm_selem_t *)(elem)->private_data)->caps & (SM_CAP_PENUM|SM_CAP_CENUM))) \
+		return -EINVAL;
+
+#define COND_CAPS(xelem, what) \
+	!!(((sm_selem_t *)(elem)->private_data)->caps & (what))
+
+#endif /* !DOC_HIDDEN */
+
+#ifndef DOC_HIDDEN
+int snd_mixer_selem_compare(const snd_mixer_elem_t *c1, const snd_mixer_elem_t *c2)
+{
+	sm_selem_t *s1 = c1->private_data;
+	sm_selem_t *s2 = c2->private_data;
+	int res = strcmp(s1->id->name, s2->id->name);
+	if (res)
+		return res;
+	return s1->id->index - s2->id->index;
+}
+#endif
+	
+/**
+ * \brief Find a mixer simple element
+ * \param mixer Mixer handle
+ * \param id Mixer simple element identifier
+ * \return mixer simple element handle or NULL if not found
+ */
+snd_mixer_elem_t *snd_mixer_find_selem(snd_mixer_t *mixer,
+				       const snd_mixer_selem_id_t *id)
+{
+	struct list_head *list;
+	snd_mixer_elem_t *e;
+	sm_selem_t *s;
+
+	list_for_each(list, &mixer->elems) {
+		e = list_entry(list, snd_mixer_elem_t, list);
+		if (e->type != SND_MIXER_ELEM_SIMPLE)
+			continue;
+		s = e->private_data;
+		if (!strcmp(s->id->name, id->name) && s->id->index == id->index)
+			return e;
+	}
+	return NULL;
+}
+
+/**
+ * \brief Get mixer simple element identifier
+ * \param elem Mixer simple element handle
+ * \param id returned mixer simple element identifier
+ */
+void snd_mixer_selem_get_id(snd_mixer_elem_t *elem,
+			    snd_mixer_selem_id_t *id)
+{
+	sm_selem_t *s;
+	assert(id);
+	CHECK_BASIC(elem);
+	s = elem->private_data;
+	*id = *s->id;
+}
+
+/**
+ * \brief Get name part of mixer simple element identifier
+ * \param elem Mixer simple element handle
+ * \return name part of simple element identifier
+ */
+const char *snd_mixer_selem_get_name(snd_mixer_elem_t *elem)
+{
+	sm_selem_t *s;
+	CHECK_BASIC(elem);
+	s = elem->private_data;
+	return s->id->name;
+}
+
+/**
+ * \brief Get index part of mixer simple element identifier
+ * \param elem Mixer simple element handle
+ * \return index part of simple element identifier
+ */
+unsigned int snd_mixer_selem_get_index(snd_mixer_elem_t *elem)
+{
+	sm_selem_t *s;
+	CHECK_BASIC(elem);
+	s = elem->private_data;
+	return s->id->index;
+}
+
+/**
+ * \brief Return true if mixer simple element has only one volume control for both playback and capture
+ * \param elem Mixer simple element handle
+ * \return 0 separated control, 1 common control
+ */
+int snd_mixer_selem_has_common_volume(snd_mixer_elem_t *elem)
+{
+	CHECK_BASIC(elem);
+	return COND_CAPS(elem, SM_CAP_GVOLUME);
+}
+
+/**
+ * \brief Return true if mixer simple element has only one switch control for both playback and capture
+ * \param elem Mixer simple element handle
+ * \return 0 separated control, 1 common control
+ */
+int snd_mixer_selem_has_common_switch(snd_mixer_elem_t *elem)
+{
+	CHECK_BASIC(elem);
+	return COND_CAPS(elem, SM_CAP_GSWITCH);
+}
+
+/**
+ * \brief Return name of mixer simple element channel
+ * \param channel mixer simple element channel identifier
+ * \return channel name
+ */
+const char *snd_mixer_selem_channel_name(snd_mixer_selem_channel_id_t channel)
+{
+	static const char *const array[SND_MIXER_SCHN_LAST + 1] = {
+		[SND_MIXER_SCHN_FRONT_LEFT] = "Front Left",
+		[SND_MIXER_SCHN_FRONT_RIGHT] = "Front Right",
+		[SND_MIXER_SCHN_REAR_LEFT] = "Rear Left",
+		[SND_MIXER_SCHN_REAR_RIGHT] = "Rear Right",
+		[SND_MIXER_SCHN_FRONT_CENTER] = "Front Center",
+		[SND_MIXER_SCHN_WOOFER] = "Woofer",
+		[SND_MIXER_SCHN_SIDE_LEFT] = "Side Left",
+		[SND_MIXER_SCHN_SIDE_RIGHT] = "Side Right",
+		[SND_MIXER_SCHN_REAR_CENTER] = "Rear Center"
+	};
+	const char *p;
+	assert(channel <= SND_MIXER_SCHN_LAST);
+	p = array[channel];
+	if (!p)
+		return "?";
+	return p;
+}
+
+/**
+ * \brief Get info about the active state of a mixer simple element
+ * \param elem Mixer simple element handle
+ * \return 0 if not active, 1 if active
+ */
+int snd_mixer_selem_is_active(snd_mixer_elem_t *elem)
+{
+	CHECK_BASIC(elem);
+	return sm_selem_ops(elem)->is(elem, SM_PLAY, SM_OPS_IS_ACTIVE, 0);
+}
+
+/**
+ * \brief Get info about channels of playback stream of a mixer simple element
+ * \param elem Mixer simple element handle
+ * \return 0 if not mono, 1 if mono
+ */
+int snd_mixer_selem_is_playback_mono(snd_mixer_elem_t *elem)
+{
+	CHECK_BASIC(elem);
+	return sm_selem_ops(elem)->is(elem, SM_PLAY, SM_OPS_IS_MONO, 0);
+}
+
+/**
+ * \brief Get info about channels of playback stream of a mixer simple element
+ * \param elem Mixer simple element handle
+ * \param channel Mixer simple element channel identifier
+ * \return 0 if channel is not present, 1 if present
+ */
+int snd_mixer_selem_has_playback_channel(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel)
+{
+	CHECK_BASIC(elem);
+	return sm_selem_ops(elem)->is(elem, SM_PLAY, SM_OPS_IS_CHANNEL, (int)channel);
+}
+
+/**
+ * \brief Get range for playback volume of a mixer simple element
+ * \param elem Mixer simple element handle
+ * \param min Pointer to returned minimum
+ * \param max Pointer to returned maximum
+ */
+int snd_mixer_selem_get_playback_volume_range(snd_mixer_elem_t *elem,
+					       long *min, long *max)
+{
+	CHECK_BASIC(elem);
+	CHECK_DIR(elem, SM_CAP_PVOLUME);
+	return sm_selem_ops(elem)->get_range(elem, SM_PLAY, min, max);
+}
+
+/**
+ * \brief Get range in dB for playback volume of a mixer simple element
+ * \param elem Mixer simple element handle
+ * \param min Pointer to returned minimum (dB * 100)
+ * \param max Pointer to returned maximum (dB * 100)
+ */
+int snd_mixer_selem_get_playback_dB_range(snd_mixer_elem_t *elem,
+					  long *min, long *max)
+{
+	CHECK_BASIC(elem);
+	CHECK_DIR(elem, SM_CAP_PVOLUME);
+	return sm_selem_ops(elem)->get_dB_range(elem, SM_PLAY, min, max);
+}
+
+/**
+ * \brief Set range for playback volume of a mixer simple element
+ * \param elem Mixer simple element handle
+ * \param min minimum volume value
+ * \param max maximum volume value
+ */
+int snd_mixer_selem_set_playback_volume_range(snd_mixer_elem_t *elem, 
+					      long min, long max)
+{
+	CHECK_BASIC(elem);
+	assert(min < max);
+	CHECK_DIR(elem, SM_CAP_PVOLUME);
+	return sm_selem_ops(elem)->set_range(elem, SM_PLAY, min, max);
+}
+
+/**
+ * \brief Return info about playback volume control of a mixer simple element
+ * \param elem Mixer simple element handle
+ * \return 0 if no control is present, 1 if it's present
+ */
+int snd_mixer_selem_has_playback_volume(snd_mixer_elem_t *elem)
+{
+	CHECK_BASIC(elem);
+	return COND_CAPS(elem, SM_CAP_PVOLUME);
+}
+
+/**
+ * \brief Return info about playback volume control of a mixer simple element
+ * \param elem Mixer simple element handle
+ * \return 0 if control is separated per channel, 1 if control acts on all channels together
+ */
+int snd_mixer_selem_has_playback_volume_joined(snd_mixer_elem_t *elem)
+{
+	CHECK_BASIC(elem);
+	return COND_CAPS(elem, SM_CAP_PVOLUME_JOIN);
+}
+
+/**
+ * \brief Return info about playback switch control existence of a mixer simple element
+ * \param elem Mixer simple element handle
+ * \return 0 if no control is present, 1 if it's present
+ */
+int snd_mixer_selem_has_playback_switch(snd_mixer_elem_t *elem)
+{
+	CHECK_BASIC(elem);
+	return COND_CAPS(elem, SM_CAP_PSWITCH);
+}
+
+/**
+ * \brief Return info about playback switch control of a mixer simple element
+ * \param elem Mixer simple element handle
+ * \return 0 if control is separated per channel, 1 if control acts on all channels together
+ */
+int snd_mixer_selem_has_playback_switch_joined(snd_mixer_elem_t *elem)
+{
+	CHECK_BASIC(elem);
+	return COND_CAPS(elem, SM_CAP_PSWITCH_JOIN);
+}
+
+/**
+ * \brief Return corresponding dB value to an integer playback volume for a mixer simple element
+ * \param elem Mixer simple element handle
+ * \param value value to be converted to dB range
+ * \param dBvalue pointer to returned dB value
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_mixer_selem_ask_playback_vol_dB(snd_mixer_elem_t *elem, long value, long *dBvalue)
+{
+	CHECK_BASIC(elem);
+	CHECK_DIR(elem, SM_CAP_PVOLUME);
+	return sm_selem_ops(elem)->ask_vol_dB(elem, SM_PLAY, value, dBvalue);
+}
+
+/**
+ * \brief Return corresponding integer playback volume for given dB value for a mixer simple element
+ * \param elem Mixer simple element handle
+ * \param value value to be converted to dB range
+ * \param dir select direction (-1 = accurate or first bellow, 0 = accurate, 1 = accurate or first above)
+ * \param dBvalue pointer to returned dB value
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_mixer_selem_ask_playback_dB_vol(snd_mixer_elem_t *elem, long dBvalue, int dir, long *value)
+{
+	CHECK_BASIC(elem);
+	CHECK_DIR(elem, SM_CAP_PVOLUME);
+	return sm_selem_ops(elem)->ask_dB_vol(elem, SM_PLAY, dBvalue, value, dir);
+}
+
+/**
+ * \brief Return value of playback volume control of a mixer simple element
+ * \param elem Mixer simple element handle
+ * \param channel mixer simple element channel identifier
+ * \param value pointer to returned value
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_mixer_selem_get_playback_volume(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, long *value)
+{
+	CHECK_BASIC(elem);
+	CHECK_DIR_CHN(elem, SM_CAP_PVOLUME, SM_CAP_PVOLUME_JOIN, channel);
+	return sm_selem_ops(elem)->get_volume(elem, SM_PLAY, channel, value);
+}
+
+/**
+ * \brief Return value of playback volume in dB control of a mixer simple element
+ * \param elem Mixer simple element handle
+ * \param channel mixer simple element channel identifier
+ * \param value pointer to returned value (dB * 100)
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_mixer_selem_get_playback_dB(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, long *value)
+{
+	unsigned int caps;
+
+	CHECK_BASIC(elem);
+	caps = ((sm_selem_t *)elem->private_data)->caps;
+	if (!(caps & SM_CAP_PVOLUME))
+		return -EINVAL;
+	if (caps & SM_CAP_PVOLUME_JOIN)
+		channel = 0;
+	return sm_selem_ops(elem)->get_dB(elem, SM_PLAY, channel, value);
+}
+
+/**
+ * \brief Return value of playback switch control of a mixer simple element
+ * \param elem Mixer simple element handle
+ * \param channel mixer simple element channel identifier
+ * \param value pointer to returned value
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_mixer_selem_get_playback_switch(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, int *value)
+{
+	CHECK_BASIC(elem);
+	CHECK_DIR_CHN(elem, SM_CAP_PSWITCH, SM_CAP_PSWITCH_JOIN, channel);
+	return sm_selem_ops(elem)->get_switch(elem, SM_PLAY, channel, value);
+}
+
+/**
+ * \brief Set value of playback volume control of a mixer simple element
+ * \param elem Mixer simple element handle
+ * \param channel mixer simple element channel identifier
+ * \param value control value
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_mixer_selem_set_playback_volume(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, long value)
+{
+	CHECK_BASIC(elem);
+	CHECK_DIR_CHN(elem, SM_CAP_PVOLUME, SM_CAP_PVOLUME_JOIN, channel);
+	return sm_selem_ops(elem)->set_volume(elem, SM_PLAY, channel, value);
+}
+
+/**
+ * \brief Set value in dB of playback volume control of a mixer simple element
+ * \param elem Mixer simple element handle
+ * \param channel mixer simple element channel identifier
+ * \param value control value in dB * 100
+ * \param dir select direction (-1 = accurate or first bellow, 0 = accurate, 1 = accurate or first above)
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_mixer_selem_set_playback_dB(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, long value, int dir)
+{
+	CHECK_BASIC(elem);
+	CHECK_DIR_CHN(elem, SM_CAP_PVOLUME, SM_CAP_PVOLUME_JOIN, channel);
+	return sm_selem_ops(elem)->set_dB(elem, SM_PLAY, channel, value, dir);
+}
+
+/**
+ * \brief Set value of playback volume control for all channels of a mixer simple element
+ * \param elem Mixer simple element handle
+ * \param value control value
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_mixer_selem_set_playback_volume_all(snd_mixer_elem_t *elem, long value)
+{
+	snd_mixer_selem_channel_id_t chn;
+	int err;
+
+	for (chn = 0; chn < 32; chn++) {
+		if (!snd_mixer_selem_has_playback_channel(elem, chn))
+			continue;
+		err = snd_mixer_selem_set_playback_volume(elem, chn, value);
+		if (err < 0)
+			return err;
+		if (chn == 0 && snd_mixer_selem_has_playback_volume_joined(elem))
+			return 0;
+	}
+	return 0;
+}
+
+/**
+ * \brief Set value in dB of playback volume control for all channels of a mixer simple element
+ * \param elem Mixer simple element handle
+ * \param value control value in dB * 100
+ * \param dir select direction (-1 = accurate or first bellow, 0 = accurate, 1 = accurate or first above)
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_mixer_selem_set_playback_dB_all(snd_mixer_elem_t *elem, long value, int dir)
+{
+	snd_mixer_selem_channel_id_t chn;
+	int err;
+
+	for (chn = 0; chn < 32; chn++) {
+		if (!snd_mixer_selem_has_playback_channel(elem, chn))
+			continue;
+		err = snd_mixer_selem_set_playback_dB(elem, chn, value, dir);
+		if (err < 0)
+			return err;
+		if (chn == 0 && snd_mixer_selem_has_playback_volume_joined(elem))
+			return 0;
+	}
+	return 0;
+}
+
+/**
+ * \brief Set value of playback switch control of a mixer simple element
+ * \param elem Mixer simple element handle
+ * \param channel mixer simple element channel identifier
+ * \param value control value
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_mixer_selem_set_playback_switch(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, int value)
+{
+	CHECK_BASIC(elem);
+	CHECK_DIR_CHN(elem, SM_CAP_PSWITCH, SM_CAP_PSWITCH_JOIN, channel);
+	return sm_selem_ops(elem)->set_switch(elem, SM_PLAY, channel, value);
+}
+
+/**
+ * \brief Set value of playback switch control for all channels of a mixer simple element
+ * \param elem Mixer simple element handle
+ * \param value control value
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_mixer_selem_set_playback_switch_all(snd_mixer_elem_t *elem, int value)
+{
+	snd_mixer_selem_channel_id_t chn;
+	int err;
+
+	CHECK_BASIC(elem);
+	for (chn = 0; chn < 32; chn++) {
+		if (!snd_mixer_selem_has_playback_channel(elem, chn))
+			continue;
+		err = snd_mixer_selem_set_playback_switch(elem, chn, value);
+		if (err < 0)
+			return err;
+		if (chn == 0 && snd_mixer_selem_has_playback_switch_joined(elem))
+			return 0;
+	}
+	return 0;
+}
+
+/**
+ * \brief Get info about channels of capture stream of a mixer simple element
+ * \param elem Mixer simple element handle
+ * \return 0 if not mono, 1 if mono
+ */
+int snd_mixer_selem_is_capture_mono(snd_mixer_elem_t *elem)
+{
+	CHECK_BASIC(elem);
+	CHECK_DIR(elem, SM_CAP_CVOLUME|SM_CAP_CSWITCH);
+	return sm_selem_ops(elem)->is(elem, SM_CAPT, SM_OPS_IS_MONO, 0);
+}
+
+/**
+ * \brief Get info about channels of capture stream of a mixer simple element
+ * \param elem Mixer simple element handle
+ * \param channel Mixer simple element channel identifier
+ * \return 0 if channel is not present, 1 if present
+ */
+int snd_mixer_selem_has_capture_channel(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel)
+{
+	CHECK_BASIC(elem);
+	CHECK_DIR(elem, SM_CAP_CVOLUME|SM_CAP_CSWITCH);
+	return sm_selem_ops(elem)->is(elem, SM_CAPT, SM_OPS_IS_CHANNEL, channel);
+}
+
+/**
+ * \brief Get range for capture volume of a mixer simple element
+ * \param elem Mixer simple element handle
+ * \param min Pointer to returned minimum
+ * \param max Pointer to returned maximum
+ */
+int snd_mixer_selem_get_capture_volume_range(snd_mixer_elem_t *elem,
+					     long *min, long *max)
+{
+	CHECK_BASIC(elem);
+	CHECK_DIR(elem, SM_CAP_CVOLUME);
+	return sm_selem_ops(elem)->get_range(elem, SM_CAPT, min, max);
+}
+
+/**
+ * \brief Get range in dB for capture volume of a mixer simple element
+ * \param elem Mixer simple element handle
+ * \param min Pointer to returned minimum (dB * 100)
+ * \param max Pointer to returned maximum (dB * 100)
+ */
+int snd_mixer_selem_get_capture_dB_range(snd_mixer_elem_t *elem,
+					 long *min, long *max)
+{
+	CHECK_BASIC(elem);
+	CHECK_DIR(elem, SM_CAP_CVOLUME);
+	return sm_selem_ops(elem)->get_dB_range(elem, SM_CAPT, min, max);
+}
+
+/**
+ * \brief Set range for capture volume of a mixer simple element
+ * \param elem Mixer simple element handle
+ * \param min minimum volume value
+ * \param max maximum volume value
+ */
+int snd_mixer_selem_set_capture_volume_range(snd_mixer_elem_t *elem, 
+					     long min, long max)
+{
+	CHECK_BASIC(elem);
+	assert(min < max);
+	CHECK_DIR(elem, SM_CAP_CVOLUME);
+	return sm_selem_ops(elem)->set_range(elem, SM_CAPT, min, max);
+}
+
+/**
+ * \brief Return info about capture volume control of a mixer simple element
+ * \param elem Mixer simple element handle
+ * \return 0 if no control is present, 1 if it's present
+ */
+int snd_mixer_selem_has_capture_volume(snd_mixer_elem_t *elem)
+{
+	CHECK_BASIC(elem);
+	return COND_CAPS(elem, SM_CAP_CVOLUME);
+}
+
+/**
+ * \brief Return info about capture volume control of a mixer simple element
+ * \param elem Mixer simple element handle
+ * \return 0 if control is separated per channel, 1 if control acts on all channels together
+ */
+int snd_mixer_selem_has_capture_volume_joined(snd_mixer_elem_t *elem)
+{
+	CHECK_BASIC(elem);
+	return COND_CAPS(elem, SM_CAP_CVOLUME_JOIN);
+}
+
+/**
+ * \brief Return info about capture switch control existence of a mixer simple element
+ * \param elem Mixer simple element handle
+ * \return 0 if no control is present, 1 if it's present
+ */
+int snd_mixer_selem_has_capture_switch(snd_mixer_elem_t *elem)
+{
+	CHECK_BASIC(elem);
+	return COND_CAPS(elem, SM_CAP_CSWITCH);
+}
+
+/**
+ * \brief Return info about capture switch control of a mixer simple element
+ * \param elem Mixer simple element handle
+ * \return 0 if control is separated per channel, 1 if control acts on all channels together
+ */
+int snd_mixer_selem_has_capture_switch_joined(snd_mixer_elem_t *elem)
+{
+	CHECK_BASIC(elem);
+	return COND_CAPS(elem, SM_CAP_CSWITCH_JOIN);
+}
+
+/**
+ * \brief Return info about capture switch control of a mixer simple element
+ * \param elem Mixer simple element handle
+ * \return 0 if control is separated per element, 1 if control acts on other elements too (i.e. only one active at a time inside a group)
+ */
+int snd_mixer_selem_has_capture_switch_exclusive(snd_mixer_elem_t *elem)
+{
+	CHECK_BASIC(elem);
+	return COND_CAPS(elem, SM_CAP_CSWITCH_EXCL);
+}
+
+/**
+ * \brief Return info about capture switch control of a mixer simple element
+ * \param elem Mixer simple element handle
+ * \return group for switch exclusivity (see #snd_mixer_selem_has_capture_switch_exclusive)
+ */
+int snd_mixer_selem_get_capture_group(snd_mixer_elem_t *elem)
+{
+	sm_selem_t *s;
+	CHECK_BASIC(elem);
+	s = elem->private_data;
+	if (! (s->caps & SM_CAP_CSWITCH_EXCL))
+		return -EINVAL;
+	return s->capture_group;
+}
+
+/**
+ * \brief Return corresponding dB value to an integer capture volume for a mixer simple element
+ * \param elem Mixer simple element handle
+ * \param value value to be converted to dB range
+ * \param dBvalue pointer to returned dB value
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_mixer_selem_ask_capture_vol_dB(snd_mixer_elem_t *elem, long value, long *dBvalue)
+{
+	CHECK_BASIC(elem);
+	CHECK_DIR(elem, SM_CAP_CVOLUME);
+	return sm_selem_ops(elem)->ask_vol_dB(elem, SM_CAPT, value, dBvalue);
+}
+
+/**
+ * \brief Return corresponding integer capture volume for given dB value for a mixer simple element
+ * \param elem Mixer simple element handle
+ * \param dBvalue dB value to be converted to integer range
+ * \param value pointer to returned integer value
+ * \param dir select direction (-1 = accurate or first bellow, 0 = accurate, 1 = accurate or first above)
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_mixer_selem_ask_capture_dB_vol(snd_mixer_elem_t *elem, long dBvalue, int dir, long *value)
+{
+	CHECK_BASIC(elem);
+	CHECK_DIR(elem, SM_CAP_CVOLUME);
+	return sm_selem_ops(elem)->ask_dB_vol(elem, SM_CAPT, dBvalue, value, dir);
+}
+
+/**
+ * \brief Return value of capture volume control of a mixer simple element
+ * \param elem Mixer simple element handle
+ * \param channel mixer simple element channel identifier
+ * \param value pointer to returned value
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_mixer_selem_get_capture_volume(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, long *value)
+{
+	CHECK_BASIC(elem);
+	CHECK_DIR_CHN(elem, SM_CAP_CVOLUME, SM_CAP_CVOLUME_JOIN, channel);
+	return sm_selem_ops(elem)->get_volume(elem, SM_CAPT, channel, value);
+}
+
+/**
+ * \brief Return value of capture volume in dB control of a mixer simple element
+ * \param elem Mixer simple element handle
+ * \param channel mixer simple element channel identifier
+ * \param value pointer to returned value (dB * 100)
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_mixer_selem_get_capture_dB(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, long *value)
+{
+	CHECK_BASIC(elem);
+	CHECK_DIR_CHN(elem, SM_CAP_CVOLUME, SM_CAP_CVOLUME_JOIN, channel);
+	return sm_selem_ops(elem)->get_dB(elem, SM_CAPT, channel, value);
+}
+
+/**
+ * \brief Return value of capture switch control of a mixer simple element
+ * \param elem Mixer simple element handle
+ * \param channel mixer simple element channel identifier
+ * \param value pointer to returned value
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_mixer_selem_get_capture_switch(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, int *value)
+{
+	CHECK_BASIC(elem);
+	CHECK_DIR_CHN(elem, SM_CAP_CSWITCH, SM_CAP_CSWITCH_JOIN, channel);
+	return sm_selem_ops(elem)->get_switch(elem, SM_CAPT, channel, value);
+}
+
+/**
+ * \brief Set value of capture volume control of a mixer simple element
+ * \param elem Mixer simple element handle
+ * \param channel mixer simple element channel identifier
+ * \param value control value
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_mixer_selem_set_capture_volume(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, long value)
+{
+	CHECK_BASIC(elem);
+	CHECK_DIR_CHN(elem, SM_CAP_CVOLUME, SM_CAP_CVOLUME_JOIN, channel);
+	return sm_selem_ops(elem)->set_volume(elem, SM_CAPT, channel, value);
+}
+
+/**
+ * \brief Set value in dB of capture volume control of a mixer simple element
+ * \param elem Mixer simple element handle
+ * \param channel mixer simple element channel identifier
+ * \param value control value in dB * 100
+ * \param dir select direction (-1 = accurate or first bellow, 0 = accurate, 1 = accurate or first above)
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_mixer_selem_set_capture_dB(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, long value, int dir)
+{
+	CHECK_BASIC(elem);
+	CHECK_DIR_CHN(elem, SM_CAP_CVOLUME, SM_CAP_CVOLUME_JOIN, channel);
+	return sm_selem_ops(elem)->set_dB(elem, SM_CAPT, channel, value, dir);
+}
+
+/**
+ * \brief Set value of capture volume control for all channels of a mixer simple element
+ * \param elem Mixer simple element handle
+ * \param value control value
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_mixer_selem_set_capture_volume_all(snd_mixer_elem_t *elem, long value)
+{
+	snd_mixer_selem_channel_id_t chn;
+	int err;
+
+	for (chn = 0; chn < 32; chn++) {
+		if (!snd_mixer_selem_has_capture_channel(elem, chn))
+			continue;
+		err = snd_mixer_selem_set_capture_volume(elem, chn, value);
+		if (err < 0)
+			return err;
+		if (chn == 0 && snd_mixer_selem_has_capture_volume_joined(elem))
+			return 0;
+	}
+	return 0;
+}
+
+/**
+ * \brief Set value in dB of capture volume control for all channels of a mixer simple element
+ * \param elem Mixer simple element handle
+ * \param value control value in dB * 100
+ * \param dir select direction (-1 = accurate or first bellow, 0 = accurate, 1 = accurate or first above)
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_mixer_selem_set_capture_dB_all(snd_mixer_elem_t *elem, long value, int dir)
+{
+	snd_mixer_selem_channel_id_t chn;
+	int err;
+
+	for (chn = 0; chn < 32; chn++) {
+		if (!snd_mixer_selem_has_capture_channel(elem, chn))
+			continue;
+		err = snd_mixer_selem_set_capture_dB(elem, chn, value, dir);
+		if (err < 0)
+			return err;
+		if (chn == 0 && snd_mixer_selem_has_capture_volume_joined(elem))
+			return 0;
+	}
+	return 0;
+}
+
+/**
+ * \brief Set value of capture switch control of a mixer simple element
+ * \param elem Mixer simple element handle
+ * \param channel mixer simple element channel identifier
+ * \param value control value
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_mixer_selem_set_capture_switch(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, int value)
+{
+	CHECK_BASIC(elem);
+	CHECK_DIR_CHN(elem, SM_CAP_CSWITCH, SM_CAP_CSWITCH_JOIN, channel);
+	return sm_selem_ops(elem)->set_switch(elem, SM_CAPT, channel, value);
+}
+
+/**
+ * \brief Set value of capture switch control for all channels of a mixer simple element
+ * \param elem Mixer simple element handle
+ * \param value control value
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_mixer_selem_set_capture_switch_all(snd_mixer_elem_t *elem, int value)
+{
+	snd_mixer_selem_channel_id_t chn;
+	int err;
+
+	for (chn = 0; chn < 32; chn++) {
+		if (!snd_mixer_selem_has_capture_channel(elem, chn))
+			continue;
+		err = snd_mixer_selem_set_capture_switch(elem, chn, value);
+		if (err < 0)
+			return err;
+		if (chn == 0 && snd_mixer_selem_has_capture_switch_joined(elem))
+			return 0;
+	}
+	return 0;
+}
+
+/**
+ * \brief Return true if mixer simple element is an enumerated control
+ * \param elem Mixer simple element handle
+ * \return 0 normal volume/switch control, 1 enumerated control
+ */
+int snd_mixer_selem_is_enumerated(snd_mixer_elem_t *elem)
+{
+	CHECK_BASIC(elem);
+	return sm_selem_ops(elem)->is(elem, SM_PLAY, SM_OPS_IS_ENUMERATED, 0);
+}
+
+/**
+ * \brief Return true if mixer simple enumerated element belongs to the playback direction
+ * \param elem Mixer simple element handle
+ * \return 0 no playback direction, 1 playback direction
+ */
+int snd_mixer_selem_is_enum_playback(snd_mixer_elem_t *elem)
+{
+	CHECK_BASIC(elem);
+	CHECK_ENUM(elem);
+	return sm_selem_ops(elem)->is(elem, SM_PLAY, SM_OPS_IS_ENUMERATED, 1);
+}
+
+/**
+ * \brief Return true if mixer simple enumerated element belongs to the capture direction
+ * \param elem Mixer simple element handle
+ * \return 0 no capture direction, 1 capture direction
+ */
+int snd_mixer_selem_is_enum_capture(snd_mixer_elem_t *elem)
+{
+	CHECK_BASIC(elem);
+	CHECK_ENUM(elem);
+	return sm_selem_ops(elem)->is(elem, SM_CAPT, SM_OPS_IS_ENUMERATED, 1);
+}
+
+/**
+ * \brief Return the number of enumerated items of the given mixer simple element
+ * \param elem Mixer simple element handle
+ * \return the number of enumerated items, otherwise a negative error code
+ */
+int snd_mixer_selem_get_enum_items(snd_mixer_elem_t *elem)
+{
+	CHECK_BASIC(elem);
+	CHECK_ENUM(elem);
+	return sm_selem_ops(elem)->is(elem, SM_PLAY, SM_OPS_IS_ENUMCNT, 0);
+}
+
+/**
+ * \brief get the enumerated item string for the given mixer simple element
+ * \param elem Mixer simple element handle
+ * \param item the index of the enumerated item to query
+ * \param maxlen the maximal length to be stored
+ * \param buf the buffer to store the name string
+ * \return 0 if successful, otherwise a negative error code
+ */
+int snd_mixer_selem_get_enum_item_name(snd_mixer_elem_t *elem,
+				       unsigned int item,
+				       size_t maxlen, char *buf)
+{
+	CHECK_BASIC(elem);
+	CHECK_ENUM(elem);
+	return sm_selem_ops(elem)->enum_item_name(elem, item, maxlen, buf);
+}
+
+/**
+ * \brief get the current selected enumerated item for the given mixer simple element
+ * \param elem Mixer simple element handle
+ * \param channel mixer simple element channel identifier
+ * \param itemp the pointer to store the index of the enumerated item
+ * \return 0 if successful, otherwise a negative error code
+ */
+int snd_mixer_selem_get_enum_item(snd_mixer_elem_t *elem,
+				  snd_mixer_selem_channel_id_t channel,
+				  unsigned int *itemp)
+{
+	CHECK_BASIC(elem);
+	CHECK_ENUM(elem);
+	return sm_selem_ops(elem)->get_enum_item(elem, channel, itemp);
+}
+
+/**
+ * \brief set the current selected enumerated item for the given mixer simple element
+ * \param elem Mixer simple element handle
+ * \param channel mixer simple element channel identifier
+ * \param item the enumerated item index
+ * \return 0 if successful, otherwise a negative error code
+ */
+int snd_mixer_selem_set_enum_item(snd_mixer_elem_t *elem,
+				  snd_mixer_selem_channel_id_t channel,
+				  unsigned int item)
+{
+	CHECK_BASIC(elem);
+	CHECK_ENUM(elem);
+	return sm_selem_ops(elem)->set_enum_item(elem, channel, item);
+}
+
+/**
+ * \brief get size of #snd_mixer_selem_id_t
+ * \return size in bytes
+ */
+size_t snd_mixer_selem_id_sizeof()
+{
+	return sizeof(snd_mixer_selem_id_t);
+}
+
+/**
+ * \brief allocate an invalid #snd_mixer_selem_id_t using standard malloc
+ * \param ptr returned pointer
+ * \return 0 on success otherwise negative error code
+ */
+int snd_mixer_selem_id_malloc(snd_mixer_selem_id_t **ptr)
+{
+	assert(ptr);
+	*ptr = calloc(1, sizeof(snd_mixer_selem_id_t));
+	if (!*ptr)
+		return -ENOMEM;
+	return 0;
+}
+
+/**
+ * \brief frees a previously allocated #snd_mixer_selem_id_t
+ * \param obj pointer to object to free
+ */
+void snd_mixer_selem_id_free(snd_mixer_selem_id_t *obj)
+{
+	free(obj);
+}
+
+/**
+ * \brief copy one #snd_mixer_selem_id_t to another
+ * \param dst pointer to destination
+ * \param src pointer to source
+ */
+void snd_mixer_selem_id_copy(snd_mixer_selem_id_t *dst, const snd_mixer_selem_id_t *src)
+{
+	assert(dst && src);
+	*dst = *src;
+}
+
+/**
+ * \brief Get name part of a mixer simple element identifier
+ * \param obj Mixer simple element identifier
+ * \return name part
+ */
+const char *snd_mixer_selem_id_get_name(const snd_mixer_selem_id_t *obj)
+{
+	assert(obj);
+	return obj->name;
+}
+
+/**
+ * \brief Get index part of a mixer simple element identifier
+ * \param obj Mixer simple element identifier
+ * \return index part
+ */
+unsigned int snd_mixer_selem_id_get_index(const snd_mixer_selem_id_t *obj)
+{
+	assert(obj);
+	return obj->index;
+}
+
+/**
+ * \brief Set name part of a mixer simple element identifier
+ * \param obj Mixer simple element identifier
+ * \param val name part
+ */
+void snd_mixer_selem_id_set_name(snd_mixer_selem_id_t *obj, const char *val)
+{
+	assert(obj);
+	strncpy(obj->name, val, sizeof(obj->name));
+	obj->name[sizeof(obj->name)-1] = '\0';
+}
+
+/**
+ * \brief Set index part of a mixer simple element identifier
+ * \param obj Mixer simple element identifier
+ * \param val index part
+ */
+void snd_mixer_selem_id_set_index(snd_mixer_selem_id_t *obj, unsigned int val)
+{
+	assert(obj);
+	obj->index = val;
+}
diff --git a/src/mixer/simple_abst.c b/src/mixer/simple_abst.c
new file mode 100644
index 0000000..9e9aaf5
--- /dev/null
+++ b/src/mixer/simple_abst.c
@@ -0,0 +1,420 @@
+/**
+ * \file mixer/simple_abst.c
+ * \brief Mixer Simple Element Class Interface - Module Abstraction
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \date 2005
+ *
+ * Mixer simple element class interface.
+ */
+/*
+ *  Mixer Interface - simple controls - abstraction module
+ *  Copyright (c) 2005 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <math.h>
+#include <dlfcn.h>
+#include "config.h"
+#include "asoundlib.h"
+#include "mixer_simple.h"
+
+#ifndef DOC_HIDDEN
+
+#define SO_PATH ALSA_PLUGIN_DIR "/smixer"
+
+typedef struct _class_priv {
+	char *device;
+	snd_ctl_t *ctl;
+	snd_hctl_t *hctl;
+	int attach_flag;
+	snd_ctl_card_info_t *info;
+	void *dlhandle;
+	void *private_data;
+	void (*private_free)(snd_mixer_class_t *class);
+} class_priv_t;
+
+typedef int (*snd_mixer_sbasic_init_t)(snd_mixer_class_t *class);
+typedef int (*snd_mixer_sfbasic_init_t)(snd_mixer_class_t *class,
+					snd_mixer_t *mixer,
+					const char *device);
+
+#endif /* !DOC_HIDDEN */
+
+static int try_open(snd_mixer_class_t *class, const char *lib)
+{
+	class_priv_t *priv = snd_mixer_class_get_private(class);
+	snd_mixer_event_t event_func;
+	snd_mixer_sbasic_init_t init_func = NULL;
+	char *xlib, *path;
+	void *h;
+	int err = 0;
+
+	path = getenv("ALSA_MIXER_SIMPLE_MODULES");
+	if (!path)
+		path = SO_PATH;
+	xlib = malloc(strlen(lib) + strlen(path) + 1 + 1);
+	if (xlib == NULL)
+		return -ENOMEM;
+	strcpy(xlib, path);
+	strcat(xlib, "/");
+	strcat(xlib, lib);
+	h = snd_dlopen(xlib, RTLD_NOW);
+	if (h == NULL) {
+		SNDERR("Unable to open library '%s'", xlib);
+		free(xlib);
+		return -ENXIO;
+	}
+	priv->dlhandle = h;
+	event_func = snd_dlsym(h, "alsa_mixer_simple_event", NULL);
+	if (event_func == NULL) {
+		SNDERR("Symbol 'alsa_mixer_simple_event' was not found in '%s'", xlib);
+		err = -ENXIO;
+	}
+	if (err == 0) {
+		init_func = snd_dlsym(h, "alsa_mixer_simple_init", NULL);
+		if (init_func == NULL) {
+			SNDERR("Symbol 'alsa_mixer_simple_init' was not found in '%s'", xlib);
+			err = -ENXIO;
+		}
+	}
+	free(xlib);
+	err = err == 0 ? init_func(class) : err;
+	if (err < 0)
+		return err;
+	snd_mixer_class_set_event(class, event_func);
+	return 1;
+}
+
+static int try_open_full(snd_mixer_class_t *class, snd_mixer_t *mixer,
+			 const char *lib, const char *device)
+{
+	class_priv_t *priv = snd_mixer_class_get_private(class);
+	snd_mixer_event_t event_func;
+	snd_mixer_sfbasic_init_t init_func = NULL;
+	char *xlib, *path;
+	void *h;
+	int err = 0;
+
+	path = getenv("ALSA_MIXER_SIMPLE_MODULES");
+	if (!path)
+		path = SO_PATH;
+	xlib = malloc(strlen(lib) + strlen(path) + 1 + 1);
+	if (xlib == NULL)
+		return -ENOMEM;
+	strcpy(xlib, path);
+	strcat(xlib, "/");
+	strcat(xlib, lib);
+	/* note python modules requires RTLD_GLOBAL */
+	h = snd_dlopen(xlib, RTLD_NOW|RTLD_GLOBAL);
+	if (h == NULL) {
+		SNDERR("Unable to open library '%s'", xlib);
+		free(xlib);
+		return -ENXIO;
+	}
+	priv->dlhandle = h;
+	event_func = snd_dlsym(h, "alsa_mixer_simple_event", NULL);
+	if (event_func == NULL) {
+		SNDERR("Symbol 'alsa_mixer_simple_event' was not found in '%s'", xlib);
+		err = -ENXIO;
+	}
+	if (err == 0) {
+		init_func = snd_dlsym(h, "alsa_mixer_simple_finit", NULL);
+		if (init_func == NULL) {
+			SNDERR("Symbol 'alsa_mixer_simple_finit' was not found in '%s'", xlib);
+			err = -ENXIO;
+		}
+	}
+	free(xlib);
+	err = err == 0 ? init_func(class, mixer, device) : err;
+	if (err < 0)
+		return err;
+	snd_mixer_class_set_event(class, event_func);
+	return 1;
+}
+
+static int match(snd_mixer_class_t *class, const char *lib, const char *searchl)
+{
+	class_priv_t *priv = snd_mixer_class_get_private(class);
+	const char *components;
+
+	if (searchl == NULL)
+		return try_open(class, lib);
+	components = snd_ctl_card_info_get_components(priv->info);
+	while (*components != '\0') {
+		if (!strncmp(components, searchl, strlen(searchl)))
+			return try_open(class, lib);
+		while (*components != ' ' && *components != '\0')
+			components++;
+		while (*components == ' ' && *components != '\0')
+			components++;
+	}
+	return 0;
+}
+
+static int find_full(snd_mixer_class_t *class, snd_mixer_t *mixer,
+		     snd_config_t *top, const char *device)
+{
+	snd_config_iterator_t i, next;
+	char *lib;
+	const char *id;
+	int err;
+
+	snd_config_for_each(i, next, top) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (strcmp(id, "_full"))
+			continue;
+		err = snd_config_get_string(n, (const char **)&lib);
+		if (err < 0)
+			return err;
+		err = try_open_full(class, mixer, lib, device);
+		if (err < 0)
+			return err;
+		return 0;
+	}
+	return -ENOENT;
+}
+
+static int find_module(snd_mixer_class_t *class, snd_config_t *top)
+{
+	snd_config_iterator_t i, next;
+	snd_config_iterator_t j, jnext;
+	char *lib, *searchl;
+	const char *id;
+	int err;
+
+	snd_config_for_each(i, next, top) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (*id == '_')
+			continue;
+		searchl = NULL;
+		lib = NULL;
+		snd_config_for_each(j, jnext, n) {
+			snd_config_t *m = snd_config_iterator_entry(j);
+			if (snd_config_get_id(m, &id) < 0)
+				continue;
+			if (!strcmp(id, "searchl")) {
+				err = snd_config_get_string(m, (const char **)&searchl);
+				if (err < 0)
+					return err;
+				continue;
+			}
+			if (!strcmp(id, "lib")) {
+				err = snd_config_get_string(m, (const char **)&lib);
+				if (err < 0)
+					return err;
+				continue;
+			}
+		}
+		err = match(class, lib, searchl);
+		if (err == 1)
+			return 0;
+		if (err < 0)
+			return err;
+	}
+	return -ENOENT;
+}
+
+static void private_free(snd_mixer_class_t *class)
+{
+	class_priv_t *priv = snd_mixer_class_get_private(class);
+	
+	if (priv->private_free)
+		priv->private_free(class);
+	if (priv->dlhandle)
+		snd_dlclose(priv->dlhandle);
+	if (priv->info)
+		snd_ctl_card_info_free(priv->info);
+	if (priv->hctl) {
+		if (priv->attach_flag)
+			snd_mixer_detach_hctl(snd_mixer_class_get_mixer(class), priv->hctl);
+		snd_hctl_close(priv->hctl);
+	} else if (priv->ctl)
+		snd_ctl_close(priv->ctl);
+	free(priv->device);
+	free(priv);
+}
+
+/**
+ * \brief Register mixer simple element class - basic abstraction
+ * \param mixer Mixer handle
+ * \param options Options container
+ * \param classp Pointer to returned mixer simple element class handle (or NULL
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_mixer_simple_basic_register(snd_mixer_t *mixer,
+				    struct snd_mixer_selem_regopt *options,
+				    snd_mixer_class_t **classp)
+{
+	snd_mixer_class_t *class;
+	class_priv_t *priv = calloc(1, sizeof(*priv));
+	const char *file;
+	snd_input_t *input;
+	snd_config_t *top = NULL;
+	int err;
+
+	if (priv == NULL)
+		return -ENOMEM;
+	if (options->device == NULL) {
+		free(priv);
+		return -EINVAL;
+	}
+	if (snd_mixer_class_malloc(&class)) {
+		free(priv);
+		return -ENOMEM;
+	}
+	priv->device = strdup(options->device);
+	if (priv->device == NULL) {
+		free(priv);
+		snd_mixer_class_free(class);
+		return -ENOMEM;
+	}
+	snd_mixer_class_set_compare(class, snd_mixer_selem_compare);
+	snd_mixer_class_set_private(class, priv);
+	snd_mixer_class_set_private_free(class, private_free);
+	file = getenv("ALSA_MIXER_SIMPLE");
+	if (!file)
+		file = ALSA_CONFIG_DIR "/smixer.conf";
+	err = snd_config_top(&top);
+	if (err >= 0) {
+		err = snd_input_stdio_open(&input, file, "r");
+		if (err < 0) {
+			SNDERR("unable to open simple mixer configuration file '%s'", file);
+			goto __error;
+		}
+		err = snd_config_load(top, input);
+		snd_input_close(input);
+		if (err < 0) {
+			SNDERR("%s may be old or corrupted: consider to remove or fix it", file);
+			goto __error;
+		}
+		err = find_full(class, mixer, top, priv->device);
+		if (err >= 0)
+			goto __full;
+	}
+	if (err >= 0) {
+		err = snd_ctl_open(&priv->ctl, priv->device, 0);
+		if (err < 0) {
+			SNDERR("unable to open control device '%s': %s", priv->device, snd_strerror(err));
+			goto __error;
+		}
+		err = snd_hctl_open_ctl(&priv->hctl, priv->ctl);
+		if (err < 0)
+			goto __error;
+		err = snd_ctl_card_info_malloc(&priv->info);
+		if (err < 0)
+			goto __error;
+		err = snd_ctl_card_info(priv->ctl, priv->info);
+		if (err < 0)
+			goto __error;
+	}
+	if (err >= 0)
+		err = find_module(class, top);
+	if (err >= 0)
+		err = snd_mixer_attach_hctl(mixer, priv->hctl);
+	if (err >= 0) {
+		priv->attach_flag = 1;
+		err = snd_mixer_class_register(class, mixer);
+	}
+      __full:
+	if (err < 0) {
+	      __error:
+		if (top)
+			snd_config_delete(top);
+	      	if (class)
+			snd_mixer_class_free(class);
+		return err;
+	}
+	if (top)
+		snd_config_delete(top);
+	if (classp)
+		*classp = class;
+	return 0;
+}
+
+/**
+ * \brief Basic Mixer Abstraction - Get information about device
+ * \param class Mixer class
+ * \param info Info structure
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_mixer_sbasic_info(const snd_mixer_class_t *class, sm_class_basic_t *info)
+{
+	class_priv_t *priv = snd_mixer_class_get_private(class);
+
+	if (class == NULL || info == NULL)
+		return -EINVAL;
+	info->device = priv->device;
+	info->ctl = priv->ctl;
+	info->hctl = priv->hctl;
+	info->info = priv->info;
+	return 0;
+}
+
+/**
+ * \brief Get private data for basic abstraction
+ * \param class Mixer class
+ * \return private data
+ */
+void *snd_mixer_sbasic_get_private(const snd_mixer_class_t *class)
+{
+	class_priv_t *priv = snd_mixer_class_get_private(class);
+
+	if (class == NULL)
+		return NULL;
+	return priv->private_data;
+}
+
+/**
+ * \brief Set private data for basic abstraction
+ * \param class Mixer class
+ * \param private_data Private data
+ */
+void snd_mixer_sbasic_set_private(const snd_mixer_class_t *class, void *private_data)
+{
+	class_priv_t *priv;
+
+	if (class == NULL)
+		return;
+	priv = snd_mixer_class_get_private(class);
+	priv->private_data = private_data;
+}
+
+/**
+ * \brief Set private data free callback for basic abstraction
+ * \param class Mixer class
+ * \param private_free free callback for private data
+ */
+void snd_mixer_sbasic_set_private_free(const snd_mixer_class_t *class, void (*private_free)(snd_mixer_class_t *class))
+{
+	class_priv_t *priv;
+
+	if (class == NULL)
+		return;
+	priv = snd_mixer_class_get_private(class);
+	priv->private_free = private_free;
+}
diff --git a/src/mixer/simple_none.c b/src/mixer/simple_none.c
new file mode 100644
index 0000000..b11e9e8
--- /dev/null
+++ b/src/mixer/simple_none.c
@@ -0,0 +1,1732 @@
+/**
+ * \file mixer/simple_none.c
+ * \brief Mixer Simple Element Class Interface
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \date 2001-2004
+ *
+ * Mixer simple element class interface.
+ */
+/*
+ *  Mixer Interface - simple controls
+ *  Copyright (c) 2000,2004 by Jaroslav Kysela <perex@perex.cz>
+ *  Copyright (c) 2001 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <assert.h>
+#include <math.h>
+#include <limits.h>
+#include <alsa/asoundlib.h>
+#include "mixer_simple.h"
+#include "config.h"
+
+#ifndef DOC_HIDDEN
+
+#define MIXER_COMPARE_WEIGHT_SIMPLE_BASE        0
+#define MIXER_COMPARE_WEIGHT_NEXT_BASE          10000000
+#define MIXER_COMPARE_WEIGHT_NOT_FOUND          1000000000
+
+typedef enum _selem_ctl_type {
+	CTL_SINGLE,
+	CTL_GLOBAL_ENUM,
+	CTL_GLOBAL_SWITCH,
+	CTL_GLOBAL_VOLUME,
+	CTL_GLOBAL_ROUTE,
+	CTL_PLAYBACK_ENUM,
+	CTL_PLAYBACK_SWITCH,
+	CTL_PLAYBACK_VOLUME,
+	CTL_PLAYBACK_ROUTE,
+	CTL_CAPTURE_ENUM,
+	CTL_CAPTURE_SWITCH,
+	CTL_CAPTURE_VOLUME,
+	CTL_CAPTURE_ROUTE,
+	CTL_CAPTURE_SOURCE,
+	CTL_LAST = CTL_CAPTURE_SOURCE,
+} selem_ctl_type_t;
+
+typedef struct _selem_ctl {
+	snd_hctl_elem_t *elem;
+	snd_ctl_elem_type_t type;
+	unsigned int inactive: 1;
+	unsigned int values;
+	long min, max;
+} selem_ctl_t;
+
+typedef struct _selem_none {
+	sm_selem_t selem;
+	selem_ctl_t ctls[CTL_LAST + 1];
+	unsigned int capture_item;
+	struct selem_str {
+		unsigned int range: 1;	/* Forced range */
+		unsigned int db_initialized: 1;
+		unsigned int db_init_error: 1;
+		long min, max;
+		unsigned int channels;
+		long vol[32];
+		unsigned int sw;
+		unsigned int *db_info;
+	} str[2];
+} selem_none_t;
+
+static const struct mixer_name_table {
+	const char *longname;
+	const char *shortname;
+} name_table[] = {
+	{"Tone Control - Switch", "Tone"},
+	{"Tone Control - Bass", "Bass"},
+	{"Tone Control - Treble", "Treble"},
+	{"Synth Tone Control - Switch", "Synth Tone"},
+	{"Synth Tone Control - Bass", "Synth Bass"},
+	{"Synth Tone Control - Treble", "Synth Treble"},
+	{0, 0},
+};
+
+#endif /* !DOC_HIDDEN */
+
+static const char *get_short_name(const char *lname)
+{
+	const struct mixer_name_table *p;
+	for (p = name_table; p->longname; p++) {
+		if (!strcmp(lname, p->longname))
+			return p->shortname;
+	}
+	return lname;
+}
+
+static int compare_mixer_priority_lookup(const char **name, const char * const *names, int coef)
+{
+	int res;
+
+	for (res = 0; *names; names++, res += coef) {
+		if (!strncmp(*name, *names, strlen(*names))) {
+			*name += strlen(*names);
+			if (**name == ' ')
+				(*name)++;
+			return res+1;
+		}
+	}
+	return MIXER_COMPARE_WEIGHT_NOT_FOUND;
+}
+
+static int get_compare_weight(const char *name, unsigned int idx)
+{
+	static const char *const names[] = {
+		"Master",
+		"Headphone",
+		"Speaker",
+		"Tone",
+		"Bass",
+		"Treble",
+		"3D Control",
+		"PCM",
+		"Front",
+		"Surround",
+		"Center",
+		"LFE",
+		"Side",
+		"Synth",
+		"FM",
+		"Wave",
+		"Music",
+		"DSP",
+		"Line",
+		"CD",
+		"Mic",
+		"Video",
+		"Zoom Video",
+		"Phone",
+		"I2S",
+		"IEC958",
+		"PC Speaker",
+		"Beep",
+		"Aux",
+		"Mono",
+		"Playback",
+		"Capture",
+		"Mix",
+		NULL
+	};
+	static const char *const names1[] = {
+		"-",
+		NULL,
+	};
+	static const char *const names2[] = {
+		"Mono",
+		"Digital",
+		"Switch",
+		"Depth",
+		"Wide",
+		"Space",
+		"Level",
+		"Center",
+		"Output",
+		"Boost",
+		"Tone",
+		"Bass",
+		"Treble",
+		NULL,
+	};
+	const char *name1;
+	int res, res1;
+
+	if ((res = compare_mixer_priority_lookup((const char **)&name, names, 1000)) == MIXER_COMPARE_WEIGHT_NOT_FOUND)
+		return MIXER_COMPARE_WEIGHT_NOT_FOUND;
+	if (*name == '\0')
+		goto __res;
+	for (name1 = name; *name1 != '\0'; name1++);
+	for (name1--; name1 != name && *name1 != ' '; name1--);
+	while (name1 != name && *name1 == ' ')
+		name1--;
+	if (name1 != name) {
+		for (; name1 != name && *name1 != ' '; name1--);
+		name = name1;
+		if ((res1 = compare_mixer_priority_lookup((const char **)&name, names1, 200)) == MIXER_COMPARE_WEIGHT_NOT_FOUND)
+			return res;
+		res += res1;
+	} else {
+		name = name1;
+	}
+	if ((res1 = compare_mixer_priority_lookup((const char **)&name, names2, 20)) == MIXER_COMPARE_WEIGHT_NOT_FOUND)
+		return res;
+      __res:
+	return MIXER_COMPARE_WEIGHT_SIMPLE_BASE + res + idx;
+}
+
+static long to_user(selem_none_t *s, int dir, selem_ctl_t *c, long value)
+{
+	int64_t n;
+	if (c->max == c->min)
+		return s->str[dir].min;
+	n = (int64_t) (value - c->min) * (s->str[dir].max - s->str[dir].min);
+	return s->str[dir].min + (n + (c->max - c->min) / 2) / (c->max - c->min);
+}
+
+static long from_user(selem_none_t *s, int dir, selem_ctl_t *c, long value)
+{
+	int64_t n;
+	if (s->str[dir].max == s->str[dir].min)
+		return c->min;
+	n = (int64_t) (value - s->str[dir].min) * (c->max - c->min);
+	return c->min + (n + (s->str[dir].max - s->str[dir].min) / 2) / (s->str[dir].max - s->str[dir].min);
+}
+
+static int elem_read_volume(selem_none_t *s, int dir, selem_ctl_type_t type)
+{
+	snd_ctl_elem_value_t *ctl;
+	unsigned int idx;
+	int err;
+	selem_ctl_t *c = &s->ctls[type];
+	snd_ctl_elem_value_alloca(&ctl);
+	if ((err = snd_hctl_elem_read(c->elem, ctl)) < 0)
+		return err;
+	for (idx = 0; idx < s->str[dir].channels; idx++) {
+		unsigned int idx1 = idx;
+		if (idx >= c->values)
+			idx1 = 0;
+		s->str[dir].vol[idx] = to_user(s, dir, c, snd_ctl_elem_value_get_integer(ctl, idx1));
+	}
+	return 0;
+}
+
+static int elem_read_switch(selem_none_t *s, int dir, selem_ctl_type_t type)
+{
+	snd_ctl_elem_value_t *ctl;
+	unsigned int idx;
+	int err;
+	selem_ctl_t *c = &s->ctls[type];
+	snd_ctl_elem_value_alloca(&ctl);
+	if ((err = snd_hctl_elem_read(c->elem, ctl)) < 0)
+		return err;
+	for (idx = 0; idx < s->str[dir].channels; idx++) {
+		unsigned int idx1 = idx;
+		if (idx >= c->values)
+			idx1 = 0;
+		if (!snd_ctl_elem_value_get_integer(ctl, idx1))
+			s->str[dir].sw &= ~(1 << idx);
+	}
+	return 0;
+}
+
+static int elem_read_route(selem_none_t *s, int dir, selem_ctl_type_t type)
+{
+	snd_ctl_elem_value_t *ctl;
+	unsigned int idx;
+	int err;
+	selem_ctl_t *c = &s->ctls[type];
+	snd_ctl_elem_value_alloca(&ctl);
+	if ((err = snd_hctl_elem_read(c->elem, ctl)) < 0)
+		return err;
+	for (idx = 0; idx < s->str[dir].channels; idx++) {
+		unsigned int idx1 = idx;
+		if (idx >= c->values)
+			idx1 = 0;
+		if (!snd_ctl_elem_value_get_integer(ctl, idx1 * c->values + idx1))
+			s->str[dir].sw &= ~(1 << idx);
+	}
+	return 0;
+}
+
+static int elem_read_enum(selem_none_t *s)
+{
+	snd_ctl_elem_value_t *ctl;
+	unsigned int idx;
+	int err;
+	int type;
+	selem_ctl_t *c;
+	type = CTL_GLOBAL_ENUM;
+	if ( (s->selem.caps & (SM_CAP_CENUM | SM_CAP_PENUM)) == (SM_CAP_CENUM | SM_CAP_PENUM) )
+		type = CTL_GLOBAL_ENUM;
+	else if (s->selem.caps & SM_CAP_PENUM)
+		type = CTL_PLAYBACK_ENUM;
+	else if (s->selem.caps & SM_CAP_CENUM)
+		type = CTL_CAPTURE_ENUM;
+	c = &s->ctls[type];
+	snd_ctl_elem_value_alloca(&ctl);
+	if ((err = snd_hctl_elem_read(c->elem, ctl)) < 0)
+		return err;
+	for (idx = 0; idx < s->str[0].channels; idx++) {
+		unsigned int idx1 = idx;
+		if (idx >= c->values)
+			idx1 = 0;
+		s->str[0].vol[idx] = snd_ctl_elem_value_get_enumerated(ctl, idx1);
+	}
+	return 0;
+}
+
+static int selem_read(snd_mixer_elem_t *elem)
+{
+	selem_none_t *s;
+	unsigned int idx;
+	int err = 0;
+	long pvol[32], cvol[32];
+	unsigned int psw, csw;
+
+	assert(snd_mixer_elem_get_type(elem) == SND_MIXER_ELEM_SIMPLE);
+	s = snd_mixer_elem_get_private(elem);
+
+	memcpy(pvol, s->str[SM_PLAY].vol, sizeof(pvol));
+	memset(&s->str[SM_PLAY].vol, 0, sizeof(s->str[SM_PLAY].vol));
+	psw = s->str[SM_PLAY].sw;
+	s->str[SM_PLAY].sw = ~0U;
+	memcpy(cvol, s->str[SM_CAPT].vol, sizeof(cvol));
+	memset(&s->str[SM_CAPT].vol, 0, sizeof(s->str[SM_CAPT].vol));
+	csw = s->str[SM_CAPT].sw;
+	s->str[SM_CAPT].sw = ~0U;
+
+	if (s->ctls[CTL_GLOBAL_ENUM].elem) {
+		err = elem_read_enum(s);
+		if (err < 0)
+			return err;
+		goto __skip_cswitch;
+	}
+
+	if (s->ctls[CTL_CAPTURE_ENUM].elem) {
+		err = elem_read_enum(s);
+		if (err < 0)
+			return err;
+		goto __skip_cswitch;
+	}
+
+	if (s->ctls[CTL_PLAYBACK_ENUM].elem) {
+		err = elem_read_enum(s);
+		if (err < 0)
+			return err;
+		goto __skip_cswitch;
+	}
+
+
+	if (s->ctls[CTL_PLAYBACK_VOLUME].elem)
+		err = elem_read_volume(s, SM_PLAY, CTL_PLAYBACK_VOLUME);
+	else if (s->ctls[CTL_GLOBAL_VOLUME].elem)
+		err = elem_read_volume(s, SM_PLAY, CTL_GLOBAL_VOLUME);
+	else if (s->ctls[CTL_SINGLE].elem &&
+		 s->ctls[CTL_SINGLE].type == SND_CTL_ELEM_TYPE_INTEGER)
+		err = elem_read_volume(s, SM_PLAY, CTL_SINGLE);
+	if (err < 0)
+		return err;
+
+	if ((s->selem.caps & (SM_CAP_GSWITCH|SM_CAP_PSWITCH)) == 0) {
+		s->str[SM_PLAY].sw = 0;
+		goto __skip_pswitch;
+	}
+	if (s->ctls[CTL_PLAYBACK_SWITCH].elem) {
+		err = elem_read_switch(s, SM_PLAY, CTL_PLAYBACK_SWITCH);
+		if (err < 0)
+			return err;
+	}
+	if (s->ctls[CTL_GLOBAL_SWITCH].elem) {
+		err = elem_read_switch(s, SM_PLAY, CTL_GLOBAL_SWITCH);
+		if (err < 0)
+			return err;
+	}
+	if (s->ctls[CTL_SINGLE].elem &&
+	    s->ctls[CTL_SINGLE].type == SND_CTL_ELEM_TYPE_BOOLEAN) {
+		err = elem_read_switch(s, SM_PLAY, CTL_SINGLE);
+		if (err < 0)
+			return err;
+	}
+	if (s->ctls[CTL_PLAYBACK_ROUTE].elem) {
+		err = elem_read_route(s, SM_PLAY, CTL_PLAYBACK_ROUTE);
+		if (err < 0)
+			return err;
+	}
+	if (s->ctls[CTL_GLOBAL_ROUTE].elem) {
+		err = elem_read_route(s, SM_PLAY, CTL_GLOBAL_ROUTE);
+		if (err < 0)
+			return err;
+	}
+      __skip_pswitch:
+
+	if (s->ctls[CTL_CAPTURE_VOLUME].elem)
+		err = elem_read_volume(s, SM_CAPT, CTL_CAPTURE_VOLUME);
+	else if (s->ctls[CTL_GLOBAL_VOLUME].elem)
+		err = elem_read_volume(s, SM_CAPT, CTL_GLOBAL_VOLUME);
+	else if (s->ctls[CTL_SINGLE].elem &&
+		 s->ctls[CTL_SINGLE].type == SND_CTL_ELEM_TYPE_INTEGER)
+		err = elem_read_volume(s, SM_CAPT, CTL_SINGLE);
+	if (err < 0)
+		return err;
+
+	if ((s->selem.caps & (SM_CAP_GSWITCH|SM_CAP_CSWITCH)) == 0) {
+		s->str[SM_CAPT].sw = 0;
+		goto __skip_cswitch;
+	}
+	if (s->ctls[CTL_CAPTURE_SWITCH].elem) {
+		err = elem_read_switch(s, SM_CAPT, CTL_CAPTURE_SWITCH);
+		if (err < 0)
+			return err;
+	}
+	if (s->ctls[CTL_GLOBAL_SWITCH].elem) {
+		err = elem_read_switch(s, SM_CAPT, CTL_GLOBAL_SWITCH);
+		if (err < 0)
+			return err;
+	}
+	if (s->ctls[CTL_SINGLE].elem &&
+	    s->ctls[CTL_SINGLE].type == SND_CTL_ELEM_TYPE_BOOLEAN) {
+		err = elem_read_switch(s, SM_CAPT, CTL_SINGLE);
+		if (err < 0)
+			return err;
+	}
+	if (s->ctls[CTL_CAPTURE_ROUTE].elem) {
+		err = elem_read_route(s, SM_CAPT, CTL_CAPTURE_ROUTE);
+		if (err < 0)
+			return err;
+	}
+	if (s->ctls[CTL_GLOBAL_ROUTE].elem) {
+		err = elem_read_route(s, SM_CAPT, CTL_GLOBAL_ROUTE);
+		if (err < 0)
+			return err;
+	}
+	if (s->ctls[CTL_CAPTURE_SOURCE].elem) {
+		snd_ctl_elem_value_t *ctl;
+		selem_ctl_t *c = &s->ctls[CTL_CAPTURE_SOURCE];
+		snd_ctl_elem_value_alloca(&ctl);
+		err = snd_hctl_elem_read(c->elem, ctl);
+		if (err < 0)
+			return err;
+		for (idx = 0; idx < s->str[SM_CAPT].channels; idx++) {
+			unsigned int idx1 = idx;
+			if (idx >= c->values)
+				idx1 = 0;
+			if (snd_ctl_elem_value_get_enumerated(ctl, idx1) != s->capture_item)
+				s->str[SM_CAPT].sw &= ~(1 << idx);
+		}
+	}
+      __skip_cswitch:
+
+	if (memcmp(pvol, s->str[SM_PLAY].vol, sizeof(pvol)) ||
+	    psw != s->str[SM_PLAY].sw ||
+	    memcmp(cvol, s->str[SM_CAPT].vol, sizeof(cvol)) ||
+	    csw != s->str[SM_CAPT].sw)
+		return 1;
+	return 0;
+}
+
+static int elem_write_volume(selem_none_t *s, int dir, selem_ctl_type_t type)
+{
+	snd_ctl_elem_value_t *ctl;
+	unsigned int idx;
+	int err;
+	selem_ctl_t *c = &s->ctls[type];
+	snd_ctl_elem_value_alloca(&ctl);
+	if ((err = snd_hctl_elem_read(c->elem, ctl)) < 0)
+		return err;
+	for (idx = 0; idx < c->values; idx++)
+		snd_ctl_elem_value_set_integer(ctl, idx, from_user(s, dir, c, s->str[dir].vol[idx]));
+	if ((err = snd_hctl_elem_write(c->elem, ctl)) < 0)
+		return err;
+	return 0;
+}
+
+static int elem_write_switch(selem_none_t *s, int dir, selem_ctl_type_t type)
+{
+	snd_ctl_elem_value_t *ctl;
+	unsigned int idx;
+	int err;
+	selem_ctl_t *c = &s->ctls[type];
+	snd_ctl_elem_value_alloca(&ctl);
+	if ((err = snd_hctl_elem_read(c->elem, ctl)) < 0)
+		return err;
+	for (idx = 0; idx < c->values; idx++)
+		snd_ctl_elem_value_set_integer(ctl, idx, !!(s->str[dir].sw & (1 << idx)));
+	if ((err = snd_hctl_elem_write(c->elem, ctl)) < 0)
+		return err;
+	return 0;
+}
+
+static int elem_write_switch_constant(selem_none_t *s, selem_ctl_type_t type, int val)
+{
+	snd_ctl_elem_value_t *ctl;
+	unsigned int idx;
+	int err;
+	selem_ctl_t *c = &s->ctls[type];
+	snd_ctl_elem_value_alloca(&ctl);
+	if ((err = snd_hctl_elem_read(c->elem, ctl)) < 0)
+		return err;
+	for (idx = 0; idx < c->values; idx++)
+		snd_ctl_elem_value_set_integer(ctl, idx, !!val);
+	if ((err = snd_hctl_elem_write(c->elem, ctl)) < 0)
+		return err;
+	return 0;
+}
+
+static int elem_write_route(selem_none_t *s, int dir, selem_ctl_type_t type)
+{
+	snd_ctl_elem_value_t *ctl;
+	unsigned int idx;
+	int err;
+	selem_ctl_t *c = &s->ctls[type];
+	snd_ctl_elem_value_alloca(&ctl);
+	if ((err = snd_hctl_elem_read(c->elem, ctl)) < 0)
+		return err;
+	for (idx = 0; idx < c->values * c->values; idx++)
+		snd_ctl_elem_value_set_integer(ctl, idx, 0);
+	for (idx = 0; idx < c->values; idx++)
+		snd_ctl_elem_value_set_integer(ctl, idx * c->values + idx, !!(s->str[dir].sw & (1 << idx)));
+	if ((err = snd_hctl_elem_write(c->elem, ctl)) < 0)
+		return err;
+	return 0;
+}
+
+static int elem_write_enum(selem_none_t *s)
+{
+	snd_ctl_elem_value_t *ctl;
+	unsigned int idx;
+	int err;
+	int type;
+	selem_ctl_t *c;
+	type = CTL_GLOBAL_ENUM;
+	if ( (s->selem.caps & (SM_CAP_CENUM | SM_CAP_PENUM) ) == (SM_CAP_CENUM | SM_CAP_PENUM) )
+		type = CTL_GLOBAL_ENUM;
+	else if (s->selem.caps & SM_CAP_PENUM)
+		type = CTL_PLAYBACK_ENUM;
+	else if (s->selem.caps & SM_CAP_CENUM)
+		type = CTL_CAPTURE_ENUM;
+	c = &s->ctls[type];
+	snd_ctl_elem_value_alloca(&ctl);
+	if ((err = snd_hctl_elem_read(c->elem, ctl)) < 0)
+		return err;
+	for (idx = 0; idx < c->values; idx++)
+		snd_ctl_elem_value_set_enumerated(ctl, idx, (unsigned int)s->str[0].vol[idx]);
+	if ((err = snd_hctl_elem_write(c->elem, ctl)) < 0)
+		return err;
+	return 0;
+}
+
+static int selem_write_main(snd_mixer_elem_t *elem)
+{
+	selem_none_t *s;
+	unsigned int idx;
+	int err;
+
+	assert(snd_mixer_elem_get_type(elem) == SND_MIXER_ELEM_SIMPLE);
+	s = snd_mixer_elem_get_private(elem);
+
+	if (s->ctls[CTL_GLOBAL_ENUM].elem)
+		return elem_write_enum(s);
+
+	if (s->ctls[CTL_PLAYBACK_ENUM].elem)
+		return elem_write_enum(s);
+
+	if (s->ctls[CTL_CAPTURE_ENUM].elem)
+		return elem_write_enum(s);
+
+	if (s->ctls[CTL_SINGLE].elem) {
+		if (s->ctls[CTL_SINGLE].type == SND_CTL_ELEM_TYPE_INTEGER)
+			err = elem_write_volume(s, SM_PLAY, CTL_SINGLE);
+		else
+			err = elem_write_switch(s, SM_PLAY, CTL_SINGLE);
+		if (err < 0)
+			return err;
+	}
+	if (s->ctls[CTL_GLOBAL_VOLUME].elem) {
+		err = elem_write_volume(s, SM_PLAY, CTL_GLOBAL_VOLUME);
+		if (err < 0)
+			return err;
+	}
+	if (s->ctls[CTL_GLOBAL_SWITCH].elem) {
+		if (s->ctls[CTL_PLAYBACK_SWITCH].elem && s->ctls[CTL_CAPTURE_SWITCH].elem)
+			err = elem_write_switch_constant(s, CTL_GLOBAL_SWITCH, 1);
+		else
+			err = elem_write_switch(s, SM_PLAY, CTL_GLOBAL_SWITCH);
+		if (err < 0)
+			return err;
+	}
+	if (s->ctls[CTL_PLAYBACK_VOLUME].elem) {
+		err = elem_write_volume(s, SM_PLAY, CTL_PLAYBACK_VOLUME);
+		if (err < 0)
+			return err;
+	}
+	if (s->ctls[CTL_PLAYBACK_SWITCH].elem) {
+		err = elem_write_switch(s, SM_PLAY, CTL_PLAYBACK_SWITCH);
+		if (err < 0)
+			return err;
+	}
+	if (s->ctls[CTL_PLAYBACK_ROUTE].elem) {
+		err = elem_write_route(s, SM_PLAY, CTL_PLAYBACK_ROUTE);
+		if (err < 0)
+			return err;
+	}
+	if (s->ctls[CTL_CAPTURE_VOLUME].elem) {
+		err = elem_write_volume(s, SM_CAPT, CTL_CAPTURE_VOLUME);
+		if (err < 0)
+			return err;
+	}
+	if (s->ctls[CTL_CAPTURE_SWITCH].elem) {
+		err = elem_write_switch(s, SM_CAPT, CTL_CAPTURE_SWITCH);
+		if (err < 0)
+			return err;
+	}
+	if (s->ctls[CTL_CAPTURE_ROUTE].elem) {
+		err = elem_write_route(s, SM_CAPT, CTL_CAPTURE_ROUTE);
+		if (err < 0)
+			return err;
+	}
+	if (s->ctls[CTL_CAPTURE_SOURCE].elem) {
+		snd_ctl_elem_value_t *ctl;
+		selem_ctl_t *c = &s->ctls[CTL_CAPTURE_SOURCE];
+		snd_ctl_elem_value_alloca(&ctl);
+		if ((err = snd_hctl_elem_read(c->elem, ctl)) < 0)
+			return err;
+		for (idx = 0; idx < c->values; idx++) {
+			if (s->str[SM_CAPT].sw & (1 << idx))
+				snd_ctl_elem_value_set_enumerated(ctl, idx, s->capture_item);
+		}
+		if ((err = snd_hctl_elem_write(c->elem, ctl)) < 0)
+			return err;
+		/* update the element, don't remove */
+		err = selem_read(elem);
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+static int selem_write(snd_mixer_elem_t *elem)
+{
+	int err;
+	
+	err = selem_write_main(elem);
+	if (err < 0)
+		selem_read(elem);
+	return err;
+}
+
+static void selem_free(snd_mixer_elem_t *elem)
+{
+	selem_none_t *simple = snd_mixer_elem_get_private(elem);
+	assert(snd_mixer_elem_get_type(elem) == SND_MIXER_ELEM_SIMPLE);
+	if (simple->selem.id)
+		snd_mixer_selem_id_free(simple->selem.id);
+	/* free db range information */
+	free(simple->str[0].db_info);
+	free(simple->str[1].db_info);
+	free(simple);
+}
+
+static int simple_update(snd_mixer_elem_t *melem)
+{
+	selem_none_t *simple;
+	unsigned int caps, pchannels, cchannels;
+	long pmin, pmax, cmin, cmax;
+	selem_ctl_t *ctl;
+	const char *name;
+
+	caps = 0;
+	pchannels = 0;
+	pmin = LONG_MAX;
+	pmax = LONG_MIN;
+	cchannels = 0;
+	cmin = LONG_MAX;
+	cmax = LONG_MIN;
+	assert(snd_mixer_elem_get_type(melem) == SND_MIXER_ELEM_SIMPLE);
+	simple = snd_mixer_elem_get_private(melem);
+	name = snd_mixer_selem_get_name(melem);
+	ctl = &simple->ctls[CTL_SINGLE];
+	if (ctl->elem) {
+		pchannels = cchannels = ctl->values;
+		if (ctl->type == SND_CTL_ELEM_TYPE_INTEGER) {
+			caps |= SM_CAP_GVOLUME;
+			pmin = cmin = ctl->min;
+			pmax = cmax = ctl->max;
+		} else
+			caps |= SM_CAP_GSWITCH;
+	}
+	ctl = &simple->ctls[CTL_GLOBAL_SWITCH];
+	if (ctl->elem) {
+		if (pchannels < ctl->values)
+			pchannels = ctl->values;
+		if (cchannels < ctl->values)
+			cchannels = ctl->values;
+		caps |= SM_CAP_GSWITCH;
+	}
+	ctl = &simple->ctls[CTL_GLOBAL_ROUTE];
+	if (ctl->elem) {
+		if (pchannels < ctl->values)
+			pchannels = ctl->values;
+		if (cchannels < ctl->values)
+			cchannels = ctl->values;
+		caps |= SM_CAP_GSWITCH;
+	}
+	ctl = &simple->ctls[CTL_GLOBAL_VOLUME];
+	if (ctl->elem) {
+		if (pchannels < ctl->values)
+			pchannels = ctl->values;
+		if (pmin > ctl->min)
+			pmin = ctl->min;
+		if (pmax < ctl->max)
+			pmax = ctl->max;
+		if (cchannels < ctl->values)
+			cchannels = ctl->values;
+		if (cmin > ctl->min)
+			cmin = ctl->min;
+		if (cmax < ctl->max)
+			cmax = ctl->max;
+		caps |= SM_CAP_GVOLUME;
+	}
+	ctl = &simple->ctls[CTL_PLAYBACK_SWITCH];
+	if (ctl->elem) {
+		if (pchannels < ctl->values)
+			pchannels = ctl->values;
+		caps |= SM_CAP_PSWITCH;
+		caps &= ~SM_CAP_GSWITCH;
+	}
+	ctl = &simple->ctls[CTL_PLAYBACK_ROUTE];
+	if (ctl->elem) {
+		if (pchannels < ctl->values)
+			pchannels = ctl->values;
+		caps |= SM_CAP_PSWITCH;
+		caps &= ~SM_CAP_GSWITCH;
+	}
+	ctl = &simple->ctls[CTL_CAPTURE_SWITCH];
+	if (ctl->elem) {
+		if (cchannels < ctl->values)
+			cchannels = ctl->values;
+		caps |= SM_CAP_CSWITCH;
+		caps &= ~SM_CAP_GSWITCH;
+	}
+	ctl = &simple->ctls[CTL_CAPTURE_ROUTE];
+	if (ctl->elem) {
+		if (cchannels < ctl->values)
+			cchannels = ctl->values;
+		caps |= SM_CAP_CSWITCH;
+		caps &= ~SM_CAP_GSWITCH;
+	}
+	ctl = &simple->ctls[CTL_PLAYBACK_VOLUME];
+	if (ctl->elem) {
+		if (pchannels < ctl->values)
+			pchannels = ctl->values;
+		if (pmin > ctl->min)
+			pmin = ctl->min;
+		if (pmax < ctl->max)
+			pmax = ctl->max;
+		caps |= SM_CAP_PVOLUME;
+		caps &= ~SM_CAP_GVOLUME;
+	}
+	ctl = &simple->ctls[CTL_CAPTURE_VOLUME];
+	if (ctl->elem) {
+		if (cchannels < ctl->values)
+			cchannels = ctl->values;
+		if (cmin > ctl->min)
+			cmin = ctl->min;
+		if (cmax < ctl->max)
+			cmax = ctl->max;
+		caps |= SM_CAP_CVOLUME;
+		caps &= ~SM_CAP_GVOLUME;
+	}
+	ctl = &simple->ctls[CTL_CAPTURE_SOURCE];
+	if (ctl->elem) {
+		if (cchannels < ctl->values)
+			cchannels = ctl->values;
+		caps |= SM_CAP_CSWITCH | SM_CAP_CSWITCH_EXCL;
+		caps &= ~SM_CAP_GSWITCH;
+	}
+	ctl = &simple->ctls[CTL_GLOBAL_ENUM];
+	if (ctl->elem) {
+		if (pchannels < ctl->values)
+			pchannels = ctl->values;
+		caps |= SM_CAP_PENUM | SM_CAP_CENUM;
+	}
+	ctl = &simple->ctls[CTL_PLAYBACK_ENUM];
+	if (ctl->elem) {
+		if (pchannels < ctl->values)
+			pchannels = ctl->values;
+		caps |= SM_CAP_PENUM;
+	}
+	ctl = &simple->ctls[CTL_CAPTURE_ENUM];
+	if (ctl->elem) {
+		if (pchannels < ctl->values)
+			pchannels = ctl->values;
+		caps |= SM_CAP_CENUM;
+	}
+	if (pchannels > 32)
+		pchannels = 32;
+	if (cchannels > 32)
+		cchannels = 32;
+	if (caps & (SM_CAP_GSWITCH|SM_CAP_PSWITCH))
+		caps |= SM_CAP_PSWITCH_JOIN;
+	if (caps & (SM_CAP_GVOLUME|SM_CAP_PVOLUME))
+		caps |= SM_CAP_PVOLUME_JOIN;
+	if (caps & (SM_CAP_GSWITCH|SM_CAP_CSWITCH))
+		caps |= SM_CAP_CSWITCH_JOIN;
+	if (caps & (SM_CAP_GVOLUME|SM_CAP_CVOLUME))
+		caps |= SM_CAP_CVOLUME_JOIN;
+	if (pchannels > 1 || cchannels > 1) {
+		if (simple->ctls[CTL_SINGLE].elem &&
+		    simple->ctls[CTL_SINGLE].values > 1) {
+			if (caps & SM_CAP_GSWITCH)
+				caps &= ~(SM_CAP_PSWITCH_JOIN|SM_CAP_CSWITCH_JOIN);
+			else
+				caps &= ~(SM_CAP_PVOLUME_JOIN|SM_CAP_CVOLUME_JOIN);
+		}
+		if (simple->ctls[CTL_GLOBAL_ROUTE].elem ||
+		    (simple->ctls[CTL_GLOBAL_SWITCH].elem &&
+		     simple->ctls[CTL_GLOBAL_SWITCH].values > 1)) {
+			caps &= ~(SM_CAP_PSWITCH_JOIN|SM_CAP_CSWITCH_JOIN);
+		}
+		if (simple->ctls[CTL_GLOBAL_VOLUME].elem &&
+		    simple->ctls[CTL_GLOBAL_VOLUME].values > 1) {
+			caps &= ~(SM_CAP_PVOLUME_JOIN|SM_CAP_CVOLUME_JOIN);
+		}
+	}
+	if (pchannels > 1) {
+		if (simple->ctls[CTL_PLAYBACK_ROUTE].elem ||
+		    (simple->ctls[CTL_PLAYBACK_SWITCH].elem &&
+		     simple->ctls[CTL_PLAYBACK_SWITCH].values > 1)) {
+			caps &= ~SM_CAP_PSWITCH_JOIN;
+		}
+		if (simple->ctls[CTL_PLAYBACK_VOLUME].elem &&
+		    simple->ctls[CTL_PLAYBACK_VOLUME].values > 1) {
+			caps &= ~SM_CAP_PVOLUME_JOIN;
+		}
+	}
+	if (cchannels > 1) {
+		if (simple->ctls[CTL_CAPTURE_ROUTE].elem ||
+		    (simple->ctls[CTL_CAPTURE_SWITCH].elem &&
+		     simple->ctls[CTL_CAPTURE_SWITCH].values > 1) ||
+		    (simple->ctls[CTL_CAPTURE_SOURCE].elem &&
+		     simple->ctls[CTL_CAPTURE_SOURCE].values > 1)) {
+			caps &= ~SM_CAP_CSWITCH_JOIN;
+		}
+		if (simple->ctls[CTL_CAPTURE_VOLUME].elem &&
+		    simple->ctls[CTL_CAPTURE_VOLUME].values > 1) {
+			caps &= ~SM_CAP_CVOLUME_JOIN;
+		}
+	}
+
+	/* exceptions */
+	if ((caps & (SM_CAP_GSWITCH|SM_CAP_PSWITCH|SM_CAP_CSWITCH)) &&
+	    (caps & (SM_CAP_GSWITCH|SM_CAP_PSWITCH|SM_CAP_CSWITCH)) == (caps & SM_CAP_GSWITCH)) {
+		caps &= ~(SM_CAP_GSWITCH|SM_CAP_CSWITCH_JOIN|SM_CAP_CSWITCH_EXCL);
+		caps |= SM_CAP_PSWITCH;
+	}
+
+	if ((caps & SM_CAP_GSWITCH) &&
+	    (caps & (SM_CAP_PSWITCH|SM_CAP_CSWITCH)) == 0)
+		caps |= SM_CAP_PSWITCH|SM_CAP_CSWITCH;
+
+	if ((caps & SM_CAP_GVOLUME) &&
+	    (caps & (SM_CAP_PVOLUME|SM_CAP_CVOLUME)) == 0)
+		caps |= SM_CAP_PVOLUME|SM_CAP_CVOLUME;
+
+	simple->selem.caps = caps;
+	simple->str[SM_PLAY].channels = pchannels;
+	if (!simple->str[SM_PLAY].range) {
+		simple->str[SM_PLAY].min = pmin != LONG_MAX ? pmin : 0;
+		simple->str[SM_PLAY].max = pmax != LONG_MIN ? pmax : 0;
+	}
+	simple->str[SM_CAPT].channels = cchannels;
+	if (!simple->str[SM_CAPT].range) {
+		simple->str[SM_CAPT].min = cmin != LONG_MAX ? cmin : 0;
+		simple->str[SM_CAPT].max = cmax != LONG_MIN ? cmax : 0;
+	}
+	return 0;
+}	   
+
+#ifndef DOC_HIDDEN
+static const struct suf {
+	const char *suffix;
+	selem_ctl_type_t type;
+} suffixes[] = {
+	{" Playback Enum", CTL_PLAYBACK_ENUM},
+	{" Playback Switch", CTL_PLAYBACK_SWITCH},
+	{" Playback Route", CTL_PLAYBACK_ROUTE},
+	{" Playback Volume", CTL_PLAYBACK_VOLUME},
+	{" Capture Enum", CTL_CAPTURE_ENUM},
+	{" Capture Switch", CTL_CAPTURE_SWITCH},
+	{" Capture Route", CTL_CAPTURE_ROUTE},
+	{" Capture Volume", CTL_CAPTURE_VOLUME},
+	{" Enum", CTL_GLOBAL_ENUM},
+	{" Switch", CTL_GLOBAL_SWITCH},
+	{" Route", CTL_GLOBAL_ROUTE},
+	{" Volume", CTL_GLOBAL_VOLUME},
+	{NULL, 0}
+};
+#endif
+
+/* Return base length or 0 on failure */
+static int base_len(const char *name, selem_ctl_type_t *type)
+{
+	const struct suf *p;
+	size_t nlen = strlen(name);
+	p = suffixes;
+	while (p->suffix) {
+		size_t slen = strlen(p->suffix);
+		size_t l;
+		if (nlen > slen) {
+			l = nlen - slen;
+			if (strncmp(name + l, p->suffix, slen) == 0 &&
+			    (l < 1 || name[l-1] != '-')) {	/* 3D Control - Switch */
+				*type = p->type;
+				return l;
+			}
+		}
+		p++;
+	}
+
+	/* Special case - handle "Input Source" as a capture route.
+	 * Note that it's *NO* capture source.  A capture source is split over
+	 * sub-elements, and multiple capture-sources will result in an error.
+	 * That's why some drivers use "Input Source" as a workaround.
+	 * Hence, this is a workaround for a workaround to get the things
+	 * straight back again.  Sigh.
+	 */
+	if (!strcmp(name, "Input Source")) {
+		*type = CTL_CAPTURE_ROUTE;
+		return strlen(name);
+	}
+	if (strstr(name, "3D Control")) {
+		if (strstr(name, "Depth")) {
+			*type = CTL_PLAYBACK_VOLUME;
+			return strlen(name);
+		}
+	}
+	return 0;
+}
+
+
+/*
+ * Simple Mixer Operations
+ */
+	
+static int _snd_mixer_selem_set_volume(snd_mixer_elem_t *elem, int dir, snd_mixer_selem_channel_id_t channel, long value)
+{
+	selem_none_t *s = snd_mixer_elem_get_private(elem);
+	if (s->selem.caps & SM_CAP_GVOLUME)
+		dir = SM_PLAY;
+	if ((unsigned int) channel >= s->str[dir].channels)
+		return 0;
+	if (value < s->str[dir].min || value > s->str[dir].max)
+		return 0;
+	if (s->selem.caps & 
+	    (dir == SM_PLAY ? SM_CAP_PVOLUME_JOIN : SM_CAP_CVOLUME_JOIN))
+		channel = 0;
+	if (value != s->str[dir].vol[channel]) {
+		s->str[dir].vol[channel] = value;
+		return 1;
+	}
+	return 0;
+}
+
+static int _snd_mixer_selem_set_switch(snd_mixer_elem_t *elem, int dir, snd_mixer_selem_channel_id_t channel, int value)
+{
+	selem_none_t *s = snd_mixer_elem_get_private(elem);
+	if ((unsigned int) channel >= s->str[dir].channels)
+		return 0;
+	if (s->selem.caps & 
+	    (dir == SM_PLAY ? SM_CAP_PSWITCH_JOIN : SM_CAP_CSWITCH_JOIN))
+		channel = 0;
+	if (value) {
+		if (!(s->str[dir].sw & (1 << channel))) {
+			s->str[dir].sw |= 1 << channel;
+			return 1;
+		}
+	} else {
+		if (s->str[dir].sw & (1 << channel)) {
+			s->str[dir].sw &= ~(1 << channel);
+			return 1;
+		}
+	}
+	return 0;
+}
+
+static int is_ops(snd_mixer_elem_t *elem, int dir, int cmd, int val)
+{
+	selem_none_t *s = snd_mixer_elem_get_private(elem);
+	
+	switch (cmd) {
+
+	case SM_OPS_IS_ACTIVE: {
+		selem_ctl_type_t ctl;
+		for (ctl = CTL_SINGLE; ctl <= CTL_LAST; ctl++)
+			if (s->ctls[ctl].elem != NULL && s->ctls[ctl].inactive)
+				return 0;
+		return 1;
+	}
+
+	case SM_OPS_IS_MONO:
+		return s->str[dir].channels == 1;
+
+	case SM_OPS_IS_CHANNEL:
+		return (unsigned int) val < s->str[dir].channels;
+
+	case SM_OPS_IS_ENUMERATED:
+		if (val == 1) {
+			if (dir == SM_PLAY && (s->selem.caps & SM_CAP_PENUM) && !(s->selem.caps & SM_CAP_CENUM) )
+				return 1;
+			if (dir == SM_CAPT && (s->selem.caps & SM_CAP_CENUM) && !(s->selem.caps & SM_CAP_PENUM) )
+				return 1;
+			return 0;
+		}
+		if (s->selem.caps & (SM_CAP_CENUM | SM_CAP_PENUM) )
+			return 1;
+		return 0;
+	
+	case SM_OPS_IS_ENUMCNT:
+		/* Both */
+		if ( (s->selem.caps & (SM_CAP_CENUM | SM_CAP_PENUM)) == (SM_CAP_CENUM | SM_CAP_PENUM) ) {
+			if (! s->ctls[CTL_GLOBAL_ENUM].elem)
+				return -EINVAL;
+			return s->ctls[CTL_GLOBAL_ENUM].max;
+		/* Only Playback */
+		} else if (s->selem.caps & SM_CAP_PENUM ) {
+			if (! s->ctls[CTL_PLAYBACK_ENUM].elem)
+				return -EINVAL;
+			return s->ctls[CTL_PLAYBACK_ENUM].max;
+		/* Only Capture */
+		} else if (s->selem.caps & SM_CAP_CENUM ) {
+			if (! s->ctls[CTL_CAPTURE_ENUM].elem)
+				return -EINVAL;
+			return s->ctls[CTL_CAPTURE_ENUM].max;
+		}
+
+	}
+	
+	return 1;
+}
+
+static int get_range_ops(snd_mixer_elem_t *elem, int dir,
+			 long *min, long *max)
+{
+	selem_none_t *s = snd_mixer_elem_get_private(elem);
+	*min = s->str[dir].min;
+	*max = s->str[dir].max;
+	return 0;
+}
+
+static int set_range_ops(snd_mixer_elem_t *elem, int dir,
+			 long min, long max)
+{
+	selem_none_t *s = snd_mixer_elem_get_private(elem);
+	int err;
+
+	s->str[dir].range = 1;
+	s->str[dir].min = min;
+	s->str[dir].max = max;
+	if ((err = selem_read(elem)) < 0)
+		return err;
+	return 0;
+}
+
+static int get_volume_ops(snd_mixer_elem_t *elem, int dir,
+			  snd_mixer_selem_channel_id_t channel, long *value)
+{
+	selem_none_t *s = snd_mixer_elem_get_private(elem);
+	if (s->selem.caps & SM_CAP_GVOLUME)
+		dir = SM_PLAY;
+	if ((unsigned int) channel >= s->str[dir].channels)
+		return -EINVAL;
+	*value = s->str[dir].vol[channel];
+	return 0;
+}
+
+static int init_db_range(snd_hctl_elem_t *ctl, struct selem_str *rec);
+
+static int convert_to_dB(snd_hctl_elem_t *ctl, struct selem_str *rec,
+			 long volume, long *db_gain)
+{
+	if (init_db_range(ctl, rec) < 0)
+		return -EINVAL;
+	return snd_tlv_convert_to_dB(rec->db_info, rec->min, rec->max,
+				     volume, db_gain);
+}
+
+/* initialize dB range information, reading TLV via hcontrol
+ */
+static int init_db_range(snd_hctl_elem_t *ctl, struct selem_str *rec)
+{
+	snd_ctl_elem_info_t *info;
+	unsigned int *tlv = NULL;
+	const unsigned int tlv_size = 4096;
+	unsigned int *dbrec;
+	int db_size;
+
+	if (rec->db_init_error)
+		return -EINVAL;
+	if (rec->db_initialized)
+		return 0;
+
+	snd_ctl_elem_info_alloca(&info);
+	if (snd_hctl_elem_info(ctl, info) < 0)
+		goto error;
+	if (! snd_ctl_elem_info_is_tlv_readable(info))
+		goto error;
+	tlv = malloc(tlv_size);
+	if (! tlv)
+		return -ENOMEM;
+	if (snd_hctl_elem_tlv_read(ctl, tlv, tlv_size) < 0)
+		goto error;
+	db_size = snd_tlv_parse_dB_info(tlv, tlv_size, &dbrec);
+	if (db_size < 0)
+		goto error;
+	rec->db_info = malloc(db_size);
+	if (!rec->db_info)
+		goto error;
+	memcpy(rec->db_info, dbrec, db_size);
+	free(tlv);
+	rec->db_initialized = 1;
+	return 0;
+
+ error:
+	free(tlv);
+	rec->db_init_error = 1;
+	return -EINVAL;
+}
+
+/* get selem_ctl for TLV access */
+static selem_ctl_t *get_selem_ctl(selem_none_t *s, int dir)
+{
+	selem_ctl_t *c;
+	if (dir == SM_PLAY)
+		c = &s->ctls[CTL_PLAYBACK_VOLUME];
+	else if (dir == SM_CAPT)
+		c = &s->ctls[CTL_CAPTURE_VOLUME];
+	else
+		return NULL;
+	if (! c->elem) {
+		c = &s->ctls[CTL_GLOBAL_VOLUME];
+		if (! c->elem)
+			return NULL;
+	}
+	if (c->type != SND_CTL_ELEM_TYPE_INTEGER)
+		return NULL;
+	return c;
+}
+
+static int get_dB_range(snd_hctl_elem_t *ctl, struct selem_str *rec,
+			long *min, long *max)
+{
+	if (init_db_range(ctl, rec) < 0)
+		return -EINVAL;
+
+	return snd_tlv_get_dB_range(rec->db_info, rec->min, rec->max, min, max);
+}
+	
+static int get_dB_range_ops(snd_mixer_elem_t *elem, int dir,
+			    long *min, long *max)
+{
+	selem_none_t *s = snd_mixer_elem_get_private(elem);
+	selem_ctl_t *c;
+
+	if (s->selem.caps & SM_CAP_GVOLUME)
+		dir = SM_PLAY;
+	c = get_selem_ctl(s, dir);
+	if (! c)
+		return -EINVAL;
+	return get_dB_range(c->elem, &s->str[dir], min, max);
+}
+
+static int convert_from_dB(snd_hctl_elem_t *ctl, struct selem_str *rec,
+			   long db_gain, long *value, int xdir)
+{
+	if (init_db_range(ctl, rec) < 0)
+		return -EINVAL;
+
+	return snd_tlv_convert_from_dB(rec->db_info, rec->min, rec->max,
+				       db_gain, value, xdir);
+}
+
+static int ask_vol_dB_ops(snd_mixer_elem_t *elem,
+			  int dir,
+			  long value,
+			  long *dBvalue)
+{
+	selem_none_t *s = snd_mixer_elem_get_private(elem);
+	selem_ctl_t *c;
+
+	c = get_selem_ctl(s, dir);
+	if (! c)
+		return -EINVAL;
+	int res = convert_to_dB(c->elem, &s->str[dir], value, dBvalue);
+	return res;
+}
+
+static int get_dB_ops(snd_mixer_elem_t *elem,
+                      int dir,
+                      snd_mixer_selem_channel_id_t channel,
+                      long *value)
+{
+	selem_none_t *s = snd_mixer_elem_get_private(elem);
+	selem_ctl_t *c;
+	int err;
+	long volume, db_gain;
+
+	if (s->selem.caps & SM_CAP_GVOLUME)
+		dir = SM_PLAY;
+	c = get_selem_ctl(s, dir);
+	if (! c)
+		return -EINVAL;
+	if ((err = get_volume_ops(elem, dir, channel, &volume)) < 0)
+		goto _err;
+	if ((err = convert_to_dB(c->elem, &s->str[dir], volume, &db_gain)) < 0)
+		goto _err;
+	err = 0;
+	*value = db_gain;
+ _err:
+	return err;
+}
+
+static int get_switch_ops(snd_mixer_elem_t *elem, int dir,
+			  snd_mixer_selem_channel_id_t channel, int *value)
+{
+	selem_none_t *s = snd_mixer_elem_get_private(elem);
+	if (s->selem.caps & SM_CAP_GSWITCH)
+		dir = SM_PLAY;
+	if ((unsigned int) channel >= s->str[dir].channels)
+		return -EINVAL;
+	*value = !!(s->str[dir].sw & (1 << channel));
+	return 0;
+}
+
+static int set_volume_ops(snd_mixer_elem_t *elem, int dir,
+			  snd_mixer_selem_channel_id_t channel, long value)
+{
+	int changed;
+	changed = _snd_mixer_selem_set_volume(elem, dir, channel, value);
+	if (changed < 0)
+		return changed;
+	if (changed)
+		return selem_write(elem);
+	return 0;
+}
+
+static int ask_dB_vol_ops(snd_mixer_elem_t *elem, int dir,
+		          long dbValue, long *value, int xdir)
+{
+	selem_none_t *s = snd_mixer_elem_get_private(elem);
+	selem_ctl_t *c;
+
+	if (s->selem.caps & SM_CAP_GVOLUME)
+		dir = SM_PLAY;
+	c = get_selem_ctl(s, dir);
+	if (! c)
+		return -EINVAL;
+	return convert_from_dB(c->elem, &s->str[dir], dbValue, value, xdir);
+}
+
+static int set_dB_ops(snd_mixer_elem_t *elem, int dir,
+		      snd_mixer_selem_channel_id_t channel,
+		      long db_gain, int xdir)
+{
+	selem_none_t *s = snd_mixer_elem_get_private(elem);
+	selem_ctl_t *c;
+	long value;
+	int err;
+
+	if (s->selem.caps & SM_CAP_GVOLUME)
+		dir = SM_PLAY;
+	c = get_selem_ctl(s, dir);
+	if (! c)
+		return -EINVAL;
+	err = convert_from_dB(c->elem, &s->str[dir], db_gain, &value, xdir);
+	if (err < 0)
+		return err;
+	return set_volume_ops(elem, dir, channel, value);
+}
+
+static int set_switch_ops(snd_mixer_elem_t *elem, int dir,
+			  snd_mixer_selem_channel_id_t channel, int value)
+{
+	int changed;
+	selem_none_t *s = snd_mixer_elem_get_private(elem);
+	if (s->selem.caps & SM_CAP_GSWITCH)
+		dir = SM_PLAY;
+	if (dir == SM_PLAY) {
+		if (! (s->selem.caps & (SM_CAP_GSWITCH|SM_CAP_PSWITCH)))
+			return -EINVAL;
+	} else {
+		if (! (s->selem.caps & (SM_CAP_GSWITCH|SM_CAP_CSWITCH)))
+			return -EINVAL;
+	}
+	changed = _snd_mixer_selem_set_switch(elem, dir, channel, value);
+	if (changed < 0)
+		return changed;
+	if (changed)
+		return selem_write(elem);
+	return 0;
+}
+
+static int enum_item_name_ops(snd_mixer_elem_t *elem,
+			      unsigned int item,
+			      size_t maxlen, char *buf)
+{
+	selem_none_t *s = snd_mixer_elem_get_private(elem);
+	snd_ctl_elem_info_t *info;
+	snd_hctl_elem_t *helem;
+	int type;
+
+	type = CTL_GLOBAL_ENUM;
+	helem = s->ctls[type].elem;
+	if (!helem) {
+		type = CTL_PLAYBACK_ENUM;
+		helem = s->ctls[type].elem;
+	}
+	if (!helem) {
+		type = CTL_CAPTURE_ENUM;
+		helem = s->ctls[type].elem;
+	}
+	assert(helem);
+	if (item >= (unsigned int)s->ctls[type].max)
+		return -EINVAL;
+	snd_ctl_elem_info_alloca(&info);
+	snd_hctl_elem_info(helem, info);
+	snd_ctl_elem_info_set_item(info, item);
+	snd_hctl_elem_info(helem, info);
+	strncpy(buf, snd_ctl_elem_info_get_item_name(info), maxlen);
+	return 0;
+}
+
+static int get_enum_item_ops(snd_mixer_elem_t *elem,
+			     snd_mixer_selem_channel_id_t channel,
+			     unsigned int *itemp)
+{
+	selem_none_t *s = snd_mixer_elem_get_private(elem);
+	snd_ctl_elem_value_t *ctl;
+	snd_hctl_elem_t *helem;
+	int err;
+
+	if ((unsigned int) channel >= s->str[0].channels)
+		return -EINVAL;
+	helem = s->ctls[CTL_GLOBAL_ENUM].elem;
+	if (!helem) helem = s->ctls[CTL_PLAYBACK_ENUM].elem;
+	if (!helem) helem = s->ctls[CTL_CAPTURE_ENUM].elem;
+	assert(helem);
+	snd_ctl_elem_value_alloca(&ctl);
+	err = snd_hctl_elem_read(helem, ctl);
+	if (! err)
+		*itemp = snd_ctl_elem_value_get_enumerated(ctl, channel);
+	return err;
+}
+
+static int set_enum_item_ops(snd_mixer_elem_t *elem,
+			     snd_mixer_selem_channel_id_t channel,
+			     unsigned int item)
+{
+	selem_none_t *s = snd_mixer_elem_get_private(elem);
+	snd_ctl_elem_value_t *ctl;
+	snd_hctl_elem_t *helem;
+	int err;
+	int type;
+
+	if ((unsigned int) channel >= s->str[0].channels) {
+		return -EINVAL;
+	}
+	type = CTL_GLOBAL_ENUM;
+	helem = s->ctls[type].elem;
+	if (!helem) {
+		type = CTL_PLAYBACK_ENUM;
+		helem = s->ctls[type].elem;
+	}
+	if (!helem) {
+		type = CTL_CAPTURE_ENUM;
+		helem = s->ctls[type].elem;
+	}
+	assert(helem);
+	if (item >= (unsigned int)s->ctls[type].max) {
+		return -EINVAL;
+	}
+	snd_ctl_elem_value_alloca(&ctl);
+	err = snd_hctl_elem_read(helem, ctl);
+	if (err < 0) {
+		return err;
+	}
+	snd_ctl_elem_value_set_enumerated(ctl, channel, item);
+	return snd_hctl_elem_write(helem, ctl);
+}
+
+static struct sm_elem_ops simple_none_ops = {
+	.is		= is_ops,
+	.get_range	= get_range_ops,
+	.get_dB_range	= get_dB_range_ops,
+	.set_range	= set_range_ops,
+	.ask_vol_dB	= ask_vol_dB_ops,
+	.ask_dB_vol	= ask_dB_vol_ops,
+	.get_volume	= get_volume_ops,
+	.get_dB		= get_dB_ops,
+	.set_volume	= set_volume_ops,
+	.set_dB		= set_dB_ops,
+	.get_switch	= get_switch_ops,
+	.set_switch	= set_switch_ops,
+	.enum_item_name	= enum_item_name_ops,
+	.get_enum_item	= get_enum_item_ops,
+	.set_enum_item	= set_enum_item_ops
+};
+
+static int simple_add1(snd_mixer_class_t *class, const char *name,
+		       snd_hctl_elem_t *helem, selem_ctl_type_t type,
+		       unsigned int value)
+{
+	snd_mixer_elem_t *melem;
+	snd_mixer_selem_id_t *id;
+	int new = 0;
+	int err;
+	snd_ctl_elem_info_t *info;
+	selem_none_t *simple;
+	const char *name1;
+	snd_ctl_elem_type_t ctype;
+	unsigned long values;
+
+	snd_ctl_elem_info_alloca(&info);
+	err = snd_hctl_elem_info(helem, info);
+	if (err < 0)
+		return err;
+	ctype = snd_ctl_elem_info_get_type(info);
+	values = snd_ctl_elem_info_get_count(info);
+	switch (type) {
+	case CTL_SINGLE:
+		if (ctype == SND_CTL_ELEM_TYPE_ENUMERATED)
+			type = CTL_GLOBAL_ENUM;
+		else if (ctype != SND_CTL_ELEM_TYPE_BOOLEAN &&
+		         ctype != SND_CTL_ELEM_TYPE_INTEGER)
+			return 0;
+		break;
+	case CTL_GLOBAL_ROUTE:
+	case CTL_PLAYBACK_ROUTE:
+	case CTL_CAPTURE_ROUTE:
+	{
+		unsigned int n;
+		if (ctype == SND_CTL_ELEM_TYPE_ENUMERATED) {
+			if (type == CTL_PLAYBACK_ROUTE)
+				type = CTL_PLAYBACK_ENUM;
+			else if (type == CTL_CAPTURE_ROUTE)
+				type = CTL_CAPTURE_ENUM;
+			else
+				type = CTL_GLOBAL_ENUM;
+			break;
+		}
+		if (ctype != SND_CTL_ELEM_TYPE_BOOLEAN)
+			return 0;
+#ifdef HAVE_SOFT_FLOAT
+		/* up to 256 channels */
+		for (n = 1; n < 256; n++)
+			if (n * n == values)
+				break;
+#else
+		n = sqrt((double)values);
+#endif
+		if (n * n != values)
+			return 0;
+		values = n;
+		break;
+	}
+	case CTL_GLOBAL_SWITCH:
+	case CTL_PLAYBACK_SWITCH:
+	case CTL_CAPTURE_SWITCH:
+		if (ctype == SND_CTL_ELEM_TYPE_ENUMERATED) {
+			if (type == CTL_PLAYBACK_SWITCH)
+				type = CTL_PLAYBACK_ENUM;
+			else if (type == CTL_CAPTURE_SWITCH)
+				type = CTL_CAPTURE_ENUM;
+			else
+				type = CTL_GLOBAL_ENUM;
+			break;
+		}
+		if (ctype != SND_CTL_ELEM_TYPE_BOOLEAN)
+			return 0;
+		break;
+	case CTL_GLOBAL_VOLUME:
+	case CTL_PLAYBACK_VOLUME:
+	case CTL_CAPTURE_VOLUME:
+		if (ctype == SND_CTL_ELEM_TYPE_ENUMERATED) {
+			if (type == CTL_PLAYBACK_VOLUME)
+				type = CTL_PLAYBACK_ENUM;
+			else if (type == CTL_CAPTURE_VOLUME)
+				type = CTL_CAPTURE_ENUM;
+			else
+				type = CTL_GLOBAL_ENUM;
+			break;
+		}
+		if (ctype != SND_CTL_ELEM_TYPE_INTEGER)
+			return 0;
+		break;
+	case CTL_CAPTURE_SOURCE:
+		if (ctype != SND_CTL_ELEM_TYPE_ENUMERATED)
+			return 0;
+		break;
+	case CTL_GLOBAL_ENUM:
+	case CTL_PLAYBACK_ENUM:
+	case CTL_CAPTURE_ENUM:
+		if (ctype != SND_CTL_ELEM_TYPE_ENUMERATED)
+			return 0;
+		break;
+	default:
+		assert(0);
+		break;
+	}
+	name1 = get_short_name(name);
+	if (snd_mixer_selem_id_malloc(&id))
+		return -ENOMEM;
+	snd_mixer_selem_id_set_name(id, name1);
+	snd_mixer_selem_id_set_index(id, snd_hctl_elem_get_index(helem));
+	melem = snd_mixer_find_selem(snd_mixer_class_get_mixer(class), id);
+	if (!melem) {
+		simple = calloc(1, sizeof(*simple));
+		if (!simple) {
+			snd_mixer_selem_id_free(id);
+			return -ENOMEM;
+		}
+		simple->selem.id = id;
+		simple->selem.ops = &simple_none_ops;
+		err = snd_mixer_elem_new(&melem, SND_MIXER_ELEM_SIMPLE,
+					 get_compare_weight(snd_mixer_selem_id_get_name(simple->selem.id), snd_mixer_selem_id_get_index(simple->selem.id)),
+					 simple, selem_free);
+		if (err < 0) {
+			snd_mixer_selem_id_free(id);
+			free(simple);
+			return err;
+		}
+		new = 1;
+	} else {
+		simple = snd_mixer_elem_get_private(melem);
+		snd_mixer_selem_id_free(id);
+	}
+	if (simple->ctls[type].elem) {
+		SNDERR("helem (%s,'%s',%u,%u,%u) appears twice or more",
+				snd_ctl_elem_iface_name(snd_hctl_elem_get_interface(helem)),
+				snd_hctl_elem_get_name(helem),
+				snd_hctl_elem_get_index(helem),
+				snd_hctl_elem_get_device(helem),
+				snd_hctl_elem_get_subdevice(helem));
+		err = -EINVAL;
+		goto __error;
+	}
+	simple->ctls[type].elem = helem;
+	simple->ctls[type].type = snd_ctl_elem_info_get_type(info);
+	simple->ctls[type].inactive = snd_ctl_elem_info_is_inactive(info);
+	simple->ctls[type].values = values;
+	if ( (type == CTL_GLOBAL_ENUM) ||
+	     (type == CTL_PLAYBACK_ENUM) ||
+	     (type == CTL_CAPTURE_ENUM) ) {
+		simple->ctls[type].min = 0;
+		simple->ctls[type].max = snd_ctl_elem_info_get_items(info);
+	} else {
+		if (ctype == SND_CTL_ELEM_TYPE_INTEGER) {
+			simple->ctls[type].min = snd_ctl_elem_info_get_min(info);
+			simple->ctls[type].max = snd_ctl_elem_info_get_max(info);
+		}
+	}
+	switch (type) {
+	case CTL_CAPTURE_SOURCE:
+		simple->capture_item = value;
+		break;
+	default:
+		break;
+	}
+	err = snd_mixer_elem_attach(melem, helem);
+	if (err < 0)
+		goto __error;
+	err = simple_update(melem);
+	if (err < 0) {
+		if (new)
+			goto __error;
+		return err;
+	}
+	if (new)
+		err = snd_mixer_elem_add(melem, class);
+	else
+		err = snd_mixer_elem_info(melem);
+	if (err < 0)
+		return err;
+	err = selem_read(melem);
+	if (err < 0)
+		return err;
+	if (err)
+		err = snd_mixer_elem_value(melem);
+	return err;
+      __error:
+	if (new)
+		snd_mixer_elem_free(melem);
+	return -EINVAL;
+}
+
+static int simple_event_add(snd_mixer_class_t *class, snd_hctl_elem_t *helem)
+{
+	const char *name = snd_hctl_elem_get_name(helem);
+	size_t len;
+	selem_ctl_type_t type = CTL_SINGLE; /* to shut up warning */
+	if (snd_hctl_elem_get_interface(helem) != SND_CTL_ELEM_IFACE_MIXER)
+		return 0;
+	if (strcmp(name, "Capture Source") == 0) {
+		snd_ctl_elem_info_t *info;
+		unsigned int k, items;
+		int err;
+		snd_ctl_elem_info_alloca(&info);
+		err = snd_hctl_elem_info(helem, info);
+		assert(err >= 0);
+		if (snd_ctl_elem_info_get_type(info) != SND_CTL_ELEM_TYPE_ENUMERATED)
+			return 0;
+		items = snd_ctl_elem_info_get_items(info);
+		for (k = 0; k < items; ++k) {
+			const char *n;
+			snd_ctl_elem_info_set_item(info, k);
+			err = snd_hctl_elem_info(helem, info);
+			if (err < 0)
+				return err;
+			n = snd_ctl_elem_info_get_item_name(info);
+			err = simple_add1(class, n, helem, CTL_CAPTURE_SOURCE, k);
+			if (err < 0)
+				return err;
+		}
+		return 0;
+	}
+	len = base_len(name, &type);
+	if (len == 0) {
+		return simple_add1(class, name, helem, CTL_SINGLE, 0);
+	} else {
+		char ename[128];
+		if (len >= sizeof(ename))
+			len = sizeof(ename) - 1;
+		memcpy(ename, name, len);
+		ename[len] = 0;
+		/* exception: Capture Volume and Capture Switch */
+		if (type == CTL_GLOBAL_VOLUME && !strcmp(ename, "Capture"))
+			type = CTL_CAPTURE_VOLUME;
+		else if (type == CTL_GLOBAL_SWITCH && !strcmp(ename, "Capture"))
+			type = CTL_CAPTURE_SWITCH;
+		return simple_add1(class, ename, helem, type, 0);
+	}
+}
+
+static int simple_event_remove(snd_hctl_elem_t *helem,
+			       snd_mixer_elem_t *melem)
+{
+	selem_none_t *simple = snd_mixer_elem_get_private(melem);
+	int err;
+	int k;
+	for (k = 0; k <= CTL_LAST; k++) {
+		if (simple->ctls[k].elem == helem)
+			break;
+	}
+	assert(k <= CTL_LAST);
+	simple->ctls[k].elem = NULL;
+	err = snd_mixer_elem_detach(melem, helem);
+	if (err < 0)
+		return err;
+	if (snd_mixer_elem_empty(melem))
+		return snd_mixer_elem_remove(melem);
+	err = simple_update(melem);
+	return snd_mixer_elem_info(melem);
+}
+
+static int simple_event(snd_mixer_class_t *class, unsigned int mask,
+			snd_hctl_elem_t *helem, snd_mixer_elem_t *melem)
+{
+	int err;
+	if (mask == SND_CTL_EVENT_MASK_REMOVE)
+		return simple_event_remove(helem, melem);
+	if (mask & SND_CTL_EVENT_MASK_ADD) {
+		err = simple_event_add(class, helem);
+		if (err < 0)
+			return err;
+	}
+	if (mask & SND_CTL_EVENT_MASK_INFO) {
+		err = simple_event_remove(helem, melem);
+		if (err < 0)
+			return err;
+		err = simple_event_add(class, helem);
+		if (err < 0)
+			return err;
+		return 0;
+	}
+	if (mask & SND_CTL_EVENT_MASK_VALUE) {
+		err = selem_read(melem);
+		if (err < 0)
+			return err;
+		if (err) {
+			err = snd_mixer_elem_value(melem);
+			if (err < 0)
+				return err;
+		}
+	}
+	return 0;
+}
+
+/**
+ * \brief Register mixer simple element class - none abstraction
+ * \param mixer Mixer handle
+ * \param options Options container
+ * \param classp Pointer to returned mixer simple element class handle (or NULL)
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_mixer_simple_none_register(snd_mixer_t *mixer,
+				   struct snd_mixer_selem_regopt *options ATTRIBUTE_UNUSED,
+				   snd_mixer_class_t **classp)
+{
+	snd_mixer_class_t *class;
+	int err;
+
+	if (snd_mixer_class_malloc(&class))
+		return -ENOMEM;
+	snd_mixer_class_set_event(class, simple_event);
+	snd_mixer_class_set_compare(class, snd_mixer_selem_compare);
+	err = snd_mixer_class_register(class, mixer);
+	if (err < 0) {
+		free(class);
+		return err;
+	}
+	if (classp)
+		*classp = class;
+	return 0;
+}
diff --git a/src/names.c b/src/names.c
new file mode 100644
index 0000000..000e574
--- /dev/null
+++ b/src/names.c
@@ -0,0 +1,56 @@
+/**
+ * \file names.c
+ * \ingroup Configuration
+ * \brief Configuration helper functions - device names
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \date 2005
+ *
+ * Provide a list of device names for applications.
+ *
+ * See the \ref conf page for more details.
+ */
+/*
+ *  Configuration helper functions - device names
+ *  Copyright (c) 2005 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <stdarg.h>
+#include <limits.h>
+#include <sys/stat.h>
+#include "local.h"
+
+/** 
+ * \brief This function is unimplemented.
+ * \deprecated Since 1.0.14
+ */
+int snd_names_list(const char *iface ATTRIBUTE_UNUSED,
+		   snd_devname_t **list ATTRIBUTE_UNUSED)
+{
+	return -ENXIO;
+}
+link_warning(snd_names_list, "Warning: snd_names_list is deprecated, use snd_device_name_hint");
+
+/**
+ * \brief This function is unimplemented.
+ * \deprecated Since 1.0.14
+ */
+void snd_names_list_free(snd_devname_t *list ATTRIBUTE_UNUSED)
+{
+}
+link_warning(snd_names_list_free, "Warning: snd_names_list_free is deprecated, use snd_device_name_free_hint");
diff --git a/src/output.c b/src/output.c
new file mode 100644
index 0000000..adc461a
--- /dev/null
+++ b/src/output.c
@@ -0,0 +1,380 @@
+/**
+ * \file output.c
+ * \brief Generic stdio-like output interface
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \date 2000
+ *
+ * Generic stdio-like output interface
+ */
+/*
+ *  Output object
+ *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include "local.h"
+
+#ifndef DOC_HIDDEN
+typedef struct _snd_output_ops {
+	int (*close)(snd_output_t *output);
+	int (*print)(snd_output_t *output, const char *format, va_list args);
+	int (*puts)(snd_output_t *output, const char *str);
+	int (*putch)(snd_output_t *output, int c);
+	int (*flush)(snd_output_t *output);
+} snd_output_ops_t;
+
+struct _snd_output {
+	snd_output_type_t type;
+	const snd_output_ops_t *ops;
+	void *private_data;
+};
+#endif
+
+/**
+ * \brief Closes an output handle.
+ * \param output The output handle to be closed.
+ * \return Zero if successful, otherwise a negative error code.
+ */
+int snd_output_close(snd_output_t *output)
+{
+	int err = output->ops->close(output);
+	free(output);
+	return err;
+}
+
+/**
+ * \brief Writes formatted output (like \c fprintf(3)) to an output handle.
+ * \param output The output handle.
+ * \param format Format string in \c fprintf format.
+ * \param ... Other \c fprintf arguments.
+ * \return The number of characters written, or a negative error code.
+ */
+int snd_output_printf(snd_output_t *output, const char *format, ...)
+{
+	int result;
+	va_list args;
+	va_start(args, format);
+	result = output->ops->print(output, format, args);
+	va_end(args);
+	return result;
+}
+
+/**
+ * \brief Writes formatted output (like \c fprintf(3)) to an output handle.
+ * \param output The output handle.
+ * \param format Format string in \c fprintf format.
+ * \param args Other \c fprintf arguments.
+ * \return The number of characters written, or a negative error code.
+ */
+int snd_output_vprintf(snd_output_t *output, const char *format, va_list args)
+{
+	return output->ops->print(output, format, args);
+}
+
+/**
+ * \brief Writes a string to an output handle (like \c fputs(3)).
+ * \param output The output handle.
+ * \param str Pointer to the string.
+ * \return Zero if successful, otherwise a negative error code or \c EOF.
+ */
+int snd_output_puts(snd_output_t *output, const char *str)
+{
+	return output->ops->puts(output, str);
+}
+			
+/**
+ * \brief Writes a character to an output handle (like \c putc(3)).
+ * \param output The output handle.
+ * \param c The character.
+ * \return Zero if successful, otherwise a negative error code or \c EOF.
+ */
+int snd_output_putc(snd_output_t *output, int c)
+{
+	return output->ops->putch(output, c);
+}
+
+/**
+ * \brief Flushes an output handle (like fflush(3)).
+ * \param output The output handle.
+ * \return Zero if successful, otherwise \c EOF.
+ *
+ * If the underlying destination is a stdio stream, this function calls
+ * \c fflush. If the underlying destination is a memory buffer, the write
+ * position is reset to the beginning of the buffer. \c =:-o
+ */
+int snd_output_flush(snd_output_t *output)
+{
+	return output->ops->flush(output);
+}
+
+#ifndef DOC_HIDDEN
+typedef struct _snd_output_stdio {
+	int close;
+	FILE *fp;
+} snd_output_stdio_t;
+
+static int snd_output_stdio_close(snd_output_t *output)
+{
+	snd_output_stdio_t *stdio = output->private_data;
+	if (stdio->close)
+		fclose(stdio->fp);
+	free(stdio);
+	return 0;
+}
+
+static int snd_output_stdio_print(snd_output_t *output, const char *format, va_list args)
+{
+	snd_output_stdio_t *stdio = output->private_data;
+	return vfprintf(stdio->fp, format, args);
+}
+
+static int snd_output_stdio_puts(snd_output_t *output, const char *str)
+{
+	snd_output_stdio_t *stdio = output->private_data;
+	return fputs(str, stdio->fp);
+}
+			
+static int snd_output_stdio_putc(snd_output_t *output, int c)
+{
+	snd_output_stdio_t *stdio = output->private_data;
+	return putc(c, stdio->fp);
+}
+
+static int snd_output_stdio_flush(snd_output_t *output)
+{
+	snd_output_stdio_t *stdio = output->private_data;
+	return fflush(stdio->fp);
+}
+
+static const snd_output_ops_t snd_output_stdio_ops = {
+	.close		= snd_output_stdio_close,
+	.print		= snd_output_stdio_print,
+	.puts		= snd_output_stdio_puts,
+	.putch		= snd_output_stdio_putc,
+	.flush		= snd_output_stdio_flush,
+};
+
+#endif
+
+/**
+ * \brief Creates a new output object using an existing stdio \c FILE pointer.
+ * \param outputp The function puts the pointer to the new output object
+ *                at the address specified by \p outputp.
+ * \param fp The \c FILE pointer to write to. Characters are written
+ *           to the file starting at the current file position.
+ * \param _close Close flag. Set this to 1 if #snd_output_close should close
+ *              \p fp by calling \c fclose.
+ * \return Zero if successful, otherwise a negative error code.
+ */
+int snd_output_stdio_attach(snd_output_t **outputp, FILE *fp, int _close)
+{
+	snd_output_t *output;
+	snd_output_stdio_t *stdio;
+	assert(outputp && fp);
+	stdio = calloc(1, sizeof(*stdio));
+	if (!stdio)
+		return -ENOMEM;
+	output = calloc(1, sizeof(*output));
+	if (!output) {
+		free(stdio);
+		return -ENOMEM;
+	}
+	stdio->fp = fp;
+	stdio->close = _close;
+	output->type = SND_OUTPUT_STDIO;
+	output->ops = &snd_output_stdio_ops;
+	output->private_data = stdio;
+	*outputp = output;
+	return 0;
+}
+	
+/**
+ * \brief Creates a new output object writing to a file.
+ * \param outputp The function puts the pointer to the new output object
+ *                at the address specified by \p outputp.
+ * \param file The name of the file to open.
+ * \param mode The open mode, like \c fopen(3).
+ * \return Zero if successful, otherwise a negative error code.
+ */
+int snd_output_stdio_open(snd_output_t **outputp, const char *file, const char *mode)
+{
+	int err;
+	FILE *fp = fopen(file, mode);
+	if (!fp) {
+		//SYSERR("fopen");
+		return -errno;
+	}
+	err = snd_output_stdio_attach(outputp, fp, 1);
+	if (err < 0)
+		fclose(fp);
+	return err;
+}
+
+#ifndef DOC_HIDDEN
+
+typedef struct _snd_output_buffer {
+	unsigned char *buf;
+	size_t alloc;
+	size_t size;
+} snd_output_buffer_t;
+
+static int snd_output_buffer_close(snd_output_t *output)
+{
+	snd_output_buffer_t *buffer = output->private_data;
+	free(buffer->buf);
+	free(buffer);
+	return 0;
+}
+
+static int snd_output_buffer_need(snd_output_t *output, size_t size)
+{
+	snd_output_buffer_t *buffer = output->private_data;
+	size_t _free = buffer->alloc - buffer->size;
+	size_t alloc;
+	unsigned char *buf;
+
+	if (_free >= size)
+		return _free;
+	if (buffer->alloc == 0)
+		alloc = 256;
+	else
+		alloc = buffer->alloc;
+	while (alloc < buffer->size + size)
+		alloc *= 2;
+	buf = realloc(buffer->buf, alloc);
+	if (!buf)
+		return -ENOMEM;
+	buffer->buf = buf;
+	buffer->alloc = alloc;
+	return buffer->alloc - buffer->size;
+}
+
+static int snd_output_buffer_print(snd_output_t *output, const char *format, va_list args)
+{
+	snd_output_buffer_t *buffer = output->private_data;
+	size_t size = 256;
+	int result;
+	result = snd_output_buffer_need(output, size);
+	if (result < 0)
+		return result;
+	result = vsnprintf((char *)buffer->buf + buffer->size, size, format, args);
+	assert(result >= 0);
+	if ((size_t)result <= size) {
+		buffer->size += result;
+		return result;
+	}
+	size = result;
+	result = snd_output_buffer_need(output, size);
+	if (result < 0)
+		return result;
+	result = vsnprintf((char *)buffer->buf + buffer->size, result, format, args);
+	assert(result == (int)size);
+	buffer->size += result;
+	return result;
+}
+
+static int snd_output_buffer_puts(snd_output_t *output, const char *str)
+{
+	snd_output_buffer_t *buffer = output->private_data;
+	size_t size = strlen(str);
+	int err;
+	err = snd_output_buffer_need(output, size);
+	if (err < 0)
+		return err;
+	memcpy(buffer->buf + buffer->size, str, size);
+	buffer->size += size;
+	return size;
+}
+			
+static int snd_output_buffer_putc(snd_output_t *output, int c)
+{
+	snd_output_buffer_t *buffer = output->private_data;
+	int err;
+	err = snd_output_buffer_need(output, 1);
+	if (err < 0)
+		return err;
+	buffer->buf[buffer->size++] = c;
+	return 0;
+}
+
+static int snd_output_buffer_flush(snd_output_t *output ATTRIBUTE_UNUSED)
+{
+	snd_output_buffer_t *buffer = output->private_data;
+	buffer->size = 0;
+	return 0;
+}
+
+static const snd_output_ops_t snd_output_buffer_ops = {
+	.close		= snd_output_buffer_close,
+	.print		= snd_output_buffer_print,
+	.puts		= snd_output_buffer_puts,
+	.putch		= snd_output_buffer_putc,
+	.flush		= snd_output_buffer_flush,
+};
+#endif
+
+/**
+ * \brief Returns the address of the buffer of a #SND_OUTPUT_BUFFER output handle.
+ * \param output The output handle.
+ * \param buf The functions puts the current address of the buffer at the
+ *            address specified by \p buf.
+ * \return The current size of valid data in the buffer.
+ *
+ * The address of the buffer may become invalid when output functions or
+ * #snd_output_close are called.
+ */
+size_t snd_output_buffer_string(snd_output_t *output, char **buf)
+{
+	snd_output_buffer_t *buffer = output->private_data;
+	*buf = (char *)buffer->buf;
+	return buffer->size;
+}
+
+/**
+ * \brief Creates a new output object with an auto-extending memory buffer.
+ * \param outputp The function puts the pointer to the new output object
+ *                at the address specified by \p outputp.
+ * \return Zero if successful, otherwise a negative error code.
+ */
+int snd_output_buffer_open(snd_output_t **outputp)
+{
+	snd_output_t *output;
+	snd_output_buffer_t *buffer;
+	assert(outputp);
+	buffer = calloc(1, sizeof(*buffer));
+	if (!buffer)
+		return -ENOMEM;
+	output = calloc(1, sizeof(*output));
+	if (!output) {
+		free(buffer);
+		return -ENOMEM;
+	}
+	buffer->buf = NULL;
+	buffer->alloc = 0;
+	buffer->size = 0;
+	output->type = SND_OUTPUT_BUFFER;
+	output->ops = &snd_output_buffer_ops;
+	output->private_data = buffer;
+	*outputp = output;
+	return 0;
+}
+	
diff --git a/src/pcm/Makefile.am b/src/pcm/Makefile.am
new file mode 100644
index 0000000..28faa54
--- /dev/null
+++ b/src/pcm/Makefile.am
@@ -0,0 +1,117 @@
+SUBDIRS =
+DIST_SUBDIRS = scopes
+
+EXTRA_LTLIBRARIES = libpcm.la
+
+libpcm_la_SOURCES = atomic.c mask.c interval.c \
+		    pcm.c pcm_params.c pcm_simple.c \
+		    pcm_hw.c pcm_misc.c pcm_mmap.c pcm_symbols.c
+
+if BUILD_PCM_PLUGIN
+libpcm_la_SOURCES += pcm_generic.c pcm_plugin.c
+endif
+if BUILD_PCM_PLUGIN_COPY
+libpcm_la_SOURCES += pcm_copy.c
+endif
+if BUILD_PCM_PLUGIN_LINEAR
+libpcm_la_SOURCES += pcm_linear.c
+endif
+if BUILD_PCM_PLUGIN_ROUTE
+libpcm_la_SOURCES += pcm_route.c
+endif
+if BUILD_PCM_PLUGIN_MULAW
+libpcm_la_SOURCES += pcm_mulaw.c
+endif
+if BUILD_PCM_PLUGIN_ALAW
+libpcm_la_SOURCES += pcm_alaw.c
+endif
+if BUILD_PCM_PLUGIN_ADPCM
+libpcm_la_SOURCES += pcm_adpcm.c
+endif
+if BUILD_PCM_PLUGIN_RATE
+libpcm_la_SOURCES += pcm_rate.c pcm_rate_linear.c
+endif
+if BUILD_PCM_PLUGIN_PLUG
+libpcm_la_SOURCES += pcm_plug.c
+endif
+if BUILD_PCM_PLUGIN_MULTI
+libpcm_la_SOURCES += pcm_multi.c
+endif
+if BUILD_PCM_PLUGIN_SHM
+libpcm_la_SOURCES += pcm_shm.c
+endif
+if BUILD_PCM_PLUGIN_FILE
+libpcm_la_SOURCES += pcm_file.c
+endif
+if BUILD_PCM_PLUGIN_NULL
+libpcm_la_SOURCES += pcm_null.c
+endif
+if BUILD_PCM_PLUGIN_EMPTY
+libpcm_la_SOURCES += pcm_empty.c
+endif
+if BUILD_PCM_PLUGIN_SHARE
+libpcm_la_SOURCES += pcm_share.c
+endif
+if BUILD_PCM_PLUGIN_METER
+libpcm_la_SOURCES += pcm_meter.c
+endif
+if BUILD_PCM_PLUGIN_HOOKS
+libpcm_la_SOURCES += pcm_hooks.c
+endif
+if BUILD_PCM_PLUGIN_LFLOAT
+libpcm_la_SOURCES += pcm_lfloat.c
+endif
+if BUILD_PCM_PLUGIN_LADSPA
+libpcm_la_SOURCES += pcm_ladspa.c
+endif
+if BUILD_PCM_PLUGIN_DMIX
+libpcm_la_SOURCES += pcm_dmix.c
+endif
+if BUILD_PCM_PLUGIN_DSHARE
+libpcm_la_SOURCES += pcm_dshare.c
+endif
+if BUILD_PCM_PLUGIN_DSNOOP
+libpcm_la_SOURCES += pcm_dsnoop.c
+endif
+if BUILD_PCM_PLUGIN_DMIX
+libpcm_la_SOURCES += pcm_direct.c
+else
+if BUILD_PCM_PLUGIN_DSHARE
+libpcm_la_SOURCES += pcm_direct.c
+else
+if BUILD_PCM_PLUGIN_DSNOOP
+libpcm_la_SOURCES += pcm_direct.c
+endif
+endif
+endif
+if BUILD_PCM_PLUGIN_ASYM
+libpcm_la_SOURCES += pcm_asym.c
+endif
+if BUILD_PCM_PLUGIN_IEC958
+libpcm_la_SOURCES += pcm_iec958.c
+endif
+if BUILD_PCM_PLUGIN_SOFTVOL
+libpcm_la_SOURCES += pcm_softvol.c
+endif
+if BUILD_PCM_PLUGIN_EXTPLUG
+libpcm_la_SOURCES += pcm_extplug.c
+endif
+if BUILD_PCM_PLUGIN_IOPLUG
+libpcm_la_SOURCES += pcm_ioplug.c
+endif
+if BUILD_PCM_PLUGIN_MMAP_EMUL
+libpcm_la_SOURCES += pcm_mmap_emul.c
+endif
+
+EXTRA_DIST = pcm_dmix_i386.c pcm_dmix_x86_64.c pcm_dmix_generic.c
+
+noinst_HEADERS = pcm_local.h pcm_plugin.h mask.h mask_inline.h \
+	         interval.h interval_inline.h plugin_ops.h ladspa.h \
+		 pcm_direct.h pcm_dmix_i386.h pcm_dmix_x86_64.h \
+		 pcm_generic.h pcm_ext_parm.h
+
+alsadir = $(datadir)/alsa
+
+all: libpcm.la
+
+INCLUDES=-I$(top_srcdir)/include
diff --git a/src/pcm/atomic.c b/src/pcm/atomic.c
new file mode 100644
index 0000000..7565945
--- /dev/null
+++ b/src/pcm/atomic.c
@@ -0,0 +1,43 @@
+/*
+ *  Atomic read/write
+ *  Copyright (c) 2001 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+#include <stdlib.h>
+#include <time.h>
+#include <sched.h>
+#include "iatomic.h"
+
+void snd_atomic_read_wait(snd_atomic_read_t *t)
+{
+	volatile const snd_atomic_write_t *w = t->write;
+	unsigned int loops = 0;
+	struct timespec ts;
+	while (w->begin != w->end) {
+		if (loops < MAX_SPIN_COUNT) {
+			sched_yield();
+			loops++;
+			continue;
+		}
+		loops = 0;
+		ts.tv_sec = 0;
+		ts.tv_nsec = SPIN_SLEEP_DURATION;
+		nanosleep(&ts, NULL);
+	}
+}
+
diff --git a/src/pcm/interval.c b/src/pcm/interval.c
new file mode 100644
index 0000000..6e39808
--- /dev/null
+++ b/src/pcm/interval.c
@@ -0,0 +1,441 @@
+/*
+ *  Interval functions
+ *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+#define SND_INTERVAL_C
+#define SND_INTERVAL_INLINE
+
+#include <sys/types.h>
+#include <limits.h>
+#include "pcm_local.h"
+
+static inline void div64_32(u_int64_t *n, u_int32_t d, u_int32_t *rem)
+{
+	*rem = *n % d;
+	*n /= d;
+}
+
+static inline unsigned int div32(unsigned int a, unsigned int b, 
+				 unsigned int *r)
+{
+	if (b == 0) {
+		*r = 0;
+		return UINT_MAX;
+	}
+	*r = a % b;
+	return a / b;
+}
+
+static inline unsigned int div_down(unsigned int a, unsigned int b)
+{
+	if (b == 0)
+		return UINT_MAX;
+	return a / b;
+}
+
+static inline unsigned int div_up(unsigned int a, unsigned int b)
+{
+	unsigned int r;
+	unsigned int q;
+	if (b == 0)
+		return UINT_MAX;
+	q = div32(a, b, &r);
+	if (r)
+		++q;
+	return q;
+}
+
+static inline unsigned int mul(unsigned int a, unsigned int b)
+{
+	if (a == 0)
+		return 0;
+	if (div_down(UINT_MAX, a) < b)
+		return UINT_MAX;
+	return a * b;
+}
+
+static inline unsigned int add(unsigned int a, unsigned int b)
+{
+	if (a >= UINT_MAX - b)
+		return UINT_MAX;
+	return a + b;
+}
+
+static inline unsigned int sub(unsigned int a, unsigned int b)
+{
+	if (a > b)
+		return a - b;
+	return 0;
+}
+
+static inline unsigned int muldiv32(unsigned int a, unsigned int b,
+				    unsigned int c, unsigned int *r)
+{
+	u_int64_t n = (u_int64_t) a * b;
+	if (c == 0) {
+		assert(n > 0);
+		*r = 0;
+		return UINT_MAX;
+	}
+	div64_32(&n, c, r);
+	if (n >= UINT_MAX) {
+		*r = 0;
+		return UINT_MAX;
+	}
+	return n;
+}
+
+int snd_interval_refine_min(snd_interval_t *i, unsigned int min, int openmin)
+{
+	int changed = 0;
+	if (snd_interval_empty(i))
+		return -ENOENT;
+	if (i->min < min) {
+		i->min = min;
+		i->openmin = openmin;
+		changed = 1;
+	} else if (i->min == min && !i->openmin && openmin) {
+		i->openmin = 1;
+		changed = 1;
+	}
+	if (i->integer) {
+		if (i->openmin) {
+			i->min++;
+			i->openmin = 0;
+		}
+	}
+	if (snd_interval_checkempty(i)) {
+		snd_interval_none(i);
+		return -EINVAL;
+	}
+	return changed;
+}
+
+int snd_interval_refine_max(snd_interval_t *i, unsigned int max, int openmax)
+{
+	int changed = 0;
+	if (snd_interval_empty(i))
+		return -ENOENT;
+	if (i->max > max) {
+		i->max = max;
+		i->openmax = openmax;
+		changed = 1;
+	} else if (i->max == max && !i->openmax && openmax) {
+		i->openmax = 1;
+		changed = 1;
+	}
+	if (i->integer) {
+		if (i->openmax) {
+			i->max--;
+			i->openmax = 0;
+		}
+	}
+	if (snd_interval_checkempty(i)) {
+		snd_interval_none(i);
+		return -EINVAL;
+	}
+	return changed;
+}
+
+/* r <- v */
+int snd_interval_refine(snd_interval_t *i, const snd_interval_t *v)
+{
+	int changed = 0;
+	if (snd_interval_empty(i))
+		return -ENOENT;
+	if (i->min < v->min) {
+		i->min = v->min;
+		i->openmin = v->openmin;
+		changed = 1;
+	} else if (i->min == v->min && !i->openmin && v->openmin) {
+		i->openmin = 1;
+		changed = 1;
+	}
+	if (i->max > v->max) {
+		i->max = v->max;
+		i->openmax = v->openmax;
+		changed = 1;
+	} else if (i->max == v->max && !i->openmax && v->openmax) {
+		i->openmax = 1;
+		changed = 1;
+	}
+	if (!i->integer && v->integer) {
+		i->integer = 1;
+		changed = 1;
+	}
+	if (i->integer) {
+		if (i->openmin) {
+			i->min++;
+			i->openmin = 0;
+		}
+		if (i->openmax) {
+			i->max--;
+			i->openmax = 0;
+		}
+	} else if (!i->openmin && !i->openmax && i->min == i->max)
+		i->integer = 1;
+	if (snd_interval_checkempty(i)) {
+		snd_interval_none(i);
+		return -EINVAL;
+	}
+	return changed;
+}
+
+int snd_interval_refine_first(snd_interval_t *i)
+{
+	if (snd_interval_empty(i))
+		return -ENOENT;
+	if (snd_interval_single(i))
+		return 0;
+	i->max = i->min;
+	i->openmax = i->openmin;
+	if (i->openmax)
+		i->max++;
+	return 1;
+}
+
+int snd_interval_refine_last(snd_interval_t *i)
+{
+	if (snd_interval_empty(i))
+		return -ENOENT;
+	if (snd_interval_single(i))
+		return 0;
+	i->min = i->max;
+	i->openmin = i->openmax;
+	if (i->openmin)
+		i->min--;
+	return 1;
+}
+
+int snd_interval_refine_set(snd_interval_t *i, unsigned int val)
+{
+	snd_interval_t t;
+	t.empty = 0;
+	t.min = t.max = val;
+	t.openmin = t.openmax = 0;
+	t.integer = 1;
+	return snd_interval_refine(i, &t);
+}
+
+void snd_interval_add(const snd_interval_t *a, const snd_interval_t *b, snd_interval_t *c)
+{
+	if (a->empty || b->empty) {
+		snd_interval_none(c);
+		return;
+	}
+	c->empty = 0;
+	c->min = add(a->min, b->min);
+	c->openmin = (a->openmin || b->openmin);
+	c->max = add(a->max,  b->max);
+	c->openmax = (a->openmax || b->openmax);
+	c->integer = (a->integer && b->integer);
+}
+
+void snd_interval_sub(const snd_interval_t *a, const snd_interval_t *b, snd_interval_t *c)
+{
+	if (a->empty || b->empty) {
+		snd_interval_none(c);
+		return;
+	}
+	c->empty = 0;
+	c->min = sub(a->min, b->max);
+	c->openmin = (a->openmin || b->openmax);
+	c->max = add(a->max,  b->min);
+	c->openmax = (a->openmax || b->openmin);
+	c->integer = (a->integer && b->integer);
+}
+
+void snd_interval_mul(const snd_interval_t *a, const snd_interval_t *b, snd_interval_t *c)
+{
+	if (a->empty || b->empty) {
+		snd_interval_none(c);
+		return;
+	}
+	c->empty = 0;
+	c->min = mul(a->min, b->min);
+	c->openmin = (a->openmin || b->openmin);
+	c->max = mul(a->max,  b->max);
+	c->openmax = (a->openmax || b->openmax);
+	c->integer = (a->integer && b->integer);
+}
+
+void snd_interval_div(const snd_interval_t *a, const snd_interval_t *b, snd_interval_t *c)
+{
+	unsigned int r;
+	if (a->empty || b->empty) {
+		snd_interval_none(c);
+		return;
+	}
+	c->empty = 0;
+	c->min = div32(a->min, b->max, &r);
+	c->openmin = (r || a->openmin || b->openmax);
+	if (b->min > 0) {
+		c->max = div32(a->max, b->min, &r);
+		if (r) {
+			c->max++;
+			c->openmax = 1;
+		} else
+			c->openmax = (a->openmax || b->openmin);
+	} else {
+		c->max = UINT_MAX;
+		c->openmax = 0;
+	}
+	c->integer = 0;
+}
+
+/* a * b / c */
+void snd_interval_muldiv(const snd_interval_t *a, const snd_interval_t *b,
+		     const snd_interval_t *c, snd_interval_t *d)
+{
+	unsigned int r;
+	if (a->empty || b->empty || c->empty) {
+		snd_interval_none(d);
+		return;
+	}
+	d->empty = 0;
+	d->min = muldiv32(a->min, b->min, c->max, &r);
+	d->openmin = (r || a->openmin || b->openmin || c->openmax);
+	d->max = muldiv32(a->max, b->max, c->min, &r);
+	if (r) {
+		d->max++;
+		d->openmax = 1;
+	} else
+		d->openmax = (a->openmax || b->openmax || c->openmin);
+	d->integer = 0;
+}
+
+/* a * b / k */
+void snd_interval_muldivk(const snd_interval_t *a, const snd_interval_t *b,
+		      unsigned int k, snd_interval_t *c)
+{
+	unsigned int r;
+	if (a->empty || b->empty) {
+		snd_interval_none(c);
+		return;
+	}
+	c->empty = 0;
+	c->min = muldiv32(a->min, b->min, k, &r);
+	c->openmin = (r || a->openmin || b->openmin);
+	c->max = muldiv32(a->max, b->max, k, &r);
+	if (r) {
+		c->max++;
+		c->openmax = 1;
+	} else
+		c->openmax = (a->openmax || b->openmax);
+	c->integer = 0;
+}
+
+/* a * k / b */
+void snd_interval_mulkdiv(const snd_interval_t *a, unsigned int k,
+		      const snd_interval_t *b, snd_interval_t *c)
+{
+	unsigned int r;
+	if (a->empty || b->empty) {
+		snd_interval_none(c);
+		return;
+	}
+	c->empty = 0;
+	c->min = muldiv32(a->min, k, b->max, &r);
+	c->openmin = (r || a->openmin || b->openmax);
+	if (b->min > 0) {
+		c->max = muldiv32(a->max, k, b->min, &r);
+		if (r) {
+			c->max++;
+			c->openmax = 1;
+		} else
+			c->openmax = (a->openmax || b->openmin);
+	} else {
+		c->max = UINT_MAX;
+		c->openmax = 0;
+	}
+	c->integer = 0;
+}
+
+void snd_interval_print(const snd_interval_t *i, snd_output_t *out)
+{
+	if (snd_interval_empty(i))
+		snd_output_printf(out, "NONE");
+	else if (i->min == 0 && i->openmin == 0 && 
+		 i->max == UINT_MAX && i->openmax == 0)
+		snd_output_printf(out, "ALL");
+	else if (snd_interval_single(i) && i->integer)
+		snd_output_printf(out, "%u", snd_interval_value(i));
+	else
+		snd_output_printf(out, "%c%u %u%c",
+				i->openmin ? '(' : '[',
+				i->min, i->max,
+				i->openmax ? ')' : ']');
+}
+
+#if 0
+static void boundary_abs(int a, int adir, int *b, int *bdir)
+{
+	if (a < 0 || (a == 0 && adir < 0)) {
+		*b = -a;
+		*bdir = -adir;
+	} else {
+		*b = a;
+		*bdir = adir;
+	}
+}
+#endif
+
+void boundary_sub(int a, int adir, int b, int bdir, int *c, int *cdir)
+{
+	adir = adir < 0 ? -1 : (adir > 0 ? 1 : 0);
+	bdir = bdir < 0 ? -1 : (bdir > 0 ? 1 : 0);
+	*c = a - b;
+	*cdir = adir - bdir;
+	if (*cdir == -2) {
+		assert(*c > INT_MIN);
+		(*c)--;
+	} else if (*cdir == 2) {
+		assert(*c < INT_MAX);
+		(*c)++;
+	}
+}
+
+int boundary_lt(unsigned int a, int adir, unsigned int b, int bdir)
+{
+	assert(a > 0 || adir >= 0);
+	assert(b > 0 || bdir >= 0);
+	if (adir < 0) {
+		a--;
+		adir = 1;
+	} else if (adir > 0)
+		adir = 1;
+	if (bdir < 0) {
+		b--;
+		bdir = 1;
+	} else if (bdir > 0)
+		bdir = 1;
+	return a < b || (a == b && adir < bdir);
+}
+
+/* Return 1 if min is nearer to best than max */
+int boundary_nearer(int min, int mindir, int best, int bestdir, int max, int maxdir)
+{
+	int dmin, dmindir;
+	int dmax, dmaxdir;
+	boundary_sub(best, bestdir, min, mindir, &dmin, &dmindir);
+	boundary_sub(max, maxdir, best, bestdir, &dmax, &dmaxdir);
+	return boundary_lt(dmin, dmindir, dmax, dmaxdir);
+}
+
diff --git a/src/pcm/interval.h b/src/pcm/interval.h
new file mode 100644
index 0000000..330b056
--- /dev/null
+++ b/src/pcm/interval.h
@@ -0,0 +1,80 @@
+/*
+ *  Interval header
+ *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+typedef struct _snd_interval snd_interval_t;
+
+#ifdef SND_INTERVAL_INLINE
+#include "interval_inline.h"
+#else
+void snd_interval_any(snd_interval_t *i);
+void snd_interval_none(snd_interval_t *i);
+int snd_interval_setinteger(snd_interval_t *i);
+int snd_interval_empty(const snd_interval_t *i);
+int snd_interval_single(const snd_interval_t *i);
+int snd_interval_value(const snd_interval_t *i);
+void snd_interval_set_value(snd_interval_t *i, unsigned int val);
+int snd_interval_min(const snd_interval_t *i);
+int snd_interval_max(const snd_interval_t *i);
+void snd_interval_set_minmax(snd_interval_t *i, unsigned int min, unsigned int max);
+int snd_interval_test(const snd_interval_t *i, unsigned int val);
+void snd_interval_copy(snd_interval_t *dst, const snd_interval_t *src);
+void snd_interval_floor(snd_interval_t *i);
+void snd_interval_unfloor(snd_interval_t *i);
+int snd_interval_always_eq(const snd_interval_t *i1, const snd_interval_t *i2);
+int snd_interval_never_eq(const snd_interval_t *i1, const snd_interval_t *i2);
+#endif
+
+/* make local functions really local */
+#define snd_interval_add	snd1_interval_add
+#define snd_interval_sub	snd1_interval_sub
+#define snd_interval_mul	snd1_interval_mul
+#define snd_interval_div	snd1_interval_div
+#define snd_interval_muldiv	snd1_interval_muldiv
+#define snd_interval_muldivk	snd1_interval_muldivk
+#define snd_interval_mulkdiv	snd1_interval_mulkdiv
+#define snd_interval_print	snd1_interval_print
+#define snd_interval_refine_min	snd1_interval_refine_min
+#define snd_interval_refine_max	snd1_interval_refine_max
+#define snd_interval_refine	snd1_interval_refine
+#define snd_interval_refine_first snd1_interval_refine_first
+#define snd_interval_refine_last snd1_interval_refine_last
+#define snd_interval_refine_set	snd1_interval_refine_set
+
+void snd_interval_add(const snd_interval_t *a, const snd_interval_t *b, snd_interval_t *c);
+void snd_interval_sub(const snd_interval_t *a, const snd_interval_t *b, snd_interval_t *c);
+void snd_interval_mul(const snd_interval_t *a, const snd_interval_t *b, snd_interval_t *c);
+void snd_interval_div(const snd_interval_t *a, const snd_interval_t *b, snd_interval_t *c);
+void snd_interval_muldiv(const snd_interval_t *a, const snd_interval_t *b, 
+		     const snd_interval_t *c, snd_interval_t *d);
+void snd_interval_muldivk(const snd_interval_t *a, const snd_interval_t *b, 
+		      unsigned int k, snd_interval_t *c);
+void snd_interval_mulkdiv(const snd_interval_t *a, unsigned int k,
+		      const snd_interval_t *b, snd_interval_t *c);
+void snd_interval_print(const snd_interval_t *i, snd_output_t *out);
+int snd_interval_refine_min(snd_interval_t *i, unsigned int min, int openmin);
+int snd_interval_refine_max(snd_interval_t *i, unsigned int max, int openmax);
+int snd_interval_refine(snd_interval_t *i, const snd_interval_t *v);
+int snd_interval_refine_first(snd_interval_t *i);
+int snd_interval_refine_last(snd_interval_t *i);
+int snd_interval_refine_set(snd_interval_t *i, unsigned int val);
+void boundary_sub(int a, int adir, int b, int bdir, int *c, int *cdir);
+int boundary_lt(unsigned int a, int adir, unsigned int b, int bdir);
+int boundary_nearer(int min, int mindir, int best, int bestdir, int max, int maxdir);
diff --git a/src/pcm/interval_inline.h b/src/pcm/interval_inline.h
new file mode 100644
index 0000000..bf6b784
--- /dev/null
+++ b/src/pcm/interval_inline.h
@@ -0,0 +1,157 @@
+/*
+ *  Interval inlines
+ *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+#define INTERVAL_INLINE static inline
+
+INTERVAL_INLINE void snd_interval_any(snd_interval_t *i)
+{
+	i->min = 0;
+	i->openmin = 0;
+	i->max = UINT_MAX;
+	i->openmax = 0;
+	i->integer = 0;
+	i->empty = 0;
+}
+
+INTERVAL_INLINE void snd_interval_none(snd_interval_t *i)
+{
+	i->empty = 1;
+}
+
+INTERVAL_INLINE int snd_interval_checkempty(const snd_interval_t *i)
+{
+	return (i->min > i->max ||
+		(i->min == i->max && (i->openmin || i->openmax)));
+}
+
+INTERVAL_INLINE int snd_interval_empty(const snd_interval_t *i)
+{
+	return i->empty;
+}
+
+INTERVAL_INLINE int snd_interval_single(const snd_interval_t *i)
+{
+	assert(!snd_interval_empty(i));
+	return (i->min == i->max || 
+		(i->min + 1 == i->max && i->openmax));
+}
+
+INTERVAL_INLINE int snd_interval_value(const snd_interval_t *i)
+{
+	assert(snd_interval_single(i));
+	return i->min;
+}
+
+INTERVAL_INLINE void snd_interval_set_value(snd_interval_t *i, unsigned int val)
+{
+	i->openmax = i->openmin = 0;
+	i->min = i->max = val;
+	i->integer = 0;
+	i->empty = 0;
+}
+
+INTERVAL_INLINE int snd_interval_min(const snd_interval_t *i)
+{
+	assert(!snd_interval_empty(i));
+	return i->min;
+}
+
+INTERVAL_INLINE int snd_interval_max(const snd_interval_t *i)
+{
+	assert(!snd_interval_empty(i));
+	return i->max;
+}
+
+INTERVAL_INLINE void snd_interval_set_minmax(snd_interval_t *i, unsigned int min, unsigned int max)
+{
+	i->openmax = i->openmin = 0;
+	i->min = min;
+	i->max = max;
+	i->integer = 0;
+	i->empty = 0;
+}
+
+INTERVAL_INLINE int snd_interval_test(const snd_interval_t *i, unsigned int val)
+{
+	return !((i->min > val || (i->min == val && i->openmin) ||
+		  i->max < val || (i->max == val && i->openmax)));
+}
+
+INTERVAL_INLINE void snd_interval_copy(snd_interval_t *d, const snd_interval_t *s)
+{
+	*d = *s;
+}
+
+INTERVAL_INLINE int snd_interval_setinteger(snd_interval_t *i)
+{
+	if (i->integer)
+		return 0;
+	if (i->openmin && i->openmax && i->min == i->max)
+		return -EINVAL;
+	i->integer = 1;
+	return 1;
+}
+
+INTERVAL_INLINE void snd_interval_floor(snd_interval_t *i)
+{
+	if (i->integer || snd_interval_empty(i))
+		return;
+	i->openmin = 0;
+	if (i->openmax) {
+		i->max--;
+		i->openmax = 0;
+	}
+	i->integer = 1;
+}
+
+INTERVAL_INLINE void snd_interval_unfloor(snd_interval_t *i)
+{
+	if (snd_interval_empty(i))
+		return;
+	if (i->max == UINT_MAX)
+		return;
+	if (i->openmax)
+		return;
+	i->max++;
+	i->openmax = 1;
+	i->integer = 0;
+}
+
+
+INTERVAL_INLINE int snd_interval_always_eq(const snd_interval_t *i1, const snd_interval_t *i2)
+{
+	return snd_interval_single(i1) && snd_interval_single(i2) &&
+		snd_interval_value(i1) == snd_interval_value(i2);
+}
+
+INTERVAL_INLINE int snd_interval_never_eq(const snd_interval_t *i1, const snd_interval_t *i2)
+{
+	
+	return (i1->max < i2->min || 
+		(i1->max == i2->min &&
+		 (i1->openmax || i1->openmin)) ||
+		i1->min > i2->max ||
+		(i1->min == i2->max &&
+		 (i1->openmin || i2->openmax)));
+}
+
+
+
diff --git a/src/pcm/ladspa.h b/src/pcm/ladspa.h
new file mode 100644
index 0000000..5c30a8a
--- /dev/null
+++ b/src/pcm/ladspa.h
@@ -0,0 +1,603 @@
+/* ladspa.h
+
+   Linux Audio Developer's Simple Plugin API Version 1.1[LGPL].
+   Copyright (C) 2000-2002 Richard W.E. Furse, Paul Barton-Davis,
+   Stefan Westerfeld.
+   
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public License
+   as published by the Free Software Foundation; either version 2.1 of
+   the License, or (at your option) any later version.
+   
+   This library is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+   Lesser General Public License for more details.
+   
+   You should have received a copy of the GNU Lesser General Public
+   License along with this library; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+   USA. */
+
+#ifndef LADSPA_INCLUDED
+#define LADSPA_INCLUDED
+
+#define LADSPA_VERSION "1.1"
+#define LADSPA_VERSION_MAJOR 1
+#define LADSPA_VERSION_MINOR 1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*****************************************************************************/
+
+/* Overview: 
+
+   There is a large number of synthesis packages in use or development
+   on the Linux platform at this time. This API (`The Linux Audio
+   Developer's Simple Plugin API') attempts to give programmers the
+   ability to write simple `plugin' audio processors in C/C++ and link
+   them dynamically (`plug') into a range of these packages (`hosts').
+   It should be possible for any host and any plugin to communicate
+   completely through this interface.
+
+   This API is deliberately short and simple. To achieve compatibility
+   with a range of promising Linux sound synthesis packages it
+   attempts to find the `greatest common divisor' in their logical
+   behaviour. Having said this, certain limiting decisions are
+   implicit, notably the use of a fixed type (LADSPA_Data) for all
+   data transfer and absence of a parameterised `initialisation'
+   phase. See below for the LADSPA_Data typedef.
+
+   Plugins are expected to distinguish between control and audio
+   data. Plugins have `ports' that are inputs or outputs for audio or
+   control data and each plugin is `run' for a `block' corresponding
+   to a short time interval measured in samples. Audio data is
+   communicated using arrays of LADSPA_Data, allowing a block of audio
+   to be processed by the plugin in a single pass. Control data is
+   communicated using single LADSPA_Data values. Control data has a
+   single value at the start of a call to the `run()' or `run_adding()'
+   function, and may be considered to remain this value for its
+   duration. The plugin may assume that all its input and output ports
+   have been connected to the relevant data location (see the
+   `connect_port()' function below) before it is asked to run.
+
+   Plugins will reside in shared object files suitable for dynamic
+   linking by dlopen() and family. The file will provide a number of
+   `plugin types' that can be used to instantiate actual plugins
+   (sometimes known as `plugin instances') that can be connected
+   together to perform tasks.
+
+   This API contains very limited error-handling. */
+
+/*****************************************************************************/
+
+/* Fundamental data type passed in and out of plugin. This data type
+   is used to communicate audio samples and control values. It is
+   assumed that the plugin will work sensibly given any numeric input
+   value although it may have a preferred range (see hints below). 
+
+   For audio it is generally assumed that 1.0f is the `0dB' reference
+   amplitude and is a `normal' signal level. */
+
+typedef float LADSPA_Data;
+
+/*****************************************************************************/
+
+/* Special Plugin Properties: 
+ 
+   Optional features of the plugin type are encapsulated in the
+   LADSPA_Properties type. This is assembled by ORing individual
+   properties together. */
+
+typedef int LADSPA_Properties;
+
+/* Property LADSPA_PROPERTY_REALTIME indicates that the plugin has a
+   real-time dependency (e.g. listens to a MIDI device) and so its
+   output must not be cached or subject to significant latency. */
+#define LADSPA_PROPERTY_REALTIME        0x1
+
+/* Property LADSPA_PROPERTY_INPLACE_BROKEN indicates that the plugin
+   may cease to work correctly if the host elects to use the same data
+   location for both input and output (see connect_port()). This
+   should be avoided as enabling this flag makes it impossible for
+   hosts to use the plugin to process audio `in-place.' */
+#define LADSPA_PROPERTY_INPLACE_BROKEN  0x2
+
+/* Property LADSPA_PROPERTY_HARD_RT_CAPABLE indicates that the plugin
+   is capable of running not only in a conventional host but also in a
+   `hard real-time' environment. To qualify for this the plugin must
+   satisfy all of the following:
+
+   (1) The plugin must not use malloc(), free() or other heap memory
+   management within its run() or run_adding() functions. All new
+   memory used in run() must be managed via the stack. These
+   restrictions only apply to the run() function.
+
+   (2) The plugin will not attempt to make use of any library
+   functions with the exceptions of functions in the ANSI standard C
+   and C maths libraries, which the host is expected to provide.
+
+   (3) The plugin will not access files, devices, pipes, sockets, IPC
+   or any other mechanism that might result in process or thread
+   blocking.
+      
+   (4) The plugin will take an amount of time to execute a run() or
+   run_adding() call approximately of form (A+B*SampleCount) where A
+   and B depend on the machine and host in use. This amount of time
+   may not depend on input signals or plugin state. The host is left
+   the responsibility to perform timings to estimate upper bounds for
+   A and B. */
+#define LADSPA_PROPERTY_HARD_RT_CAPABLE 0x4
+
+#define LADSPA_IS_REALTIME(x)        ((x) & LADSPA_PROPERTY_REALTIME)
+#define LADSPA_IS_INPLACE_BROKEN(x)  ((x) & LADSPA_PROPERTY_INPLACE_BROKEN)
+#define LADSPA_IS_HARD_RT_CAPABLE(x) ((x) & LADSPA_PROPERTY_HARD_RT_CAPABLE)
+
+/*****************************************************************************/
+
+/* Plugin Ports: 
+
+   Plugins have `ports' that are inputs or outputs for audio or
+   data. Ports can communicate arrays of LADSPA_Data (for audio
+   inputs/outputs) or single LADSPA_Data values (for control
+   input/outputs). This information is encapsulated in the
+   LADSPA_PortDescriptor type which is assembled by ORing individual
+   properties together.
+
+   Note that a port must be an input or an output port but not both
+   and that a port must be a control or audio port but not both. */
+
+typedef int LADSPA_PortDescriptor;
+
+/* Property LADSPA_PORT_INPUT indicates that the port is an input. */
+#define LADSPA_PORT_INPUT   0x1
+
+/* Property LADSPA_PORT_OUTPUT indicates that the port is an output. */
+#define LADSPA_PORT_OUTPUT  0x2
+
+/* Property LADSPA_PORT_CONTROL indicates that the port is a control
+   port. */
+#define LADSPA_PORT_CONTROL 0x4
+
+/* Property LADSPA_PORT_AUDIO indicates that the port is a audio
+   port. */
+#define LADSPA_PORT_AUDIO   0x8
+
+#define LADSPA_IS_PORT_INPUT(x)   ((x) & LADSPA_PORT_INPUT)
+#define LADSPA_IS_PORT_OUTPUT(x)  ((x) & LADSPA_PORT_OUTPUT)
+#define LADSPA_IS_PORT_CONTROL(x) ((x) & LADSPA_PORT_CONTROL)
+#define LADSPA_IS_PORT_AUDIO(x)   ((x) & LADSPA_PORT_AUDIO)
+
+/*****************************************************************************/
+
+/* Plugin Port Range Hints: 
+
+   The host may wish to provide a representation of data entering or
+   leaving a plugin (e.g. to generate a GUI automatically). To make
+   this more meaningful, the plugin should provide `hints' to the host
+   describing the usual values taken by the data.
+   
+   Note that these are only hints. The host may ignore them and the
+   plugin must not assume that data supplied to it is meaningful. If
+   the plugin receives invalid input data it is expected to continue
+   to run without failure and, where possible, produce a sensible
+   output (e.g. a high-pass filter given a negative cutoff frequency
+   might switch to an all-pass mode).
+    
+   Hints are meaningful for all input and output ports but hints for
+   input control ports are expected to be particularly useful.
+   
+   More hint information is encapsulated in the
+   LADSPA_PortRangeHintDescriptor type which is assembled by ORing
+   individual hint types together. Hints may require further
+   LowerBound and UpperBound information.
+
+   All the hint information for a particular port is aggregated in the
+   LADSPA_PortRangeHint structure. */
+
+typedef int LADSPA_PortRangeHintDescriptor;
+
+/* Hint LADSPA_HINT_BOUNDED_BELOW indicates that the LowerBound field
+   of the LADSPA_PortRangeHint should be considered meaningful. The
+   value in this field should be considered the (inclusive) lower
+   bound of the valid range. If LADSPA_HINT_SAMPLE_RATE is also
+   specified then the value of LowerBound should be multiplied by the
+   sample rate. */
+#define LADSPA_HINT_BOUNDED_BELOW   0x1
+
+/* Hint LADSPA_HINT_BOUNDED_ABOVE indicates that the UpperBound field
+   of the LADSPA_PortRangeHint should be considered meaningful. The
+   value in this field should be considered the (inclusive) upper
+   bound of the valid range. If LADSPA_HINT_SAMPLE_RATE is also
+   specified then the value of UpperBound should be multiplied by the
+   sample rate. */
+#define LADSPA_HINT_BOUNDED_ABOVE   0x2
+
+/* Hint LADSPA_HINT_TOGGLED indicates that the data item should be
+   considered a Boolean toggle. Data less than or equal to zero should
+   be considered `off' or `false,' and data above zero should be
+   considered `on' or `true.' LADSPA_HINT_TOGGLED may not be used in
+   conjunction with any other hint except LADSPA_HINT_DEFAULT_0 or
+   LADSPA_HINT_DEFAULT_1. */
+#define LADSPA_HINT_TOGGLED         0x4
+
+/* Hint LADSPA_HINT_SAMPLE_RATE indicates that any bounds specified
+   should be interpreted as multiples of the sample rate. For
+   instance, a frequency range from 0Hz to the Nyquist frequency (half
+   the sample rate) could be requested by this hint in conjunction
+   with LowerBound = 0 and UpperBound = 0.5. Hosts that support bounds
+   at all must support this hint to retain meaning. */
+#define LADSPA_HINT_SAMPLE_RATE     0x8
+
+/* Hint LADSPA_HINT_LOGARITHMIC indicates that it is likely that the
+   user will find it more intuitive to view values using a logarithmic
+   scale. This is particularly useful for frequencies and gains. */
+#define LADSPA_HINT_LOGARITHMIC     0x10
+
+/* Hint LADSPA_HINT_INTEGER indicates that a user interface would
+   probably wish to provide a stepped control taking only integer
+   values. Any bounds set should be slightly wider than the actual
+   integer range required to avoid floating point rounding errors. For
+   instance, the integer set {0,1,2,3} might be described as [-0.1,
+   3.1]. */
+#define LADSPA_HINT_INTEGER         0x20
+
+/* The various LADSPA_HINT_HAS_DEFAULT_* hints indicate a `normal'
+   value for the port that is sensible as a default. For instance,
+   this value is suitable for use as an initial value in a user
+   interface or as a value the host might assign to a control port
+   when the user has not provided one. Defaults are encoded using a
+   mask so only one default may be specified for a port. Some of the
+   hints make use of lower and upper bounds, in which case the
+   relevant bound or bounds must be available and
+   LADSPA_HINT_SAMPLE_RATE must be applied as usual. The resulting
+   default must be rounded if LADSPA_HINT_INTEGER is present. Default
+   values were introduced in LADSPA v1.1. */
+#define LADSPA_HINT_DEFAULT_MASK    0x3C0
+
+/* This default values indicates that no default is provided. */
+#define LADSPA_HINT_DEFAULT_NONE    0x0
+
+/* This default hint indicates that the suggested lower bound for the
+   port should be used. */
+#define LADSPA_HINT_DEFAULT_MINIMUM 0x40
+
+/* This default hint indicates that a low value between the suggested
+   lower and upper bounds should be chosen. For ports with
+   LADSPA_HINT_LOGARITHMIC, this should be exp(log(lower) * 0.75 +
+   log(upper) * 0.25). Otherwise, this should be (lower * 0.75 + upper
+   * 0.25). */
+#define LADSPA_HINT_DEFAULT_LOW     0x80
+
+/* This default hint indicates that a middle value between the
+   suggested lower and upper bounds should be chosen. For ports with
+   LADSPA_HINT_LOGARITHMIC, this should be exp(log(lower) * 0.5 +
+   log(upper) * 0.5). Otherwise, this should be (lower * 0.5 + upper *
+   0.5). */
+#define LADSPA_HINT_DEFAULT_MIDDLE  0xC0
+
+/* This default hint indicates that a high value between the suggested
+   lower and upper bounds should be chosen. For ports with
+   LADSPA_HINT_LOGARITHMIC, this should be exp(log(lower) * 0.25 +
+   log(upper) * 0.75). Otherwise, this should be (lower * 0.25 + upper
+   * 0.75). */
+#define LADSPA_HINT_DEFAULT_HIGH    0x100
+
+/* This default hint indicates that the suggested upper bound for the
+   port should be used. */
+#define LADSPA_HINT_DEFAULT_MAXIMUM 0x140
+
+/* This default hint indicates that the number 0 should be used. Note
+   that this default may be used in conjunction with
+   LADSPA_HINT_TOGGLED. */
+#define LADSPA_HINT_DEFAULT_0       0x200
+
+/* This default hint indicates that the number 1 should be used. Note
+   that this default may be used in conjunction with
+   LADSPA_HINT_TOGGLED. */
+#define LADSPA_HINT_DEFAULT_1       0x240
+
+/* This default hint indicates that the number 100 should be used. */
+#define LADSPA_HINT_DEFAULT_100     0x280
+
+/* This default hint indicates that the Hz frequency of `concert A'
+   should be used. This will be 440 unless the host uses an unusual
+   tuning convention, in which case it may be within a few Hz. */
+#define LADSPA_HINT_DEFAULT_440     0x2C0
+
+#define LADSPA_IS_HINT_BOUNDED_BELOW(x)   ((x) & LADSPA_HINT_BOUNDED_BELOW)
+#define LADSPA_IS_HINT_BOUNDED_ABOVE(x)   ((x) & LADSPA_HINT_BOUNDED_ABOVE)
+#define LADSPA_IS_HINT_TOGGLED(x)         ((x) & LADSPA_HINT_TOGGLED)
+#define LADSPA_IS_HINT_SAMPLE_RATE(x)     ((x) & LADSPA_HINT_SAMPLE_RATE)
+#define LADSPA_IS_HINT_LOGARITHMIC(x)     ((x) & LADSPA_HINT_LOGARITHMIC)
+#define LADSPA_IS_HINT_INTEGER(x)         ((x) & LADSPA_HINT_INTEGER)
+
+#define LADSPA_IS_HINT_HAS_DEFAULT(x)     ((x) & LADSPA_HINT_DEFAULT_MASK)
+#define LADSPA_IS_HINT_DEFAULT_MINIMUM(x) (((x) & LADSPA_HINT_DEFAULT_MASK)   \
+                                           == LADSPA_HINT_DEFAULT_MINIMUM)
+#define LADSPA_IS_HINT_DEFAULT_LOW(x)     (((x) & LADSPA_HINT_DEFAULT_MASK)   \
+                                           == LADSPA_HINT_DEFAULT_LOW)
+#define LADSPA_IS_HINT_DEFAULT_MIDDLE(x)  (((x) & LADSPA_HINT_DEFAULT_MASK)   \
+                                           == LADSPA_HINT_DEFAULT_MIDDLE)
+#define LADSPA_IS_HINT_DEFAULT_HIGH(x)    (((x) & LADSPA_HINT_DEFAULT_MASK)   \
+                                           == LADSPA_HINT_DEFAULT_HIGH)
+#define LADSPA_IS_HINT_DEFAULT_MAXIMUM(x) (((x) & LADSPA_HINT_DEFAULT_MASK)   \
+                                           == LADSPA_HINT_DEFAULT_MAXIMUM)
+#define LADSPA_IS_HINT_DEFAULT_0(x)       (((x) & LADSPA_HINT_DEFAULT_MASK)   \
+                                           == LADSPA_HINT_DEFAULT_0)
+#define LADSPA_IS_HINT_DEFAULT_1(x)       (((x) & LADSPA_HINT_DEFAULT_MASK)   \
+                                           == LADSPA_HINT_DEFAULT_1)
+#define LADSPA_IS_HINT_DEFAULT_100(x)     (((x) & LADSPA_HINT_DEFAULT_MASK)   \
+                                           == LADSPA_HINT_DEFAULT_100)
+#define LADSPA_IS_HINT_DEFAULT_440(x)     (((x) & LADSPA_HINT_DEFAULT_MASK)   \
+                                            == LADSPA_HINT_DEFAULT_440)
+
+typedef struct _LADSPA_PortRangeHint {
+
+  /* Hints about the port. */
+  LADSPA_PortRangeHintDescriptor HintDescriptor;
+
+  /* Meaningful when hint LADSPA_HINT_BOUNDED_BELOW is active. When
+     LADSPA_HINT_SAMPLE_RATE is also active then this value should be
+     multiplied by the relevant sample rate. */
+  LADSPA_Data LowerBound;
+
+  /* Meaningful when hint LADSPA_HINT_BOUNDED_ABOVE is active. When
+     LADSPA_HINT_SAMPLE_RATE is also active then this value should be
+     multiplied by the relevant sample rate. */
+  LADSPA_Data UpperBound;
+
+} LADSPA_PortRangeHint;
+
+/*****************************************************************************/
+
+/* Plugin Handles: 
+
+   This plugin handle indicates a particular instance of the plugin
+   concerned. It is valid to compare this to NULL (0 for C++) but
+   otherwise the host should not attempt to interpret it. The plugin
+   may use it to reference internal instance data. */
+
+typedef void * LADSPA_Handle;
+
+/*****************************************************************************/
+
+/* Descriptor for a Type of Plugin: 
+
+   This structure is used to describe a plugin type. It provides a
+   number of functions to examine the type, instantiate it, link it to
+   buffers and workspaces and to run it. */
+
+typedef struct _LADSPA_Descriptor { 
+
+  /* This numeric identifier indicates the plugin type
+     uniquely. Plugin programmers may reserve ranges of IDs from a
+     central body to avoid clashes. Hosts may assume that IDs are
+     below 0x1000000. */
+  unsigned long UniqueID;
+
+  /* This identifier can be used as a unique, case-sensitive
+     identifier for the plugin type within the plugin file. Plugin
+     types should be identified by file and label rather than by index
+     or plugin name, which may be changed in new plugin
+     versions. Labels must not contain white-space characters. */
+  const char * Label;
+
+  /* This indicates a number of properties of the plugin. */
+  LADSPA_Properties Properties;
+
+  /* This member points to the null-terminated name of the plugin
+     (e.g. "Sine Oscillator"). */
+  const char * Name;
+
+  /* This member points to the null-terminated string indicating the
+     maker of the plugin. This can be an empty string but not NULL. */
+  const char * Maker;
+
+  /* This member points to the null-terminated string indicating any
+     copyright applying to the plugin. If no Copyright applies the
+     string "None" should be used. */
+  const char * Copyright;
+
+  /* This indicates the number of ports (input AND output) present on
+     the plugin. */
+  unsigned long PortCount;
+
+  /* This member indicates an array of port descriptors. Valid indices
+     vary from 0 to PortCount-1. */
+  const LADSPA_PortDescriptor * PortDescriptors;
+
+  /* This member indicates an array of null-terminated strings
+     describing ports (e.g. "Frequency (Hz)"). Valid indices vary from
+     0 to PortCount-1. */
+  const char * const * PortNames;
+
+  /* This member indicates an array of range hints for each port (see
+     above). Valid indices vary from 0 to PortCount-1. */
+  const LADSPA_PortRangeHint * PortRangeHints;
+
+  /* This may be used by the plugin developer to pass any custom
+     implementation data into an instantiate call. It must not be used
+     or interpreted by the host. It is expected that most plugin
+     writers will not use this facility as LADSPA_Handle should be
+     used to hold instance data. */
+  void * ImplementationData;
+
+  /* This member is a function pointer that instantiates a plugin. A
+     handle is returned indicating the new plugin instance. The
+     instantiation function accepts a sample rate as a parameter. The
+     plugin descriptor from which this instantiate function was found
+     must also be passed. This function must return NULL if
+     instantiation fails. 
+
+     Note that instance initialisation should generally occur in
+     activate() rather than here. */
+  LADSPA_Handle (*instantiate)(const struct _LADSPA_Descriptor * Descriptor,
+                               unsigned long                     SampleRate);
+
+  /* This member is a function pointer that connects a port on an
+     instantiated plugin to a memory location at which a block of data
+     for the port will be read/written. The data location is expected
+     to be an array of LADSPA_Data for audio ports or a single
+     LADSPA_Data value for control ports. Memory issues will be
+     managed by the host. The plugin must read/write the data at these
+     locations every time run() or run_adding() is called and the data
+     present at the time of this connection call should not be
+     considered meaningful.
+
+     connect_port() may be called more than once for a plugin instance
+     to allow the host to change the buffers that the plugin is
+     reading or writing. These calls may be made before or after
+     activate() or deactivate() calls.
+
+     connect_port() must be called at least once for each port before
+     run() or run_adding() is called. When working with blocks of
+     LADSPA_Data the plugin should pay careful attention to the block
+     size passed to the run function as the block allocated may only
+     just be large enough to contain the block of samples.
+
+     Plugin writers should be aware that the host may elect to use the
+     same buffer for more than one port and even use the same buffer
+     for both input and output (see LADSPA_PROPERTY_INPLACE_BROKEN).
+     However, overlapped buffers or use of a single buffer for both
+     audio and control data may result in unexpected behaviour. */
+   void (*connect_port)(LADSPA_Handle Instance,
+                        unsigned long Port,
+                        LADSPA_Data * DataLocation);
+
+  /* This member is a function pointer that initialises a plugin
+     instance and activates it for use. This is separated from
+     instantiate() to aid real-time support and so that hosts can
+     reinitialise a plugin instance by calling deactivate() and then
+     activate(). In this case the plugin instance must reset all state
+     information dependent on the history of the plugin instance
+     except for any data locations provided by connect_port() and any
+     gain set by set_run_adding_gain(). If there is nothing for
+     activate() to do then the plugin writer may provide a NULL rather
+     than an empty function.
+
+     When present, hosts must call this function once before run() (or
+     run_adding()) is called for the first time. This call should be
+     made as close to the run() call as possible and indicates to
+     real-time plugins that they are now live. Plugins should not rely
+     on a prompt call to run() after activate(). activate() may not be
+     called again unless deactivate() is called first. Note that
+     connect_port() may be called before or after a call to
+     activate(). */
+  void (*activate)(LADSPA_Handle Instance);
+
+  /* This method is a function pointer that runs an instance of a
+     plugin for a block. Two parameters are required: the first is a
+     handle to the particular instance to be run and the second
+     indicates the block size (in samples) for which the plugin
+     instance may run.
+
+     Note that if an activate() function exists then it must be called
+     before run() or run_adding(). If deactivate() is called for a
+     plugin instance then the plugin instance may not be reused until
+     activate() has been called again.
+
+     If the plugin has the property LADSPA_PROPERTY_HARD_RT_CAPABLE
+     then there are various things that the plugin should not do
+     within the run() or run_adding() functions (see above). */
+  void (*run)(LADSPA_Handle Instance,
+              unsigned long SampleCount);
+
+  /* This method is a function pointer that runs an instance of a
+     plugin for a block. This has identical behaviour to run() except
+     in the way data is output from the plugin. When run() is used,
+     values are written directly to the memory areas associated with
+     the output ports. However when run_adding() is called, values
+     must be added to the values already present in the memory
+     areas. Furthermore, output values written must be scaled by the
+     current gain set by set_run_adding_gain() (see below) before
+     addition.
+
+     run_adding() is optional. When it is not provided by a plugin,
+     this function pointer must be set to NULL. When it is provided,
+     the function set_run_adding_gain() must be provided also. */
+  void (*run_adding)(LADSPA_Handle Instance,
+                     unsigned long SampleCount);
+
+  /* This method is a function pointer that sets the output gain for
+     use when run_adding() is called (see above). If this function is
+     never called the gain is assumed to default to 1. Gain
+     information should be retained when activate() or deactivate()
+     are called.
+
+     This function should be provided by the plugin if and only if the
+     run_adding() function is provided. When it is absent this
+     function pointer must be set to NULL. */
+  void (*set_run_adding_gain)(LADSPA_Handle Instance,
+                              LADSPA_Data   Gain);
+
+  /* This is the counterpart to activate() (see above). If there is
+     nothing for deactivate() to do then the plugin writer may provide
+     a NULL rather than an empty function.
+
+     Hosts must deactivate all activated units after they have been
+     run() (or run_adding()) for the last time. This call should be
+     made as close to the last run() call as possible and indicates to
+     real-time plugins that they are no longer live. Plugins should
+     not rely on prompt deactivation. Note that connect_port() may be
+     called before or after a call to deactivate().
+
+     Deactivation is not similar to pausing as the plugin instance
+     will be reinitialised when activate() is called to reuse it. */
+  void (*deactivate)(LADSPA_Handle Instance);
+
+  /* Once an instance of a plugin has been finished with it can be
+     deleted using the following function. The instance handle passed
+     ceases to be valid after this call.
+  
+     If activate() was called for a plugin instance then a
+     corresponding call to deactivate() must be made before cleanup()
+     is called. */
+  void (*cleanup)(LADSPA_Handle Instance);
+
+} LADSPA_Descriptor;
+
+/**********************************************************************/
+
+/* Accessing a Plugin: */
+
+/* The exact mechanism by which plugins are loaded is host-dependent,
+   however all most hosts will need to know is the name of shared
+   object file containing the plugin types. To allow multiple hosts to
+   share plugin types, hosts may wish to check for environment
+   variable LADSPA_PATH. If present, this should contain a
+   colon-separated path indicating directories that should be searched
+   (in order) when loading plugin types.
+
+   A plugin programmer must include a function called
+   "ladspa_descriptor" with the following function prototype within
+   the shared object file. This function will have C-style linkage (if
+   you are using C++ this is taken care of by the `extern "C"' clause
+   at the top of the file).
+
+   A host will find the plugin shared object file by one means or
+   another, find the ladspa_descriptor() function, call it, and
+   proceed from there.
+
+   Plugin types are accessed by index (not ID) using values from 0
+   upwards. Out of range indexes must result in this function
+   returning NULL, so the plugin count can be determined by checking
+   for the least index that results in NULL being returned. */
+
+const LADSPA_Descriptor * ladspa_descriptor(unsigned long Index);
+
+/* Datatype corresponding to the ladspa_descriptor() function. */
+typedef const LADSPA_Descriptor * 
+(*LADSPA_Descriptor_Function)(unsigned long Index);
+
+/**********************************************************************/
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LADSPA_INCLUDED */
+
+/* EOF */
diff --git a/src/pcm/mask.c b/src/pcm/mask.c
new file mode 100644
index 0000000..ebf1072
--- /dev/null
+++ b/src/pcm/mask.c
@@ -0,0 +1,28 @@
+/*
+ *  Mask functions
+ *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+#define SND_MASK_C
+#define SND_MASK_INLINE
+
+#include <sys/types.h>
+#include <limits.h>
+#include "pcm_local.h"
+
diff --git a/src/pcm/mask.h b/src/pcm/mask.h
new file mode 100644
index 0000000..ab80f20
--- /dev/null
+++ b/src/pcm/mask.h
@@ -0,0 +1,57 @@
+/*
+ *  Mask header
+ *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+typedef struct _snd_mask snd_mask_t;
+
+#define SND_MASK_MAX 64
+
+#ifdef SND_MASK_INLINE
+#include "mask_inline.h"
+#else
+void snd_mask_none(snd_mask_t *mask);
+void snd_mask_any(snd_mask_t *mask);
+void snd_mask_load(snd_mask_t *mask, unsigned int msk);
+int snd_mask_empty(const snd_mask_t *mask);
+int snd_mask_full(const snd_mask_t *mask);
+void snd_mask_set(snd_mask_t *mask, unsigned int val);
+void snd_mask_reset(snd_mask_t *mask, unsigned int val);
+void snd_mask_copy(snd_mask_t *mask, const snd_mask_t *v);
+int snd_mask_test(const snd_mask_t *mask, unsigned int val);
+void snd_mask_intersect(snd_mask_t *mask, const snd_mask_t *v);
+void snd_mask_union(snd_mask_t *mask, const snd_mask_t *v);
+unsigned int snd_mask_count(const snd_mask_t *mask);
+unsigned int snd_mask_min(const snd_mask_t *mask);
+unsigned int snd_mask_max(const snd_mask_t *mask);
+void snd_mask_set_range(snd_mask_t *mask, unsigned int from, unsigned int to);
+void snd_mask_reset_range(snd_mask_t *mask, unsigned int from, unsigned int to);
+void snd_mask_leave(snd_mask_t *mask, unsigned int val);
+int snd_mask_eq(const snd_mask_t *mask, const snd_mask_t *v);
+int snd_mask_single(const snd_mask_t *mask);
+int snd_mask_refine(snd_mask_t *mask, const snd_mask_t *v);
+int snd_mask_refine_first(snd_mask_t *mask);
+int snd_mask_refine_last(snd_mask_t *mask);
+int snd_mask_refine_min(snd_mask_t *mask, unsigned int val);
+int snd_mask_refine_max(snd_mask_t *mask, unsigned int val);
+int snd_mask_refine_set(snd_mask_t *mask, unsigned int val);
+int snd_mask_value(const snd_mask_t *mask);
+int snd_mask_always_eq(const snd_mask_t *m1, const snd_mask_t *m2);
+int snd_mask_never_eq(const snd_mask_t *m1, const snd_mask_t *m2);
+#endif
diff --git a/src/pcm/mask_inline.h b/src/pcm/mask_inline.h
new file mode 100644
index 0000000..f656568
--- /dev/null
+++ b/src/pcm/mask_inline.h
@@ -0,0 +1,299 @@
+/*
+ *  Mask inlines
+ *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+#include <sys/types.h>
+
+#define MASK_INLINE static inline
+
+#define MASK_MAX SND_MASK_MAX
+#define MASK_SIZE (MASK_MAX / 32)
+
+#define MASK_OFS(i)	((i) >> 5)
+#define MASK_BIT(i)	(1U << ((i) & 31))
+
+MASK_INLINE unsigned int ld2(u_int32_t v)
+{
+        unsigned r = 0;
+
+        if (v >= 0x10000) {
+                v >>= 16;
+                r += 16;
+        }
+        if (v >= 0x100) {
+                v >>= 8;
+                r += 8;
+        }
+        if (v >= 0x10) {
+                v >>= 4;
+                r += 4;
+        }
+        if (v >= 4) {
+                v >>= 2;
+                r += 2;
+        }
+        if (v >= 2)
+                r++;
+        return r;
+}
+
+MASK_INLINE unsigned int hweight32(u_int32_t v)
+{
+        v = (v & 0x55555555) + ((v >> 1) & 0x55555555);
+        v = (v & 0x33333333) + ((v >> 2) & 0x33333333);
+        v = (v & 0x0F0F0F0F) + ((v >> 4) & 0x0F0F0F0F);
+        v = (v & 0x00FF00FF) + ((v >> 8) & 0x00FF00FF);
+        return (v & 0x0000FFFF) + ((v >> 16) & 0x0000FFFF);
+}
+
+MASK_INLINE size_t snd_mask_sizeof(void)
+{
+	return sizeof(snd_mask_t);
+}
+
+MASK_INLINE void snd_mask_none(snd_mask_t *mask)
+{
+	memset(mask, 0, sizeof(*mask));
+}
+
+MASK_INLINE void snd_mask_any(snd_mask_t *mask)
+{
+	memset(mask, 0xff, MASK_SIZE * sizeof(u_int32_t));
+}
+
+MASK_INLINE int snd_mask_empty(const snd_mask_t *mask)
+{
+	int i;
+	for (i = 0; i < MASK_SIZE; i++)
+		if (mask->bits[i])
+			return 0;
+	return 1;
+}
+
+MASK_INLINE int snd_mask_full(const snd_mask_t *mask)
+{
+	int i;
+	for (i = 0; i < MASK_SIZE; i++)
+		if (mask->bits[i] != 0xffffffff)
+			return 0;
+	return 1;
+}
+
+MASK_INLINE unsigned int snd_mask_count(const snd_mask_t *mask)
+{
+	int i, w = 0;
+	for (i = 0; i < MASK_SIZE; i++)
+		w += hweight32(mask->bits[i]);
+	return w;
+}
+
+MASK_INLINE unsigned int snd_mask_min(const snd_mask_t *mask)
+{
+	int i;
+	assert(!snd_mask_empty(mask));
+	for (i = 0; i < MASK_SIZE; i++) {
+		if (mask->bits[i])
+			return ffs(mask->bits[i]) - 1 + (i << 5);
+	}
+	return 0;
+}
+
+MASK_INLINE unsigned int snd_mask_max(const snd_mask_t *mask)
+{
+	int i;
+	assert(!snd_mask_empty(mask));
+	for (i = MASK_SIZE - 1; i >= 0; i--) {
+		if (mask->bits[i])
+			return ld2(mask->bits[i]) + (i << 5);
+	}
+	return 0;
+}
+
+MASK_INLINE void snd_mask_set(snd_mask_t *mask, unsigned int val)
+{
+	assert(val <= SND_MASK_MAX);
+	mask->bits[MASK_OFS(val)] |= MASK_BIT(val);
+}
+
+MASK_INLINE void snd_mask_reset(snd_mask_t *mask, unsigned int val)
+{
+	assert(val <= SND_MASK_MAX);
+	mask->bits[MASK_OFS(val)] &= ~MASK_BIT(val);
+}
+
+MASK_INLINE void snd_mask_set_range(snd_mask_t *mask, unsigned int from, unsigned int to)
+{
+	unsigned int i;
+	assert(to <= SND_MASK_MAX && from <= to);
+	for (i = from; i <= to; i++)
+		mask->bits[MASK_OFS(i)] |= MASK_BIT(i);
+}
+
+MASK_INLINE void snd_mask_reset_range(snd_mask_t *mask, unsigned int from, unsigned int to)
+{
+	unsigned int i;
+	assert(to <= SND_MASK_MAX && from <= to);
+	for (i = from; i <= to; i++)
+		mask->bits[MASK_OFS(i)] &= ~MASK_BIT(i);
+}
+
+MASK_INLINE void snd_mask_leave(snd_mask_t *mask, unsigned int val)
+{
+	unsigned int v;
+	assert(val <= SND_MASK_MAX);
+	v = mask->bits[MASK_OFS(val)] & MASK_BIT(val);
+	snd_mask_none(mask);
+	mask->bits[MASK_OFS(val)] = v;
+}
+
+MASK_INLINE void snd_mask_intersect(snd_mask_t *mask, const snd_mask_t *v)
+{
+	int i;
+	for (i = 0; i < MASK_SIZE; i++)
+		mask->bits[i] &= v->bits[i];
+}
+
+MASK_INLINE void snd_mask_union(snd_mask_t *mask, const snd_mask_t *v)
+{
+	int i;
+	for (i = 0; i < MASK_SIZE; i++)
+		mask->bits[i] |= v->bits[i];
+}
+
+MASK_INLINE int snd_mask_eq(const snd_mask_t *mask, const snd_mask_t *v)
+{
+	return ! memcmp(mask, v, MASK_SIZE * 4);
+}
+
+MASK_INLINE void snd_mask_copy(snd_mask_t *mask, const snd_mask_t *v)
+{
+	*mask = *v;
+}
+
+MASK_INLINE int snd_mask_test(const snd_mask_t *mask, unsigned int val)
+{
+	assert(val <= SND_MASK_MAX);
+	return mask->bits[MASK_OFS(val)] & MASK_BIT(val);
+}
+
+MASK_INLINE int snd_mask_single(const snd_mask_t *mask)
+{
+	int i, c = 0;
+	assert(!snd_mask_empty(mask));
+	for (i = 0; i < MASK_SIZE; i++) {
+		if (! mask->bits[i])
+			continue;
+		if (mask->bits[i] & (mask->bits[i] - 1))
+			return 0;
+		if (c)
+			return 0;
+		c++;
+	}
+	return 1;
+}
+
+MASK_INLINE int snd_mask_refine(snd_mask_t *mask, const snd_mask_t *v)
+{
+	snd_mask_t old;
+	if (snd_mask_empty(mask))
+		return -ENOENT;
+	snd_mask_copy(&old, mask);
+	snd_mask_intersect(mask, v);
+	if (snd_mask_empty(mask))
+		return -EINVAL;
+	return !snd_mask_eq(mask, &old);
+}
+
+MASK_INLINE int snd_mask_refine_first(snd_mask_t *mask)
+{
+	if (snd_mask_empty(mask))
+		return -ENOENT;
+	if (snd_mask_single(mask))
+		return 0;
+	snd_mask_leave(mask, snd_mask_min(mask));
+	return 1;
+}
+
+MASK_INLINE int snd_mask_refine_last(snd_mask_t *mask)
+{
+	if (snd_mask_empty(mask))
+		return -ENOENT;
+	if (snd_mask_single(mask))
+		return 0;
+	snd_mask_leave(mask, snd_mask_max(mask));
+	return 1;
+}
+
+MASK_INLINE int snd_mask_refine_min(snd_mask_t *mask, unsigned int val)
+{
+	if (snd_mask_empty(mask))
+		return -ENOENT;
+	if (snd_mask_min(mask) >= val)
+		return 0;
+	snd_mask_reset_range(mask, 0, val - 1);
+	if (snd_mask_empty(mask))
+		return -EINVAL;
+	return 1;
+}
+
+MASK_INLINE int snd_mask_refine_max(snd_mask_t *mask, unsigned int val)
+{
+	if (snd_mask_empty(mask))
+		return -ENOENT;
+	if (snd_mask_max(mask) <= val)
+		return 0;
+	snd_mask_reset_range(mask, val + 1, SND_MASK_MAX);
+	if (snd_mask_empty(mask))
+		return -EINVAL;
+	return 1;
+}
+
+MASK_INLINE int snd_mask_refine_set(snd_mask_t *mask, unsigned int val)
+{
+	int changed;
+	if (snd_mask_empty(mask))
+		return -ENOENT;
+	changed = !snd_mask_single(mask);
+	snd_mask_leave(mask, val);
+	if (snd_mask_empty(mask))
+		return -EINVAL;
+	return changed;
+}
+
+MASK_INLINE int snd_mask_value(const snd_mask_t *mask)
+{
+	assert(!snd_mask_empty(mask));
+	return snd_mask_min(mask);
+}
+
+MASK_INLINE int snd_mask_always_eq(const snd_mask_t *m1, const snd_mask_t *m2)
+{
+	return snd_mask_single(m1) && snd_mask_single(m2) &&
+		snd_mask_value(m1) == snd_mask_value(m2);
+}
+
+MASK_INLINE int snd_mask_never_eq(const snd_mask_t *m1, const snd_mask_t *m2)
+{
+	int i;
+	for (i = 0; i < MASK_SIZE; i++)
+		if (m1->bits[i] & m2->bits[i])
+			return 0;
+	return 1;
+}
diff --git a/src/pcm/pcm.c b/src/pcm/pcm.c
new file mode 100644
index 0000000..ea1afdc
--- /dev/null
+++ b/src/pcm/pcm.c
@@ -0,0 +1,7547 @@
+/**
+ * \file pcm/pcm.c
+ * \ingroup PCM
+ * \brief PCM Interface
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \date 2000-2001
+ *
+ * PCM Interface is designed to write or read digital audio frames. A
+ * frame is the data unit converted into/from sound in one time unit
+ * (1/rate seconds), by example if you set your playback PCM rate to
+ * 44100 you'll hear 44100 frames per second. The size in bytes of a
+ * frame may be obtained from bits needed to store a sample and
+ * channels count.
+ *
+ * See the \ref pcm page for more details.
+ */
+/*
+ *  PCM Interface - main file
+ *  Copyright (c) 1998 by Jaroslav Kysela <perex@perex.cz>
+ *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+/*! \page pcm PCM (digital audio) interface
+
+<P>Although abbreviation PCM stands for Pulse Code Modulation, we are
+understanding it as general digital audio processing with volume samples
+generated in continuous time periods.</P>
+
+<P>The analog signal is recorded via analog to digital converters (ADC).
+The digital value (de-facto a volume at a specific time) obtained
+from ADC can be further processed. The following picture shows a perfect
+sinus waveform:</P>
+
+<BR>
+\image html wave1.gif
+
+<P>Next image shows digitized representation:</P>
+
+<BR>
+\image html wave2.gif
+
+<P>As you may see, the quality of digital audio signal depends on the time
+(recording rate) and voltage resolution (usually in an linear integer
+representation with basic unit one bit).</P>
+
+<P>The stored digital signal can be converted back to voltage (analog)
+representation via digital to analog converters (DAC).</P>
+
+<P>One digital value is called sample. More samples are collected to frames
+(frame is terminology for ALSA) depending on count of converters used at one
+specific time. One frame might contain one sample (when only one converter is
+used - mono) or more samples (for example: stereo has signals from two converters
+recorded at same time). Digital audio stream contains collection of frames
+recorded at boundaries of continuous time periods.</P>
+
+\section pcm_general_overview General overview
+
+ALSA uses the ring buffer to store outgoing (playback) and incoming (capture,
+record) samples. There are two pointers being maintained to allow
+a precise communication between application and device pointing to current
+processed sample by hardware and last processed sample by application.
+The modern audio chips allow to program the transfer time periods.
+It means that the stream of samples is divided to small chunks. Device
+acknowledges to application when the transfer of a chunk is complete.
+
+\section pcm_transfer Transfer methods in UNIX environments
+
+In the UNIX environment, data chunk acknowledges are received via standard I/O
+calls or event waiting routines (poll or select function). To accomplish
+this list, the asynchronous notification of acknowledges should be listed
+here. The ALSA implementation for these methods is described in
+the \ref alsa_transfers section.
+
+\subsection pcm_transfer_io Standard I/O transfers
+
+The standard I/O transfers are using the read (see 'man 2 read') and write
+(see 'man 2 write') C functions. There are two basic behaviours of these
+functions - blocked and non-blocked (see the O_NONBLOCK flag for the
+standard C open function - see 'man 2 open'). In non-blocked behaviour,
+these I/O functions never stops, they return -EAGAIN error code, when no
+data can be transferred (the ring buffer is full in our case). In blocked
+behaviour, these I/O functions stop and wait until there is a room in the
+ring buffer (playback) or until there are a new samples (capture). The ALSA
+implementation can be found in the \ref alsa_pcm_rw section.
+
+\subsection pcm_transfer_event Event waiting routines
+
+The poll or select functions (see 'man 2 poll' or 'man 2 select' for further
+details) allows to receive requests/events from the device while
+an application is waiting on events from other sources (like keyboard, screen,
+network etc.), too. \ref snd_pcm_poll_descriptors can be used to get file
+descriptors to poll or select on (note that wait direction might be diferent
+than expected - do not use only returned file descriptors, but handle
+events member as well - see \ref snd_pcm_poll_descriptors function
+description for more details and \ref snd_pcm_poll_descriptors_revents for
+events demangling). The implemented transfer routines can be found in
+the \ref alsa_transfers section.
+
+\subsection pcm_transfer_async Asynchronous notification
+
+ALSA driver and library knows to handle the asynchronous notifications over
+the SIGIO signal. This signal allows to interrupt application and transfer
+data in the signal handler. For further details see the sigaction function
+('man 2 sigaction'). The section \ref pcm_async describes the ALSA API for
+this extension. The implemented transfer routines can be found in the
+\ref alsa_transfers section.
+
+\section pcm_open_behaviour Blocked and non-blocked open
+
+The ALSA PCM API uses a different behaviour when the device is opened
+with blocked or non-blocked mode. The mode can be specified with
+\a mode argument in #snd_pcm_open() function.
+The blocked mode is the default (without #SND_PCM_NONBLOCK mode).
+In this mode, the behaviour is that if the resources have already used
+with another application, then it blocks the caller, until resources are
+free. The non-blocked behaviour (with #SND_PCM_NONBLOCK)
+doesn't block the caller in any way and returns -EBUSY error when the
+resources are not available. Note that the mode also determines the
+behaviour of standard I/O calls, returning -EAGAIN when non-blocked mode is
+used and the ring buffer is full (playback) or empty (capture).
+The operation mode for I/O calls can be changed later with
+the #snd_pcm_nonblock() function.
+
+\section pcm_async Asynchronous mode
+
+There is also possibility to receive asynchronous notification after
+specified time periods. You may see the #SND_PCM_ASYNC
+mode for #snd_pcm_open() function and
+#snd_async_add_pcm_handler() function for further details.
+
+\section pcm_handshake Handshake between application and library
+
+The ALSA PCM API design uses the states to determine the communication
+phase between application and library. The actual state can be determined
+using #snd_pcm_state() call. There are these states:
+
+\par SND_PCM_STATE_OPEN
+The PCM device is in the open state. After the #snd_pcm_open() open call,
+the device is in this state. Also, when #snd_pcm_hw_params() call fails,
+then this state is entered to force application calling 
+#snd_pcm_hw_params() function to set right communication
+parameters.
+
+\par SND_PCM_STATE_SETUP
+The PCM device has accepted communication parameters and it is waiting
+for #snd_pcm_prepare() call to prepare the hardware for
+selected operation (playback or capture).
+
+\par SND_PCM_STATE_PREPARE
+The PCM device is prepared for operation. Application can use
+#snd_pcm_start() call, write or read data to start
+the operation.
+
+\par SND_PCM_STATE_RUNNING
+The PCM device has been started and is running. It processes the samples. The stream can
+be stopped using the #snd_pcm_drop() or
+#snd_pcm_drain() calls.
+
+\par SND_PCM_STATE_XRUN
+The PCM device reached overrun (capture) or underrun (playback).
+You can use the -EPIPE return code from I/O functions
+(#snd_pcm_writei(), #snd_pcm_writen(), #snd_pcm_readi(), #snd_pcm_readn())
+to determine this state without checking
+the actual state via #snd_pcm_state() call. It is recommended to use
+the helper function #snd_pcm_recover() to recover from this state, but you can also use #snd_pcm_prepare(),
+#snd_pcm_drop() or #snd_pcm_drain() calls.
+
+\par SND_PCM_STATE_DRAINING
+The device is in this state when application using the capture mode
+called #snd_pcm_drain() function. Until all data are
+read from the internal ring buffer using I/O routines
+(#snd_pcm_readi(), #snd_pcm_readn()),
+then the device stays in this state.
+
+\par SND_PCM_STATE_PAUSED
+The device is in this state when application called
+the #snd_pcm_pause() function until the pause is released.
+Not all hardware supports this feature. Application should check the
+capability with the #snd_pcm_hw_params_can_pause().
+
+\par SND_PCM_STATE_SUSPENDED
+The device is in the suspend state provoked with the power management
+system. The stream can be resumed using #snd_pcm_resume()
+call, but not all hardware supports this feature. Application should check
+the capability with the #snd_pcm_hw_params_can_resume().
+In other case, the calls #snd_pcm_prepare(),
+#snd_pcm_drop(), #snd_pcm_drain() can be used
+to leave this state.
+
+\par SND_PCM_STATE_DISCONNECTED
+The device is physicaly disconnected. It does not accept any I/O calls in this state.
+
+\section pcm_formats PCM formats
+
+The full list of formats present the #snd_pcm_format_t type.
+The 24-bit linear samples uses 32-bit physical space, but the sample is
+stored in low three bits. Some hardware does not support processing of full
+range, thus you may get the significant bits for linear samples via
+#snd_pcm_hw_params_get_sbits() function. The example: ICE1712
+chips support 32-bit sample processing, but low byte is ignored (playback)
+or zero (capture). The function snd_pcm_hw_params_get_sbits()
+returns 24 in the case.
+
+\section alsa_transfers ALSA transfers
+
+There are two methods to transfer samples in application. The first method
+is the standard read / write one. The second method, uses the direct audio
+buffer to communicate with the device while ALSA library manages this space
+itself. You can find examples of all communication schemes for playback
+in \ref example_test_pcm "Sine-wave generator example". To complete the
+list, we should note that #snd_pcm_wait() function contains
+embedded poll waiting implementation.
+
+\subsection alsa_pcm_rw Read / Write transfer
+
+There are two versions of read / write routines. The first expects the
+interleaved samples at input (#SND_PCM_ACCESS_RW_INTERLEAVED access method),
+and the second one expects non-interleaved (samples in separated buffers -
+#SND_PCM_ACCESS_RW_NONINTERLEAVED access method) at input. There are these
+functions for interleaved transfers: #snd_pcm_writei()
+#snd_pcm_readi(). For non-interleaved transfers, there are
+these functions: #snd_pcm_writen() and #snd_pcm_readn().
+
+\subsection alsa_mmap_rw Direct Read / Write transfer (via mmap'ed areas)
+
+Three kinds of organization of ring buffer memory areas exist in ALSA API.
+Access #SND_PCM_ACCESS_MMAP_INTERLEAVED has interleaved samples. Access
+#SND_PCM_ACCESS_MMAP_NONINTERLEAVED expects continous sample areas for
+one channel. Access #SND_PCM_ACCESS_MMAP_COMPLEX does not fit to interleaved
+and non-interleaved ring buffer organization.
+
+There are two functions for this kind of transfer. Application can get an
+access to memory areas via #snd_pcm_mmap_begin() function.
+This function returns the areas (single area is equal to a channel)
+containing the direct pointers to memory and sample position description
+in #snd_pcm_channel_area_t structure. After application
+transfers the data in the memory areas, then it must be acknowledged
+the end of transfer via #snd_pcm_mmap_commit() function
+to allow the ALSA library update the pointers to ring buffer. This kind of
+communication is also called "zero-copy", because the device does not require
+to copy the samples from application to another place in system memory.
+
+If you like to use the compatibility functions in mmap mode, there are
+read / write routines equaling to standard read / write transfers. Using
+these functions discards the benefits of direct access to memory region.
+See the #snd_pcm_mmap_readi(),
+#snd_pcm_writei(), #snd_pcm_readn()
+and #snd_pcm_writen() functions.
+
+\section pcm_errors Error codes
+
+\par -EPIPE
+
+This error means xrun (underrun for playback or overrun for capture).
+The underrun can happen when an application does not feed new samples
+in time to alsa-lib (due CPU usage). The overrun can happen when
+an application does not take new captured samples in time from alsa-lib.
+
+\par -ESTRPIPE
+
+This error means that system has suspended drivers. The application
+should wait in loop when snd_pcm_resume() != -EAGAIN and then
+call snd_pcm_prepare() when snd_pcm_resume() return an error code.
+If snd_pcm_resume() does not fail (a zero value is returned), driver
+supports resume and the snd_pcm_prepare() call can be ommited.
+
+\par -EBADFD
+
+This error means that the device is in a bad state. It means that
+the handskahe between application and alsa-lib is corrupted.
+
+\par -ENOTTY, -ENODEV
+
+This error can happen when device is physically removed (for example
+some hotplug devices like USB or PCMCIA, CardBus or ExpressCard
+can be removed on the fly).
+
+\section pcm_params Managing parameters
+
+The ALSA PCM device uses two groups of PCM related parameters. The hardware
+parameters contains the stream description like format, rate, count of
+channels, ring buffer size etc. The software parameters contains the
+software (driver) related parameters. The communication behaviour can be
+controlled via these parameters, like automatic start, automatic stop,
+interrupting (chunk acknowledge) etc. The software parameters can be
+modified at any time (when valid hardware parameters are set). It includes
+the running state as well.
+
+\subsection pcm_hw_params Hardware related parameters
+
+The ALSA PCM devices use the parameter refining system for hardware
+parameters - #snd_pcm_hw_params_t. It means, that
+application choose the full-range of configurations at first and then
+application sets single parameters until all parameters are elementary
+(definite).
+
+\par Access modes
+
+ALSA knows about five access modes. The first three can be used for direct
+communication. The access mode #SND_PCM_ACCESS_MMAP_INTERLEAVED
+determines the direct memory area and interleaved sample organization.
+Interleaved organization means, that samples from channels are mixed together.
+The access mode #SND_PCM_ACCESS_MMAP_NONINTERLEAVED
+determines the direct memory area and non-interleaved sample organization.
+Each channel has a separate buffer in the case. The complex direct memory
+organization represents the #SND_PCM_ACCESS_MMAP_COMPLEX
+access mode. The sample organization does not fit the interleaved or
+non-interleaved access modes in the case. The last two access modes
+describes the read / write access methods.
+The #SND_PCM_ACCESS_RW_INTERLEAVED access represents the read /
+write interleaved access and the #SND_PCM_ACCESS_RW_NONINTERLEAVED
+represents the non-interleaved access.
+
+\par Formats
+
+The full list of formats is available in #snd_pcm_format_t
+enumeration.
+
+\subsection pcm_sw_params Software related parameters
+
+These parameters - #snd_pcm_sw_params_t can be modified at
+any time including the running state.
+
+\par Minimum available count of samples
+
+This parameter controls the wakeup point. If the count of available samples
+is equal or greater than this value, then application will be activated.
+
+\par Timestamp mode
+
+The timestamp mode specifies, if timestamps are activated. Currently, only
+#SND_PCM_TSTAMP_NONE and #SND_PCM_TSTAMP_MMAP
+modes are known. The mmap mode means that timestamp is taken
+on every period time boundary. Corresponding position in the ring buffer
+assigned to timestamp can be obtained using #snd_pcm_htimestamp() function.
+
+\par Transfer align
+
+The read / write transfers can be aligned to this sample count. The modulo
+is ignored by device. Usually, this value is set to one (no align).
+
+\par Start threshold
+
+The start threshold parameter is used to determine the start point in
+stream. For playback, if samples in ring buffer is equal or greater than
+the start threshold parameters and the stream is not running, the stream will
+be started automatically from the device. For capture, if the application wants
+to read count of samples equal or greater then the stream will be started.
+If you want to use explicit start (#snd_pcm_start), you can
+set this value greater than ring buffer size (in samples), but use the
+constant MAXINT is not a bad idea.
+
+\par Stop threshold
+
+Similarly, the stop threshold parameter is used to automatically stop
+the running stream, when the available samples crosses this boundary.
+It means, for playback, the empty samples in ring buffer and for capture,
+the filled (used) samples in ring buffer.
+
+\par Silence threshold
+
+The silence threshold specifies count of samples filled with silence
+ahead of the current application pointer for playback. It is usable
+for applications when an overrun is possible (like tasks depending on
+network I/O etc.). If application wants to manage the ahead samples itself,
+the #snd_pcm_rewind() function allows to forget the last
+samples in the stream.
+
+\section pcm_status Obtaining stream status
+
+The stream status is stored in #snd_pcm_status_t structure.
+These parameters can be obtained: the current stream state -
+#snd_pcm_status_get_state(), timestamp of trigger -
+#snd_pcm_status_get_trigger_tstamp(), timestamp of last
+pointer update #snd_pcm_status_get_tstamp(), delay in samples -
+#snd_pcm_status_get_delay(), available count in samples -
+#snd_pcm_status_get_avail(), maximum available samples -
+#snd_pcm_status_get_avail_max(), ADC over-range count in
+samples - #snd_pcm_status_get_overrange(). The last two
+parameters - avail_max and overrange are reset to zero after the status
+call.
+
+\subsection pcm_status_fast Obtaining stream state fast and update r/w pointer
+
+<p>
+The function #snd_pcm_avail_update() updates the current
+available count of samples for writing (playback) or filled samples for
+reading (capture). This call is mandatory for updating actual r/w pointer.
+Using standalone, it is a light method to obtain current stream position,
+because it does not require the user <-> kernel context switch, but the value
+is less accurate, because ring buffer pointers are updated in kernel drivers
+only when an interrupt occurs. If you want to get accurate stream state,
+use functions #snd_pcm_avail(), #snd_pcm_delay() or #snd_pcm_avail_delay().
+</p>
+<p>
+The function #snd_pcm_avail() reads the current hardware pointer
+in the ring buffer from hardware and calls #snd_pcm_avail_update() then.
+</p>
+<p>
+The function #snd_pcm_delay() returns the delay in samples.
+For playback, it means count of samples in the ring buffer before
+the next sample will be sent to DAC. For capture, it means count of samples
+in the ring buffer before the next sample will be captured from ADC. It works
+only when the stream is in the running or draining (playback only) state.
+Note that this function does not update the current r/w pointer for applications,
+so the function #snd_pcm_avail_update() must be called afterwards
+before any read/write begin+commit operations.
+</p>
+<p>
+The function #snd_pcm_avail_delay() combines #snd_pcm_avail() and
+#snd_pcm_delay() and returns both values in sync.
+</p>
+
+\section pcm_action Managing the stream state
+
+The following functions directly and indirectly affect the stream state:
+
+\par snd_pcm_hw_params
+The #snd_pcm_hw_params() function brings the stream state
+to #SND_PCM_STATE_SETUP
+if successfully finishes, otherwise the state #SND_PCM_STATE_OPEN
+is entered.
+When it is brought to SETUP state, this function automatically
+calls #snd_pcm_prepare() function to bring to the PREPARE state
+as below.
+
+\par snd_pcm_prepare
+The #snd_pcm_prepare() function enters from #SND_PCM_STATE_SETUP
+to the #SND_PCM_STATE_PREPARED after a successful finish.
+
+\par snd_pcm_start
+The #snd_pcm_start() function enters
+the #SND_PCM_STATE_RUNNING after a successful finish.
+
+\par snd_pcm_drop
+The #snd_pcm_drop() function enters the
+#SND_PCM_STATE_SETUP state.
+
+\par snd_pcm_drain
+The #snd_pcm_drain() function enters the
+#SND_PCM_STATE_DRAINING, if
+the capture device has some samples in the ring buffer otherwise
+#SND_PCM_STATE_SETUP state is entered.
+
+\par snd_pcm_pause
+The #snd_pcm_pause() function enters the
+#SND_PCM_STATE_PAUSED or #SND_PCM_STATE_RUNNING.
+
+\par snd_pcm_writei, snd_pcm_writen
+The #snd_pcm_writei() and #snd_pcm_writen()
+functions can conditionally start the stream -
+#SND_PCM_STATE_RUNNING. They depend on the start threshold
+software parameter.
+
+\par snd_pcm_readi, snd_pcm_readn
+The #snd_pcm_readi() and #snd_pcm_readn()
+functions can conditionally start the stream -
+#SND_PCM_STATE_RUNNING. They depend on the start threshold
+software parameter.
+
+\section pcm_sync Streams synchronization
+
+There are two functions allowing link multiple streams together. In the
+case, the linking means that all operations are synchronized. Because the
+drivers cannot guarantee the synchronization (sample resolution) on hardware
+lacking this feature, the #snd_pcm_info_get_sync() function
+returns synchronization ID - #snd_pcm_sync_id_t, which is equal
+for hardware synchronized streams. When the #snd_pcm_link()
+function is called, all operations managing the stream state for these two
+streams are joined. The opposite function is #snd_pcm_unlink().
+
+\section pcm_dev_names PCM naming conventions
+
+The ALSA library uses a generic string representation for names of devices.
+The devices might be virtual, physical or a mix of both. The generic string
+is passed to #snd_pcm_open() or #snd_pcm_open_lconf().
+It contains two parts: device name and arguments. Devices and arguments are described
+in configuration files. The usual place for default definitions is at /usr/share/alsa/alsa.conf.
+For detailed descriptions about integrated PCM plugins look to \ref pcm_plugins.
+
+\subsection pcm_dev_names_default Default device
+
+The default device is equal to plug plugin with hw plugin as slave. The defaults are
+used:
+
+\code
+defaults.pcm.card 0
+defaults.pcm.device 0
+defaults.pcm.subdevice -1
+\endcode
+
+These defaults can be freely overwritten in local configuration files.
+
+Example:
+
+\code
+default
+\endcode
+
+\subsection pcm_dev_names_hw HW device
+
+The hw device description uses the hw plugin. The three arguments (in order: CARD,DEV,SUBDEV)
+specify card number or identifier, device number and subdevice number (-1 means any).
+
+Example:
+
+\code
+hw
+hw:0
+hw:0,0
+hw:supersonic,1
+hw:soundwave,1,2
+hw:DEV=1,CARD=soundwave,SUBDEV=2
+\endcode
+
+\subsection pcm_dev_names_plughw Plug->HW device
+
+The plughw device description uses the plug plugin and hw plugin as slave. The arguments
+are same as for hw device.
+
+Example:
+
+\code
+plughw
+plughw:0
+plughw:0,0
+plughw:supersonic,1
+plughw:soundwave,1,2
+plughw:DEV=1,CARD=soundwave,SUBDEV=2
+\endcode
+
+\subsection pcm_dev_names_plug Plug device
+
+The plug device uses the plug plugin. The one SLAVE argument specifies the slave plugin.
+
+Example:
+
+\code
+plug:mypcmdef
+plug:hw
+plug:'hw:0,0'
+plug:SLAVE=hw
+\endcode
+
+\subsection pcm_dev_names_shm Shared memory device
+
+The shm device uses the shm plugin. The two arguments (in order: SOCKET,PCM) specify
+UNIX socket name (for example /tmp/alsa.socket) for server communication and server's PCM name.
+
+Example:
+
+\code
+shm:'/tmp/alsa.sock',default
+shm:SOCKET='/tmp/alsa.sock',PCM=default
+\endcode
+
+\subsection pcm_dev_names_tee Tee device
+
+The tee device stores contents of a stream to given file plus transfers it to given slave plugin.
+The three arguments (in order: SLAVE,FILE,FORMAT) specify slave plugin, filename and file format.
+
+Example:
+
+\code
+tee:hw,'/tmp/out.raw',raw
+\endcode
+
+\subsection pcm_dev_names_file File device
+
+The file device is file plugin with null plugin as slave. The arguments (in order: FILE,FORMAT)
+specify filename and file format.
+
+Example:
+
+\code
+file:'/tmp/out.raw',raw
+\endcode
+
+\subsection pcm_dev_names_null Null device
+
+The null device is null plugin. This device has not any arguments.
+
+
+\section pcm_examples Examples
+
+The full featured examples with cross-links can be found in Examples section
+(see top of page):
+
+\anchor example_test_pcm
+\par Sine-wave generator
+\par
+alsa-lib/test/pcm.c example shows various transfer methods for the playback direction.
+
+\par Minimalistic PCM playback code
+\par
+alsa-lib/test/pcm_min.c example shows the minimal code to produce a sound.
+
+\par Latency measuring tool
+\par
+alsa-lib/test/latency.c example shows the measuring of minimal latency between capture and
+playback devices.
+
+*/
+
+/**
+\example ../../test/pcm.c
+*/
+/**
+\example ../../test/pcm_min.c
+*/
+/**
+\example ../../test/latency.c
+*/
+
+#include <stdio.h>
+#include <string.h>
+#include <malloc.h>
+#include <stdarg.h>
+#include <signal.h>
+#include <sys/poll.h>
+#include <sys/shm.h>
+#include <sys/mman.h>
+#include <limits.h>
+#include "pcm_local.h"
+
+/**
+ * \brief get identifier of PCM handle
+ * \param pcm PCM handle
+ * \return ascii identifier of PCM handle
+ *
+ * Returns the ASCII identifier of given PCM handle. It's the same
+ * identifier specified in snd_pcm_open().
+ */
+const char *snd_pcm_name(snd_pcm_t *pcm)
+{
+	assert(pcm);
+	return pcm->name;
+}
+
+/**
+ * \brief get type of PCM handle
+ * \param pcm PCM handle
+ * \return type of PCM handle
+ *
+ * Returns the type #snd_pcm_type_t of given PCM handle.
+ */
+snd_pcm_type_t snd_pcm_type(snd_pcm_t *pcm)
+{
+	assert(pcm);
+	return pcm->type;
+}
+
+/**
+ * \brief get stream for a PCM handle
+ * \param pcm PCM handle
+ * \return stream of PCM handle
+ *
+ * Returns the type #snd_pcm_stream_t of given PCM handle.
+ */
+snd_pcm_stream_t snd_pcm_stream(snd_pcm_t *pcm)
+{
+	assert(pcm);
+	return pcm->stream;
+}
+
+/**
+ * \brief close PCM handle
+ * \param pcm PCM handle
+ * \return 0 on success otherwise a negative error code
+ *
+ * Closes the specified PCM handle and frees all associated
+ * resources.
+ */
+int snd_pcm_close(snd_pcm_t *pcm)
+{
+	int res = 0, err;
+	assert(pcm);
+	if (pcm->setup && !pcm->donot_close) {
+		snd_pcm_drop(pcm);
+		err = snd_pcm_hw_free(pcm);
+		if (err < 0)
+			res = err;
+	}
+	if (pcm->mmap_channels)
+		snd_pcm_munmap(pcm);
+	while (!list_empty(&pcm->async_handlers)) {
+		snd_async_handler_t *h = list_entry(pcm->async_handlers.next, snd_async_handler_t, hlist);
+		snd_async_del_handler(h);
+	}
+	err = pcm->ops->close(pcm->op_arg);
+	if (err < 0)
+		res = err;
+	err = snd_pcm_free(pcm);
+	if (err < 0)
+		res = err;
+	return res;
+}	
+
+/**
+ * \brief set nonblock mode
+ * \param pcm PCM handle
+ * \param nonblock 0 = block, 1 = nonblock mode
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_pcm_nonblock(snd_pcm_t *pcm, int nonblock)
+{
+	int err;
+	assert(pcm);
+	if ((err = pcm->ops->nonblock(pcm->op_arg, nonblock)) < 0)
+		return err;
+	if (nonblock)
+		pcm->mode |= SND_PCM_NONBLOCK;
+	else {
+		if (pcm->hw_flags & SND_PCM_HW_PARAMS_NO_PERIOD_WAKEUP)
+			return -EINVAL;
+		pcm->mode &= ~SND_PCM_NONBLOCK;
+	}
+	return 0;
+}
+
+#ifndef DOC_HIDDEN
+/**
+ * \brief set async mode
+ * \param pcm PCM handle
+ * \param sig Signal to raise: < 0 disable, 0 default (SIGIO)
+ * \param pid Process ID to signal: 0 current
+ * \return 0 on success otherwise a negative error code
+ *
+ * A signal is raised every period.
+ */
+int snd_pcm_async(snd_pcm_t *pcm, int sig, pid_t pid)
+{
+	assert(pcm);
+	if (sig == 0)
+		sig = SIGIO;
+	if (pid == 0)
+		pid = getpid();
+	return pcm->ops->async(pcm->op_arg, sig, pid);
+}
+#endif
+
+/**
+ * \brief Obtain general (static) information for PCM handle
+ * \param pcm PCM handle
+ * \param info Information container
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_pcm_info(snd_pcm_t *pcm, snd_pcm_info_t *info)
+{
+	assert(pcm && info);
+	return pcm->ops->info(pcm->op_arg, info);
+}
+
+/** \brief Retreive current PCM hardware configuration chosen with #snd_pcm_hw_params
+ * \param pcm PCM handle
+ * \param params Configuration space definition container
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_pcm_hw_params_current(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	unsigned int frame_bits;
+
+	assert(pcm && params);
+	if (!pcm->setup)
+		return -EBADFD;
+	memset(params, 0, snd_pcm_hw_params_sizeof());
+	params->flags = pcm->hw_flags;
+	snd_mask_set(&params->masks[SND_PCM_HW_PARAM_ACCESS - SND_PCM_HW_PARAM_FIRST_MASK], pcm->access);
+	snd_mask_set(&params->masks[SND_PCM_HW_PARAM_FORMAT - SND_PCM_HW_PARAM_FIRST_MASK], pcm->format);
+	snd_mask_set(&params->masks[SND_PCM_HW_PARAM_SUBFORMAT - SND_PCM_HW_PARAM_FIRST_MASK], pcm->subformat);
+	frame_bits = snd_pcm_format_physical_width(pcm->format) * pcm->channels;
+	snd_interval_set_value(&params->intervals[SND_PCM_HW_PARAM_FRAME_BITS - SND_PCM_HW_PARAM_FIRST_INTERVAL], frame_bits);
+	snd_interval_set_value(&params->intervals[SND_PCM_HW_PARAM_CHANNELS - SND_PCM_HW_PARAM_FIRST_INTERVAL], pcm->channels);
+	snd_interval_set_value(&params->intervals[SND_PCM_HW_PARAM_RATE - SND_PCM_HW_PARAM_FIRST_INTERVAL], pcm->rate);
+	snd_interval_set_value(&params->intervals[SND_PCM_HW_PARAM_PERIOD_TIME - SND_PCM_HW_PARAM_FIRST_INTERVAL], pcm->period_time);
+	snd_interval_set_value(&params->intervals[SND_PCM_HW_PARAM_PERIOD_SIZE - SND_PCM_HW_PARAM_FIRST_INTERVAL], pcm->period_size);
+	snd_interval_copy(&params->intervals[SND_PCM_HW_PARAM_PERIODS - SND_PCM_HW_PARAM_FIRST_INTERVAL], &pcm->periods);
+	snd_interval_copy(&params->intervals[SND_PCM_HW_PARAM_BUFFER_TIME - SND_PCM_HW_PARAM_FIRST_INTERVAL], &pcm->buffer_time);
+	snd_interval_set_value(&params->intervals[SND_PCM_HW_PARAM_BUFFER_SIZE - SND_PCM_HW_PARAM_FIRST_INTERVAL], pcm->buffer_size);
+	snd_interval_set_value(&params->intervals[SND_PCM_HW_PARAM_BUFFER_BYTES - SND_PCM_HW_PARAM_FIRST_INTERVAL], (pcm->buffer_size * frame_bits) / 8);
+	params->info = pcm->info;
+	params->msbits = pcm->msbits;
+	params->rate_num = pcm->rate_num;
+	params->rate_den = pcm->rate_den;
+	params->fifo_size = pcm->fifo_size;
+	return 0;
+} 
+
+/** \brief Install one PCM hardware configuration chosen from a configuration space and #snd_pcm_prepare it
+ * \param pcm PCM handle
+ * \param params Configuration space definition container
+ * \return 0 on success otherwise a negative error code
+ *
+ * The configuration is chosen fixing single parameters in this order:
+ * first access, first format, first subformat, min channels, min rate, 
+ * min period time, max buffer size, min tick time. If no mutually
+ * compatible set of parameters can be chosen, a negative error code
+ * will be returned.
+ *
+ * After this call, #snd_pcm_prepare() is called automatically and
+ * the stream is brought to \c #SND_PCM_STATE_PREPARED state.
+ *
+ * The hardware parameters cannot be changed when the stream is
+ * running (active). The software parameters can be changed
+ * at any time.
+ *
+ * The configuration space will be updated to reflect the chosen
+ * parameters.
+ */
+int snd_pcm_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	int err;
+	assert(pcm && params);
+	err = _snd_pcm_hw_params(pcm, params);
+	if (err < 0)
+		return err;
+	err = snd_pcm_prepare(pcm);
+	return err;
+}
+
+/** \brief Remove PCM hardware configuration and free associated resources
+ * \param pcm PCM handle
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_pcm_hw_free(snd_pcm_t *pcm)
+{
+	int err;
+	if (! pcm->setup)
+		return 0;
+	if (pcm->mmap_channels) {
+		err = snd_pcm_munmap(pcm);
+		if (err < 0)
+			return err;
+	}
+	// assert(snd_pcm_state(pcm) == SND_PCM_STATE_SETUP ||
+	//        snd_pcm_state(pcm) == SND_PCM_STATE_PREPARED);
+	err = pcm->ops->hw_free(pcm->op_arg);
+	pcm->setup = 0;
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+/** \brief Install PCM software configuration defined by params
+ * \param pcm PCM handle
+ * \param params Configuration container
+ * \return 0 on success otherwise a negative error code
+ *
+ * The software parameters can be changed at any time.
+ * The hardware parameters cannot be changed when the stream is
+ * running (active).
+ */
+int snd_pcm_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t *params)
+{
+	int err;
+	/* the hw_params must be set at first!!! */
+	if (CHECK_SANITY(! pcm->setup)) {
+		SNDMSG("PCM not set up");
+		return -EIO;
+	}
+	if (! params->avail_min) {
+		SNDMSG("params->avail_min is 0");
+		return -EINVAL;
+	}
+#if 0
+	/* disable the check below - it looks too restrictive
+	 * (start_threshold is basically independent from avail_min)
+	 */
+	if (params->start_threshold <= pcm->buffer_size &&
+	    params->start_threshold > (pcm->buffer_size / params->avail_min) * params->avail_min) {
+		SNDMSG("params->avail_min problem for start_threshold");
+		return -EINVAL;
+	}
+#endif
+	err = pcm->ops->sw_params(pcm->op_arg, params);
+	if (err < 0)
+		return err;
+	pcm->tstamp_mode = params->tstamp_mode;
+	pcm->period_step = params->period_step;
+	pcm->avail_min = params->avail_min;
+	pcm->period_event = params->period_event;
+	pcm->start_threshold = params->start_threshold;
+	pcm->stop_threshold = params->stop_threshold;
+	pcm->silence_threshold = params->silence_threshold;
+	pcm->silence_size = params->silence_size;
+	pcm->boundary = params->boundary;
+	return 0;
+}
+
+/**
+ * \brief Obtain status (runtime) information for PCM handle
+ * \param pcm PCM handle
+ * \param status Status container
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_pcm_status(snd_pcm_t *pcm, snd_pcm_status_t *status)
+{
+	assert(pcm && status);
+	return pcm->fast_ops->status(pcm->fast_op_arg, status);
+}
+
+/**
+ * \brief Return PCM state
+ * \param pcm PCM handle
+ * \return PCM state #snd_pcm_state_t of given PCM handle
+ *
+ * This is a faster way to obtain only the PCM state without calling
+ * \link ::snd_pcm_status() \endlink.
+ */
+snd_pcm_state_t snd_pcm_state(snd_pcm_t *pcm)
+{
+	assert(pcm);
+	return pcm->fast_ops->state(pcm->fast_op_arg);
+}
+
+/**
+ * \brief (DEPRECATED) Synchronize stream position with hardware
+ * \param pcm PCM handle
+ * \return 0 on success otherwise a negative error code
+ *
+ * Note this function does not update the actual r/w pointer
+ * for applications. The function #snd_pcm_avail_update()
+ * have to be called before any mmap begin+commit operation.
+ */
+int snd_pcm_hwsync(snd_pcm_t *pcm)
+{
+	assert(pcm);
+	if (CHECK_SANITY(! pcm->setup)) {
+		SNDMSG("PCM not set up");
+		return -EIO;
+	}
+	return pcm->fast_ops->hwsync(pcm->fast_op_arg);
+}
+#ifndef DOC_HIDDEN
+link_warning(snd_pcm_hwsync, "Warning: snd_pcm_hwsync() is deprecated, consider to use snd_pcm_avail()");
+#endif
+
+/**
+ * \brief Obtain delay for a running PCM handle
+ * \param pcm PCM handle
+ * \param delayp Returned delay in frames
+ * \return 0 on success otherwise a negative error code
+ *
+ * For playback the delay is defined as the time that a frame that is written
+ * to the PCM stream shortly after this call will take to be actually
+ * audible. It is as such the overall latency from the write call to the final
+ * DAC.
+ *
+ * For capture the delay is defined as the time that a frame that was
+ * digitized by the audio device takes until it can be read from the PCM
+ * stream shortly after this call returns. It is as such the overall latency
+ * from the initial ADC to the read call.
+ *
+ * Please note that hence in case of a playback underrun this value will not
+ * necessarily got down to 0.
+ *
+ * If the application is interested in the fill level of the playback buffer
+ * of the device, it should use #snd_pcm_avail*() functions. The
+ * value returned by that call is not directly related to the delay, since the
+ * latter might include some additional, fixed latencies the former does not.
+ *
+ * Note this function does not update the actual r/w pointer
+ * for applications. The function #snd_pcm_avail_update()
+ * have to be called before any begin+commit operation.
+ */
+int snd_pcm_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp)
+{
+	assert(pcm);
+	if (CHECK_SANITY(! pcm->setup)) {
+		SNDMSG("PCM not set up");
+		return -EIO;
+	}
+	return pcm->fast_ops->delay(pcm->fast_op_arg, delayp);
+}
+
+/**
+ * \brief Resume from suspend, no samples are lost
+ * \param pcm PCM handle
+ * \return 0 on success otherwise a negative error code
+ * \retval -EAGAIN resume can't be proceed immediately (audio hardware is probably still suspended)
+ * \retval -ENOSYS hardware doesn't support this feature
+ *
+ * This function can be used when the stream is in the suspend state
+ * to do the fine resume from this state. Not all hardware supports
+ * this feature, when an -ENOSYS error is returned, use the \link ::snd_pcm_prepare() \endlink
+ * function to recovery.
+ */
+int snd_pcm_resume(snd_pcm_t *pcm)
+{
+	assert(pcm);
+	if (CHECK_SANITY(! pcm->setup)) {
+		SNDMSG("PCM not set up");
+		return -EIO;
+	}
+	return pcm->fast_ops->resume(pcm->fast_op_arg);
+}
+
+/**
+ * \brief Obtain last position update hi-res timestamp
+ * \param pcm PCM handle
+ * \param avail Number of available frames when timestamp was grabbed
+ * \param tstamp Hi-res timestamp
+ * \return 0 on success otherwise a negative error code
+ *
+ * Note this function does not update the actual r/w pointer
+ * for applications.
+ */
+int snd_pcm_htimestamp(snd_pcm_t *pcm, snd_pcm_uframes_t *avail, snd_htimestamp_t *tstamp)
+{
+	assert(pcm);
+	if (CHECK_SANITY(! pcm->setup)) {
+		SNDMSG("PCM not set up");
+		return -EIO;
+	}
+	return pcm->fast_ops->htimestamp(pcm->fast_op_arg, avail, tstamp);
+}
+
+/**
+ * \brief Prepare PCM for use
+ * \param pcm PCM handle
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_pcm_prepare(snd_pcm_t *pcm)
+{
+	assert(pcm);
+	if (CHECK_SANITY(! pcm->setup)) {
+		SNDMSG("PCM not set up");
+		return -EIO;
+	}
+	return pcm->fast_ops->prepare(pcm->fast_op_arg);
+}
+
+/**
+ * \brief Reset PCM position
+ * \param pcm PCM handle
+ * \return 0 on success otherwise a negative error code
+ *
+ * Reduce PCM delay to 0.
+ */
+int snd_pcm_reset(snd_pcm_t *pcm)
+{
+	assert(pcm);
+	if (CHECK_SANITY(! pcm->setup)) {
+		SNDMSG("PCM not set up");
+		return -EIO;
+	}
+	return pcm->fast_ops->reset(pcm->fast_op_arg);
+}
+
+/**
+ * \brief Start a PCM
+ * \param pcm PCM handle
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_pcm_start(snd_pcm_t *pcm)
+{
+	assert(pcm);
+	if (CHECK_SANITY(! pcm->setup)) {
+		SNDMSG("PCM not set up");
+		return -EIO;
+	}
+	return pcm->fast_ops->start(pcm->fast_op_arg);
+}
+
+/**
+ * \brief Stop a PCM dropping pending frames
+ * \param pcm PCM handle
+ * \return 0 on success otherwise a negative error code
+ *
+ * This function stops the PCM <i>immediately</i>.
+ * The pending samples on the buffer are ignored.
+ *
+ * For processing all pending samples, use \link ::snd_pcm_drain() \endlink
+ * instead.
+ */
+int snd_pcm_drop(snd_pcm_t *pcm)
+{
+	assert(pcm);
+	if (CHECK_SANITY(! pcm->setup)) {
+		SNDMSG("PCM not set up");
+		return -EIO;
+	}
+	return pcm->fast_ops->drop(pcm->fast_op_arg);
+}
+
+/**
+ * \brief Stop a PCM preserving pending frames
+ * \param pcm PCM handle
+ * \return 0 on success otherwise a negative error code
+ * \retval -ESTRPIPE a suspend event occurred
+ *
+ * For playback wait for all pending frames to be played and then stop
+ * the PCM.
+ * For capture stop PCM permitting to retrieve residual frames.
+ *
+ * For stopping the PCM stream immediately, use \link ::snd_pcm_drop() \endlink
+ * instead.
+ */
+int snd_pcm_drain(snd_pcm_t *pcm)
+{
+	assert(pcm);
+	if (CHECK_SANITY(! pcm->setup)) {
+		SNDMSG("PCM not set up");
+		return -EIO;
+	}
+	return pcm->fast_ops->drain(pcm->fast_op_arg);
+}
+
+/**
+ * \brief Pause/resume PCM
+ * \param pcm PCM handle
+ * \param enable 0 = resume, 1 = pause
+ * \return 0 on success otherwise a negative error code
+ *
+ * Note that this function works only on the hardware which supports
+ * pause feature.  You can check it via \link ::snd_pcm_hw_params_can_pause() \endlink
+ * function.
+ */
+int snd_pcm_pause(snd_pcm_t *pcm, int enable)
+{
+	assert(pcm);
+	if (CHECK_SANITY(! pcm->setup)) {
+		SNDMSG("PCM not set up");
+		return -EIO;
+	}
+	return pcm->fast_ops->pause(pcm->fast_op_arg, enable);
+}
+
+/**
+ * \brief Get safe count of frames which can be rewinded
+ * \param pcm PCM handle
+ * \return a positive number of frames or negative error code
+ *
+ * Note: The snd_pcm_rewind() can accept bigger value than returned
+ * by this function. But it is not guaranteed that output stream
+ * will be consistent with bigger value.
+ */
+snd_pcm_sframes_t snd_pcm_rewindable(snd_pcm_t *pcm)
+{
+	assert(pcm);
+	if (CHECK_SANITY(! pcm->setup)) {
+		SNDMSG("PCM not set up");
+		return -EIO;
+	}
+	return pcm->fast_ops->rewindable(pcm->fast_op_arg);
+}
+
+/**
+ * \brief Move application frame position backward
+ * \param pcm PCM handle
+ * \param frames wanted displacement in frames
+ * \return a positive number for actual displacement otherwise a
+ * negative error code
+ */
+snd_pcm_sframes_t snd_pcm_rewind(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
+{
+	assert(pcm);
+	if (CHECK_SANITY(! pcm->setup)) {
+		SNDMSG("PCM not set up");
+		return -EIO;
+	}
+	if (frames == 0)
+		return 0;
+	return pcm->fast_ops->rewind(pcm->fast_op_arg, frames);
+}
+
+/**
+ * \brief Get safe count of frames which can be forwarded
+ * \param pcm PCM handle
+ * \return a positive number of frames or negative error code
+ *
+ * Note: The snd_pcm_forward() can accept bigger value than returned
+ * by this function. But it is not guaranteed that output stream
+ * will be consistent with bigger value.
+ */
+snd_pcm_sframes_t snd_pcm_forwardable(snd_pcm_t *pcm)
+{
+	assert(pcm);
+	if (CHECK_SANITY(! pcm->setup)) {
+		SNDMSG("PCM not set up");
+		return -EIO;
+	}
+	return pcm->fast_ops->forwardable(pcm->fast_op_arg);
+}
+
+/**
+ * \brief Move application frame position forward
+ * \param pcm PCM handle
+ * \param frames wanted skip in frames
+ * \return a positive number for actual skip otherwise a negative error code
+ * \retval 0 means no action
+ */
+#ifndef DOXYGEN
+snd_pcm_sframes_t INTERNAL(snd_pcm_forward)(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
+#else
+snd_pcm_sframes_t snd_pcm_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
+#endif
+{
+	assert(pcm);
+	if (CHECK_SANITY(! pcm->setup)) {
+		SNDMSG("PCM not set up");
+		return -EIO;
+	}
+	if (frames == 0)
+		return 0;
+	return pcm->fast_ops->forward(pcm->fast_op_arg, frames);
+}
+use_default_symbol_version(__snd_pcm_forward, snd_pcm_forward, ALSA_0.9.0rc8);
+
+/**
+ * \brief Write interleaved frames to a PCM
+ * \param pcm PCM handle
+ * \param buffer frames containing buffer
+ * \param size frames to be written
+ * \return a positive number of frames actually written otherwise a
+ * negative error code
+ * \retval -EBADFD PCM is not in the right state (#SND_PCM_STATE_PREPARED or #SND_PCM_STATE_RUNNING)
+ * \retval -EPIPE an underrun occurred
+ * \retval -ESTRPIPE a suspend event occurred (stream is suspended and waiting for an application recovery)
+ *
+ * If the blocking behaviour is selected and it is running, then routine waits until
+ * all requested frames are played or put to the playback ring buffer.
+ * The returned number of frames can be less only if a signal or underrun occurred.
+ *
+ * If the non-blocking behaviour is selected, then routine doesn't wait at all.
+ */ 
+snd_pcm_sframes_t snd_pcm_writei(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size)
+{
+	assert(pcm);
+	assert(size == 0 || buffer);
+	if (CHECK_SANITY(! pcm->setup)) {
+		SNDMSG("PCM not set up");
+		return -EIO;
+	}
+	if (pcm->access != SND_PCM_ACCESS_RW_INTERLEAVED) {
+		SNDMSG("invalid access type %s", snd_pcm_access_name(pcm->access));
+		return -EINVAL;
+	}
+	return _snd_pcm_writei(pcm, buffer, size);
+}
+
+/**
+ * \brief Write non interleaved frames to a PCM
+ * \param pcm PCM handle
+ * \param bufs frames containing buffers (one for each channel)
+ * \param size frames to be written
+ * \return a positive number of frames actually written otherwise a
+ * negative error code
+ * \retval -EBADFD PCM is not in the right state (#SND_PCM_STATE_PREPARED or #SND_PCM_STATE_RUNNING)
+ * \retval -EPIPE an underrun occurred
+ * \retval -ESTRPIPE a suspend event occurred (stream is suspended and waiting for an application recovery)
+ *
+ * If the blocking behaviour is selected and it is running, then routine waits until
+ * all requested frames are played or put to the playback ring buffer.
+ * The returned number of frames can be less only if a signal or underrun occurred.
+ *
+ * If the non-blocking behaviour is selected, then routine doesn't wait at all.
+ */ 
+snd_pcm_sframes_t snd_pcm_writen(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size)
+{
+	assert(pcm);
+	assert(size == 0 || bufs);
+	if (CHECK_SANITY(! pcm->setup)) {
+		SNDMSG("PCM not set up");
+		return -EIO;
+	}
+	if (pcm->access != SND_PCM_ACCESS_RW_NONINTERLEAVED) {
+		SNDMSG("invalid access type %s", snd_pcm_access_name(pcm->access));
+		return -EINVAL;
+	}
+	return _snd_pcm_writen(pcm, bufs, size);
+}
+
+/**
+ * \brief Read interleaved frames from a PCM
+ * \param pcm PCM handle
+ * \param buffer frames containing buffer
+ * \param size frames to be read
+ * \return a positive number of frames actually read otherwise a
+ * negative error code
+ * \retval -EBADFD PCM is not in the right state (#SND_PCM_STATE_PREPARED or #SND_PCM_STATE_RUNNING)
+ * \retval -EPIPE an overrun occurred
+ * \retval -ESTRPIPE a suspend event occurred (stream is suspended and waiting for an application recovery)
+ *
+ * If the blocking behaviour was selected and it is running, then routine waits until
+ * all requested frames are filled. The returned number of frames can be less only
+ * if a signal or underrun occurred.
+ *
+ * If the non-blocking behaviour is selected, then routine doesn't wait at all.
+ */ 
+snd_pcm_sframes_t snd_pcm_readi(snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t size)
+{
+	assert(pcm);
+	assert(size == 0 || buffer);
+	if (CHECK_SANITY(! pcm->setup)) {
+		SNDMSG("PCM not set up");
+		return -EIO;
+	}
+	if (pcm->access != SND_PCM_ACCESS_RW_INTERLEAVED) {
+		SNDMSG("invalid access type %s", snd_pcm_access_name(pcm->access));
+		return -EINVAL;
+	}
+	return _snd_pcm_readi(pcm, buffer, size);
+}
+
+/**
+ * \brief Read non interleaved frames to a PCM
+ * \param pcm PCM handle
+ * \param bufs frames containing buffers (one for each channel)
+ * \param size frames to be read
+ * \return a positive number of frames actually read otherwise a
+ * negative error code
+ * \retval -EBADFD PCM is not in the right state (#SND_PCM_STATE_PREPARED or #SND_PCM_STATE_RUNNING)
+ * \retval -EPIPE an overrun occurred
+ * \retval -ESTRPIPE a suspend event occurred (stream is suspended and waiting for an application recovery)
+ *
+ * If the blocking behaviour was selected and it is running, then routine waits until
+ * all requested frames are filled. The returned number of frames can be less only
+ * if a signal or underrun occurred.
+ *
+ * If the non-blocking behaviour is selected, then routine doesn't wait at all.
+ */ 
+snd_pcm_sframes_t snd_pcm_readn(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size)
+{
+	assert(pcm);
+	assert(size == 0 || bufs);
+	if (CHECK_SANITY(! pcm->setup)) {
+		SNDMSG("PCM not set up");
+		return -EIO;
+	}
+	if (pcm->access != SND_PCM_ACCESS_RW_NONINTERLEAVED) {
+		SNDMSG("invalid access type %s", snd_pcm_access_name(pcm->access));
+		return -EINVAL;
+	}
+	return _snd_pcm_readn(pcm, bufs, size);
+}
+
+/**
+ * \brief Link two PCMs
+ * \param pcm1 first PCM handle
+ * \param pcm2 first PCM handle
+ * \return 0 on success otherwise a negative error code
+ *
+ * The two PCMs will start/stop/prepare in sync.
+ */ 
+int snd_pcm_link(snd_pcm_t *pcm1, snd_pcm_t *pcm2)
+{
+	assert(pcm1);
+	assert(pcm2);
+	if (pcm1->fast_ops->link)
+		return pcm1->fast_ops->link(pcm1, pcm2);
+	return -ENOSYS;
+}
+
+/**
+ * \brief Remove a PCM from a linked group
+ * \param pcm PCM handle
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_pcm_unlink(snd_pcm_t *pcm)
+{
+	assert(pcm);
+	if (pcm->fast_ops->unlink)
+		return pcm->fast_ops->unlink(pcm);
+	return -ENOSYS;
+}
+
+/**
+ * \brief get count of poll descriptors for PCM handle
+ * \param pcm PCM handle
+ * \return count of poll descriptors
+ */
+int snd_pcm_poll_descriptors_count(snd_pcm_t *pcm)
+{
+	assert(pcm);
+	if (pcm->fast_ops->poll_descriptors_count)
+		return pcm->fast_ops->poll_descriptors_count(pcm->fast_op_arg);
+	return pcm->poll_fd_count;
+}
+
+
+/**
+ * \brief get poll descriptors
+ * \param pcm PCM handle
+ * \param pfds array of poll descriptors
+ * \param space space in the poll descriptor array
+ * \return count of filled descriptors
+ *
+ * This function fills the given poll descriptor structs for the specified
+ * PCM handle.  The poll desctiptor array should have the size returned by
+ * \link ::snd_pcm_poll_descriptors_count() \endlink function.
+ *
+ * The result is intended for direct use with the poll() syscall.
+ *
+ * For reading the returned events of poll descriptor after poll() system
+ * call, use \link ::snd_pcm_poll_descriptors_revents() \endlink function.
+ * The field values in pollfd structs may be bogus regarding the stream
+ * direction from the application perspective (POLLIN might not imply read
+ * direction and POLLOUT might not imply write), but
+ * the \link ::snd_pcm_poll_descriptors_revents() \endlink function
+ * does the right "demangling".
+ *
+ * You can use output from this function as arguments for the select()
+ * syscall, too. Do not forget to translate POLLIN and POLLOUT events to
+ * corresponding FD_SET arrays and demangle events using
+ * \link ::snd_pcm_poll_descriptors_revents() \endlink .
+ */
+int snd_pcm_poll_descriptors(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int space)
+{
+	assert(pcm && pfds);
+	if (pcm->fast_ops->poll_descriptors)
+		return pcm->fast_ops->poll_descriptors(pcm->fast_op_arg, pfds, space);
+	if (pcm->poll_fd < 0) {
+		SNDMSG("poll_fd < 0");
+		return -EIO;
+	}
+	if (space >= 1 && pfds) {
+		pfds->fd = pcm->poll_fd;
+		pfds->events = pcm->poll_events | POLLERR | POLLNVAL;
+	} else {
+		return 0;
+	}
+	return 1;
+}
+
+/**
+ * \brief get returned events from poll descriptors
+ * \param pcm PCM handle
+ * \param pfds array of poll descriptors
+ * \param nfds count of poll descriptors
+ * \param revents pointer to the returned (single) event
+ * \return zero if success, otherwise a negative error code
+ *
+ * This function does "demangling" of the revents mask returned from
+ * the poll() syscall to correct semantics (POLLIN = read, POLLOUT = write).
+ *
+ * Note: The null event also exists. Even if poll() or select()
+ * syscall returned that some events are waiting, this function might
+ * return empty set of events. In this case, application should
+ * do next event waiting using poll() or select().
+ *
+ * Note: Even if multiple poll descriptors are used (i.e. pfds > 1),
+ * this function returns only a single event.
+ */
+int snd_pcm_poll_descriptors_revents(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int nfds, unsigned short *revents)
+{
+	assert(pcm && pfds && revents);
+	if (pcm->fast_ops->poll_revents)
+		return pcm->fast_ops->poll_revents(pcm->fast_op_arg, pfds, nfds, revents);
+	if (nfds == 1) {
+		*revents = pfds->revents;
+		return 0;
+	}
+	return -EINVAL;
+}
+
+#ifndef DOC_HIDDEN
+#define PCMTYPE(v) [SND_PCM_TYPE_##v] = #v
+#define STATE(v) [SND_PCM_STATE_##v] = #v
+#define STREAM(v) [SND_PCM_STREAM_##v] = #v
+#define READY(v) [SND_PCM_READY_##v] = #v
+#define XRUN(v) [SND_PCM_XRUN_##v] = #v
+#define SILENCE(v) [SND_PCM_SILENCE_##v] = #v
+#define TSTAMP(v) [SND_PCM_TSTAMP_##v] = #v
+#define ACCESS(v) [SND_PCM_ACCESS_##v] = #v
+#define START(v) [SND_PCM_START_##v] = #v
+#define HW_PARAM(v) [SND_PCM_HW_PARAM_##v] = #v
+#define SW_PARAM(v) [SND_PCM_SW_PARAM_##v] = #v
+#define FORMAT(v) [SND_PCM_FORMAT_##v] = #v
+#define SUBFORMAT(v) [SND_PCM_SUBFORMAT_##v] = #v 
+
+#define FORMATD(v, d) [SND_PCM_FORMAT_##v] = d
+#define SUBFORMATD(v, d) [SND_PCM_SUBFORMAT_##v] = d 
+
+
+static const char *const snd_pcm_stream_names[] = {
+	STREAM(PLAYBACK),
+	STREAM(CAPTURE),
+};
+
+static const char *const snd_pcm_state_names[] = {
+	STATE(OPEN),
+	STATE(SETUP),
+	STATE(PREPARED),
+	STATE(RUNNING),
+	STATE(XRUN),
+	STATE(DRAINING),
+	STATE(PAUSED),
+	STATE(SUSPENDED),
+	STATE(DISCONNECTED),
+};
+
+static const char *const snd_pcm_access_names[] = {
+	ACCESS(MMAP_INTERLEAVED), 
+	ACCESS(MMAP_NONINTERLEAVED),
+	ACCESS(MMAP_COMPLEX),
+	ACCESS(RW_INTERLEAVED),
+	ACCESS(RW_NONINTERLEAVED),
+};
+
+static const char *const snd_pcm_format_names[] = {
+	FORMAT(S8),
+	FORMAT(U8),
+	FORMAT(S16_LE),
+	FORMAT(S16_BE),
+	FORMAT(U16_LE),
+	FORMAT(U16_BE),
+	FORMAT(S24_LE),
+	FORMAT(S24_BE),
+	FORMAT(U24_LE),
+	FORMAT(U24_BE),
+	FORMAT(S32_LE),
+	FORMAT(S32_BE),
+	FORMAT(U32_LE),
+	FORMAT(U32_BE),
+	FORMAT(FLOAT_LE),
+	FORMAT(FLOAT_BE),
+	FORMAT(FLOAT64_LE),
+	FORMAT(FLOAT64_BE),
+	FORMAT(IEC958_SUBFRAME_LE),
+	FORMAT(IEC958_SUBFRAME_BE),
+	FORMAT(MU_LAW),
+	FORMAT(A_LAW),
+	FORMAT(IMA_ADPCM),
+	FORMAT(MPEG),
+	FORMAT(GSM),
+	FORMAT(SPECIAL),
+	FORMAT(S24_3LE),
+	FORMAT(S24_3BE),
+	FORMAT(U24_3LE),
+	FORMAT(U24_3BE),
+	FORMAT(S20_3LE),
+	FORMAT(S20_3BE),
+	FORMAT(U20_3LE),
+	FORMAT(U20_3BE),
+	FORMAT(S18_3LE),
+	FORMAT(S18_3BE),
+	FORMAT(U18_3LE),
+	FORMAT(U18_3BE),
+};
+
+static const char *const snd_pcm_format_aliases[SND_PCM_FORMAT_LAST+1] = {
+	FORMAT(S16),
+	FORMAT(U16),
+	FORMAT(S24),
+	FORMAT(U24),
+	FORMAT(S32),
+	FORMAT(U32),
+	FORMAT(FLOAT),
+	FORMAT(FLOAT64),
+	FORMAT(IEC958_SUBFRAME),
+};
+
+static const char *const snd_pcm_format_descriptions[] = {
+	FORMATD(S8, "Signed 8 bit"), 
+	FORMATD(U8, "Unsigned 8 bit"),
+	FORMATD(S16_LE, "Signed 16 bit Little Endian"),
+	FORMATD(S16_BE, "Signed 16 bit Big Endian"),
+	FORMATD(U16_LE, "Unsigned 16 bit Little Endian"),
+	FORMATD(U16_BE, "Unsigned 16 bit Big Endian"),
+	FORMATD(S24_LE, "Signed 24 bit Little Endian"),
+	FORMATD(S24_BE, "Signed 24 bit Big Endian"),
+	FORMATD(U24_LE, "Unsigned 24 bit Little Endian"),
+	FORMATD(U24_BE, "Unsigned 24 bit Big Endian"),
+	FORMATD(S32_LE, "Signed 32 bit Little Endian"),
+	FORMATD(S32_BE, "Signed 32 bit Big Endian"),
+	FORMATD(U32_LE, "Unsigned 32 bit Little Endian"),
+	FORMATD(U32_BE, "Unsigned 32 bit Big Endian"),
+	FORMATD(FLOAT_LE, "Float 32 bit Little Endian"),
+	FORMATD(FLOAT_BE, "Float 32 bit Big Endian"),
+	FORMATD(FLOAT64_LE, "Float 64 bit Little Endian"),
+	FORMATD(FLOAT64_BE, "Float 64 bit Big Endian"),
+	FORMATD(IEC958_SUBFRAME_LE, "IEC-958 Little Endian"),
+	FORMATD(IEC958_SUBFRAME_BE, "IEC-958 Big Endian"),
+	FORMATD(MU_LAW, "Mu-Law"),
+	FORMATD(A_LAW, "A-Law"),
+	FORMATD(IMA_ADPCM, "Ima-ADPCM"),
+	FORMATD(MPEG, "MPEG"),
+	FORMATD(GSM, "GSM"),
+	FORMATD(SPECIAL, "Special"),
+	FORMATD(S24_3LE, "Signed 24 bit Little Endian in 3bytes"),
+	FORMATD(S24_3BE, "Signed 24 bit Big Endian in 3bytes"),
+	FORMATD(U24_3LE, "Unsigned 24 bit Little Endian in 3bytes"),
+	FORMATD(U24_3BE, "Unsigned 24 bit Big Endian in 3bytes"),
+	FORMATD(S20_3LE, "Signed 20 bit Little Endian in 3bytes"),
+	FORMATD(S20_3BE, "Signed 20 bit Big Endian in 3bytes"),
+	FORMATD(U20_3LE, "Unsigned 20 bit Little Endian in 3bytes"),
+	FORMATD(U20_3BE, "Unsigned 20 bit Big Endian in 3bytes"),
+	FORMATD(S18_3LE, "Signed 18 bit Little Endian in 3bytes"),
+	FORMATD(S18_3BE, "Signed 18 bit Big Endian in 3bytes"),
+	FORMATD(U18_3LE, "Unsigned 18 bit Little Endian in 3bytes"),
+	FORMATD(U18_3BE, "Unsigned 18 bit Big Endian in 3bytes"),
+};
+
+static const char *const snd_pcm_type_names[] = {
+	PCMTYPE(HW), 
+	PCMTYPE(HOOKS), 
+	PCMTYPE(MULTI), 
+	PCMTYPE(FILE), 
+	PCMTYPE(NULL), 
+	PCMTYPE(SHM), 
+	PCMTYPE(INET), 
+	PCMTYPE(COPY), 
+	PCMTYPE(LINEAR), 
+	PCMTYPE(ALAW), 
+	PCMTYPE(MULAW), 
+	PCMTYPE(ADPCM), 
+	PCMTYPE(RATE), 
+	PCMTYPE(ROUTE), 
+	PCMTYPE(PLUG), 
+	PCMTYPE(SHARE), 
+	PCMTYPE(METER), 
+	PCMTYPE(MIX), 
+	PCMTYPE(DROUTE), 
+	PCMTYPE(LBSERVER), 
+	PCMTYPE(LINEAR_FLOAT), 
+	PCMTYPE(LADSPA), 
+	PCMTYPE(DMIX), 
+	PCMTYPE(JACK),
+        PCMTYPE(DSNOOP),
+        PCMTYPE(IEC958),
+	PCMTYPE(SOFTVOL),
+        PCMTYPE(IOPLUG),
+        PCMTYPE(EXTPLUG),
+	PCMTYPE(MMAP_EMUL),
+};
+
+static const char *const snd_pcm_subformat_names[] = {
+	SUBFORMAT(STD), 
+};
+
+static const char *const snd_pcm_subformat_descriptions[] = {
+	SUBFORMATD(STD, "Standard"), 
+};
+
+static const char *const snd_pcm_start_mode_names[] = {
+	START(EXPLICIT),
+	START(DATA),
+};
+
+static const char *const snd_pcm_xrun_mode_names[] = {
+	XRUN(NONE),
+	XRUN(STOP),
+};
+
+static const char *const snd_pcm_tstamp_mode_names[] = {
+	TSTAMP(NONE),
+	TSTAMP(ENABLE),
+};
+#endif
+
+/**
+ * \brief get name of PCM stream type
+ * \param stream PCM stream type
+ * \return ascii name of PCM stream type
+ */
+const char *snd_pcm_stream_name(snd_pcm_stream_t stream)
+{
+	if (stream > SND_PCM_STREAM_LAST)
+		return NULL;
+	return snd_pcm_stream_names[stream];
+}
+
+/**
+ * \brief get name of PCM access type
+ * \param acc PCM access type
+ * \return ascii name of PCM access type
+ */
+const char *snd_pcm_access_name(snd_pcm_access_t acc)
+{
+	if (acc > SND_PCM_ACCESS_LAST)
+		return NULL;
+	return snd_pcm_access_names[acc];
+}
+
+/**
+ * \brief get name of PCM sample format
+ * \param format PCM sample format
+ * \return ascii name of PCM sample format
+ */
+const char *snd_pcm_format_name(snd_pcm_format_t format)
+{
+	if (format > SND_PCM_FORMAT_LAST)
+		return NULL;
+	return snd_pcm_format_names[format];
+}
+
+/**
+ * \brief get description of PCM sample format
+ * \param format PCM sample format
+ * \return ascii description of PCM sample format
+ */
+const char *snd_pcm_format_description(snd_pcm_format_t format)
+{
+	if (format > SND_PCM_FORMAT_LAST)
+		return NULL;
+	return snd_pcm_format_descriptions[format];
+}
+
+/**
+ * \brief get PCM sample format from name
+ * \param name PCM sample format name (case insensitive)
+ * \return PCM sample format
+ */
+snd_pcm_format_t snd_pcm_format_value(const char* name)
+{
+	snd_pcm_format_t format;
+	for (format = 0; format <= SND_PCM_FORMAT_LAST; format++) {
+		if (snd_pcm_format_names[format] &&
+		    strcasecmp(name, snd_pcm_format_names[format]) == 0) {
+			return format;
+		}
+		if (snd_pcm_format_aliases[format] &&
+		    strcasecmp(name, snd_pcm_format_aliases[format]) == 0) {
+			return format;
+		}
+	}
+	for (format = 0; format <= SND_PCM_FORMAT_LAST; format++) {
+		if (snd_pcm_format_descriptions[format] &&
+		    strcasecmp(name, snd_pcm_format_descriptions[format]) == 0) {
+			return format;
+		}
+	}
+	return SND_PCM_FORMAT_UNKNOWN;
+}
+
+/**
+ * \brief get name of PCM sample subformat
+ * \param subformat PCM sample subformat
+ * \return ascii name of PCM sample subformat
+ */
+const char *snd_pcm_subformat_name(snd_pcm_subformat_t subformat)
+{
+	if (subformat > SND_PCM_SUBFORMAT_LAST)
+		return NULL;
+	return snd_pcm_subformat_names[subformat];
+}
+
+/**
+ * \brief get description of PCM sample subformat
+ * \param subformat PCM sample subformat
+ * \return ascii description of PCM sample subformat
+ */
+const char *snd_pcm_subformat_description(snd_pcm_subformat_t subformat)
+{
+	if (subformat > SND_PCM_SUBFORMAT_LAST)
+		return NULL;
+	return snd_pcm_subformat_descriptions[subformat];
+}
+
+/**
+ * \brief (DEPRECATED) get name of PCM start mode setting
+ * \param mode PCM start mode
+ * \return ascii name of PCM start mode setting
+ */
+const char *snd_pcm_start_mode_name(snd_pcm_start_t mode)
+{
+	if (mode > SND_PCM_START_LAST)
+		return NULL;
+	return snd_pcm_start_mode_names[mode];
+}
+
+#ifndef DOC_HIDDEN
+link_warning(snd_pcm_start_mode_name, "Warning: start_mode is deprecated, consider to use start_threshold");
+#endif
+
+/**
+ * \brief (DEPRECATED) get name of PCM xrun mode setting
+ * \param mode PCM xrun mode
+ * \return ascii name of PCM xrun mode setting
+ */
+const char *snd_pcm_xrun_mode_name(snd_pcm_xrun_t mode)
+{
+	if (mode > SND_PCM_XRUN_LAST)
+		return NULL;
+	return snd_pcm_xrun_mode_names[mode];
+}
+
+#ifndef DOC_HIDDEN
+link_warning(snd_pcm_xrun_mode_name, "Warning: xrun_mode is deprecated, consider to use stop_threshold");
+#endif
+
+/**
+ * \brief get name of PCM tstamp mode setting
+ * \param mode PCM tstamp mode
+ * \return ascii name of PCM tstamp mode setting
+ */
+const char *snd_pcm_tstamp_mode_name(snd_pcm_tstamp_t mode)
+{
+	if (mode > SND_PCM_TSTAMP_LAST)
+		return NULL;
+	return snd_pcm_tstamp_mode_names[mode];
+}
+
+/**
+ * \brief get name of PCM state
+ * \param state PCM state
+ * \return ascii name of PCM state
+ */
+const char *snd_pcm_state_name(snd_pcm_state_t state)
+{
+	if (state > SND_PCM_STATE_LAST)
+		return NULL;
+	return snd_pcm_state_names[state];
+}
+
+/**
+ * \brief get name of PCM type
+ * \param type PCM type
+ * \return ascii name of PCM type
+ */
+#ifndef DOXYGEN
+const char *INTERNAL(snd_pcm_type_name)(snd_pcm_type_t type)
+#else
+const char *snd_pcm_type_name(snd_pcm_type_t type)
+#endif
+{
+	if (type > SND_PCM_TYPE_LAST)
+		return NULL;
+	return snd_pcm_type_names[type];
+}
+use_default_symbol_version(__snd_pcm_type_name, snd_pcm_type_name, ALSA_0.9.0);
+
+/**
+ * \brief Dump current hardware setup for PCM
+ * \param pcm PCM handle
+ * \param out Output handle
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_pcm_dump_hw_setup(snd_pcm_t *pcm, snd_output_t *out)
+{
+	assert(pcm);
+	assert(out);
+	if (CHECK_SANITY(! pcm->setup)) {
+		SNDMSG("PCM not set up");
+		return -EIO;
+	}
+        snd_output_printf(out, "  stream       : %s\n", snd_pcm_stream_name(pcm->stream));
+	snd_output_printf(out, "  access       : %s\n", snd_pcm_access_name(pcm->access));
+	snd_output_printf(out, "  format       : %s\n", snd_pcm_format_name(pcm->format));
+	snd_output_printf(out, "  subformat    : %s\n", snd_pcm_subformat_name(pcm->subformat));
+	snd_output_printf(out, "  channels     : %u\n", pcm->channels);
+	snd_output_printf(out, "  rate         : %u\n", pcm->rate);
+	snd_output_printf(out, "  exact rate   : %g (%u/%u)\n",
+			  (pcm->rate_den ? ((double) pcm->rate_num / pcm->rate_den) : 0.0),
+			  pcm->rate_num, pcm->rate_den);
+	snd_output_printf(out, "  msbits       : %u\n", pcm->msbits);
+	snd_output_printf(out, "  buffer_size  : %lu\n", pcm->buffer_size);
+	snd_output_printf(out, "  period_size  : %lu\n", pcm->period_size);
+	snd_output_printf(out, "  period_time  : %u\n", pcm->period_time);
+	return 0;
+}
+
+/**
+ * \brief Dump current software setup for PCM
+ * \param pcm PCM handle
+ * \param out Output handle
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_pcm_dump_sw_setup(snd_pcm_t *pcm, snd_output_t *out)
+{
+	assert(pcm);
+	assert(out);
+	if (CHECK_SANITY(! pcm->setup)) {
+		SNDMSG("PCM not set up");
+		return -EIO;
+	}
+	snd_output_printf(out, "  tstamp_mode  : %s\n", snd_pcm_tstamp_mode_name(pcm->tstamp_mode));
+	snd_output_printf(out, "  period_step  : %d\n", pcm->period_step);
+	snd_output_printf(out, "  avail_min    : %ld\n", pcm->avail_min);
+	snd_output_printf(out, "  period_event : %i\n", pcm->period_event);
+	snd_output_printf(out, "  start_threshold  : %ld\n", pcm->start_threshold);
+	snd_output_printf(out, "  stop_threshold   : %ld\n", pcm->stop_threshold);
+	snd_output_printf(out, "  silence_threshold: %ld\n", pcm->silence_threshold);
+	snd_output_printf(out, "  silence_size : %ld\n", pcm->silence_size);
+	snd_output_printf(out, "  boundary     : %ld\n", pcm->boundary);
+	return 0;
+}
+
+/**
+ * \brief Dump current setup (hardware and software) for PCM
+ * \param pcm PCM handle
+ * \param out Output handle
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_pcm_dump_setup(snd_pcm_t *pcm, snd_output_t *out)
+{
+	snd_pcm_dump_hw_setup(pcm, out);
+	snd_pcm_dump_sw_setup(pcm, out);
+	return 0;
+}
+
+/**
+ * \brief Dump status
+ * \param status Status container
+ * \param out Output handle
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_pcm_status_dump(snd_pcm_status_t *status, snd_output_t *out)
+{
+	assert(status);
+	snd_output_printf(out, "  state       : %s\n", snd_pcm_state_name((snd_pcm_state_t) status->state));
+	snd_output_printf(out, "  trigger_time: %ld.%06ld\n",
+		status->trigger_tstamp.tv_sec, status->trigger_tstamp.tv_nsec);
+	snd_output_printf(out, "  tstamp      : %ld.%06ld\n",
+		status->tstamp.tv_sec, status->tstamp.tv_nsec);
+	snd_output_printf(out, "  delay       : %ld\n", (long)status->delay);
+	snd_output_printf(out, "  avail       : %ld\n", (long)status->avail);
+	snd_output_printf(out, "  avail_max   : %ld\n", (long)status->avail_max);
+	return 0;
+}
+
+/**
+ * \brief Dump PCM info
+ * \param pcm PCM handle
+ * \param out Output handle
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_pcm_dump(snd_pcm_t *pcm, snd_output_t *out)
+{
+	assert(pcm);
+	assert(out);
+	pcm->ops->dump(pcm->op_arg, out);
+	return 0;
+}
+
+/**
+ * \brief Convert bytes in frames for a PCM
+ * \param pcm PCM handle
+ * \param bytes quantity in bytes
+ * \return quantity expressed in frames
+ */
+snd_pcm_sframes_t snd_pcm_bytes_to_frames(snd_pcm_t *pcm, ssize_t bytes)
+{
+	assert(pcm);
+	if (CHECK_SANITY(! pcm->setup)) {
+		SNDMSG("PCM not set up");
+		return -EIO;
+	}
+	return bytes * 8 / pcm->frame_bits;
+}
+
+/**
+ * \brief Convert frames in bytes for a PCM
+ * \param pcm PCM handle
+ * \param frames quantity in frames
+ * \return quantity expressed in bytes
+ */
+ssize_t snd_pcm_frames_to_bytes(snd_pcm_t *pcm, snd_pcm_sframes_t frames)
+{
+	assert(pcm);
+	if (CHECK_SANITY(! pcm->setup)) {
+		SNDMSG("PCM not set up");
+		return -EIO;
+	}
+	return frames * pcm->frame_bits / 8;
+}
+
+/**
+ * \brief Convert bytes in samples for a PCM
+ * \param pcm PCM handle
+ * \param bytes quantity in bytes
+ * \return quantity expressed in samples
+ */
+long snd_pcm_bytes_to_samples(snd_pcm_t *pcm, ssize_t bytes)
+{
+	assert(pcm);
+	if (CHECK_SANITY(! pcm->setup)) {
+		SNDMSG("PCM not set up");
+		return -EIO;
+	}
+	return bytes * 8 / pcm->sample_bits;
+}
+
+/**
+ * \brief Convert samples in bytes for a PCM
+ * \param pcm PCM handle
+ * \param samples quantity in samples
+ * \return quantity expressed in bytes
+ */
+ssize_t snd_pcm_samples_to_bytes(snd_pcm_t *pcm, long samples)
+{
+	assert(pcm);
+	if (CHECK_SANITY(! pcm->setup)) {
+		SNDMSG("PCM not set up");
+		return -EIO;
+	}
+	return samples * pcm->sample_bits / 8;
+}
+
+/**
+ * \brief Add an async handler for a PCM
+ * \param handler Returned handler handle
+ * \param pcm PCM handle
+ * \param callback Callback function
+ * \param private_data Callback private data
+ * \return 0 otherwise a negative error code on failure
+ *
+ * The asynchronous callback is called when period boundary elapses.
+ */
+int snd_async_add_pcm_handler(snd_async_handler_t **handler, snd_pcm_t *pcm, 
+			      snd_async_callback_t callback, void *private_data)
+{
+	int err;
+	int was_empty;
+	snd_async_handler_t *h;
+	err = snd_async_add_handler(&h, _snd_pcm_async_descriptor(pcm),
+				    callback, private_data);
+	if (err < 0)
+		return err;
+	h->type = SND_ASYNC_HANDLER_PCM;
+	h->u.pcm = pcm;
+	was_empty = list_empty(&pcm->async_handlers);
+	list_add_tail(&h->hlist, &pcm->async_handlers);
+	if (was_empty) {
+		err = snd_pcm_async(pcm, snd_async_handler_get_signo(h), getpid());
+		if (err < 0) {
+			snd_async_del_handler(h);
+			return err;
+		}
+	}
+	*handler = h;
+	return 0;
+}
+
+/**
+ * \brief Return PCM handle related to an async handler
+ * \param handler Async handler handle
+ * \return PCM handle
+ */
+snd_pcm_t *snd_async_handler_get_pcm(snd_async_handler_t *handler)
+{
+	if (handler->type != SND_ASYNC_HANDLER_PCM) {
+		SNDMSG("invalid handler type %d", handler->type);
+		return NULL;
+	}
+	return handler->u.pcm;
+}
+
+static const char *const build_in_pcms[] = {
+	"adpcm", "alaw", "copy", "dmix", "file", "hooks", "hw", "ladspa", "lfloat",
+	"linear", "meter", "mulaw", "multi", "null", "empty", "plug", "rate", "route", "share",
+	"shm", "dsnoop", "dshare", "asym", "iec958", "softvol", "mmap_emul",
+	NULL
+};
+
+static int snd_pcm_open_conf(snd_pcm_t **pcmp, const char *name,
+			     snd_config_t *pcm_root, snd_config_t *pcm_conf,
+			     snd_pcm_stream_t stream, int mode)
+{
+	const char *str;
+	char *buf = NULL, *buf1 = NULL;
+	int err;
+	snd_config_t *conf, *type_conf = NULL, *tmp;
+	snd_config_iterator_t i, next;
+	const char *id;
+	const char *lib = NULL, *open_name = NULL;
+	int (*open_func)(snd_pcm_t **, const char *, 
+			 snd_config_t *, snd_config_t *, 
+			 snd_pcm_stream_t, int) = NULL;
+#ifndef PIC
+	extern void *snd_pcm_open_symbols(void);
+#endif
+	if (snd_config_get_type(pcm_conf) != SND_CONFIG_TYPE_COMPOUND) {
+		char *val;
+		id = NULL;
+		snd_config_get_id(pcm_conf, &id);
+		val = NULL;
+		snd_config_get_ascii(pcm_conf, &val);
+		SNDERR("Invalid type for PCM %s%sdefinition (id: %s, value: %s)", name ? name : "", name ? " " : "", id, val);
+		free(val);
+		return -EINVAL;
+	}
+	err = snd_config_search(pcm_conf, "type", &conf);
+	if (err < 0) {
+		SNDERR("type is not defined");
+		return err;
+	}
+	err = snd_config_get_id(conf, &id);
+	if (err < 0) {
+		SNDERR("unable to get id");
+		return err;
+	}
+	err = snd_config_get_string(conf, &str);
+	if (err < 0) {
+		SNDERR("Invalid type for %s", id);
+		return err;
+	}
+	err = snd_config_search_definition(pcm_root, "pcm_type", str, &type_conf);
+	if (err >= 0) {
+		if (snd_config_get_type(type_conf) != SND_CONFIG_TYPE_COMPOUND) {
+			SNDERR("Invalid type for PCM type %s definition", str);
+			goto _err;
+		}
+		snd_config_for_each(i, next, type_conf) {
+			snd_config_t *n = snd_config_iterator_entry(i);
+			const char *id;
+			if (snd_config_get_id(n, &id) < 0)
+				continue;
+			if (strcmp(id, "comment") == 0)
+				continue;
+			if (strcmp(id, "lib") == 0) {
+				err = snd_config_get_string(n, &lib);
+				if (err < 0) {
+					SNDERR("Invalid type for %s", id);
+					goto _err;
+				}
+				continue;
+			}
+			if (strcmp(id, "open") == 0) {
+				err = snd_config_get_string(n, &open_name);
+				if (err < 0) {
+					SNDERR("Invalid type for %s", id);
+					goto _err;
+				}
+				continue;
+			}
+			SNDERR("Unknown field %s", id);
+			err = -EINVAL;
+			goto _err;
+		}
+	}
+	if (!open_name) {
+		buf = malloc(strlen(str) + 32);
+		if (buf == NULL) {
+			err = -ENOMEM;
+			goto _err;
+		}
+		open_name = buf;
+		sprintf(buf, "_snd_pcm_%s_open", str);
+	}
+	if (!lib) {
+		const char *const *build_in = build_in_pcms;
+		while (*build_in) {
+			if (!strcmp(*build_in, str))
+				break;
+			build_in++;
+		}
+		if (*build_in == NULL) {
+			buf1 = malloc(strlen(str) + sizeof(ALSA_PLUGIN_DIR) + 32);
+			if (buf1 == NULL) {
+				err = -ENOMEM;
+				goto _err;
+			}
+			lib = buf1;
+			sprintf(buf1, "%s/libasound_module_pcm_%s.so", ALSA_PLUGIN_DIR, str);
+		}
+	}
+#ifndef PIC
+	snd_pcm_open_symbols();	/* this call is for static linking only */
+#endif
+	open_func = snd_dlobj_cache_get(lib, open_name,
+			SND_DLSYM_VERSION(SND_PCM_DLSYM_VERSION), 1);
+	if (open_func) {
+		err = open_func(pcmp, name, pcm_root, pcm_conf, stream, mode);
+		if (err >= 0) {
+			(*pcmp)->open_func = open_func;
+			err = 0;
+		} else {
+			snd_dlobj_cache_put(open_func);
+		}
+	} else {
+		err = -ENXIO;
+	}
+	if (err >= 0) {
+		err = snd_config_search(pcm_root, "defaults.pcm.compat", &tmp);
+		if (err >= 0) {
+			long i;
+			if (snd_config_get_integer(tmp, &i) >= 0) {
+				if (i > 0)
+					(*pcmp)->compat = 1;
+			}
+		} else {
+			char *str = getenv("LIBASOUND_COMPAT");
+			if (str && *str)
+				(*pcmp)->compat = 1;
+		}
+		err = snd_config_search(pcm_root, "defaults.pcm.minperiodtime", &tmp);
+		if (err >= 0)
+			snd_config_get_integer(tmp, &(*pcmp)->minperiodtime);
+		err = 0;
+	}
+       _err:
+	if (type_conf)
+		snd_config_delete(type_conf);
+	free(buf);
+	free(buf1);
+	return err;
+}
+
+static int snd_pcm_open_noupdate(snd_pcm_t **pcmp, snd_config_t *root,
+				 const char *name, snd_pcm_stream_t stream,
+				 int mode, int hop)
+{
+	int err;
+	snd_config_t *pcm_conf;
+	const char *str;
+
+	err = snd_config_search_definition(root, "pcm", name, &pcm_conf);
+	if (err < 0) {
+		SNDERR("Unknown PCM %s", name);
+		return err;
+	}
+	if (snd_config_get_string(pcm_conf, &str) >= 0)
+		err = snd_pcm_open_noupdate(pcmp, root, str, stream, mode,
+					    hop + 1);
+	else {
+		snd_config_set_hop(pcm_conf, hop);
+		err = snd_pcm_open_conf(pcmp, name, root, pcm_conf, stream, mode);
+	}
+	snd_config_delete(pcm_conf);
+	return err;
+}
+
+/**
+ * \brief Opens a PCM
+ * \param pcmp Returned PCM handle
+ * \param name ASCII identifier of the PCM handle
+ * \param stream Wanted stream
+ * \param mode Open mode (see #SND_PCM_NONBLOCK, #SND_PCM_ASYNC)
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_pcm_open(snd_pcm_t **pcmp, const char *name, 
+		 snd_pcm_stream_t stream, int mode)
+{
+	int err;
+	assert(pcmp && name);
+	err = snd_config_update();
+	if (err < 0)
+		return err;
+	return snd_pcm_open_noupdate(pcmp, snd_config, name, stream, mode, 0);
+}
+
+/**
+ * \brief Opens a PCM using local configuration
+ * \param pcmp Returned PCM handle
+ * \param name ASCII identifier of the PCM handle
+ * \param stream Wanted stream
+ * \param mode Open mode (see #SND_PCM_NONBLOCK, #SND_PCM_ASYNC)
+ * \param lconf Local configuration
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_pcm_open_lconf(snd_pcm_t **pcmp, const char *name, 
+		       snd_pcm_stream_t stream, int mode,
+		       snd_config_t *lconf)
+{
+	assert(pcmp && name && lconf);
+	return snd_pcm_open_noupdate(pcmp, lconf, name, stream, mode, 0);
+}
+
+/**
+ * \brief Opens a fallback PCM
+ * \param pcmp Returned PCM handle
+ * \param root Configuration root
+ * \param name ASCII identifier of the PCM handle
+ * \param orig_name The original ASCII name
+ * \param stream Wanted stream
+ * \param mode Open mode (see #SND_PCM_NONBLOCK, #SND_PCM_ASYNC)
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_pcm_open_fallback(snd_pcm_t **pcmp, snd_config_t *root,
+			  const char *name, const char *orig_name,
+			  snd_pcm_stream_t stream, int mode)
+{
+	int err;
+	assert(pcmp && name && root);
+	err = snd_pcm_open_noupdate(pcmp, root, name, stream, mode, 0);
+	if (err >= 0) {
+		free((*pcmp)->name);
+		(*pcmp)->name = orig_name ? strdup(orig_name) : NULL;
+	}
+	return err;
+}
+
+#ifndef DOC_HIDDEN
+int snd_pcm_new(snd_pcm_t **pcmp, snd_pcm_type_t type, const char *name,
+		snd_pcm_stream_t stream, int mode)
+{
+	snd_pcm_t *pcm;
+	pcm = calloc(1, sizeof(*pcm));
+	if (!pcm)
+		return -ENOMEM;
+	pcm->type = type;
+	if (name)
+		pcm->name = strdup(name);
+	pcm->stream = stream;
+	pcm->mode = mode;
+	pcm->poll_fd_count = 1;
+	pcm->poll_fd = -1;
+	pcm->op_arg = pcm;
+	pcm->fast_op_arg = pcm;
+	INIT_LIST_HEAD(&pcm->async_handlers);
+	*pcmp = pcm;
+	return 0;
+}
+
+int snd_pcm_free(snd_pcm_t *pcm)
+{
+	assert(pcm);
+	free(pcm->name);
+	free(pcm->hw.link_dst);
+	free(pcm->appl.link_dst);
+	snd_dlobj_cache_put(pcm->open_func);
+	free(pcm);
+	return 0;
+}
+
+int snd_pcm_open_named_slave(snd_pcm_t **pcmp, const char *name,
+			     snd_config_t *root,
+			     snd_config_t *conf, snd_pcm_stream_t stream,
+			     int mode, snd_config_t *parent_conf)
+{
+	const char *str;
+	int hop;
+
+	if ((hop = snd_config_check_hop(parent_conf)) < 0)
+		return hop;
+	if (snd_config_get_string(conf, &str) >= 0)
+		return snd_pcm_open_noupdate(pcmp, root, str, stream, mode,
+					     hop + 1);
+	return snd_pcm_open_conf(pcmp, name, root, conf, stream, mode);
+}
+#endif
+
+/**
+ * \brief Wait for a PCM to become ready
+ * \param pcm PCM handle
+ * \param timeout maximum time in milliseconds to wait,
+ *        a negative value means infinity
+ * \return a positive value on success otherwise a negative error code
+ *         (-EPIPE for the xrun and -ESTRPIPE for the suspended status,
+ *          others for general errors) 
+ * \retval 0 timeout occurred
+ * \retval 1 PCM stream is ready for I/O
+ */
+int snd_pcm_wait(snd_pcm_t *pcm, int timeout)
+{
+	if (snd_pcm_mmap_avail(pcm) >= pcm->avail_min) {
+		/* check more precisely */
+		switch (snd_pcm_state(pcm)) {
+		case SND_PCM_STATE_XRUN:
+			return -EPIPE;
+		case SND_PCM_STATE_SUSPENDED:
+			return -ESTRPIPE;
+		case SND_PCM_STATE_DISCONNECTED:
+			return -ENODEV;
+		default:
+			return 1;
+		}
+	}
+	return snd_pcm_wait_nocheck(pcm, timeout);
+}
+
+#ifndef DOC_HIDDEN
+/* 
+ * like snd_pcm_wait() but doesn't check mmap_avail before calling poll()
+ *
+ * used in drain code in some plugins
+ */
+int snd_pcm_wait_nocheck(snd_pcm_t *pcm, int timeout)
+{
+	struct pollfd *pfd;
+	unsigned short revents = 0;
+	int npfds, err, err_poll;
+	
+	npfds = snd_pcm_poll_descriptors_count(pcm);
+	if (npfds <= 0 || npfds >= 16) {
+		SNDERR("Invalid poll_fds %d\n", npfds);
+		return -EIO;
+	}
+	pfd = alloca(sizeof(*pfd) * npfds);
+	err = snd_pcm_poll_descriptors(pcm, pfd, npfds);
+	if (err < 0)
+		return err;
+	if (err != npfds) {
+		SNDMSG("invalid poll descriptors %d\n", err);
+		return -EIO;
+	}
+	do {
+		err_poll = poll(pfd, npfds, timeout);
+		if (err_poll < 0) {
+		        if (errno == EINTR)
+		                continue;
+			return -errno;
+                }
+		if (! err_poll)
+			break;
+		err = snd_pcm_poll_descriptors_revents(pcm, pfd, npfds, &revents);
+		if (err < 0)
+			return err;
+		if (revents & (POLLERR | POLLNVAL)) {
+			/* check more precisely */
+			switch (snd_pcm_state(pcm)) {
+			case SND_PCM_STATE_XRUN:
+				return -EPIPE;
+			case SND_PCM_STATE_SUSPENDED:
+				return -ESTRPIPE;
+			case SND_PCM_STATE_DISCONNECTED:
+				return -ENODEV;
+			default:
+				return -EIO;
+			}
+		}
+	} while (!(revents & (POLLIN | POLLOUT)));
+#if 0 /* very useful code to test poll related problems */
+	{
+		snd_pcm_sframes_t avail_update;
+		snd_pcm_hwsync(pcm);
+		avail_update = snd_pcm_avail_update(pcm);
+		if (avail_update < (snd_pcm_sframes_t)pcm->avail_min) {
+			printf("*** snd_pcm_wait() FATAL ERROR!!!\n");
+			printf("avail_min = %li, avail_update = %li\n", pcm->avail_min, avail_update);
+		}
+	}
+#endif
+	return err_poll > 0 ? 1 : 0;
+}
+#endif
+
+/**
+ * \brief Return number of frames ready to be read (capture) / written (playback)
+ * \param pcm PCM handle
+ * \return a positive number of frames ready otherwise a negative
+ * error code
+ *
+ * On capture does all the actions needed to transport to application
+ * level all the ready frames across underlying layers.
+ *
+ * The position is not synced with hardware (driver) position in the sound
+ * ring buffer in this function. This function is a light version of
+ * #snd_pcm_avail() .
+ *
+ * Using this function is ideal after poll() or select() when audio
+ * file descriptor made the event and when application expects just period
+ * timing.
+ *
+ * Also this function might be called after #snd_pcm_delay() or
+ * #snd_pcm_hwsync() functions to move private ring buffer pointers
+ * in alsa-lib (the internal plugin chain).
+ */
+snd_pcm_sframes_t snd_pcm_avail_update(snd_pcm_t *pcm)
+{
+	return pcm->fast_ops->avail_update(pcm->fast_op_arg);
+}
+
+/**
+ * \brief Return number of frames ready to be read (capture) / written (playback)
+ * \param pcm PCM handle
+ * \return a positive number of frames ready otherwise a negative
+ * error code
+ *
+ * On capture does all the actions needed to transport to application
+ * level all the ready frames across underlying layers.
+ *
+ * The position is synced with hardware (driver) position in the sound
+ * ring buffer in this functions.
+ */
+snd_pcm_sframes_t snd_pcm_avail(snd_pcm_t *pcm)
+{
+	int err;
+
+	assert(pcm);
+	if (CHECK_SANITY(! pcm->setup)) {
+		SNDMSG("PCM not set up");
+		return -EIO;
+	}
+	err = pcm->fast_ops->hwsync(pcm->fast_op_arg);
+	if (err < 0)
+		return err;
+	return pcm->fast_ops->avail_update(pcm->fast_op_arg);
+}
+
+/**
+ * \brief Combine snd_pcm_avail and snd_pcm_delay functions
+ * \param pcm PCM handle
+ * \param availp Number of available frames in the ring buffer
+ * \param delayp Total I/O latency in frames
+ * \return zero on success otherwise a negative error code
+ *
+ * The avail and delay values retuned are in sync.
+ */
+int snd_pcm_avail_delay(snd_pcm_t *pcm,
+			snd_pcm_sframes_t *availp,
+			snd_pcm_sframes_t *delayp)
+{
+	snd_pcm_sframes_t sf;
+	int err;
+
+	assert(pcm && availp && delayp);
+	if (CHECK_SANITY(! pcm->setup)) {
+		SNDMSG("PCM not set up");
+		return -EIO;
+	}
+	err = pcm->fast_ops->hwsync(pcm->fast_op_arg);
+	if (err < 0)
+		return err;
+	sf = pcm->fast_ops->avail_update(pcm->fast_op_arg);
+	if (sf < 0)
+		return (int)sf;
+	err = pcm->fast_ops->delay(pcm->fast_op_arg, delayp);
+	if (err < 0)
+		return err;
+	*availp = sf;
+	return 0;
+}
+
+/**
+ * \brief Silence an area
+ * \param dst_area area specification
+ * \param dst_offset offset in frames inside area
+ * \param samples samples to silence
+ * \param format PCM sample format
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_pcm_area_silence(const snd_pcm_channel_area_t *dst_area, snd_pcm_uframes_t dst_offset,
+			 unsigned int samples, snd_pcm_format_t format)
+{
+	/* FIXME: sub byte resolution and odd dst_offset */
+	char *dst;
+	unsigned int dst_step;
+	int width;
+	u_int64_t silence;
+	if (!dst_area->addr)
+		return 0;
+	dst = snd_pcm_channel_area_addr(dst_area, dst_offset);
+	width = snd_pcm_format_physical_width(format);
+	silence = snd_pcm_format_silence_64(format);
+	if (dst_area->step == (unsigned int) width) {
+		unsigned int dwords = samples * width / 64;
+		u_int64_t *dstp = (u_int64_t *)dst;
+		samples -= dwords * 64 / width;
+		while (dwords-- > 0)
+			*dstp++ = silence;
+		if (samples == 0)
+			return 0;
+	}
+	dst_step = dst_area->step / 8;
+	switch (width) {
+	case 4: {
+		u_int8_t s0 = silence & 0xf0;
+		u_int8_t s1 = silence & 0x0f;
+		int dstbit = dst_area->first % 8;
+		int dstbit_step = dst_area->step % 8;
+		while (samples-- > 0) {
+			if (dstbit) {
+				*dst &= 0xf0;
+				*dst |= s1;
+			} else {
+				*dst &= 0x0f;
+				*dst |= s0;
+			}
+			dst += dst_step;
+			dstbit += dstbit_step;
+			if (dstbit == 8) {
+				dst++;
+				dstbit = 0;
+			}
+		}
+		break;
+	}
+	case 8: {
+		u_int8_t sil = silence;
+		while (samples-- > 0) {
+			*dst = sil;
+			dst += dst_step;
+		}
+		break;
+	}
+	case 16: {
+		u_int16_t sil = silence;
+		while (samples-- > 0) {
+			*(u_int16_t*)dst = sil;
+			dst += dst_step;
+		}
+		break;
+	}
+	case 24:
+#ifdef SNDRV_LITTLE_ENDIAN
+		*(dst + 0) = silence >> 0;
+		*(dst + 1) = silence >> 8;
+		*(dst + 2) = silence >> 16;
+#else
+		*(dst + 2) = silence >> 0;
+		*(dst + 1) = silence >> 8;
+		*(dst + 0) = silence >> 16;
+#endif
+		break;
+	case 32: {
+		u_int32_t sil = silence;
+		while (samples-- > 0) {
+			*(u_int32_t*)dst = sil;
+			dst += dst_step;
+		}
+		break;
+	}
+	case 64: {
+		while (samples-- > 0) {
+			*(u_int64_t*)dst = silence;
+			dst += dst_step;
+		}
+		break;
+	}
+	default:
+		SNDMSG("invalid format width %d", width);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+/**
+ * \brief Silence one or more areas
+ * \param dst_areas areas specification (one for each channel)
+ * \param dst_offset offset in frames inside area
+ * \param channels channels count
+ * \param frames frames to silence
+ * \param format PCM sample format
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_pcm_areas_silence(const snd_pcm_channel_area_t *dst_areas, snd_pcm_uframes_t dst_offset,
+			  unsigned int channels, snd_pcm_uframes_t frames, snd_pcm_format_t format)
+{
+	int width = snd_pcm_format_physical_width(format);
+	while (channels > 0) {
+		void *addr = dst_areas->addr;
+		unsigned int step = dst_areas->step;
+		const snd_pcm_channel_area_t *begin = dst_areas;
+		int channels1 = channels;
+		unsigned int chns = 0;
+		int err;
+		while (1) {
+			channels1--;
+			chns++;
+			dst_areas++;
+			if (channels1 == 0 ||
+			    dst_areas->addr != addr ||
+			    dst_areas->step != step ||
+			    dst_areas->first != dst_areas[-1].first + width)
+				break;
+		}
+		if (chns > 1 && chns * width == step) {
+			/* Collapse the areas */
+			snd_pcm_channel_area_t d;
+			d.addr = begin->addr;
+			d.first = begin->first;
+			d.step = width;
+			err = snd_pcm_area_silence(&d, dst_offset * chns, frames * chns, format);
+			channels -= chns;
+		} else {
+			err = snd_pcm_area_silence(begin, dst_offset, frames, format);
+			dst_areas = begin + 1;
+			channels--;
+		}
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+
+/**
+ * \brief Copy an area
+ * \param dst_area destination area specification
+ * \param dst_offset offset in frames inside destination area
+ * \param src_area source area specification
+ * \param src_offset offset in frames inside source area
+ * \param samples samples to copy
+ * \param format PCM sample format
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_pcm_area_copy(const snd_pcm_channel_area_t *dst_area, snd_pcm_uframes_t dst_offset,
+		      const snd_pcm_channel_area_t *src_area, snd_pcm_uframes_t src_offset,
+		      unsigned int samples, snd_pcm_format_t format)
+{
+	/* FIXME: sub byte resolution and odd dst_offset */
+	const char *src;
+	char *dst;
+	int width;
+	int src_step, dst_step;
+	if (dst_area == src_area && dst_offset == src_offset)
+		return 0;
+	if (!src_area->addr)
+		return snd_pcm_area_silence(dst_area, dst_offset, samples, format);
+	src = snd_pcm_channel_area_addr(src_area, src_offset);
+	if (!dst_area->addr)
+		return 0;
+	dst = snd_pcm_channel_area_addr(dst_area, dst_offset);
+	width = snd_pcm_format_physical_width(format);
+	if (src_area->step == (unsigned int) width &&
+	    dst_area->step == (unsigned int) width) {
+		size_t bytes = samples * width / 8;
+		samples -= bytes * 8 / width;
+		memcpy(dst, src, bytes);
+		if (samples == 0)
+			return 0;
+	}
+	src_step = src_area->step / 8;
+	dst_step = dst_area->step / 8;
+	switch (width) {
+	case 4: {
+		int srcbit = src_area->first % 8;
+		int srcbit_step = src_area->step % 8;
+		int dstbit = dst_area->first % 8;
+		int dstbit_step = dst_area->step % 8;
+		while (samples-- > 0) {
+			unsigned char srcval;
+			if (srcbit)
+				srcval = *src & 0x0f;
+			else
+				srcval = *src & 0xf0;
+			if (dstbit)
+				*dst &= 0xf0;
+			else
+				*dst &= 0x0f;
+			*dst |= srcval;
+			src += src_step;
+			srcbit += srcbit_step;
+			if (srcbit == 8) {
+				src++;
+				srcbit = 0;
+			}
+			dst += dst_step;
+			dstbit += dstbit_step;
+			if (dstbit == 8) {
+				dst++;
+				dstbit = 0;
+			}
+		}
+		break;
+	}
+	case 8: {
+		while (samples-- > 0) {
+			*dst = *src;
+			src += src_step;
+			dst += dst_step;
+		}
+		break;
+	}
+	case 16: {
+		while (samples-- > 0) {
+			*(u_int16_t*)dst = *(const u_int16_t*)src;
+			src += src_step;
+			dst += dst_step;
+		}
+		break;
+	}
+	case 24:
+		while (samples-- > 0) {
+			*(dst + 0) = *(src + 0);
+			*(dst + 1) = *(src + 1);
+			*(dst + 2) = *(src + 2);
+			src += src_step;
+			dst += dst_step;
+		}
+		break;
+	case 32: {
+		while (samples-- > 0) {
+			*(u_int32_t*)dst = *(const u_int32_t*)src;
+			src += src_step;
+			dst += dst_step;
+		}
+		break;
+	}
+	case 64: {
+		while (samples-- > 0) {
+			*(u_int64_t*)dst = *(const u_int64_t*)src;
+			src += src_step;
+			dst += dst_step;
+		}
+		break;
+	}
+	default:
+		SNDMSG("invalid format width %d", width);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+/**
+ * \brief Copy one or more areas
+ * \param dst_areas destination areas specification (one for each channel)
+ * \param dst_offset offset in frames inside destination area
+ * \param src_areas source areas specification (one for each channel)
+ * \param src_offset offset in frames inside source area
+ * \param channels channels count
+ * \param frames frames to copy
+ * \param format PCM sample format
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_pcm_areas_copy(const snd_pcm_channel_area_t *dst_areas, snd_pcm_uframes_t dst_offset,
+		       const snd_pcm_channel_area_t *src_areas, snd_pcm_uframes_t src_offset,
+		       unsigned int channels, snd_pcm_uframes_t frames, snd_pcm_format_t format)
+{
+	int width = snd_pcm_format_physical_width(format);
+	assert(dst_areas);
+	assert(src_areas);
+	if (! channels) {
+		SNDMSG("invalid channels %d", channels);
+		return -EINVAL;
+	}
+	if (! frames) {
+		SNDMSG("invalid frames %ld", frames);
+		return -EINVAL;
+	}
+	while (channels > 0) {
+		unsigned int step = src_areas->step;
+		void *src_addr = src_areas->addr;
+		const snd_pcm_channel_area_t *src_start = src_areas;
+		void *dst_addr = dst_areas->addr;
+		const snd_pcm_channel_area_t *dst_start = dst_areas;
+		int channels1 = channels;
+		unsigned int chns = 0;
+		while (dst_areas->step == step) {
+			channels1--;
+			chns++;
+			src_areas++;
+			dst_areas++;
+			if (channels1 == 0 ||
+			    src_areas->step != step ||
+			    src_areas->addr != src_addr ||
+			    dst_areas->addr != dst_addr ||
+			    src_areas->first != src_areas[-1].first + width ||
+			    dst_areas->first != dst_areas[-1].first + width)
+				break;
+		}
+		if (chns > 1 && chns * width == step) {
+			/* Collapse the areas */
+			snd_pcm_channel_area_t s, d;
+			s.addr = src_start->addr;
+			s.first = src_start->first;
+			s.step = width;
+			d.addr = dst_start->addr;
+			d.first = dst_start->first;
+			d.step = width;
+			snd_pcm_area_copy(&d, dst_offset * chns,
+					  &s, src_offset * chns, 
+					  frames * chns, format);
+			channels -= chns;
+		} else {
+			snd_pcm_area_copy(dst_start, dst_offset,
+					  src_start, src_offset,
+					  frames, format);
+			src_areas = src_start + 1;
+			dst_areas = dst_start + 1;
+			channels--;
+		}
+	}
+	return 0;
+}
+
+static void dump_one_param(snd_pcm_hw_params_t *params, unsigned int k, snd_output_t *out)
+{
+	snd_output_printf(out, "%s: ", snd_pcm_hw_param_name(k));
+	snd_pcm_hw_param_dump(params, k, out);
+	snd_output_putc(out, '\n');
+}
+
+/**
+ * \brief Dump a PCM hardware configuration space
+ * \param params Configuration space
+ * \param out Output handle
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_pcm_hw_params_dump(snd_pcm_hw_params_t *params, snd_output_t *out)
+{
+	unsigned int k;
+	for (k = SND_PCM_HW_PARAM_FIRST_MASK; k <= SND_PCM_HW_PARAM_LAST_MASK; k++)
+		dump_one_param(params, k, out);
+	for (k = SND_PCM_HW_PARAM_FIRST_INTERVAL; k <= SND_PCM_HW_PARAM_LAST_INTERVAL; k++)
+		dump_one_param(params, k, out);
+	return 0;
+}
+
+/**
+ * \brief Check if hardware supports sample-resolution mmap for given configuration
+ * \param params Configuration space
+ * \retval 0 Hardware doesn't support sample-resolution mmap
+ * \retval 1 Hardware supports sample-resolution mmap
+ *
+ * This function should only be called when the configuration space
+ * contains a single configuration. Call #snd_pcm_hw_params to choose
+ * a single configuration from the configuration space.
+ */
+int snd_pcm_hw_params_can_mmap_sample_resolution(const snd_pcm_hw_params_t *params)
+{
+	assert(params);
+	if (CHECK_SANITY(params->info == ~0U)) {
+		SNDMSG("invalid PCM info field");
+		return 0; /* FIXME: should be a negative error? */
+	}
+	return !!(params->info & SNDRV_PCM_INFO_MMAP_VALID);
+}
+
+/**
+ * \brief Check if hardware does double buffering for start/stop for given configuration
+ * \param params Configuration space
+ * \retval 0 Hardware doesn't do double buffering for start/stop
+ * \retval 1 Hardware does double buffering for start/stop
+ *
+ * This function should only be called when the configuration space
+ * contains a single configuration. Call #snd_pcm_hw_params to choose
+ * a single configuration from the configuration space.
+ */
+int snd_pcm_hw_params_is_double(const snd_pcm_hw_params_t *params)
+{
+	assert(params);
+	if (CHECK_SANITY(params->info == ~0U)) {
+		SNDMSG("invalid PCM info field");
+		return 0; /* FIXME: should be a negative error? */
+	}
+	return !!(params->info & SNDRV_PCM_INFO_DOUBLE);
+}
+
+/**
+ * \brief Check if hardware does double buffering for data transfers for given configuration
+ * \param params Configuration space
+ * \retval 0 Hardware doesn't do double buffering for data transfers
+ * \retval 1 Hardware does double buffering for data transfers
+ *
+ * This function should only be called when the configuration space
+ * contains a single configuration. Call #snd_pcm_hw_params to choose
+ * a single configuration from the configuration space.
+ */
+int snd_pcm_hw_params_is_batch(const snd_pcm_hw_params_t *params)
+{
+	assert(params);
+	if (CHECK_SANITY(params->info == ~0U)) {
+		SNDMSG("invalid PCM info field");
+		return 0; /* FIXME: should be a negative error? */
+	}
+	return !!(params->info & SNDRV_PCM_INFO_BATCH);
+}
+
+/**
+ * \brief Check if hardware does block transfers for samples for given configuration
+ * \param params Configuration space
+ * \retval 0 Hardware doesn't block transfers
+ * \retval 1 Hardware does block transfers
+ *
+ * This function should only be called when the configuration space
+ * contains a single configuration. Call #snd_pcm_hw_params to choose
+ * a single configuration from the configuration space.
+ */
+int snd_pcm_hw_params_is_block_transfer(const snd_pcm_hw_params_t *params)
+{
+	assert(params);
+	if (CHECK_SANITY(params->info == ~0U)) {
+		SNDMSG("invalid PCM info field");
+		return 0; /* FIXME: should be a negative error? */
+	}
+	return !!(params->info & SNDRV_PCM_INFO_BLOCK_TRANSFER);
+}
+
+/**
+ * \brief Check if timestamps are monotonic for given configuration
+ * \param params Configuration space
+ * \retval 0 Device doesn't do monotomic timestamps
+ * \retval 1 Device does monotonic timestamps
+ *
+ * This function should only be called when the configuration space
+ * contains a single configuration. Call #snd_pcm_hw_params to choose
+ * a single configuration from the configuration space.
+ */
+int snd_pcm_hw_params_is_monotonic(const snd_pcm_hw_params_t *params)
+{
+	assert(params);
+	if (CHECK_SANITY(params->info == ~0U)) {
+		SNDMSG("invalid PCM info field");
+		return 0; /* FIXME: should be a negative error? */
+	}
+	return !!(params->info & SND_PCM_INFO_MONOTONIC);
+}
+
+/**
+ * \brief Check if hardware supports overrange detection
+ * \param params Configuration space
+ * \retval 0 Hardware doesn't support overrange detection
+ * \retval 1 Hardware supports overrange detection
+ *
+ * This function should only be called when the configuration space
+ * contains a single configuration. Call #snd_pcm_hw_params to choose
+ * a single configuration from the configuration space.
+ */
+int snd_pcm_hw_params_can_overrange(const snd_pcm_hw_params_t *params)
+{
+	assert(params);
+	if (CHECK_SANITY(params->info == ~0U)) {
+		SNDMSG("invalid PCM info field");
+		return 0; /* FIXME: should be a negative error? */
+	}
+	return !!(params->info & SNDRV_PCM_INFO_OVERRANGE);
+}
+
+/**
+ * \brief Check if hardware supports pause
+ * \param params Configuration space
+ * \retval 0 Hardware doesn't support pause
+ * \retval 1 Hardware supports pause
+ *
+ * This function should only be called when the configuration space
+ * contains a single configuration. Call #snd_pcm_hw_params to choose
+ * a single configuration from the configuration space.
+ */
+int snd_pcm_hw_params_can_pause(const snd_pcm_hw_params_t *params)
+{
+	assert(params);
+	if (CHECK_SANITY(params->info == ~0U)) {
+		SNDMSG("invalid PCM info field");
+		return 0; /* FIXME: should be a negative error? */
+	}
+	return !!(params->info & SNDRV_PCM_INFO_PAUSE);
+}
+
+/**
+ * \brief Check if hardware supports resume
+ * \param params Configuration space
+ * \retval 0 Hardware doesn't support resume
+ * \retval 1 Hardware supports resume
+ *
+ * This function should only be called when the configuration space
+ * contains a single configuration. Call #snd_pcm_hw_params to choose
+ * a single configuration from the configuration space.
+ */
+int snd_pcm_hw_params_can_resume(const snd_pcm_hw_params_t *params)
+{
+	assert(params);
+	if (CHECK_SANITY(params->info == ~0U)) {
+		SNDMSG("invalid PCM info field");
+		return 0; /* FIXME: should be a negative error? */
+	}
+	return !!(params->info & SNDRV_PCM_INFO_RESUME);
+}
+
+/**
+ * \brief Check if hardware does half-duplex only
+ * \param params Configuration space
+ * \retval 0 Hardware doesn't do half-duplex
+ * \retval 1 Hardware does half-duplex
+ *
+ * This function should only be called when the configuration space
+ * contains a single configuration. Call #snd_pcm_hw_params to choose
+ * a single configuration from the configuration space.
+ */
+int snd_pcm_hw_params_is_half_duplex(const snd_pcm_hw_params_t *params)
+{
+	assert(params);
+	if (CHECK_SANITY(params->info == ~0U)) {
+		SNDMSG("invalid PCM info field");
+		return 0; /* FIXME: should be a negative error? */
+	}
+	return !!(params->info & SNDRV_PCM_INFO_HALF_DUPLEX);
+}
+
+/**
+ * \brief Check if hardware does joint-duplex (playback and capture are somewhat correlated)
+ * \param params Configuration space
+ * \retval 0 Hardware doesn't do joint-duplex
+ * \retval 1 Hardware does joint-duplex
+ *
+ * This function should only be called when the configuration space
+ * contains a single configuration. Call #snd_pcm_hw_params to choose
+ * a single configuration from the configuration space.
+ */
+int snd_pcm_hw_params_is_joint_duplex(const snd_pcm_hw_params_t *params)
+{
+	assert(params);
+	if (CHECK_SANITY(params->info == ~0U)) {
+		SNDMSG("invalid PCM info field");
+		return 0; /* FIXME: should be a negative error? */
+	}
+	return !!(params->info & SNDRV_PCM_INFO_JOINT_DUPLEX);
+}
+
+/**
+ * \brief Check if hardware supports synchronized start with sample resolution
+ * \param params Configuration space
+ * \retval 0 Hardware doesn't support synchronized start
+ * \retval 1 Hardware supports synchronized start
+ *
+ * This function should only be called when the configuration space
+ * contains a single configuration. Call #snd_pcm_hw_params to choose
+ * a single configuration from the configuration space.
+ */
+int snd_pcm_hw_params_can_sync_start(const snd_pcm_hw_params_t *params)
+{
+	assert(params);
+	if (CHECK_SANITY(params->info == ~0U)) {
+		SNDMSG("invalid PCM info field");
+		return 0; /* FIXME: should be a negative error? */
+	}
+	return !!(params->info & SNDRV_PCM_INFO_SYNC_START);
+}
+
+/**
+ * \brief Check if hardware can disable period wakeups
+ * \param params Configuration space
+ * \retval 0 Hardware cannot disable period wakeups
+ * \retval 1 Hardware can disable period wakeups
+ */
+int snd_pcm_hw_params_can_disable_period_wakeup(const snd_pcm_hw_params_t *params)
+{
+	assert(params);
+	if (CHECK_SANITY(params->info == ~0U)) {
+		SNDMSG("invalid PCM info field");
+		return 0; /* FIXME: should be a negative error? */
+	}
+	return !!(params->info & SNDRV_PCM_INFO_NO_PERIOD_WAKEUP);
+}
+
+/**
+ * \brief Get rate exact info from a configuration space
+ * \param params Configuration space
+ * \param rate_num Pointer to returned rate numerator
+ * \param rate_den Pointer to returned rate denominator
+ * \return 0 otherwise a negative error code if the info is not available
+ *
+ * This function should only be called when the configuration space
+ * contains a single configuration. Call #snd_pcm_hw_params to choose
+ * a single configuration from the configuration space.
+ */
+int snd_pcm_hw_params_get_rate_numden(const snd_pcm_hw_params_t *params,
+				      unsigned int *rate_num, unsigned int *rate_den)
+{
+	assert(params);
+	if (CHECK_SANITY(params->rate_den == 0)) {
+		SNDMSG("invalid rate_den value");
+		return -EINVAL;
+	}
+	*rate_num = params->rate_num;
+	*rate_den = params->rate_den;
+	return 0;
+}
+
+/**
+ * \brief Get sample resolution info from a configuration space
+ * \param params Configuration space
+ * \return signification bits in sample otherwise a negative error code if the info is not available
+ *
+ * This function should only be called when the configuration space
+ * contains a single configuration. Call #snd_pcm_hw_params to choose
+ * a single configuration from the configuration space.
+ */
+int snd_pcm_hw_params_get_sbits(const snd_pcm_hw_params_t *params)
+{
+	assert(params);
+	if (CHECK_SANITY(params->msbits == 0)) {
+		SNDMSG("invalid msbits value");
+		return -EINVAL;
+	}
+	return params->msbits;
+}
+
+/**
+ * \brief Get hardware FIFO size info from a configuration space
+ * \param params Configuration space
+ * \return FIFO size in frames otherwise a negative error code if the info is not available
+ *
+ * This function should only be called when the configuration space
+ * contains a single configuration. Call #snd_pcm_hw_params to choose
+ * a single configuration from the configuration space.
+ */
+int snd_pcm_hw_params_get_fifo_size(const snd_pcm_hw_params_t *params)
+{
+	assert(params);
+	if (CHECK_SANITY(params->info == ~0U)) {
+		SNDMSG("invalid PCM info field");
+		return -EINVAL;
+	}
+	return params->fifo_size;
+}
+
+/**
+ * \brief Fill params with a full configuration space for a PCM
+ * \param pcm PCM handle
+ * \param params Configuration space
+ *
+ * The configuration space will be filled with all possible ranges
+ * for the PCM device.
+ */
+int snd_pcm_hw_params_any(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	_snd_pcm_hw_params_any(params);
+	return snd_pcm_hw_refine(pcm, params);
+}
+
+/**
+ * \brief get size of #snd_pcm_access_mask_t
+ * \return size in bytes
+ */
+size_t snd_pcm_access_mask_sizeof()
+{
+	return sizeof(snd_pcm_access_mask_t);
+}
+
+/**
+ * \brief allocate an empty #snd_pcm_access_mask_t using standard malloc
+ * \param ptr returned pointer
+ * \return 0 on success otherwise negative error code
+ */
+int snd_pcm_access_mask_malloc(snd_pcm_access_mask_t **ptr)
+{
+	assert(ptr);
+	*ptr = calloc(1, sizeof(snd_pcm_access_mask_t));
+	if (!*ptr)
+		return -ENOMEM;
+	return 0;
+}
+
+/**
+ * \brief frees a previously allocated #snd_pcm_access_mask_t
+ * \param obj pointer to object to free
+ */
+void snd_pcm_access_mask_free(snd_pcm_access_mask_t *obj)
+{
+	free(obj);
+}
+
+/**
+ * \brief copy one #snd_pcm_access_mask_t to another
+ * \param dst pointer to destination
+ * \param src pointer to source
+ */
+void snd_pcm_access_mask_copy(snd_pcm_access_mask_t *dst, const snd_pcm_access_mask_t *src)
+{
+	assert(dst && src);
+	*dst = *src;
+}
+
+/**
+ * \brief reset all bits in a #snd_pcm_access_mask_t
+ * \param mask pointer to mask
+ */
+void snd_pcm_access_mask_none(snd_pcm_access_mask_t *mask)
+{
+	snd_mask_none((snd_mask_t *) mask);
+}
+
+/**
+ * \brief set all bits in a #snd_pcm_access_mask_t
+ * \param mask pointer to mask
+ */
+void snd_pcm_access_mask_any(snd_pcm_access_mask_t *mask)
+{
+	snd_mask_any((snd_mask_t *) mask);
+}
+
+/**
+ * \brief test the presence of an access type in a #snd_pcm_access_mask_t
+ * \param mask pointer to mask
+ * \param val access type
+ */
+int snd_pcm_access_mask_test(const snd_pcm_access_mask_t *mask, snd_pcm_access_t val)
+{
+	return snd_mask_test((const snd_mask_t *) mask, (unsigned long) val);
+}
+
+/**
+ * \brief test, if given a #snd_pcm_access_mask_t is empty
+ * \param mask pointer to mask
+ * \retval 0 not empty
+ * \retval 1 empty
+ */
+int snd_pcm_access_mask_empty(const snd_pcm_access_mask_t *mask)
+{
+	return snd_mask_empty((const snd_mask_t *) mask);
+}
+
+/**
+ * \brief make an access type present in a #snd_pcm_access_mask_t
+ * \param mask pointer to mask
+ * \param val access type
+ */
+void snd_pcm_access_mask_set(snd_pcm_access_mask_t *mask, snd_pcm_access_t val)
+{
+	snd_mask_set((snd_mask_t *) mask, (unsigned long) val);
+}
+
+/**
+ * \brief make an access type missing from a #snd_pcm_access_mask_t
+ * \param mask pointer to mask
+ * \param val access type
+ */
+void snd_pcm_access_mask_reset(snd_pcm_access_mask_t *mask, snd_pcm_access_t val)
+{
+	snd_mask_reset((snd_mask_t *) mask, (unsigned long) val);
+}
+
+/**
+ * \brief get size of #snd_pcm_format_mask_t
+ * \return size in bytes
+ */
+size_t snd_pcm_format_mask_sizeof()
+{
+	return sizeof(snd_pcm_format_mask_t);
+}
+
+/**
+ * \brief allocate an empty #snd_pcm_format_mask_t using standard malloc
+ * \param ptr returned pointer
+ * \return 0 on success otherwise negative error code
+ */
+int snd_pcm_format_mask_malloc(snd_pcm_format_mask_t **ptr)
+{
+	assert(ptr);
+	*ptr = calloc(1, sizeof(snd_pcm_format_mask_t));
+	if (!*ptr)
+		return -ENOMEM;
+	return 0;
+}
+
+/**
+ * \brief frees a previously allocated #snd_pcm_format_mask_t
+ * \param obj pointer to object to free
+ */
+void snd_pcm_format_mask_free(snd_pcm_format_mask_t *obj)
+{
+	free(obj);
+}
+
+/**
+ * \brief copy one #snd_pcm_format_mask_t to another
+ * \param dst pointer to destination
+ * \param src pointer to source
+ */
+void snd_pcm_format_mask_copy(snd_pcm_format_mask_t *dst, const snd_pcm_format_mask_t *src)
+{
+	assert(dst && src);
+	*dst = *src;
+}
+
+/**
+ * \brief reset all bits in a #snd_pcm_format_mask_t
+ * \param mask pointer to mask
+ */
+void snd_pcm_format_mask_none(snd_pcm_format_mask_t *mask)
+{
+	snd_mask_none((snd_mask_t *) mask);
+}
+
+/**
+ * \brief set all bits in a #snd_pcm_format_mask_t
+ * \param mask pointer to mask
+ */
+void snd_pcm_format_mask_any(snd_pcm_format_mask_t *mask)
+{
+	snd_mask_any((snd_mask_t *) mask);
+}
+
+/**
+ * \brief test the presence of a format in a #snd_pcm_format_mask_t
+ * \param mask pointer to mask
+ * \param val format
+ */
+int snd_pcm_format_mask_test(const snd_pcm_format_mask_t *mask, snd_pcm_format_t val)
+{
+	return snd_mask_test((const snd_mask_t *) mask, (unsigned long) val);
+}
+
+/**
+ * \brief test, if given a #snd_pcm_format_mask_t is empty
+ * \param mask pointer to mask
+ * \retval 0 not empty
+ * \retval 1 empty
+ */
+int snd_pcm_format_mask_empty(const snd_pcm_format_mask_t *mask)
+{
+	return snd_mask_empty((const snd_mask_t *) mask);
+}
+
+/**
+ * \brief make a format present in a #snd_pcm_format_mask_t
+ * \param mask pointer to mask
+ * \param val format
+ */
+void snd_pcm_format_mask_set(snd_pcm_format_mask_t *mask, snd_pcm_format_t val)
+{
+	snd_mask_set((snd_mask_t *) mask, (unsigned long) val);
+}
+
+/**
+ * \brief make a format missing from a #snd_pcm_format_mask_t
+ * \param mask pointer to mask
+ * \param val format
+ */
+void snd_pcm_format_mask_reset(snd_pcm_format_mask_t *mask, snd_pcm_format_t val)
+{
+	snd_mask_reset((snd_mask_t *) mask, (unsigned long) val);
+}
+
+
+/**
+ * \brief get size of #snd_pcm_subformat_mask_t
+ * \return size in bytes
+ */
+size_t snd_pcm_subformat_mask_sizeof()
+{
+	return sizeof(snd_pcm_subformat_mask_t);
+}
+
+/**
+ * \brief allocate an empty #snd_pcm_subformat_mask_t using standard malloc
+ * \param ptr returned pointer
+ * \return 0 on success otherwise negative error code
+ */
+int snd_pcm_subformat_mask_malloc(snd_pcm_subformat_mask_t **ptr)
+{
+	assert(ptr);
+	*ptr = calloc(1, sizeof(snd_pcm_subformat_mask_t));
+	if (!*ptr)
+		return -ENOMEM;
+	return 0;
+}
+
+/**
+ * \brief frees a previously allocated #snd_pcm_subformat_mask_t
+ * \param obj pointer to object to free
+ */
+void snd_pcm_subformat_mask_free(snd_pcm_subformat_mask_t *obj)
+{
+	free(obj);
+}
+
+/**
+ * \brief copy one #snd_pcm_subformat_mask_t to another
+ * \param dst pointer to destination
+ * \param src pointer to source
+ */
+void snd_pcm_subformat_mask_copy(snd_pcm_subformat_mask_t *dst, const snd_pcm_subformat_mask_t *src)
+{
+	assert(dst && src);
+	*dst = *src;
+}
+
+/**
+ * \brief reset all bits in a #snd_pcm_subformat_mask_t
+ * \param mask pointer to mask
+ */
+void snd_pcm_subformat_mask_none(snd_pcm_subformat_mask_t *mask)
+{
+	snd_mask_none((snd_mask_t *) mask);
+}
+
+/**
+ * \brief set all bits in a #snd_pcm_subformat_mask_t
+ * \param mask pointer to mask
+ */
+void snd_pcm_subformat_mask_any(snd_pcm_subformat_mask_t *mask)
+{
+	snd_mask_any((snd_mask_t *) mask);
+}
+
+/**
+ * \brief test the presence of a subformat in a #snd_pcm_subformat_mask_t
+ * \param mask pointer to mask
+ * \param val subformat
+ */
+int snd_pcm_subformat_mask_test(const snd_pcm_subformat_mask_t *mask, snd_pcm_subformat_t val)
+{
+	return snd_mask_test((const snd_mask_t *) mask, (unsigned long) val);
+}
+
+/**
+ * \brief test, if given a #snd_pcm_subformat_mask_t is empty
+ * \param mask pointer to mask
+ * \retval 0 not empty
+ * \retval 1 empty
+ */
+int snd_pcm_subformat_mask_empty(const snd_pcm_subformat_mask_t *mask)
+{
+	return snd_mask_empty((const snd_mask_t *) mask);
+}
+
+/**
+ * \brief make a subformat present in a #snd_pcm_subformat_mask_t
+ * \param mask pointer to mask
+ * \param val subformat
+ */
+void snd_pcm_subformat_mask_set(snd_pcm_subformat_mask_t *mask, snd_pcm_subformat_t val)
+{
+	snd_mask_set((snd_mask_t *) mask, (unsigned long) val);
+}
+
+/**
+ * \brief make a subformat missing from a #snd_pcm_subformat_mask_t
+ * \param mask pointer to mask
+ * \param val subformat
+ */
+void snd_pcm_subformat_mask_reset(snd_pcm_subformat_mask_t *mask, snd_pcm_subformat_t val)
+{
+	snd_mask_reset((snd_mask_t *) mask, (unsigned long) val);
+}
+
+
+/**
+ * \brief get size of #snd_pcm_hw_params_t
+ * \return size in bytes
+ */
+size_t snd_pcm_hw_params_sizeof()
+{
+	return sizeof(snd_pcm_hw_params_t);
+}
+
+/**
+ * \brief allocate an invalid #snd_pcm_hw_params_t using standard malloc
+ * \param ptr returned pointer
+ * \return 0 on success otherwise negative error code
+ */
+int snd_pcm_hw_params_malloc(snd_pcm_hw_params_t **ptr)
+{
+	assert(ptr);
+	*ptr = calloc(1, sizeof(snd_pcm_hw_params_t));
+	if (!*ptr)
+		return -ENOMEM;
+	return 0;
+}
+
+/**
+ * \brief frees a previously allocated #snd_pcm_hw_params_t
+ * \param obj pointer to object to free
+ */
+void snd_pcm_hw_params_free(snd_pcm_hw_params_t *obj)
+{
+	free(obj);
+}
+
+/**
+ * \brief copy one #snd_pcm_hw_params_t to another
+ * \param dst pointer to destination
+ * \param src pointer to source
+ */
+void snd_pcm_hw_params_copy(snd_pcm_hw_params_t *dst, const snd_pcm_hw_params_t *src)
+{
+	assert(dst && src);
+	*dst = *src;
+}
+
+
+/**
+ * \brief Extract access type from a configuration space
+ * \param params Configuration space
+ * \param access Returned value
+ * \return access type otherwise a negative error code if the configuration space does not contain a single value
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_get_access)(const snd_pcm_hw_params_t *params, snd_pcm_access_t *access)
+#else
+int snd_pcm_hw_params_get_access(const snd_pcm_hw_params_t *params, snd_pcm_access_t *access)
+#endif
+{
+	unsigned int _val;
+	int err = snd_pcm_hw_param_get(params, SND_PCM_HW_PARAM_ACCESS, &_val, NULL);
+	if (err >= 0)
+		*access = _val;
+	return err;
+}
+
+/**
+ * \brief Verify if an access type is available inside a configuration space for a PCM
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param access access type
+ * \return 0 if available a negative error code otherwise
+ */
+int snd_pcm_hw_params_test_access(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_access_t access)
+{
+	return snd_pcm_hw_param_set(pcm, params, SND_TEST, SND_PCM_HW_PARAM_ACCESS, access, 0);
+}
+
+/**
+ * \brief Restrict a configuration space to contain only one access type
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param access access type
+ * \return 0 otherwise a negative error code if configuration space would become empty
+ */
+int snd_pcm_hw_params_set_access(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_access_t access)
+{
+	return snd_pcm_hw_param_set(pcm, params, SND_TRY, SND_PCM_HW_PARAM_ACCESS, access, 0);
+}
+
+/**
+ * \brief Restrict a configuration space to contain only its first access type
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param access Returned first access type
+ * \return 0 otherwise a negative error code
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_set_access_first)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_access_t *access)
+#else
+int snd_pcm_hw_params_set_access_first(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_access_t *access)
+#endif
+{
+	return snd_pcm_hw_param_set_first(pcm, params, SND_PCM_HW_PARAM_ACCESS, access, NULL);
+}
+
+/**
+ * \brief Restrict a configuration space to contain only its last access type
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param access Returned last access type
+ * \return 0 otherwise a negative error code
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_set_access_last)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_access_t *access)
+#else
+int snd_pcm_hw_params_set_access_last(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_access_t *access)
+#endif
+{
+	return snd_pcm_hw_param_set_last(pcm, params, SND_PCM_HW_PARAM_ACCESS, access, NULL);
+}
+
+/**
+ * \brief Restrict a configuration space to contain only a set of access types
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param mask Access mask
+ * \return 0 otherwise a negative error code
+ */
+int snd_pcm_hw_params_set_access_mask(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_access_mask_t *mask)
+{
+	return snd_pcm_hw_param_set_mask(pcm, params, SND_TRY, SND_PCM_HW_PARAM_ACCESS, (snd_mask_t *) mask);
+}
+
+/**
+ * \brief Get access mask from a configuration space
+ * \param params Configuration space
+ * \param mask Returned Access mask
+ */
+int snd_pcm_hw_params_get_access_mask(snd_pcm_hw_params_t *params, snd_pcm_access_mask_t *mask)
+{
+	if (params == NULL || mask == NULL)
+		return -EINVAL;
+	snd_pcm_access_mask_copy(mask, snd_pcm_hw_param_get_mask(params, SND_PCM_HW_PARAM_ACCESS));
+	return 0;
+}
+
+
+/**
+ * \brief Extract format from a configuration space
+ * \param params Configuration space
+ * \param format returned format
+ * \return format otherwise a negative error code if the configuration space does not contain a single value
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_get_format)(const snd_pcm_hw_params_t *params, snd_pcm_format_t *format)
+#else
+int snd_pcm_hw_params_get_format(const snd_pcm_hw_params_t *params, snd_pcm_format_t *format)
+#endif
+{
+	return snd_pcm_hw_param_get(params, SND_PCM_HW_PARAM_FORMAT, (unsigned int *)format, NULL);
+}
+
+/**
+ * \brief Verify if a format is available inside a configuration space for a PCM
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param format format
+ * \return 0 if available a negative error code otherwise
+ */
+int snd_pcm_hw_params_test_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_format_t format)
+{
+	return snd_pcm_hw_param_set(pcm, params, SND_TEST, SND_PCM_HW_PARAM_FORMAT, format, 0);
+}
+
+/**
+ * \brief Restrict a configuration space to contain only one format
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param format format
+ * \return 0 otherwise a negative error code
+ */
+int snd_pcm_hw_params_set_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_format_t format)
+{
+	return snd_pcm_hw_param_set(pcm, params, SND_TRY, SND_PCM_HW_PARAM_FORMAT, format, 0);
+}
+
+/**
+ * \brief Restrict a configuration space to contain only its first format
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param format Returned first format
+ * \return 0 otherwise a negative error code
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_set_format_first)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_format_t *format)
+#else
+int snd_pcm_hw_params_set_format_first(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_format_t *format)
+#endif
+{
+	return snd_pcm_hw_param_set_first(pcm, params, SND_PCM_HW_PARAM_FORMAT, (unsigned int *)format, NULL);
+}
+
+/**
+ * \brief Restrict a configuration space to contain only its last format
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param format Returned last format
+ * \return 0 otherwise a negative error code
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_set_format_last)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_format_t *format)
+#else
+int snd_pcm_hw_params_set_format_last(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_format_t *format)
+#endif
+{
+	return snd_pcm_hw_param_set_last(pcm, params, SND_PCM_HW_PARAM_FORMAT, (unsigned int *)format, NULL);
+}
+
+/**
+ * \brief Restrict a configuration space to contain only a set of formats
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param mask Format mask
+ * \return 0 otherwise a negative error code
+ */
+int snd_pcm_hw_params_set_format_mask(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_format_mask_t *mask)
+{
+	return snd_pcm_hw_param_set_mask(pcm, params, SND_TRY, SND_PCM_HW_PARAM_FORMAT, (snd_mask_t *) mask);
+}
+
+/**
+ * \brief Get format mask from a configuration space
+ * \param params Configuration space
+ * \param mask Returned Format mask
+ */
+void snd_pcm_hw_params_get_format_mask(snd_pcm_hw_params_t *params, snd_pcm_format_mask_t *mask)
+{
+	snd_pcm_format_mask_copy(mask, snd_pcm_hw_param_get_mask(params, SND_PCM_HW_PARAM_FORMAT));
+}
+
+
+/**
+ * \brief Extract subformat from a configuration space
+ * \param params Configuration space
+ * \param subformat Returned subformat value
+ * \return subformat otherwise a negative error code if the configuration space does not contain a single value
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_get_subformat)(const snd_pcm_hw_params_t *params, snd_pcm_subformat_t *subformat)
+#else
+int snd_pcm_hw_params_get_subformat(const snd_pcm_hw_params_t *params, snd_pcm_subformat_t *subformat)
+#endif
+{
+	return snd_pcm_hw_param_get(params, SND_PCM_HW_PARAM_SUBFORMAT, subformat, NULL);
+}
+
+/**
+ * \brief Verify if a subformat is available inside a configuration space for a PCM
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param subformat subformat value
+ * \return 0 if available a negative error code otherwise
+ */
+int snd_pcm_hw_params_test_subformat(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_subformat_t subformat)
+{
+	return snd_pcm_hw_param_set(pcm, params, SND_TEST, SND_PCM_HW_PARAM_SUBFORMAT, subformat, 0);
+}
+
+/**
+ * \brief Restrict a configuration space to contain only one subformat
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param subformat subformat value
+ * \return 0 otherwise a negative error code if configuration space would become empty
+ */
+int snd_pcm_hw_params_set_subformat(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_subformat_t subformat)
+{
+	return snd_pcm_hw_param_set(pcm, params, SND_TRY, SND_PCM_HW_PARAM_SUBFORMAT, subformat, 0);
+}
+
+/**
+ * \brief Restrict a configuration space to contain only its first subformat
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param subformat Returned subformat
+ * \return 0 otherwise a negative error code
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_set_subformat_first)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_subformat_t *subformat)
+#else
+int snd_pcm_hw_params_set_subformat_first(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_subformat_t *subformat)
+#endif
+{
+	return snd_pcm_hw_param_set_first(pcm, params, SND_PCM_HW_PARAM_SUBFORMAT, subformat, NULL);
+}
+
+/**
+ * \brief Restrict a configuration space to contain only its last subformat
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param subformat Returned subformat
+ * \return 0 otherwise a negative error code
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_set_subformat_last)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_subformat_t *subformat)
+#else
+int snd_pcm_hw_params_set_subformat_last(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_subformat_t *subformat)
+#endif
+{
+	return snd_pcm_hw_param_set_last(pcm, params, SND_PCM_HW_PARAM_SUBFORMAT, subformat, NULL);
+}
+
+/**
+ * \brief Restrict a configuration space to contain only a set of subformats
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param mask Subformat mask
+ * \return 0 otherwise a negative error code
+ */
+int snd_pcm_hw_params_set_subformat_mask(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_subformat_mask_t *mask)
+{
+	return snd_pcm_hw_param_set_mask(pcm, params, SND_TRY, SND_PCM_HW_PARAM_SUBFORMAT, (snd_mask_t *) mask);
+}
+
+/**
+ * \brief Get subformat mask from a configuration space
+ * \param params Configuration space
+ * \param mask Returned Subformat mask
+ */
+void snd_pcm_hw_params_get_subformat_mask(snd_pcm_hw_params_t *params, snd_pcm_subformat_mask_t *mask)
+{
+	snd_pcm_subformat_mask_copy(mask, snd_pcm_hw_param_get_mask(params, SND_PCM_HW_PARAM_SUBFORMAT));
+}
+
+
+/**
+ * \brief Extract channels from a configuration space
+ * \param params Configuration space
+ * \param val Returned channels count
+ * \return 0 otherwise a negative error code if the configuration space does not contain a single value
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_get_channels)(const snd_pcm_hw_params_t *params, unsigned int *val)
+#else
+int snd_pcm_hw_params_get_channels(const snd_pcm_hw_params_t *params, unsigned int *val)
+#endif
+{
+	return snd_pcm_hw_param_get(params, SND_PCM_HW_PARAM_CHANNELS, val, NULL);
+}
+
+/**
+ * \brief Extract minimum channels count from a configuration space
+ * \param params Configuration space
+ * \param val minimum channels count
+ * \return 0 otherwise a negative error code
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_get_channels_min)(const snd_pcm_hw_params_t *params, unsigned int *val)
+#else
+int snd_pcm_hw_params_get_channels_min(const snd_pcm_hw_params_t *params, unsigned int *val)
+#endif
+{
+	return snd_pcm_hw_param_get_min(params, SND_PCM_HW_PARAM_CHANNELS, val, NULL);
+}
+
+/**
+ * \brief Extract maximum channels count from a configuration space
+ * \param params Configuration space
+ * \param val maximum channels count
+ * \return 0 otherwise a negative error code
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_get_channels_max)(const snd_pcm_hw_params_t *params, unsigned int *val)
+#else
+int snd_pcm_hw_params_get_channels_max(const snd_pcm_hw_params_t *params, unsigned int *val)
+#endif
+{
+	return snd_pcm_hw_param_get_max(params, SND_PCM_HW_PARAM_CHANNELS, val, NULL);
+}
+
+/**
+ * \brief Verify if a channels count is available inside a configuration space for a PCM
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val channels count
+ * \return 0 if available a negative error code otherwise
+ */
+int snd_pcm_hw_params_test_channels(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val)
+{
+	return snd_pcm_hw_param_set(pcm, params, SND_TEST, SND_PCM_HW_PARAM_CHANNELS, val, 0);
+}
+
+/**
+ * \brief Restrict a configuration space to contain only one channels count
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val channels count
+ * \return 0 otherwise a negative error code if configuration space would become empty
+ */
+int snd_pcm_hw_params_set_channels(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val)
+{
+	return snd_pcm_hw_param_set(pcm, params, SND_TRY, SND_PCM_HW_PARAM_CHANNELS, val, 0);
+}
+
+/**
+ * \brief Restrict a configuration space with a minimum channels count
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val minimum channels count (on return filled with actual minimum)
+ * \return 0 otherwise a negative error code if configuration space would become empty
+ */
+int snd_pcm_hw_params_set_channels_min(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val)
+{
+	return snd_pcm_hw_param_set_min(pcm, params, SND_TRY, SND_PCM_HW_PARAM_CHANNELS, val, NULL);
+}
+
+/**
+ * \brief Restrict a configuration space with a maximum channels count
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val maximum channels count (on return filled with actual maximum)
+ * \return 0 otherwise a negative error code if configuration space would become empty
+ */
+int snd_pcm_hw_params_set_channels_max(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val)
+{
+	return snd_pcm_hw_param_set_max(pcm, params, SND_TRY, SND_PCM_HW_PARAM_CHANNELS, val, NULL);
+}
+
+/**
+ * \brief Restrict a configuration space to have channels counts in a given range
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param min minimum channels count (on return filled with actual minimum)
+ * \param max maximum channels count (on return filled with actual maximum)
+ * \return 0 otherwise a negative error code if configuration space would become empty
+ */
+int snd_pcm_hw_params_set_channels_minmax(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *min, unsigned int *max)
+{
+	return snd_pcm_hw_param_set_minmax(pcm, params, SND_TRY, SND_PCM_HW_PARAM_CHANNELS, min, NULL, max, NULL);
+}
+
+/**
+ * \brief Restrict a configuration space to have channels count nearest to a target
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val target channels count, returned chosen channels count
+ * \return 0 otherwise a negative error code if configuration space is empty
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_set_channels_near)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val)
+#else
+int snd_pcm_hw_params_set_channels_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val)
+#endif
+{
+	return snd_pcm_hw_param_set_near(pcm, params, SND_PCM_HW_PARAM_CHANNELS, val, NULL);
+}
+
+/**
+ * \brief Restrict a configuration space to contain only its minimum channels count
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val minimum channels count
+ * \return 0 otherwise a negative error code
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_set_channels_first)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val)
+#else
+int snd_pcm_hw_params_set_channels_first(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val)
+#endif
+{
+	return snd_pcm_hw_param_set_first(pcm, params, SND_PCM_HW_PARAM_CHANNELS, val, NULL);
+}
+
+/**
+ * \brief Restrict a configuration space to contain only its maximum channels count
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val maximum channels count
+ * \return 0 otherwise a negative error code
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_set_channels_last)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val)
+#else
+int snd_pcm_hw_params_set_channels_last(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val)
+#endif
+{
+	return snd_pcm_hw_param_set_last(pcm, params, SND_PCM_HW_PARAM_CHANNELS, val, NULL);
+}
+
+
+/**
+ * \brief Extract rate from a configuration space
+ * \param params Configuration space
+ * \param val Returned approximate rate
+ * \param dir Sub unit direction
+ * \return 0 otherwise a negative error code if the configuration space does not contain a single value
+ *
+ * Actual exact value is <,=,> the approximate one following dir (-1, 0, 1)
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_get_rate)(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#else
+int snd_pcm_hw_params_get_rate(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#endif
+{
+	return snd_pcm_hw_param_get(params, SND_PCM_HW_PARAM_RATE, val, dir);
+}
+
+/**
+ * \brief Extract minimum rate from a configuration space
+ * \param params Configuration space
+ * \param val Returned approximate minimum rate
+ * \param dir Sub unit direction
+ * \return 0 otherwise a negative error code
+ *
+ * Exact value is <,=,> the returned one following dir (-1,0,1)
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_get_rate_min)(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#else
+int snd_pcm_hw_params_get_rate_min(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#endif
+{
+	return snd_pcm_hw_param_get_min(params, SND_PCM_HW_PARAM_RATE, val, dir);
+}
+
+/**
+ * \brief Extract maximum rate from a configuration space
+ * \param params Configuration space
+ * \param val Returned approximate maximum rate
+ * \param dir Sub unit direction
+ * \return 0 otherwise a negative error code
+ *
+ * Exact value is <,=,> the returned one following dir (-1,0,1)
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_get_rate_max)(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#else
+int snd_pcm_hw_params_get_rate_max(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#endif
+{
+	return snd_pcm_hw_param_get_max(params, SND_PCM_HW_PARAM_RATE, val, dir);
+}
+
+/**
+ * \brief Verify if a rate is available inside a configuration space for a PCM
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val approximate rate
+ * \param dir Sub unit direction
+ * \return 0 if available a negative error code otherwise
+ *
+ * Wanted exact value is <,=,> val following dir (-1,0,1)
+ */
+int snd_pcm_hw_params_test_rate(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int dir)
+{
+	return snd_pcm_hw_param_set(pcm, params, SND_TEST, SND_PCM_HW_PARAM_RATE, val, dir);
+}
+
+/**
+ * \brief Restrict a configuration space to contain only one rate
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val approximate rate
+ * \param dir Sub unit direction
+ * \return 0 otherwise a negative error code if configuration space would become empty
+ *
+ * Wanted exact value is <,=,> val following dir (-1,0,1)
+ */
+int snd_pcm_hw_params_set_rate(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int dir)
+{
+	return snd_pcm_hw_param_set(pcm, params, SND_TRY, SND_PCM_HW_PARAM_RATE, val, dir);
+}
+
+/**
+ * \brief Restrict a configuration space with a minimum rate
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val approximate minimum rate (on return filled with actual minimum)
+ * \param dir Sub unit direction (on return filled with actual direction)
+ * \return 0 otherwise a negative error code if configuration space would become empty
+ *
+ * Wanted/actual exact minimum is <,=,> val following dir (-1,0,1)
+ */
+int snd_pcm_hw_params_set_rate_min(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+{
+	return snd_pcm_hw_param_set_min(pcm, params, SND_TRY, SND_PCM_HW_PARAM_RATE, val, dir);
+}
+
+/**
+ * \brief Restrict a configuration space with a maximum rate
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val approximate maximum rate (on return filled with actual maximum)
+ * \param dir Sub unit direction (on return filled with actual direction)
+ * \return 0 otherwise a negative error code if configuration space would become empty
+ *
+ * Wanted/actual exact maximum is <,=,> val following dir (-1,0,1)
+ */
+int snd_pcm_hw_params_set_rate_max(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+{
+	return snd_pcm_hw_param_set_max(pcm, params, SND_TRY, SND_PCM_HW_PARAM_RATE, val, dir);
+}
+
+/**
+ * \brief Restrict a configuration space to have rates in a given range
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param min approximate minimum rate (on return filled with actual minimum)
+ * \param mindir Sub unit direction for minimum (on return filled with actual direction)
+ * \param max approximate maximum rate (on return filled with actual maximum)
+ * \param maxdir Sub unit direction for maximum (on return filled with actual direction)
+ * \return 0 otherwise a negative error code if configuration space would become empty
+ *
+ * Wanted/actual exact min/max is <,=,> val following dir (-1,0,1)
+ */
+int snd_pcm_hw_params_set_rate_minmax(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *min, int *mindir, unsigned int *max, int *maxdir)
+{
+	return snd_pcm_hw_param_set_minmax(pcm, params, SND_TRY, SND_PCM_HW_PARAM_RATE, min, mindir, max, maxdir);
+}
+
+/**
+ * \brief Restrict a configuration space to have rate nearest to a target
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val approximate target rate / returned approximate set rate
+ * \param dir Sub unit direction
+ * \return 0 otherwise a negative error code if configuration space is empty
+ *
+ * target/chosen exact value is <,=,> val following dir (-1,0,1)
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_set_rate_near)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#else
+int snd_pcm_hw_params_set_rate_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#endif
+{
+	return snd_pcm_hw_param_set_near(pcm, params, SND_PCM_HW_PARAM_RATE, val, dir);
+}
+
+/**
+ * \brief Restrict a configuration space to contain only its minimum rate
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val Returned minimum approximate rate
+ * \param dir Sub unit direction
+ * \return 0 otherwise a negative error code
+ *
+ * Actual exact value is <,=,> the approximate one following dir (-1, 0, 1)
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_set_rate_first)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#else
+int snd_pcm_hw_params_set_rate_first(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#endif
+{
+	return snd_pcm_hw_param_set_first(pcm, params, SND_PCM_HW_PARAM_RATE, val, dir);
+}
+
+/**
+ * \brief Restrict a configuration space to contain only its maximum rate
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val Returned maximum approximate rate
+ * \param dir Sub unit direction
+ * \return 0 otherwise a negative error code
+ *
+ * Actual exact value is <,=,> the approximate one following dir (-1, 0, 1)
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_set_rate_last)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#else
+int snd_pcm_hw_params_set_rate_last(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#endif
+{
+	return snd_pcm_hw_param_set_last(pcm, params, SND_PCM_HW_PARAM_RATE, val, dir);
+}
+
+/**
+ * \brief Restrict a configuration space to contain only real hardware rates
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val 0 = disable, 1 = enable (default) rate resampling
+ * \return 0 otherwise a negative error code
+ */
+int snd_pcm_hw_params_set_rate_resample(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val)
+{
+	assert(pcm && params);
+	if (!val)
+		params->flags |= SND_PCM_HW_PARAMS_NORESAMPLE;
+	else
+		params->flags &= ~SND_PCM_HW_PARAMS_NORESAMPLE;
+	params->rmask = ~0;
+	return snd_pcm_hw_refine(pcm, params);
+}
+
+/**
+ * \brief Extract resample state from a configuration space
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val 0 = disable, 1 = enable rate resampling
+ * \return 0 otherwise a negative error code
+ */
+int snd_pcm_hw_params_get_rate_resample(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val)
+{
+	assert(pcm && params && val);
+	*val = params->flags & SND_PCM_HW_PARAMS_NORESAMPLE ? 0 : 1;
+	return 0;
+}
+
+/**
+ * \brief Restrict a configuration space to allow the buffer to be accessible from outside
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val 0 = disable, 1 = enable (default) exporting buffer
+ * \return 0 otherwise a negative error code
+ */
+int snd_pcm_hw_params_set_export_buffer(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val)
+{
+	assert(pcm && params);
+	if (val)
+		params->flags |= SND_PCM_HW_PARAMS_EXPORT_BUFFER;
+	else
+		params->flags &= ~SND_PCM_HW_PARAMS_EXPORT_BUFFER;
+	params->rmask = ~0;
+	return snd_pcm_hw_refine(pcm, params);
+}
+
+/**
+ * \brief Extract buffer accessibility from a configuration space
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val 0 = disable, 1 = enable exporting buffer
+ * \return 0 otherwise a negative error code
+ */
+int snd_pcm_hw_params_get_export_buffer(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val)
+{
+	assert(pcm && params && val);
+	*val = params->flags & SND_PCM_HW_PARAMS_EXPORT_BUFFER ? 1 : 0;
+	return 0;
+}
+
+/**
+ * \brief Restrict a configuration space to settings without period wakeups
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val 0 = disable, 1 = enable (default) period wakeup
+ * \return Zero on success, otherwise a negative error code.
+ *
+ * This function must be called only on devices where non-blocking mode is
+ * enabled.
+ *
+ * To check whether the hardware does support disabling period wakeups, call
+ * #snd_pcm_hw_params_can_disable_period_wakeup(). If the hardware does not
+ * support this mode, standard period wakeups will be generated.
+ *
+ * Even with disabled period wakeups, the period size/time/count parameters
+ * are valid; it is suggested to use #snd_pcm_hw_params_set_period_size_last().
+ *
+ * When period wakeups are disabled, the application must not use any functions
+ * that could block on this device. The use of poll should be limited to error
+ * cases. The application needs to use an external event or a timer to
+ * check the state of the ring buffer and refill it apropriately.
+ */
+int snd_pcm_hw_params_set_period_wakeup(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val)
+{
+	assert(pcm && params);
+
+	if (!val) {
+		if (!(pcm->mode & SND_PCM_NONBLOCK))
+			return -EINVAL;
+		params->flags |= SND_PCM_HW_PARAMS_NO_PERIOD_WAKEUP;
+	} else
+		params->flags &= ~SND_PCM_HW_PARAMS_NO_PERIOD_WAKEUP;
+	params->rmask = ~0;
+
+	return snd_pcm_hw_refine(pcm, params);
+}
+
+/**
+ * \brief Extract period wakeup flag from a configuration space
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val 0 = disabled, 1 = enabled period wakeups
+ * \return Zero on success, otherwise a negative error code.
+ */
+int snd_pcm_hw_params_get_period_wakeup(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val)
+{
+	assert(pcm && params && val);
+	*val = params->flags & SND_PCM_HW_PARAMS_NO_PERIOD_WAKEUP ? 0 : 1;
+	return 0;
+}
+
+/**
+ * \brief Extract period time from a configuration space
+ * \param params Configuration space
+ * \param val Returned approximate period duration in us
+ * \param dir Sub unit direction
+ * \return 0 otherwise a negative error code if the configuration space does not contain a single value
+ *
+ * Actual exact value is <,=,> the approximate one following dir (-1, 0, 1)
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_get_period_time)(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#else
+int snd_pcm_hw_params_get_period_time(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#endif
+{
+	return snd_pcm_hw_param_get(params, SND_PCM_HW_PARAM_PERIOD_TIME, val, dir);
+}
+
+/**
+ * \brief Extract minimum period time from a configuration space
+ * \param params Configuration space
+ * \param val approximate minimum period duration in us
+ * \param dir Sub unit direction
+ * \return 0 otherwise a negative error code
+ *
+ * Exact value is <,=,> the returned one following dir (-1,0,1)
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_get_period_time_min)(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#else
+int snd_pcm_hw_params_get_period_time_min(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#endif
+{
+	return snd_pcm_hw_param_get_min(params, SND_PCM_HW_PARAM_PERIOD_TIME, val, dir);
+}
+
+/**
+ * \brief Extract maximum period time from a configuration space
+ * \param params Configuration space
+ * \param val approximate maximum period duration in us
+ * \param dir Sub unit direction
+ * \return 0 otherwise a negative error code
+ *
+ * Exact value is <,=,> the returned one following dir (-1,0,1)
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_get_period_time_max)(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#else
+int snd_pcm_hw_params_get_period_time_max(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#endif
+{
+	return snd_pcm_hw_param_get_max(params, SND_PCM_HW_PARAM_PERIOD_TIME, val, dir);
+}
+
+/**
+ * \brief Verify if a period time is available inside a configuration space for a PCM
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val approximate period duration in us
+ * \param dir Sub unit direction
+ * \return 0 if available a negative error code otherwise
+ *
+ * Wanted exact value is <,=,> val following dir (-1,0,1)
+ */
+int snd_pcm_hw_params_test_period_time(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int dir)
+{
+	return snd_pcm_hw_param_set(pcm, params, SND_TEST, SND_PCM_HW_PARAM_PERIOD_TIME, val, dir);
+}
+
+/**
+ * \brief Restrict a configuration space to contain only one period time
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val approximate period duration in us
+ * \param dir Sub unit direction
+ * \return 0 otherwise a negative error code if configuration space would become empty
+ *
+ * Wanted exact value is <,=,> val following dir (-1,0,1)
+ */
+int snd_pcm_hw_params_set_period_time(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int dir)
+{
+	return snd_pcm_hw_param_set(pcm, params, SND_TRY, SND_PCM_HW_PARAM_PERIOD_TIME, val, dir);
+}
+
+
+/**
+ * \brief Restrict a configuration space with a minimum period time
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val approximate minimum period duration in us (on return filled with actual minimum)
+ * \param dir Sub unit direction (on return filled with actual direction)
+ * \return 0 otherwise a negative error code if configuration space would become empty
+ *
+ * Wanted/actual exact minimum is <,=,> val following dir (-1,0,1)
+ */
+int snd_pcm_hw_params_set_period_time_min(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+{
+	return snd_pcm_hw_param_set_min(pcm, params, SND_TRY, SND_PCM_HW_PARAM_PERIOD_TIME, val, dir);
+}
+
+/**
+ * \brief Restrict a configuration space with a maximum period time
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val approximate maximum period duration in us (on return filled with actual maximum)
+ * \param dir Sub unit direction (on return filled with actual direction)
+ * \return 0 otherwise a negative error code if configuration space would become empty
+ *
+ * Wanted/actual exact maximum is <,=,> val following dir (-1,0,1)
+ */
+int snd_pcm_hw_params_set_period_time_max(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+{
+	return snd_pcm_hw_param_set_max(pcm, params, SND_TRY, SND_PCM_HW_PARAM_PERIOD_TIME, val, dir);
+}
+
+/**
+ * \brief Restrict a configuration space to have period times in a given range
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param min approximate minimum period duration in us (on return filled with actual minimum)
+ * \param mindir Sub unit direction for minimum (on return filled with actual direction)
+ * \param max approximate maximum period duration in us (on return filled with actual maximum)
+ * \param maxdir Sub unit direction for maximum (on return filled with actual direction)
+ * \return 0 otherwise a negative error code if configuration space would become empty
+ *
+ * Wanted/actual exact min/max is <,=,> val following dir (-1,0,1)
+ */
+int snd_pcm_hw_params_set_period_time_minmax(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *min, int *mindir, unsigned int *max, int *maxdir)
+{
+	return snd_pcm_hw_param_set_minmax(pcm, params, SND_TRY, SND_PCM_HW_PARAM_PERIOD_TIME, min, mindir, max, maxdir);
+}
+
+/**
+ * \brief Restrict a configuration space to have period time nearest to a target
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val approximate target period duration in us / returned chosen approximate target period duration
+ * \param dir Sub unit direction
+ * \return 0 otherwise a negative error code if configuration space is empty
+ *
+ * target/chosen exact value is <,=,> val following dir (-1,0,1)
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_set_period_time_near)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#else
+int snd_pcm_hw_params_set_period_time_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#endif
+{
+	return snd_pcm_hw_param_set_near(pcm, params, SND_PCM_HW_PARAM_PERIOD_TIME, val, dir);
+}
+
+/**
+ * \brief Restrict a configuration space to contain only its minimum period time
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val Returned approximate period duration in us
+ * \param dir Sub unit direction
+ * \return 0 otherwise a negative error code
+ *
+ * Actual exact value is <,=,> the approximate one following dir (-1, 0, 1)
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_set_period_time_first)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#else
+int snd_pcm_hw_params_set_period_time_first(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#endif
+{
+	return snd_pcm_hw_param_set_first(pcm, params, SND_PCM_HW_PARAM_PERIOD_TIME, val, dir);
+}
+
+/**
+ * \brief Restrict a configuration space to contain only its maximum period time
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val Returned maximum approximate period time
+ * \param dir Sub unit direction
+ * \return approximate period duration in us
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_set_period_time_last)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#else
+int snd_pcm_hw_params_set_period_time_last(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#endif
+{
+	return snd_pcm_hw_param_set_last(pcm, params, SND_PCM_HW_PARAM_PERIOD_TIME, val, dir);
+}
+
+
+/**
+ * \brief Extract period size from a configuration space
+ * \param params Configuration space
+ * \param val Returned approximate period size in frames
+ * \param dir Sub unit direction
+ * \return 0 otherwise a negative error code if the configuration space does not contain a single value
+ *
+ * Actual exact value is <,=,> the approximate one following dir (-1, 0, 1)
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_get_period_size)(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val, int *dir)
+#else
+int snd_pcm_hw_params_get_period_size(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val, int *dir)
+#endif
+{
+	unsigned int _val;
+	int err = snd_pcm_hw_param_get(params, SND_PCM_HW_PARAM_PERIOD_SIZE, &_val, dir);
+	if (err >= 0)
+		*val = _val;
+	return err;
+}
+
+/**
+ * \brief Extract minimum period size from a configuration space
+ * \param params Configuration space
+ * \param val approximate minimum period size in frames
+ * \param dir Sub unit direction
+ * \return 0 otherwise a negative error code
+ *
+ * Exact value is <,=,> the returned one following dir (-1,0,1)
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_get_period_size_min)(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val, int *dir)
+#else
+int snd_pcm_hw_params_get_period_size_min(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val, int *dir)
+#endif
+{
+	unsigned int _val = *val;
+	int err = snd_pcm_hw_param_get_min(params, SND_PCM_HW_PARAM_PERIOD_SIZE, &_val, dir);
+	if (err >= 0)
+		*val = _val;
+	return err;
+}
+
+/**
+ * \brief Extract maximum period size from a configuration space
+ * \param params Configuration space
+ * \param val approximate minimum period size in frames
+ * \param dir Sub unit direction
+ * \return 0 otherwise a negative error code
+ *
+ * Exact value is <,=,> the returned one following dir (-1,0,1)
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_get_period_size_max)(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val, int *dir)
+#else
+int snd_pcm_hw_params_get_period_size_max(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val, int *dir)
+#endif
+{
+	unsigned int _val = *val;
+	int err = snd_pcm_hw_param_get_max(params, SND_PCM_HW_PARAM_PERIOD_SIZE, &_val, dir);
+	if (err >= 0)
+		*val = _val;
+	return err;
+}
+
+/**
+ * \brief Verify if a period size is available inside a configuration space for a PCM
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val approximate period size in frames
+ * \param dir Sub unit direction
+ * \return 0 if available a negative error code otherwise
+ *
+ * Wanted exact value is <,=,> val following dir (-1,0,1)
+ */
+int snd_pcm_hw_params_test_period_size(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t val, int dir)
+{
+	return snd_pcm_hw_param_set(pcm, params, SND_TEST, SND_PCM_HW_PARAM_PERIOD_SIZE, val, dir);
+}
+
+/**
+ * \brief Restrict a configuration space to contain only one period size
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val approximate period size in frames
+ * \param dir Sub unit direction
+ * \return 0 otherwise a negative error code if configuration space would become empty
+ *
+ * Wanted exact value is <,=,> val following dir (-1,0,1)
+ */
+int snd_pcm_hw_params_set_period_size(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t val, int dir)
+{
+	return snd_pcm_hw_param_set(pcm, params, SND_TRY, SND_PCM_HW_PARAM_PERIOD_SIZE, val, dir);
+}
+
+/**
+ * \brief Restrict a configuration space with a minimum period size
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val approximate minimum period size in frames (on return filled with actual minimum)
+ * \param dir Sub unit direction (on return filled with actual direction)
+ * \return 0 otherwise a negative error code if configuration space would become empty
+ *
+ * Wanted/actual exact minimum is <,=,> val following dir (-1,0,1)
+ */
+int snd_pcm_hw_params_set_period_size_min(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val, int *dir)
+{
+	unsigned int _val = *val;
+	int err = snd_pcm_hw_param_set_min(pcm, params, SND_TRY, SND_PCM_HW_PARAM_PERIOD_SIZE, &_val, dir);
+	if (err >= 0)
+		*val = _val;
+	return err;
+}
+
+/**
+ * \brief Restrict a configuration space with a maximum period size
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val approximate maximum period size in frames (on return filled with actual maximum)
+ * \param dir Sub unit direction (on return filled with actual direction)
+ * \return 0 otherwise a negative error code if configuration space would become empty
+ *
+ * Wanted/actual exact minimum is <,=,> val following dir (-1,0,1)
+ */
+int snd_pcm_hw_params_set_period_size_max(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val, int *dir)
+{
+	unsigned int _val = *val;
+	int err = snd_pcm_hw_param_set_max(pcm, params, SND_TRY, SND_PCM_HW_PARAM_PERIOD_SIZE, &_val, dir);
+	if (err >= 0)
+		*val = _val;
+	return err;
+}
+
+/**
+ * \brief Restrict a configuration space to have period sizes in a given range
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param min approximate minimum period size in frames (on return filled with actual minimum)
+ * \param mindir Sub unit direction for minimum (on return filled with actual direction)
+ * \param max approximate maximum period size in frames (on return filled with actual maximum)
+ * \param maxdir Sub unit direction for maximum (on return filled with actual direction)
+ * \return 0 otherwise a negative error code if configuration space would become empty
+ *
+ * Wanted/actual exact min/max is <,=,> val following dir (-1,0,1)
+ */
+int snd_pcm_hw_params_set_period_size_minmax(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *min, int *mindir, snd_pcm_uframes_t *max, int *maxdir)
+{
+	unsigned int _min = *min;
+	unsigned int _max = *max;
+	int err = snd_pcm_hw_param_set_minmax(pcm, params, SND_TRY, SND_PCM_HW_PARAM_PERIOD_SIZE, &_min, mindir, &_max, maxdir);
+	*min = _min;
+	*max = _max;
+	return err;
+}
+
+/**
+ * \brief Restrict a configuration space to have period size nearest to a target
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val approximate target period size in frames / returned chosen approximate target period size
+ * \param dir Sub unit direction
+ * \return 0 otherwise a negative error code if configuration space is empty
+ *
+ * target/chosen exact value is <,=,> val following dir (-1,0,1)
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_set_period_size_near)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val, int *dir)
+#else
+int snd_pcm_hw_params_set_period_size_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val, int *dir)
+#endif
+{
+	unsigned int _val = *val;
+	int err = snd_pcm_hw_param_set_near(pcm, params, SND_PCM_HW_PARAM_PERIOD_SIZE, &_val, dir);
+	if (err >= 0)
+		*val = _val;
+	return err;
+}
+
+/**
+ * \brief Restrict a configuration space to contain only its minimum period size
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val Returned maximum approximate period size in frames
+ * \param dir Sub unit direction
+ * \return 0 otherwise a negative error code
+ *
+ * Actual exact value is <,=,> the approximate one following dir (-1, 0, 1)
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_set_period_size_first)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val, int *dir)
+#else
+int snd_pcm_hw_params_set_period_size_first(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val, int *dir)
+#endif
+{
+	unsigned int _val;
+	int err = snd_pcm_hw_param_set_first(pcm, params, SND_PCM_HW_PARAM_PERIOD_SIZE, &_val, dir);
+	if (err >= 0)
+		*val = _val;
+	return err;
+}
+
+/**
+ * \brief Restrict a configuration space to contain only its maximum period size
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val Returned maximum approximate period size in frames
+ * \param dir Sub unit direction
+ * \return 0 otherwise a negative error code
+ *
+ * Actual exact value is <,=,> the approximate one following dir (-1, 0, 1)
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_set_period_size_last)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val, int *dir)
+#else
+int snd_pcm_hw_params_set_period_size_last(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val, int *dir)
+#endif
+{
+	unsigned int _val;
+	int err = snd_pcm_hw_param_set_last(pcm, params, SND_PCM_HW_PARAM_PERIOD_SIZE, &_val, dir);
+	if (err >= 0)
+		*val = _val;
+	return err;
+}
+
+/**
+ * \brief Restrict a configuration space to contain only integer period sizes
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \return 0 otherwise a negative error code if configuration space would become empty
+ */
+int snd_pcm_hw_params_set_period_size_integer(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	return snd_pcm_hw_param_set_integer(pcm, params, SND_TRY, SND_PCM_HW_PARAM_PERIOD_SIZE);
+}
+
+
+/**
+ * \brief Extract periods from a configuration space
+ * \param params Configuration space
+ * \param val approximate periods per buffer
+ * \param dir Sub unit direction
+ * \return 0 otherwise a negative error code if the configuration space does not contain a single value
+ *
+ * Actual exact value is <,=,> the approximate one following dir (-1, 0, 1)
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_get_periods)(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#else
+int snd_pcm_hw_params_get_periods(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#endif
+{
+	return snd_pcm_hw_param_get(params, SND_PCM_HW_PARAM_PERIODS, val, dir);
+}
+
+/**
+ * \brief Extract minimum periods count from a configuration space
+ * \param params Configuration space
+ * \param val approximate minimum periods per buffer
+ * \param dir Sub unit direction
+ * \return 0 otherwise a negative error code
+ *
+ * Exact value is <,=,> the returned one following dir (-1,0,1)
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_get_periods_min)(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#else
+int snd_pcm_hw_params_get_periods_min(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#endif
+{
+	return snd_pcm_hw_param_get_min(params, SND_PCM_HW_PARAM_PERIODS, val, dir);
+}
+
+/**
+ * \brief Extract maximum periods count from a configuration space
+ * \param params Configuration space
+ * \param val approximate maximum periods per buffer
+ * \param dir Sub unit direction
+ * \return 0 otherwise a negative error code
+ *
+ * Exact value is <,=,> the returned one following dir (-1,0,1)
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_get_periods_max)(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#else
+int snd_pcm_hw_params_get_periods_max(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#endif
+{
+	return snd_pcm_hw_param_get_max(params, SND_PCM_HW_PARAM_PERIODS, val, dir);
+}
+
+/**
+ * \brief Verify if a periods count is available inside a configuration space for a PCM
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val approximate periods per buffer
+ * \param dir Sub unit direction
+ * \return 0 if available a negative error code otherwise
+ *
+ * Wanted exact value is <,=,> val following dir (-1,0,1)
+ */
+int snd_pcm_hw_params_test_periods(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int dir)
+{
+	return snd_pcm_hw_param_set(pcm, params, SND_TEST, SND_PCM_HW_PARAM_PERIODS, val, dir);
+}
+
+/**
+ * \brief Restrict a configuration space to contain only one periods count
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val approximate periods per buffer
+ * \param dir Sub unit direction
+ * \return 0 otherwise a negative error code if configuration space would become empty
+ *
+ * Wanted exact value is <,=,> val following dir (-1,0,1)
+ */
+int snd_pcm_hw_params_set_periods(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int dir)
+{
+	return snd_pcm_hw_param_set(pcm, params, SND_TRY, SND_PCM_HW_PARAM_PERIODS, val, dir);
+}
+
+/**
+ * \brief Restrict a configuration space with a minimum periods count
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val approximate minimum periods per buffer (on return filled with actual minimum)
+ * \param dir Sub unit direction (on return filled with actual direction)
+ * \return 0 otherwise a negative error code if configuration space would become empty
+ *
+ * Wanted/actual exact minimum is <,=,> val following dir (-1,0,1)
+ */
+int snd_pcm_hw_params_set_periods_min(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+{
+	return snd_pcm_hw_param_set_min(pcm, params, SND_TRY, SND_PCM_HW_PARAM_PERIODS, val, dir);
+}
+
+/**
+ * \brief Restrict a configuration space with a maximum periods count
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val approximate maximum periods per buffer (on return filled with actual maximum)
+ * \param dir Sub unit direction (on return filled with actual direction)
+ * \return 0 otherwise a negative error code if configuration space would become empty
+ *
+ * Wanted/actual exact maximum is <,=,> val following dir (-1,0,1)
+ */
+int snd_pcm_hw_params_set_periods_max(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+{
+	return snd_pcm_hw_param_set_max(pcm, params, SND_TRY, SND_PCM_HW_PARAM_PERIODS, val, dir);
+}
+
+/**
+ * \brief Restrict a configuration space to have periods counts in a given range
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param min approximate minimum periods per buffer (on return filled with actual minimum)
+ * \param mindir Sub unit direction for minimum (on return filled with actual direction)
+ * \param max approximate maximum periods per buffer (on return filled with actual maximum)
+ * \param maxdir Sub unit direction for maximum (on return filled with actual direction)
+ * \return 0 otherwise a negative error code if configuration space would become empty
+ *
+ * Wanted/actual exact min/max is <,=,> val following dir (-1,0,1)
+ */
+int snd_pcm_hw_params_set_periods_minmax(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *min, int *mindir, unsigned int *max, int *maxdir)
+{
+	return snd_pcm_hw_param_set_minmax(pcm, params, SND_TRY, SND_PCM_HW_PARAM_PERIODS, min, mindir, max, maxdir);
+}
+
+/**
+ * \brief Restrict a configuration space to have periods count nearest to a target
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val approximate target periods per buffer / returned chosen approximate target periods per buffer
+ * \param dir Sub unit direction
+ * \return 0 otherwise a negative error code if configuration space is empty
+ *
+ * target/chosen exact value is <,=,> val following dir (-1,0,1)
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_set_periods_near)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#else
+int snd_pcm_hw_params_set_periods_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#endif
+{
+	return snd_pcm_hw_param_set_near(pcm, params, SND_PCM_HW_PARAM_PERIODS, val, dir);
+}
+
+/**
+ * \brief Restrict a configuration space to contain only its minimum periods count
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val Returned approximate minimum periods per buffer
+ * \param dir Sub unit direction
+ * \return 0 otherwise a negative error code
+ *
+ * Actual exact value is <,=,> the approximate one following dir (-1, 0, 1)
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_set_periods_first)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#else
+int snd_pcm_hw_params_set_periods_first(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#endif
+{
+	return snd_pcm_hw_param_set_first(pcm, params, SND_PCM_HW_PARAM_PERIODS, val, dir);
+}
+
+/**
+ * \brief Restrict a configuration space to contain only its maximum periods count
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val Returned approximate maximum periods per buffer
+ * \param dir Sub unit direction
+ * \return 0 otherwise a negative error code
+ *
+ * Actual exact value is <,=,> the approximate one following dir (-1, 0, 1)
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_set_periods_last)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#else
+int snd_pcm_hw_params_set_periods_last(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#endif
+{
+	return snd_pcm_hw_param_set_last(pcm, params, SND_PCM_HW_PARAM_PERIODS, val, dir);
+}
+
+/**
+ * \brief Restrict a configuration space to contain only integer periods counts
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \return 0 otherwise a negative error code if configuration space would become empty
+ */
+int snd_pcm_hw_params_set_periods_integer(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	return snd_pcm_hw_param_set_integer(pcm, params, SND_TRY, SND_PCM_HW_PARAM_PERIODS);
+}
+
+
+/**
+ * \brief Extract buffer time from a configuration space
+ * \param params Configuration space
+ * \param val Returned buffer time in us
+ * \param dir Sub unit direction
+ * \return 0 otherwise a negative error code if the configuration space does not contain a single value
+ *
+ * Actual exact value is <,=,> the approximate one following dir (-1, 0, 1)
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_get_buffer_time)(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#else
+int snd_pcm_hw_params_get_buffer_time(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#endif
+{
+	return snd_pcm_hw_param_get(params, SND_PCM_HW_PARAM_BUFFER_TIME, val, dir);
+}
+
+/**
+ * \brief Extract minimum buffer time from a configuration space
+ * \param params Configuration space
+ * \param val approximate minimum buffer duration in us
+ * \param dir Sub unit direction
+ * \return 0 otherwise a negative error code
+ *
+ * Exact value is <,=,> the returned one following dir (-1,0,1)
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_get_buffer_time_min)(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#else
+int snd_pcm_hw_params_get_buffer_time_min(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#endif
+{
+	return snd_pcm_hw_param_get_min(params, SND_PCM_HW_PARAM_BUFFER_TIME, val, dir);
+}
+
+/**
+ * \brief Extract maximum buffer time from a configuration space
+ * \param params Configuration space
+ * \param val approximate maximum buffer duration in us
+ * \param dir Sub unit direction
+ * \return 0 otherwise a negative error code
+ *
+ * Exact value is <,=,> the returned one following dir (-1,0,1)
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_get_buffer_time_max)(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#else
+int snd_pcm_hw_params_get_buffer_time_max(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#endif
+{
+	return snd_pcm_hw_param_get_max(params, SND_PCM_HW_PARAM_BUFFER_TIME, val, dir);
+}
+
+/**
+ * \brief Verify if a buffer time is available inside a configuration space for a PCM
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val approximate buffer duration in us
+ * \param dir Sub unit direction
+ * \return 0 if available a negative error code otherwise
+ *
+ * Wanted exact value is <,=,> val following dir (-1,0,1)
+ */
+int snd_pcm_hw_params_test_buffer_time(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int dir)
+{
+	return snd_pcm_hw_param_set(pcm, params, SND_TEST, SND_PCM_HW_PARAM_BUFFER_TIME, val, dir);
+}
+
+/**
+ * \brief Restrict a configuration space to contain only one buffer time
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val approximate buffer duration in us
+ * \param dir Sub unit direction
+ * \return 0 otherwise a negative error code if configuration space would become empty
+ *
+ * Wanted exact value is <,=,> val following dir (-1,0,1)
+ */
+int snd_pcm_hw_params_set_buffer_time(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int dir)
+{
+	return snd_pcm_hw_param_set(pcm, params, SND_TRY, SND_PCM_HW_PARAM_BUFFER_TIME, val, dir);
+}
+
+/**
+ * \brief Restrict a configuration space with a minimum buffer time
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val approximate minimum buffer duration in us (on return filled with actual minimum)
+ * \param dir Sub unit direction (on return filled with actual direction)
+ * \return 0 otherwise a negative error code if configuration space would become empty
+ *
+ * Wanted/actual exact minimum is <,=,> val following dir (-1,0,1)
+ */
+int snd_pcm_hw_params_set_buffer_time_min(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+{
+	return snd_pcm_hw_param_set_min(pcm, params, SND_TRY, SND_PCM_HW_PARAM_BUFFER_TIME, val, dir);
+}
+
+/**
+ * \brief Restrict a configuration space with a maximum buffer time
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val approximate maximum buffer duration in us (on return filled with actual maximum)
+ * \param dir Sub unit direction (on return filled with actual direction)
+ * \return 0 otherwise a negative error code if configuration space would become empty
+ *
+ * Wanted/actual exact maximum is <,=,> val following dir (-1,0,1)
+ */
+int snd_pcm_hw_params_set_buffer_time_max(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+{
+	return snd_pcm_hw_param_set_max(pcm, params, SND_TRY, SND_PCM_HW_PARAM_BUFFER_TIME, val, dir);
+}
+
+/**
+ * \brief Restrict a configuration space to have buffer times in a given range
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param min approximate minimum buffer duration in us (on return filled with actual minimum)
+ * \param mindir Sub unit direction for minimum (on return filled with actual direction)
+ * \param max approximate maximum buffer duration in us (on return filled with actual maximum)
+ * \param maxdir Sub unit direction for maximum (on return filled with actual direction)
+ * \return 0 otherwise a negative error code if configuration space would become empty
+ *
+ * Wanted/actual exact min/max is <,=,> val following dir (-1,0,1)
+ */
+int snd_pcm_hw_params_set_buffer_time_minmax(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *min, int *mindir, unsigned int *max, int *maxdir)
+{
+	return snd_pcm_hw_param_set_minmax(pcm, params, SND_TRY, SND_PCM_HW_PARAM_BUFFER_TIME, min, mindir, max, maxdir);
+}
+
+/**
+ * \brief Restrict a configuration space to have buffer time nearest to a target
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val approximate target buffer duration in us / returned chosen approximate target buffer duration
+ * \param dir Sub unit direction
+ * \return 0 otherwise a negative error code if configuration space is empty
+ *
+ * target/chosen exact value is <,=,> val following dir (-1,0,1)
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_set_buffer_time_near)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#else
+int snd_pcm_hw_params_set_buffer_time_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#endif
+{
+	return snd_pcm_hw_param_set_near(pcm, params, SND_PCM_HW_PARAM_BUFFER_TIME, val, dir);
+}
+
+/**
+ * \brief Restrict a configuration space to contain only its minimum buffer time
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val Returned approximate minimum buffer duration in us
+ * \param dir Sub unit direction
+ * \return 0 otherwise a negative error code
+ *
+ * Actual exact value is <,=,> the approximate one following dir (-1, 0, 1)
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_set_buffer_time_first)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#else
+int snd_pcm_hw_params_set_buffer_time_first(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#endif
+{
+	return snd_pcm_hw_param_set_first(pcm, params, SND_PCM_HW_PARAM_BUFFER_TIME, val, dir);
+}
+
+/**
+ * \brief Restrict a configuration space to contain only its maximum buffered time
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val Returned approximate maximum buffer duration in us
+ * \param dir Sub unit direction
+ * \return 0 otherwise a negative error code
+ *
+ * Actual exact value is <,=,> the approximate one following dir (-1, 0, 1)
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_set_buffer_time_last)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#else
+int snd_pcm_hw_params_set_buffer_time_last(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#endif
+{
+	return snd_pcm_hw_param_set_last(pcm, params, SND_PCM_HW_PARAM_BUFFER_TIME, val, dir);
+}
+
+
+/**
+ * \brief Extract buffer size from a configuration space
+ * \param params Configuration space
+ * \param val Returned buffer size in frames
+ * \return 0 otherwise a negative error code if the configuration space does not contain a single value
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_get_buffer_size)(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val)
+#else
+int snd_pcm_hw_params_get_buffer_size(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val)
+#endif
+{
+	unsigned int _val;
+	int err = snd_pcm_hw_param_get(params, SND_PCM_HW_PARAM_BUFFER_SIZE, &_val, NULL);
+	if (err >= 0)
+		*val = _val;
+	return err;
+}
+
+/**
+ * \brief Extract minimum buffer size from a configuration space
+ * \param params Configuration space
+ * \param val Returned approximate minimum buffer size in frames
+ * \return 0 otherwise a negative error code
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_get_buffer_size_min)(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val)
+#else
+int snd_pcm_hw_params_get_buffer_size_min(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val)
+#endif
+{
+	unsigned int _val;
+	int err = snd_pcm_hw_param_get_min(params, SND_PCM_HW_PARAM_BUFFER_SIZE, &_val, NULL);
+	if (err >= 0)
+		*val = _val;
+	return err;
+}
+
+/**
+ * \brief Extract maximum buffer size from a configuration space
+ * \param params Configuration space
+ * \param val Returned approximate maximum buffer size in frames
+ * \return 0 otherwise a negative error code
+ *
+ * Exact value is <,=,> the returned one following dir (-1,0,1)
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_get_buffer_size_max)(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val)
+#else
+int snd_pcm_hw_params_get_buffer_size_max(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val)
+#endif
+{
+	unsigned int _val;
+	int err = snd_pcm_hw_param_get_max(params, SND_PCM_HW_PARAM_BUFFER_SIZE, &_val, NULL);
+	if (err >= 0)
+		*val = _val;
+	return err;
+}
+
+/**
+ * \brief Verify if a buffer size is available inside a configuration space for a PCM
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val buffer size in frames
+ * \return 0 if available a negative error code otherwise
+ *
+ * Wanted exact value is <,=,> val following dir (-1,0,1)
+ */
+int snd_pcm_hw_params_test_buffer_size(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t val)
+{
+	return snd_pcm_hw_param_set(pcm, params, SND_TEST, SND_PCM_HW_PARAM_BUFFER_SIZE, val, 0);
+}
+
+/**
+ * \brief Restrict a configuration space to contain only one buffer size
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val buffer size in frames
+ * \return 0 otherwise a negative error code if configuration space would become empty
+ *
+ * Wanted exact value is <,=,> val following dir (-1,0,1)
+ */
+int snd_pcm_hw_params_set_buffer_size(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t val)
+{
+	return snd_pcm_hw_param_set(pcm, params, SND_TRY, SND_PCM_HW_PARAM_BUFFER_SIZE, val, 0);
+}
+
+/**
+ * \brief Restrict a configuration space with a minimum buffer size
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val approximate minimum buffer size in frames (on return filled with actual minimum)
+ * \return 0 otherwise a negative error code if configuration space would become empty
+ */
+int snd_pcm_hw_params_set_buffer_size_min(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val)
+{
+	unsigned int _val = *val;
+	int err = snd_pcm_hw_param_set_min(pcm, params, SND_TRY, SND_PCM_HW_PARAM_BUFFER_SIZE, &_val, NULL);
+	if (err >= 0)
+		*val = _val;
+	return err;
+}
+
+/**
+ * \brief Restrict a configuration space with a maximum buffer size
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val approximate maximum buffer size in frames (on return filled with actual maximum)
+ * \return 0 otherwise a negative error code if configuration space would become empty
+ */
+int snd_pcm_hw_params_set_buffer_size_max(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val)
+{
+	unsigned int _val = *val;
+	int err = snd_pcm_hw_param_set_max(pcm, params, SND_TRY, SND_PCM_HW_PARAM_BUFFER_SIZE, &_val, NULL);
+	if (err >= 0)
+		*val = _val;
+	return err;
+}
+
+/**
+ * \brief Restrict a configuration space to have buffer sizes in a given range
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param min approximate minimum buffer size in frames (on return filled with actual minimum)
+ * \param max approximate maximum buffer size in frames (on return filled with actual maximum)
+ * \return 0 otherwise a negative error code if configuration space would become empty
+ */
+int snd_pcm_hw_params_set_buffer_size_minmax(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *min, snd_pcm_uframes_t *max)
+{
+	unsigned int _min = *min;
+	unsigned int _max = *max;
+	int err = snd_pcm_hw_param_set_minmax(pcm, params, SND_TRY, SND_PCM_HW_PARAM_BUFFER_SIZE, &_min, NULL, &_max, NULL);
+	*min = _min;
+	*max = _max;
+	return err;
+}
+
+/**
+ * \brief Restrict a configuration space to have buffer size nearest to a target
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val approximate target buffer size in frames / returned chosen approximate target buffer size in frames
+ * \return 0 otherwise a negative error code if configuration space is empty
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_set_buffer_size_near)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val)
+#else
+int snd_pcm_hw_params_set_buffer_size_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val)
+#endif
+{
+	unsigned int _val = *val;
+	int err = snd_pcm_hw_param_set_near(pcm, params, SND_PCM_HW_PARAM_BUFFER_SIZE, &_val, NULL);
+	if (err >= 0)
+		*val = _val;
+	return err;
+}
+
+/**
+ * \brief Restrict a configuration space to contain only its minimum buffer size
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val Returned minimum buffer size in frames
+ * \return buffer size in frames
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_set_buffer_size_first)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val)
+#else
+int snd_pcm_hw_params_set_buffer_size_first(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val)
+#endif
+{
+	unsigned int _val;
+	int err = snd_pcm_hw_param_set_first(pcm, params, SND_PCM_HW_PARAM_BUFFER_SIZE, &_val, NULL);
+	if (err >= 0)
+		*val = _val;
+	return err;
+}
+
+/**
+ * \brief Restrict a configuration space to contain only its maximum buffer size
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val Returned maximum buffer size in frames
+ * \return 0 otherwise a negative error code
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_set_buffer_size_last)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val)
+#else
+int snd_pcm_hw_params_set_buffer_size_last(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val)
+#endif
+{
+	unsigned int _val;
+	int err = snd_pcm_hw_param_set_last(pcm, params, SND_PCM_HW_PARAM_BUFFER_SIZE, &_val, NULL);
+	if (err >= 0)
+		*val = _val;
+	return err;
+}
+
+
+/**
+ * \brief (DEPRECATED) Extract tick time from a configuration space
+ * \param params Configuration space
+ * \param val Returned approximate tick duration in us
+ * \param dir Sub unit direction
+ * \return 0 otherwise a negative error code if the configuration space does not contain a single value
+ *
+ * Actual exact value is <,=,> the approximate one following dir (-1, 0, 1)
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_get_tick_time)(const snd_pcm_hw_params_t *params ATTRIBUTE_UNUSED, unsigned int *val, int *dir ATTRIBUTE_UNUSED)
+#else
+int snd_pcm_hw_params_get_tick_time(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#endif
+{
+	*val = 0;
+	return 0;
+}
+
+/**
+ * \brief (DEPRECATED) Extract minimum tick time from a configuration space
+ * \param params Configuration space
+ * \param val Returned approximate minimum tick duration in us
+ * \param dir Sub unit direction
+ * \return 0 otherwise a negative error code
+ *
+ * Exact value is <,=,> the returned one following dir (-1,0,1)
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_get_tick_time_min)(const snd_pcm_hw_params_t *params ATTRIBUTE_UNUSED, unsigned int *val, int *dir ATTRIBUTE_UNUSED)
+#else
+int snd_pcm_hw_params_get_tick_time_min(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#endif
+{
+	*val = 0;
+	return 0;
+}
+
+/**
+ * \brief (DEPRECATED) Extract maximum tick time from a configuration space
+ * \param params Configuration space
+ * \param val Returned approximate maximum tick duration in us
+ * \param dir Sub unit direction
+ * \return 0 otherwise a negative error code
+ *
+ * Exact value is <,=,> the returned one following dir (-1,0,1)
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_get_tick_time_max)(const snd_pcm_hw_params_t *params ATTRIBUTE_UNUSED, unsigned int *val, int *dir ATTRIBUTE_UNUSED)
+#else
+int snd_pcm_hw_params_get_tick_time_max(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#endif
+{
+	*val = 0;
+	return 0;
+}
+
+/**
+ * \brief (DEPRECATED) Verify if a tick time is available inside a configuration space for a PCM
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val approximate tick duration in us
+ * \param dir Sub unit direction
+ * \return 0 if available a negative error code otherwise
+ *
+ * Wanted exact value is <,=,> val following dir (-1,0,1)
+ */
+int snd_pcm_hw_params_test_tick_time(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params ATTRIBUTE_UNUSED, unsigned int val, int dir ATTRIBUTE_UNUSED)
+{
+	return val ? -EINVAL : 0;
+}
+
+/**
+ * \brief (DEPRECATED) Restrict a configuration space to contain only one tick time
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val approximate tick duration in us
+ * \param dir Sub unit direction
+ * \return 0 otherwise a negative error code if configuration space would become empty
+ *
+ * Wanted exact value is <,=,> val following dir (-1,0,1)
+ */
+int snd_pcm_hw_params_set_tick_time(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params ATTRIBUTE_UNUSED, unsigned int val ATTRIBUTE_UNUSED, int dir ATTRIBUTE_UNUSED)
+{
+	return 0;
+}
+
+/**
+ * \brief (DEPRECATED) Restrict a configuration space with a minimum tick time
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val approximate minimum tick duration in us (on return filled with actual minimum)
+ * \param dir Sub unit direction (on return filled with actual direction)
+ * \return 0 otherwise a negative error code if configuration space would become empty
+ *
+ * Wanted/actual exact minimum is <,=,> val following dir (-1,0,1)
+ */
+int snd_pcm_hw_params_set_tick_time_min(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params ATTRIBUTE_UNUSED, unsigned int *val ATTRIBUTE_UNUSED, int *dir ATTRIBUTE_UNUSED)
+{
+	return 0;
+}
+
+/**
+ * \brief (DEPRECATED) Restrict a configuration space with a maximum tick time
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val approximate maximum tick duration in us (on return filled with actual maximum)
+ * \param dir Sub unit direction (on return filled with actual direction)
+ * \return 0 otherwise a negative error code if configuration space would become empty
+ *
+ * Wanted/actual exact maximum is <,=,> val following dir (-1,0,1)
+ */
+int snd_pcm_hw_params_set_tick_time_max(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params ATTRIBUTE_UNUSED, unsigned int *val ATTRIBUTE_UNUSED, int *dir ATTRIBUTE_UNUSED)
+{
+	return 0;
+}
+
+/**
+ * \brief (DEPRECATED) Restrict a configuration space to have tick times in a given range
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param min approximate minimum tick duration in us (on return filled with actual minimum)
+ * \param mindir Sub unit direction for minimum (on return filled with actual direction)
+ * \param max approximate maximum tick duration in us (on return filled with actual maximum)
+ * \param maxdir Sub unit direction for maximum (on return filled with actual direction)
+ * \return 0 otherwise a negative error code if configuration space would become empty
+ *
+ * Wanted/actual exact min/max is <,=,> val following dir (-1,0,1)
+ */
+int snd_pcm_hw_params_set_tick_time_minmax(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params ATTRIBUTE_UNUSED, unsigned int *min ATTRIBUTE_UNUSED, int *mindir ATTRIBUTE_UNUSED, unsigned int *max ATTRIBUTE_UNUSED, int *maxdir ATTRIBUTE_UNUSED)
+{
+	return 0;
+}
+
+/**
+ * \brief (DEPRECATED) Restrict a configuration space to have tick time nearest to a target
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val approximate target tick duration in us / returned chosen approximate target tick duration in us
+ * \param dir Sub unit direction
+ * \return 0 otherwise a negative error code if configuration space is empty
+ *
+ * target/chosen exact value is <,=,> val following dir (-1,0,1)
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_set_tick_time_near)(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params ATTRIBUTE_UNUSED, unsigned int *val ATTRIBUTE_UNUSED, int *dir ATTRIBUTE_UNUSED)
+#else
+int snd_pcm_hw_params_set_tick_time_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#endif
+{
+	return 0;
+}
+
+/**
+ * \brief (DEPRECATED) Restrict a configuration space to contain only its minimum tick time
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val Returned approximate minimum tick duration in us
+ * \param dir Sub unit direction
+ * \return 0 otherwise a negative error code
+ *
+ * Actual exact value is <,=,> the approximate one following dir (-1, 0, 1)
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_set_tick_time_first)(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params ATTRIBUTE_UNUSED, unsigned int *val ATTRIBUTE_UNUSED, int *dir ATTRIBUTE_UNUSED)
+#else
+int snd_pcm_hw_params_set_tick_time_first(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#endif
+{
+	return 0;
+}
+
+/**
+ * \brief (DEPRECATED) Restrict a configuration space to contain only its maximum tick time
+ * \param pcm PCM handle
+ * \param params Configuration space
+ * \param val Returned approximate maximum tick duration in us
+ * \param dir Sub unit direction
+ * \return 0 otherwise a negative error code
+ *
+ * Actual exact value is <,=,> the approximate one following dir (-1, 0, 1)
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_hw_params_set_tick_time_last)(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params ATTRIBUTE_UNUSED, unsigned int *val ATTRIBUTE_UNUSED, int *dir ATTRIBUTE_UNUSED)
+#else
+int snd_pcm_hw_params_set_tick_time_last(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir)
+#endif
+{
+	return 0;
+}
+
+/**
+ * \brief Get the minimum transfer align value in samples
+ * \param params Configuration space
+ * \param val Returned minimum align value
+ * \return 0 otherwise a negative error code if the configuration space does not contain a single value
+ */
+int snd_pcm_hw_params_get_min_align(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val)
+{
+	unsigned int format, channels, fb, min_align;
+	int err;
+
+	err = snd_pcm_hw_param_get(params, SND_PCM_HW_PARAM_FORMAT, &format, NULL);
+	if (err < 0)
+		return err;
+	err = snd_pcm_hw_param_get(params, SND_PCM_HW_PARAM_CHANNELS, &channels, NULL);
+	if (err < 0)
+		return err;
+	// compute frame bits
+	fb = snd_pcm_format_physical_width((snd_pcm_format_t)format) * channels;
+        min_align = 1;
+	while (fb % 8) {
+		fb *= 2;
+                min_align *= 2;
+	}
+	if (val)
+		*val = min_align;
+	return 0;
+}
+
+/**
+ * \brief Return current software configuration for a PCM
+ * \param pcm PCM handle
+ * \param params Software configuration container
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_pcm_sw_params_current(snd_pcm_t *pcm, snd_pcm_sw_params_t *params)
+{
+	assert(pcm && params);
+	if (CHECK_SANITY(! pcm->setup)) {
+		SNDMSG("PCM not set up");
+		return -EIO;
+	}
+	params->tstamp_mode = pcm->tstamp_mode;
+	params->period_step = pcm->period_step;
+	params->sleep_min = 0;
+	params->avail_min = pcm->avail_min;
+	params->period_event = pcm->period_event;
+	params->xfer_align = 1;
+	params->start_threshold = pcm->start_threshold;
+	params->stop_threshold = pcm->stop_threshold;
+	params->silence_threshold = pcm->silence_threshold;
+	params->silence_size = pcm->silence_size;
+	params->boundary = pcm->boundary;
+	return 0;
+}
+
+/**
+ * \brief Dump a software configuration
+ * \param params Software configuration container
+ * \param out Output handle
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_pcm_sw_params_dump(snd_pcm_sw_params_t *params, snd_output_t *out)
+{
+	snd_output_printf(out, "tstamp_mode: %s\n", snd_pcm_tstamp_mode_name(params->tstamp_mode));
+	snd_output_printf(out, "period_step: %u\n", params->period_step);
+	snd_output_printf(out, "avail_min: %lu\n", params->avail_min);
+	snd_output_printf(out, "start_threshold: %ld\n", params->start_threshold);
+	snd_output_printf(out, "stop_threshold: %ld\n", params->stop_threshold);
+	snd_output_printf(out, "silence_threshold: %lu\n", params->silence_threshold);
+	snd_output_printf(out, "silence_size: %lu\n", params->silence_size);
+	snd_output_printf(out, "boundary: %lu\n", params->boundary);
+	return 0;
+}
+
+/**
+ * \brief get size of #snd_pcm_sw_params_t
+ * \return size in bytes
+ */
+size_t snd_pcm_sw_params_sizeof()
+{
+	return sizeof(snd_pcm_sw_params_t);
+}
+
+/**
+ * \brief allocate an invalid #snd_pcm_sw_params_t using standard malloc
+ * \param ptr returned pointer
+ * \return 0 on success otherwise negative error code
+ */
+int snd_pcm_sw_params_malloc(snd_pcm_sw_params_t **ptr)
+{
+	assert(ptr);
+	*ptr = calloc(1, sizeof(snd_pcm_sw_params_t));
+	if (!*ptr)
+		return -ENOMEM;
+	return 0;
+}
+
+/**
+ * \brief frees a previously allocated #snd_pcm_sw_params_t
+ * \param obj pointer to object to free
+ */
+void snd_pcm_sw_params_free(snd_pcm_sw_params_t *obj)
+{
+	free(obj);
+}
+
+/**
+ * \brief copy one #snd_pcm_sw_params_t to another
+ * \param dst pointer to destination
+ * \param src pointer to source
+ */
+void snd_pcm_sw_params_copy(snd_pcm_sw_params_t *dst, const snd_pcm_sw_params_t *src)
+{
+	assert(dst && src);
+	*dst = *src;
+}
+
+/**
+ * \brief Get boundary for ring pointers from a software configuration container
+ * \param params Software configuration container
+ * \param val Returned boundary in frames
+ * \return 0 otherwise a negative error code
+ */
+int snd_pcm_sw_params_get_boundary(const snd_pcm_sw_params_t *params, snd_pcm_uframes_t *val)
+{
+	assert(params);
+	*val = params->boundary;
+	return 0;
+}
+
+/**
+ * \brief (DEPRECATED) Set start mode inside a software configuration container
+ * \param pcm PCM handle
+ * \param params Software configuration container
+ * \param val Start mode
+ * \return 0 otherwise a negative error code
+ */
+int snd_pcm_sw_params_set_start_mode(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_start_t val)
+{
+	assert(pcm && params);
+	switch (val) {
+	case SND_PCM_START_DATA:
+		params->start_threshold = 1;
+		break;
+	case SND_PCM_START_EXPLICIT:
+		params->start_threshold = pcm->boundary;
+		break;
+	default:
+		SNDMSG("invalid start mode value %d\n", val);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+#ifndef DOC_HIDDEN
+link_warning(snd_pcm_sw_params_set_start_mode, "Warning: start_mode is deprecated, consider to use start_threshold");
+#endif
+
+/**
+ * \brief (DEPRECATED) Get start mode from a software configuration container
+ * \param params Software configuration container
+ * \return start mode
+ */
+snd_pcm_start_t snd_pcm_sw_params_get_start_mode(const snd_pcm_sw_params_t *params)
+{
+	assert(params);
+	/* FIXME: Ugly */
+	return params->start_threshold > 1024 * 1024 ? SND_PCM_START_EXPLICIT : SND_PCM_START_DATA;
+}
+
+#ifndef DOC_HIDDEN
+link_warning(snd_pcm_sw_params_get_start_mode, "Warning: start_mode is deprecated, consider to use start_threshold");
+#endif
+
+/**
+ * \brief (DEPRECATED) Set xrun mode inside a software configuration container
+ * \param pcm PCM handle
+ * \param params Software configuration container
+ * \param val Xrun mode
+ * \return 0 otherwise a negative error code
+ */
+#ifndef DOXYGEN
+int snd_pcm_sw_params_set_xrun_mode(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_sw_params_t *params, snd_pcm_xrun_t val)
+#else
+int snd_pcm_sw_params_set_xrun_mode(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_xrun_t val)
+#endif
+{
+	assert(pcm && params);
+	switch (val) {
+	case SND_PCM_XRUN_STOP:
+		params->stop_threshold = pcm->buffer_size;
+		break;
+	case SND_PCM_XRUN_NONE:
+		params->stop_threshold = pcm->boundary;
+		break;
+	default:
+		SNDMSG("invalid xrun mode value %d\n", val);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+#ifndef DOC_HIDDEN
+link_warning(snd_pcm_sw_params_set_xrun_mode, "Warning: xrun_mode is deprecated, consider to use stop_threshold");
+#endif
+
+/**
+ * \brief (DEPRECATED) Get xrun mode from a software configuration container
+ * \param params Software configuration container
+ * \return xrun mode
+ */
+snd_pcm_xrun_t snd_pcm_sw_params_get_xrun_mode(const snd_pcm_sw_params_t *params)
+{
+	assert(params);
+	/* FIXME: Ugly */
+	return params->stop_threshold > 1024 * 1024 ? SND_PCM_XRUN_NONE : SND_PCM_XRUN_STOP;
+}
+
+#ifndef DOC_HIDDEN
+link_warning(snd_pcm_sw_params_get_xrun_mode, "Warning: xrun_mode is deprecated, consider to use stop_threshold");
+#endif
+
+/**
+ * \brief Set timestamp mode inside a software configuration container
+ * \param pcm PCM handle
+ * \param params Software configuration container
+ * \param val Timestamp mode
+ * \return 0 otherwise a negative error code
+ */
+#ifndef DOXYGEN
+int snd_pcm_sw_params_set_tstamp_mode(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_sw_params_t *params, snd_pcm_tstamp_t val)
+#else
+int snd_pcm_sw_params_set_tstamp_mode(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_tstamp_t val)
+#endif
+{
+	assert(pcm && params);
+	if (CHECK_SANITY(val > SND_PCM_TSTAMP_LAST)) {
+		SNDMSG("invalid tstamp_mode value %d", val);
+		return -EINVAL;
+	}
+	params->tstamp_mode = val;
+	return 0;
+}
+
+/**
+ * \brief Get timestamp mode from a software configuration container
+ * \param params Software configuration container
+ * \param val Returned timestamp
+ * \return 0 otherwise a negative error code
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_sw_params_get_tstamp_mode)(const snd_pcm_sw_params_t *params, snd_pcm_tstamp_t *val)
+#else
+int snd_pcm_sw_params_get_tstamp_mode(const snd_pcm_sw_params_t *params, snd_pcm_tstamp_t *val)
+#endif
+{
+	assert(params && val);
+	*val = params->tstamp_mode;
+	return 0;
+}
+
+/**
+ * \brief (DEPRECATED) Set minimum number of ticks to sleep inside a software configuration container
+ * \param pcm PCM handle
+ * \param params Software configuration container
+ * \param val Minimum ticks to sleep or 0 to disable the use of tick timer
+ * \return 0 otherwise a negative error code
+ */
+#ifndef DOXYGEN
+int snd_pcm_sw_params_set_sleep_min(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_sw_params_t *params ATTRIBUTE_UNUSED, unsigned int val ATTRIBUTE_UNUSED)
+#else
+int snd_pcm_sw_params_set_sleep_min(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, unsigned int val)
+#endif
+{
+	return 0;
+}
+
+/**
+ * \brief (DEPRECATED) Get minimum numbers of ticks to sleep from a software configuration container
+ * \param params Software configuration container
+ * \param val returned minimum number of ticks to sleep or 0 if tick timer is disabled
+ * \return 0 otherwise a negative error code
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_sw_params_get_sleep_min)(const snd_pcm_sw_params_t *params ATTRIBUTE_UNUSED, unsigned int *val)
+#else
+int snd_pcm_sw_params_get_sleep_min(const snd_pcm_sw_params_t *params, unsigned int *val)
+#endif
+{
+	*val = 0;
+	return 0;
+}
+
+/**
+ * \brief Set avail min inside a software configuration container
+ * \param pcm PCM handle
+ * \param params Software configuration container
+ * \param val Minimum avail frames to consider PCM ready
+ * \return 0 otherwise a negative error code
+ *
+ * Note: This is similar to setting an OSS wakeup point.  The valid
+ * values for 'val' are determined by the specific hardware.  Most PC
+ * sound cards can only accept power of 2 frame counts (i.e. 512,
+ * 1024, 2048).  You cannot use this as a high resolution timer - it
+ * is limited to how often the sound card hardware raises an
+ * interrupt.
+ */
+#ifndef DOXYGEN
+int snd_pcm_sw_params_set_avail_min(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val)
+#else
+int snd_pcm_sw_params_set_avail_min(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val)
+#endif
+{
+	assert(pcm && params);
+	/* Fix avail_min if it's below period size.  The period_size
+	 * defines the minimal wake-up timing accuracy, so it doesn't
+	 * make sense to set below that.
+	 */
+	if (val < pcm->period_size)
+		val = pcm->period_size;
+	params->avail_min = val;
+	return 0;
+}
+
+/**
+ * \brief Get avail min from a software configuration container
+ * \param params Software configuration container
+ * \param val returned minimum available frames to consider PCM ready
+ * \return 0 otherwise a negative error code
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_sw_params_get_avail_min)(const snd_pcm_sw_params_t *params, snd_pcm_uframes_t *val)
+#else
+int snd_pcm_sw_params_get_avail_min(const snd_pcm_sw_params_t *params, snd_pcm_uframes_t *val)
+#endif
+{
+	assert(params && val);
+	*val = params->avail_min;
+	return 0;
+}
+
+/**
+ * \brief Set period event inside a software configuration container
+ * \param pcm PCM handle
+ * \param params Software configuration container
+ * \param val 0 = disable period event, 1 = enable period event
+ * \return 0 otherwise a negative error code
+ *
+ * An poll (select) wakeup event is raised if enabled.
+ */
+int snd_pcm_sw_params_set_period_event(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, int val)
+{
+	assert(pcm && params);
+	params->period_event = val;
+	return 0;
+}
+
+/**
+ * \brief Get period event from a software configuration container
+ * \param params Software configuration container
+ * \param val returned period event state
+ * \return 0 otherwise a negative error code
+ */
+int snd_pcm_sw_params_get_period_event(const snd_pcm_sw_params_t *params, int *val)
+{
+	assert(params && val);
+	*val = params->period_event;
+	return 0;
+}
+
+/**
+ * \brief (DEPRECATED) Set xfer align inside a software configuration container
+ * \param pcm PCM handle
+ * \param params Software configuration container
+ * \param val Chunk size (frames are attempted to be transferred in chunks)
+ * \return 0 otherwise a negative error code
+ */
+#ifndef DOXYGEN
+int snd_pcm_sw_params_set_xfer_align(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_sw_params_t *params ATTRIBUTE_UNUSED, snd_pcm_uframes_t val ATTRIBUTE_UNUSED)
+#else
+int snd_pcm_sw_params_set_xfer_align(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val)
+#endif
+{
+	return 0;
+}
+
+/**
+ * \brief (DEPRECATED) Get xfer align from a software configuration container
+ * \param params Software configuration container
+ * \param val returned chunk size (frames are attempted to be transferred in chunks)
+ * \return 0 otherwise a negative error code
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_sw_params_get_xfer_align)(const snd_pcm_sw_params_t *params ATTRIBUTE_UNUSED, snd_pcm_uframes_t *val)
+#else
+int snd_pcm_sw_params_get_xfer_align(const snd_pcm_sw_params_t *params, snd_pcm_uframes_t *val)
+#endif
+{
+	*val = 1;
+	return 0;
+}
+
+/**
+ * \brief Set start threshold inside a software configuration container
+ * \param pcm PCM handle
+ * \param params Software configuration container
+ * \param val Start threshold in frames
+ * \return 0 otherwise a negative error code
+ *
+ * PCM is automatically started when playback frames available to PCM 
+ * are >= threshold or when requested capture frames are >= threshold
+ */
+#ifndef DOXYGEN
+int snd_pcm_sw_params_set_start_threshold(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val)
+#else
+int snd_pcm_sw_params_set_start_threshold(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val)
+#endif
+{
+	assert(pcm && params);
+	params->start_threshold = val;
+	return 0;
+}
+
+/**
+ * \brief Get start threshold from a software configuration container
+ * \param params Software configuration container
+ * \param val Returned start threshold in frames
+ * \return 0 otherwise a negative error code
+ *
+ * PCM is automatically started when playback frames available to PCM 
+ * are >= threshold or when requested capture frames are >= threshold
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_sw_params_get_start_threshold)(const snd_pcm_sw_params_t *params, snd_pcm_uframes_t *val)
+#else
+int snd_pcm_sw_params_get_start_threshold(const snd_pcm_sw_params_t *params, snd_pcm_uframes_t *val)
+#endif
+{
+	assert(params);
+	*val = params->start_threshold;
+	return 0;
+}
+
+
+/**
+ * \brief Set stop threshold inside a software configuration container
+ * \param pcm PCM handle
+ * \param params Software configuration container
+ * \param val Stop threshold in frames
+ * \return 0 otherwise a negative error code
+ *
+ * PCM is automatically stopped in #SND_PCM_STATE_XRUN state when available
+ * frames is >= threshold. If the stop threshold is equal to boundary (also
+ * software parameter - sw_param) then automatic stop will be disabled
+ * (thus device will do the endless loop in the ring buffer).
+ */
+#ifndef DOXYGEN
+int snd_pcm_sw_params_set_stop_threshold(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val)
+#else
+int snd_pcm_sw_params_set_stop_threshold(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val)
+#endif
+{
+	assert(pcm && params);
+	params->stop_threshold = val;
+	return 0;
+}
+
+/**
+ * \brief Get stop threshold from a software configuration container
+ * \param params Software configuration container
+ * \param val Returned stop threshold in frames
+ * \return 0 otherwise a negative error code
+ *
+ * PCM is automatically stopped in #SND_PCM_STATE_XRUN state when available
+ * frames is >= threshold. If the stop threshold is equal to boundary (also
+ * software parameter - sw_param) then automatic stop will be disabled
+ * (thus device will do the endless loop in the ring buffer).
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_sw_params_get_stop_threshold)(const snd_pcm_sw_params_t *params, snd_pcm_uframes_t *val)
+#else
+int snd_pcm_sw_params_get_stop_threshold(const snd_pcm_sw_params_t *params, snd_pcm_uframes_t *val)
+#endif
+{
+	assert(params);
+	*val = params->stop_threshold;
+	return 0;
+}
+
+
+/**
+ * \brief Set silence threshold inside a software configuration container
+ * \param pcm PCM handle
+ * \param params Software configuration container
+ * \param val Silence threshold in frames 
+ * \return 0 otherwise a negative error code
+ *
+ * A portion of playback buffer is overwritten with silence (see 
+ * #snd_pcm_sw_params_set_silence_size) when playback underrun is nearer
+ * than silence threshold.
+ */
+#ifndef DOXYGEN
+int snd_pcm_sw_params_set_silence_threshold(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val)
+#else
+int snd_pcm_sw_params_set_silence_threshold(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val)
+#endif
+{
+	assert(pcm && params);
+	if (CHECK_SANITY(val >= pcm->buffer_size)) {
+		SNDMSG("invalid silent_threshold value %ld (buffer_size = %ld)",
+		       val, pcm->buffer_size);
+		return -EINVAL;
+	}
+	params->silence_threshold = val;
+	return 0;
+}
+
+/**
+ * \brief Get silence threshold from a software configuration container
+ * \param params Software configuration container
+ * \param val Returned silence threshold in frames
+ * \return 0 otherwise a negative error value
+ *
+ * A portion of playback buffer is overwritten with silence (see 
+ * #snd_pcm_sw_params_set_silence_size) when playback underrun is nearer
+ * than silence threshold.
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_sw_params_get_silence_threshold)(const snd_pcm_sw_params_t *params, snd_pcm_uframes_t *val)
+#else
+int snd_pcm_sw_params_get_silence_threshold(const snd_pcm_sw_params_t *params, snd_pcm_uframes_t *val)
+#endif
+{
+	assert(params && val);
+	*val = params->silence_threshold;
+	return 0;
+}
+
+
+/**
+ * \brief Set silence size inside a software configuration container
+ * \param pcm PCM handle
+ * \param params Software configuration container
+ * \param val Silence size in frames (0 for disabled)
+ * \return 0 otherwise a negative error code
+ *
+ * A portion of playback buffer is overwritten with silence when playback
+ * underrun is nearer than silence threshold (see 
+ * #snd_pcm_sw_params_set_silence_threshold)
+ *
+ * The special case is when silence size value is equal or greater than
+ * boundary. The unused portion of the ring buffer (initial written samples
+ * are untouched) is filled with silence at start. Later, only just processed
+ * sample area is filled with silence. Note: silence_threshold must be set to zero.
+ */
+#ifndef DOXYGEN
+int snd_pcm_sw_params_set_silence_size(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val)
+#else
+int snd_pcm_sw_params_set_silence_size(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val)
+#endif
+{
+	assert(pcm && params);
+	if (CHECK_SANITY(val < pcm->boundary && val > pcm->buffer_size)) {
+		SNDMSG("invalid silence_size %ld (boundary %ld, buffer_size %ld)",
+		       val, pcm->boundary, pcm->buffer_size);
+		return -EINVAL;
+	}
+	params->silence_size = val;
+	return 0;
+}
+
+/**
+ * \brief Get silence size from a software configuration container
+ * \param params Software configuration container
+ * \param val Returned silence size in frames (0 for disabled)
+ * \return 0 otherwise a negative error code
+ *
+ * A portion of playback buffer is overwritten with silence when playback
+ * underrun is nearer than silence threshold (see 
+ * #snd_pcm_sw_params_set_silence_threshold)
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_pcm_sw_params_get_silence_size)(const snd_pcm_sw_params_t *params, snd_pcm_uframes_t *val)
+#else
+int snd_pcm_sw_params_get_silence_size(const snd_pcm_sw_params_t *params, snd_pcm_uframes_t *val)
+#endif
+{
+	assert(params);
+	*val = params->silence_size;
+	return 0;
+}
+
+
+/**
+ * \brief get size of #snd_pcm_status_t
+ * \return size in bytes
+ */
+size_t snd_pcm_status_sizeof()
+{
+	return sizeof(snd_pcm_status_t);
+}
+
+/**
+ * \brief allocate an invalid #snd_pcm_status_t using standard malloc
+ * \param ptr returned pointer
+ * \return 0 on success otherwise negative error code
+ */
+int snd_pcm_status_malloc(snd_pcm_status_t **ptr)
+{
+	assert(ptr);
+	*ptr = calloc(1, sizeof(snd_pcm_status_t));
+	if (!*ptr)
+		return -ENOMEM;
+	return 0;
+}
+
+/**
+ * \brief frees a previously allocated #snd_pcm_status_t
+ * \param obj pointer to object to free
+ */
+void snd_pcm_status_free(snd_pcm_status_t *obj)
+{
+	free(obj);
+}
+
+/**
+ * \brief copy one #snd_pcm_status_t to another
+ * \param dst pointer to destination
+ * \param src pointer to source
+ */
+void snd_pcm_status_copy(snd_pcm_status_t *dst, const snd_pcm_status_t *src)
+{
+	assert(dst && src);
+	*dst = *src;
+}
+
+/** 
+ * \brief Get state from a PCM status container (see #snd_pcm_state)
+ * \param obj #snd_pcm_status_t pointer
+ * \return PCM state
+ */
+snd_pcm_state_t snd_pcm_status_get_state(const snd_pcm_status_t *obj)
+{
+	assert(obj);
+	return obj->state;
+}
+
+/** 
+ * \brief Get trigger timestamp from a PCM status container
+ * \param obj #snd_pcm_status_t pointer
+ * \param ptr Pointer to returned timestamp
+ *
+ * Trigger means a PCM state transition (from stopped to running or
+ * versa vice). It applies also to pause and suspend. In other words,
+ * timestamp contains time when stream started or when it was stopped.
+ */
+void snd_pcm_status_get_trigger_tstamp(const snd_pcm_status_t *obj, snd_timestamp_t *ptr)
+{
+	assert(obj && ptr);
+	ptr->tv_sec = obj->trigger_tstamp.tv_sec;
+	ptr->tv_usec = obj->trigger_tstamp.tv_nsec / 1000L;
+}
+
+/** 
+ * \brief Get trigger hi-res timestamp from a PCM status container
+ * \param obj #snd_pcm_status_t pointer
+ * \param ptr Pointer to returned timestamp
+ *
+ * Trigger means a PCM state transition (from stopped to running or
+ * versa vice). It applies also to pause and suspend. In other words,
+ * timestamp contains time when stream started or when it was stopped.
+ */
+#ifndef DOXYGEN
+void INTERNAL(snd_pcm_status_get_trigger_htstamp)(const snd_pcm_status_t *obj, snd_htimestamp_t *ptr)
+#else
+void snd_pcm_status_get_trigger_htstamp(const snd_pcm_status_t *obj, snd_htimestamp_t *ptr)
+#endif
+{
+	assert(obj && ptr);
+	*ptr = obj->trigger_tstamp;
+}
+use_default_symbol_version(__snd_pcm_status_get_trigger_htstamp, snd_pcm_status_get_trigger_htstamp, ALSA_0.9.0rc8);
+
+/** 
+ * \brief Get "now" timestamp from a PCM status container
+ * \param obj #snd_pcm_status_t pointer
+ * \param ptr Pointer to returned timestamp
+ */
+void snd_pcm_status_get_tstamp(const snd_pcm_status_t *obj, snd_timestamp_t *ptr)
+{
+	assert(obj && ptr);
+	ptr->tv_sec = obj->tstamp.tv_sec;
+	ptr->tv_usec = obj->tstamp.tv_nsec / 1000L;
+}
+
+/** 
+ * \brief Get "now" hi-res timestamp from a PCM status container
+ * \param obj pointer to #snd_pcm_status_t
+ * \param ptr Pointer to returned timestamp
+ */
+#ifndef DOXYGEN
+void INTERNAL(snd_pcm_status_get_htstamp)(const snd_pcm_status_t *obj, snd_htimestamp_t *ptr)
+#else
+void snd_pcm_status_get_htstamp(const snd_pcm_status_t *obj, snd_htimestamp_t *ptr)
+#endif
+{
+	assert(obj && ptr);
+	*ptr = obj->tstamp;
+}
+use_default_symbol_version(__snd_pcm_status_get_htstamp, snd_pcm_status_get_htstamp, ALSA_0.9.0rc8);
+
+/** 
+ * \brief Get delay from a PCM status container (see #snd_pcm_delay)
+ * \return Delay in frames
+ *
+ * Delay is distance between current application frame position and
+ * sound frame position.
+ * It's positive and less than buffer size in normal situation,
+ * negative on playback underrun and greater than buffer size on
+ * capture overrun.
+ */
+snd_pcm_sframes_t snd_pcm_status_get_delay(const snd_pcm_status_t *obj)
+{
+	assert(obj);
+	return obj->delay;
+}
+
+/** 
+ * \brief Get number of frames available from a PCM status container (see #snd_pcm_avail_update)
+ * \return Number of frames ready to be read/written
+ */
+snd_pcm_uframes_t snd_pcm_status_get_avail(const snd_pcm_status_t *obj)
+{
+	assert(obj);
+	return obj->avail;
+}
+
+/** 
+ * \brief Get maximum number of frames available from a PCM status container after last #snd_pcm_status call
+ * \return Maximum number of frames ready to be read/written
+ */
+snd_pcm_uframes_t snd_pcm_status_get_avail_max(const snd_pcm_status_t *obj)
+{
+	assert(obj);
+	return obj->avail_max;
+}
+
+/** 
+ * \brief Get count of ADC overrange detections since last call
+ * \return Count of ADC overrange detections
+ */
+snd_pcm_uframes_t snd_pcm_status_get_overrange(const snd_pcm_status_t *obj)
+{
+	assert(obj);
+	return obj->overrange;
+}
+
+/**
+ * \brief get size of #snd_pcm_info_t
+ * \return size in bytes
+ */
+size_t snd_pcm_info_sizeof()
+{
+	return sizeof(snd_pcm_info_t);
+}
+
+/**
+ * \brief allocate an invalid #snd_pcm_info_t using standard malloc
+ * \param ptr returned pointer
+ * \return 0 on success otherwise negative error code
+ */
+int snd_pcm_info_malloc(snd_pcm_info_t **ptr)
+{
+	assert(ptr);
+	*ptr = calloc(1, sizeof(snd_pcm_info_t));
+	if (!*ptr)
+		return -ENOMEM;
+	return 0;
+}
+
+/**
+ * \brief frees a previously allocated #snd_pcm_info_t
+ * \param obj pointer to object to free
+ */
+void snd_pcm_info_free(snd_pcm_info_t *obj)
+{
+	free(obj);
+}
+
+/**
+ * \brief copy one #snd_pcm_info_t to another
+ * \param dst pointer to destination
+ * \param src pointer to source
+ */
+void snd_pcm_info_copy(snd_pcm_info_t *dst, const snd_pcm_info_t *src)
+{
+	assert(dst && src);
+	*dst = *src;
+}
+
+/**
+ * \brief Get device from a PCM info container
+ * \param obj PCM info container
+ * \return device number
+ */
+unsigned int snd_pcm_info_get_device(const snd_pcm_info_t *obj)
+{
+	assert(obj);
+	return obj->device;
+}
+
+/**
+ * \brief Get subdevice from a PCM info container
+ * \param obj PCM info container
+ * \return subdevice number
+ */
+unsigned int snd_pcm_info_get_subdevice(const snd_pcm_info_t *obj)
+{
+	assert(obj);
+	return obj->subdevice;
+}
+
+/**
+ * \brief Get stream (direction) from a PCM info container
+ * \param obj PCM info container
+ * \return stream
+ */
+snd_pcm_stream_t snd_pcm_info_get_stream(const snd_pcm_info_t *obj)
+{
+	assert(obj);
+	return obj->stream;
+}
+
+/**
+ * \brief Get card from a PCM info container
+ * \param obj PCM info container
+ * \return card number otherwise a negative error code if not associable to a card
+ */
+int snd_pcm_info_get_card(const snd_pcm_info_t *obj)
+{
+	assert(obj);
+	return obj->card;
+}
+
+/**
+ * \brief Get id from a PCM info container
+ * \param obj PCM info container
+ * \return short id of PCM
+ */
+const char *snd_pcm_info_get_id(const snd_pcm_info_t *obj)
+{
+	assert(obj);
+	return (const char *)obj->id;
+}
+
+/**
+ * \brief Get name from a PCM info container
+ * \param obj PCM info container
+ * \return name of PCM
+ */
+const char *snd_pcm_info_get_name(const snd_pcm_info_t *obj)
+{
+	assert(obj);
+	return (const char *)obj->name;
+}
+
+/**
+ * \brief Get subdevice name from a PCM info container
+ * \param obj PCM info container
+ * \return name of used PCM subdevice
+ */
+const char *snd_pcm_info_get_subdevice_name(const snd_pcm_info_t *obj)
+{
+	assert(obj);
+	return (const char *)obj->subname;
+}
+
+/**
+ * \brief Get class from a PCM info container
+ * \param obj PCM info container
+ * \return class of PCM
+ */
+snd_pcm_class_t snd_pcm_info_get_class(const snd_pcm_info_t *obj)
+{
+	assert(obj);
+	return obj->dev_class;
+}
+
+/**
+ * \brief Get subclass from a PCM info container
+ * \param obj PCM info container
+ * \return subclass of PCM
+ */
+snd_pcm_subclass_t snd_pcm_info_get_subclass(const snd_pcm_info_t *obj)
+{
+	assert(obj);
+	return obj->dev_subclass;
+}
+
+/**
+ * \brief Get subdevices count from a PCM info container
+ * \param obj PCM info container
+ * \return subdevices total count of PCM
+ */
+unsigned int snd_pcm_info_get_subdevices_count(const snd_pcm_info_t *obj)
+{
+	assert(obj);
+	return obj->subdevices_count;
+}
+
+/**
+ * \brief Get available subdevices count from a PCM info container
+ * \param obj PCM info container
+ * \return available subdevices count of PCM
+ */
+unsigned int snd_pcm_info_get_subdevices_avail(const snd_pcm_info_t *obj)
+{
+	assert(obj);
+	return obj->subdevices_avail;
+}
+
+/**
+ * \brief Get hardware synchronization ID from a PCM info container
+ * \param obj PCM info container
+ * \return hardware synchronization ID
+ */
+snd_pcm_sync_id_t snd_pcm_info_get_sync(const snd_pcm_info_t *obj)
+{
+	snd_pcm_sync_id_t res;
+	assert(obj);
+	memcpy(&res, &obj->sync, sizeof(res));
+	return res;
+}
+
+/**
+ * \brief Set wanted device inside a PCM info container (see #snd_ctl_pcm_info)
+ * \param obj PCM info container
+ * \param val Device number
+ */
+void snd_pcm_info_set_device(snd_pcm_info_t *obj, unsigned int val)
+{
+	assert(obj);
+	obj->device = val;
+}
+
+/**
+ * \brief Set wanted subdevice inside a PCM info container (see #snd_ctl_pcm_info)
+ * \param obj PCM info container
+ * \param val Subdevice number
+ */
+void snd_pcm_info_set_subdevice(snd_pcm_info_t *obj, unsigned int val)
+{
+	assert(obj);
+	obj->subdevice = val;
+}
+
+/**
+ * \brief Set wanted stream inside a PCM info container (see #snd_ctl_pcm_info)
+ * \param obj PCM info container
+ * \param val Stream
+ */
+void snd_pcm_info_set_stream(snd_pcm_info_t *obj, snd_pcm_stream_t val)
+{
+	assert(obj);
+	obj->stream = val;
+}
+
+/**
+ * \brief Application request to access a portion of direct (mmap) area
+ * \param pcm PCM handle 
+ * \param areas Returned mmap channel areas
+ * \param offset Returned mmap area offset in area steps (== frames)
+ * \param frames mmap area portion size in frames (wanted on entry, contiguous available on exit)
+ * \return 0 on success otherwise a negative error code
+ *
+ * It is necessary to call the snd_pcm_avail_update() function directly before
+ * this call. Otherwise, this function can return a wrong count of available frames.
+ *
+ * The function should be called before a sample-direct area can be accessed.
+ * The resulting size parameter is always less or equal to the input count of frames
+ * and can be zero, if no frames can be processed (the ring buffer is full).
+ *
+ * See the snd_pcm_mmap_commit() function to finish the frame processing in
+ * the direct areas.
+ */
+int snd_pcm_mmap_begin(snd_pcm_t *pcm,
+		       const snd_pcm_channel_area_t **areas,
+		       snd_pcm_uframes_t *offset,
+		       snd_pcm_uframes_t *frames)
+{
+	snd_pcm_uframes_t cont;
+	snd_pcm_uframes_t f;
+	snd_pcm_uframes_t avail;
+	const snd_pcm_channel_area_t *xareas;
+	assert(pcm && areas && offset && frames);
+	xareas = snd_pcm_mmap_areas(pcm);
+	if (xareas == NULL)
+		return -EBADFD;
+	*areas = xareas;
+	*offset = *pcm->appl.ptr % pcm->buffer_size;
+	avail = snd_pcm_mmap_avail(pcm);
+	if (avail > pcm->buffer_size)
+		avail = pcm->buffer_size;
+	cont = pcm->buffer_size - *offset;
+	f = *frames;
+	if (f > avail)
+		f = avail;
+	if (f > cont)
+		f = cont;
+	*frames = f;
+	return 0;
+}
+
+/**
+ * \brief Application has completed the access to area requested with #snd_pcm_mmap_begin
+ * \param pcm PCM handle
+ * \param offset area offset in area steps (== frames)
+ * \param frames area portion size in frames
+ * \return count of transferred frames otherwise a negative error code
+ *
+ * You should pass this function the offset value that
+ * snd_pcm_mmap_begin() returned. The frames parameter should hold the
+ * number of frames you have written or read to/from the audio
+ * buffer. The frames parameter must never exceed the contiguous frames
+ * count that snd_pcm_mmap_begin() returned. Each call to snd_pcm_mmap_begin()
+ * must be followed by a call to snd_pcm_mmap_commit().
+ *
+ * Example:
+\code
+  double phase = 0;
+  const snd_pcm_area_t *areas;
+  snd_pcm_sframes_t avail, size, commitres;
+  snd_pcm_uframes_t offset, frames;
+  int err;
+
+  avail = snd_pcm_avail_update(pcm);
+  if (avail < 0)
+    error(avail);
+  // at this point, we can transfer at least 'avail' frames
+  
+  // we want to process frames in chunks (period_size)
+  if (avail < period_size)
+    goto _skip;
+  size = period_size;
+  // it is possible that contiguous areas are smaller, thus we use a loop
+  while (size > 0) {
+    frames = size;
+
+    err = snd_pcm_mmap_begin(pcm_handle, &areas, &offset, &frames);
+    if (err < 0)
+      error(err);
+    // this function fills the areas from offset with count of frames
+    generate_sine(areas, offset, frames, &phase);
+    commitres = snd_pcm_mmap_commit(pcm_handle, offset, frames);
+    if (commitres < 0 || commitres != frames)
+      error(commitres >= 0 ? -EPIPE : commitres);
+      
+    size -= frames;
+  }
+ _skip:
+\endcode
+ *
+ * Look to the \ref example_test_pcm "Sine-wave generator" example
+ * for more details about the generate_sine function.
+ */
+snd_pcm_sframes_t snd_pcm_mmap_commit(snd_pcm_t *pcm,
+				      snd_pcm_uframes_t offset,
+				      snd_pcm_uframes_t frames)
+{
+	assert(pcm);
+	if (CHECK_SANITY(offset != *pcm->appl.ptr % pcm->buffer_size)) {
+		SNDMSG("commit offset (%ld) doesn't match with appl_ptr (%ld) %% buf_size (%ld)",
+		       offset, *pcm->appl.ptr, pcm->buffer_size);
+		return -EPIPE;
+	}
+	if (CHECK_SANITY(frames > snd_pcm_mmap_avail(pcm))) {
+		SNDMSG("commit frames (%ld) overflow (avail = %ld)", frames,
+		       snd_pcm_mmap_avail(pcm));
+		return -EPIPE;
+	}
+	return pcm->fast_ops->mmap_commit(pcm->fast_op_arg, offset, frames);
+}
+
+#ifndef DOC_HIDDEN
+
+int _snd_pcm_poll_descriptor(snd_pcm_t *pcm)
+{
+	assert(pcm);
+	return pcm->poll_fd;
+}
+
+void snd_pcm_areas_from_buf(snd_pcm_t *pcm, snd_pcm_channel_area_t *areas, 
+			    void *buf)
+{
+	unsigned int channel;
+	unsigned int channels = pcm->channels;
+	for (channel = 0; channel < channels; ++channel, ++areas) {
+		areas->addr = buf;
+		areas->first = channel * pcm->sample_bits;
+		areas->step = pcm->frame_bits;
+	}
+}
+
+void snd_pcm_areas_from_bufs(snd_pcm_t *pcm, snd_pcm_channel_area_t *areas, 
+			     void **bufs)
+{
+	unsigned int channel;
+	unsigned int channels = pcm->channels;
+	for (channel = 0; channel < channels; ++channel, ++areas, ++bufs) {
+		areas->addr = *bufs;
+		areas->first = 0;
+		areas->step = pcm->sample_bits;
+	}
+}
+
+snd_pcm_sframes_t snd_pcm_read_areas(snd_pcm_t *pcm, const snd_pcm_channel_area_t *areas,
+				     snd_pcm_uframes_t offset, snd_pcm_uframes_t size,
+				     snd_pcm_xfer_areas_func_t func)
+{
+	snd_pcm_uframes_t xfer = 0;
+	snd_pcm_sframes_t err = 0;
+	snd_pcm_state_t state;
+
+	if (size == 0)
+		return 0;
+
+	while (size > 0) {
+		snd_pcm_uframes_t frames;
+		snd_pcm_sframes_t avail;
+	_again:
+		state = snd_pcm_state(pcm);
+		switch (state) {
+		case SND_PCM_STATE_PREPARED:
+			err = snd_pcm_start(pcm);
+			if (err < 0)
+				goto _end;
+			break;
+		case SND_PCM_STATE_RUNNING:
+			err = snd_pcm_hwsync(pcm);
+			if (err < 0)
+				goto _end;
+			break;
+		case SND_PCM_STATE_DRAINING:
+		case SND_PCM_STATE_PAUSED:
+			break;
+		case SND_PCM_STATE_XRUN:
+			err = -EPIPE;
+			goto _end;
+		case SND_PCM_STATE_SUSPENDED:
+			err = -ESTRPIPE;
+			goto _end;
+		case SND_PCM_STATE_DISCONNECTED:
+			err = -ENODEV;
+			goto _end;
+		default:
+			err = -EBADFD;
+			goto _end;
+		}
+		avail = snd_pcm_avail_update(pcm);
+		if (avail < 0) {
+			err = avail;
+			goto _end;
+		}
+		if (avail == 0) {
+			if (state == SND_PCM_STATE_DRAINING)
+				goto _end;
+			if (pcm->mode & SND_PCM_NONBLOCK) {
+				err = -EAGAIN;
+				goto _end;
+			}
+
+			err = snd_pcm_wait(pcm, -1);
+			if (err < 0)
+				break;
+			goto _again;
+			
+		}
+		frames = size;
+		if (frames > (snd_pcm_uframes_t) avail)
+			frames = avail;
+		if (! frames)
+			break;
+		err = func(pcm, areas, offset, frames);
+		if (err < 0)
+			break;
+		frames = err;
+		offset += frames;
+		size -= frames;
+		xfer += frames;
+	}
+ _end:
+	return xfer > 0 ? (snd_pcm_sframes_t) xfer : snd_pcm_check_error(pcm, err);
+}
+
+snd_pcm_sframes_t snd_pcm_write_areas(snd_pcm_t *pcm, const snd_pcm_channel_area_t *areas,
+				      snd_pcm_uframes_t offset, snd_pcm_uframes_t size,
+				      snd_pcm_xfer_areas_func_t func)
+{
+	snd_pcm_uframes_t xfer = 0;
+	snd_pcm_sframes_t err = 0;
+	snd_pcm_state_t state;
+
+	if (size == 0)
+		return 0;
+
+	while (size > 0) {
+		snd_pcm_uframes_t frames;
+		snd_pcm_sframes_t avail;
+	_again:
+		state = snd_pcm_state(pcm);
+		switch (state) {
+		case SND_PCM_STATE_PREPARED:
+		case SND_PCM_STATE_PAUSED:
+			break;
+		case SND_PCM_STATE_RUNNING:
+			err = snd_pcm_hwsync(pcm);
+			if (err < 0)
+				goto _end;
+			break;
+		case SND_PCM_STATE_XRUN:
+			err = -EPIPE;
+			goto _end;
+		case SND_PCM_STATE_SUSPENDED:
+			err = -ESTRPIPE;
+			goto _end;
+		case SND_PCM_STATE_DISCONNECTED:
+			err = -ENODEV;
+			goto _end;
+		default:
+			err = -EBADFD;
+			goto _end;
+		}
+		avail = snd_pcm_avail_update(pcm);
+		if (avail < 0) {
+			err = avail;
+			goto _end;
+		}
+		if ((state == SND_PCM_STATE_RUNNING &&
+		     (snd_pcm_uframes_t)avail < pcm->avail_min &&
+		     size > (snd_pcm_uframes_t)avail)) {
+			if (pcm->mode & SND_PCM_NONBLOCK) {
+				err = -EAGAIN;
+				goto _end;
+			}
+
+			err = snd_pcm_wait(pcm, -1);
+			if (err < 0)
+				break;
+			goto _again;			
+		}
+		frames = size;
+		if (frames > (snd_pcm_uframes_t) avail)
+			frames = avail;
+		if (! frames)
+			break;
+		err = func(pcm, areas, offset, frames);
+		if (err < 0)
+			break;
+		frames = err;
+		if (state == SND_PCM_STATE_PREPARED) {
+			snd_pcm_sframes_t hw_avail = pcm->buffer_size - avail;
+			hw_avail += frames;
+			/* some plugins might automatically start the stream */
+			state = snd_pcm_state(pcm);
+			if (state == SND_PCM_STATE_PREPARED &&
+			    hw_avail >= (snd_pcm_sframes_t) pcm->start_threshold) {
+				err = snd_pcm_start(pcm);
+				if (err < 0)
+					goto _end;
+			}
+		}
+		offset += frames;
+		size -= frames;
+		xfer += frames;
+	}
+ _end:
+	return xfer > 0 ? (snd_pcm_sframes_t) xfer : snd_pcm_check_error(pcm, err);
+}
+
+snd_pcm_uframes_t _snd_pcm_mmap_hw_ptr(snd_pcm_t *pcm)
+{
+	return *pcm->hw.ptr;
+}
+
+snd_pcm_uframes_t _snd_pcm_boundary(snd_pcm_t *pcm)
+{
+	return pcm->boundary;
+}
+
+#ifndef DOC_HIDDEN
+link_warning(_snd_pcm_mmap_hw_ptr, "Warning: _snd_pcm_mmap_hw_ptr() is deprecated, consider to not use this function");
+link_warning(_snd_pcm_boundary, "Warning: _snd_pcm_boundary() is deprecated, consider to use snd_pcm_sw_params_current()");
+#endif
+
+static const char *const names[SND_PCM_HW_PARAM_LAST_INTERVAL + 1] = {
+	[SND_PCM_HW_PARAM_FORMAT] = "format",
+	[SND_PCM_HW_PARAM_CHANNELS] = "channels",
+	[SND_PCM_HW_PARAM_RATE] = "rate",
+	[SND_PCM_HW_PARAM_PERIOD_TIME] = "period_time",
+	[SND_PCM_HW_PARAM_PERIOD_SIZE] = "period_size",
+	[SND_PCM_HW_PARAM_BUFFER_TIME] = "buffer_time",
+	[SND_PCM_HW_PARAM_BUFFER_SIZE] = "buffer_size",
+	[SND_PCM_HW_PARAM_PERIODS] = "periods"
+};
+
+int snd_pcm_slave_conf(snd_config_t *root, snd_config_t *conf,
+		       snd_config_t **_pcm_conf, unsigned int count, ...)
+{
+	snd_config_iterator_t i, next;
+	const char *str;
+	struct {
+		unsigned int index;
+		int flags;
+		void *ptr;
+		int present;
+	} fields[count];
+	unsigned int k;
+	snd_config_t *pcm_conf = NULL;
+	int err;
+	int to_free = 0;
+	va_list args;
+	assert(root);
+	assert(conf);
+	assert(_pcm_conf);
+	if (snd_config_get_string(conf, &str) >= 0) {
+		err = snd_config_search_definition(root, "pcm_slave", str, &conf);
+		if (err < 0) {
+			SNDERR("Invalid slave definition");
+			return -EINVAL;
+		}
+		to_free = 1;
+	}
+	if (snd_config_get_type(conf) != SND_CONFIG_TYPE_COMPOUND) {
+		SNDERR("Invalid slave definition");
+		err = -EINVAL;
+		goto _err;
+	}
+	va_start(args, count);
+	for (k = 0; k < count; ++k) {
+		fields[k].index = va_arg(args, int);
+		fields[k].flags = va_arg(args, int);
+		fields[k].ptr = va_arg(args, void *);
+		fields[k].present = 0;
+	}
+	va_end(args);
+	snd_config_for_each(i, next, conf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (strcmp(id, "comment") == 0)
+			continue;
+		if (strcmp(id, "pcm") == 0) {
+			if (pcm_conf != NULL)
+				snd_config_delete(pcm_conf);
+			if ((err = snd_config_copy(&pcm_conf, n)) < 0)
+				goto _err;
+			continue;
+		}
+		for (k = 0; k < count; ++k) {
+			unsigned int idx = fields[k].index;
+			long v;
+			assert(idx < SND_PCM_HW_PARAM_LAST_INTERVAL);
+			assert(names[idx]);
+			if (strcmp(id, names[idx]) != 0)
+				continue;
+			switch (idx) {
+			case SND_PCM_HW_PARAM_FORMAT:
+			{
+				snd_pcm_format_t f;
+				err = snd_config_get_string(n, &str);
+				if (err < 0) {
+				_invalid:
+					SNDERR("invalid type for %s", id);
+					goto _err;
+				}
+				if ((fields[k].flags & SCONF_UNCHANGED) &&
+				    strcasecmp(str, "unchanged") == 0) {
+					*(snd_pcm_format_t*)fields[k].ptr = (snd_pcm_format_t) -2;
+					break;
+				}
+				f = snd_pcm_format_value(str);
+				if (f == SND_PCM_FORMAT_UNKNOWN) {
+					SNDERR("unknown format %s", str);
+					err = -EINVAL;
+					goto _err;
+				}
+				*(snd_pcm_format_t*)fields[k].ptr = f;
+				break;
+			}
+			default:
+				if ((fields[k].flags & SCONF_UNCHANGED)) {
+					err = snd_config_get_string(n, &str);
+					if (err >= 0 &&
+					    strcasecmp(str, "unchanged") == 0) {
+						*(int*)fields[k].ptr = -2;
+						break;
+					}
+				}
+				err = snd_config_get_integer(n, &v);
+				if (err < 0)
+					goto _invalid;
+				*(int*)fields[k].ptr = v;
+				break;
+			}
+			fields[k].present = 1;
+			break;
+		}
+		if (k < count)
+			continue;
+		SNDERR("Unknown field %s", id);
+		err = -EINVAL;
+		goto _err;
+	}
+	if (!pcm_conf) {
+		SNDERR("missing field pcm");
+		err = -EINVAL;
+		goto _err;
+	}
+	for (k = 0; k < count; ++k) {
+		if ((fields[k].flags & SCONF_MANDATORY) && !fields[k].present) {
+			SNDERR("missing field %s", names[fields[k].index]);
+			err = -EINVAL;
+			goto _err;
+		}
+	}
+	*_pcm_conf = pcm_conf;
+	pcm_conf = NULL;
+	err = 0;
+ _err:
+ 	if (pcm_conf)
+ 		snd_config_delete(pcm_conf);
+	if (to_free)
+		snd_config_delete(conf);
+	return err;
+}
+		
+
+int snd_pcm_conf_generic_id(const char *id)
+{
+	static const char ids[3][8] = { "comment", "type", "hint" };
+	unsigned int k;
+	for (k = 0; k < sizeof(ids) / sizeof(ids[0]); ++k) {
+		if (strcmp(id, ids[k]) == 0)
+			return 1;
+	}
+	return 0;
+}
+
+static void snd_pcm_set_ptr(snd_pcm_t *pcm, snd_pcm_rbptr_t *rbptr,
+			    volatile snd_pcm_uframes_t *hw_ptr, int fd, off_t offset)
+{
+	rbptr->master = NULL;	/* I'm master */
+	rbptr->ptr = hw_ptr;
+	rbptr->fd = fd;
+	rbptr->offset = offset;
+	if (rbptr->changed)
+		rbptr->changed(pcm, NULL);
+}
+
+void snd_pcm_set_hw_ptr(snd_pcm_t *pcm, volatile snd_pcm_uframes_t *hw_ptr, int fd, off_t offset)
+{
+	assert(pcm);
+	assert(hw_ptr);
+	snd_pcm_set_ptr(pcm, &pcm->hw, hw_ptr, fd, offset);
+}
+
+void snd_pcm_set_appl_ptr(snd_pcm_t *pcm, volatile snd_pcm_uframes_t *appl_ptr, int fd, off_t offset)
+{
+	assert(pcm);
+	assert(appl_ptr);
+	snd_pcm_set_ptr(pcm, &pcm->appl, appl_ptr, fd, offset);
+}
+
+static void snd_pcm_link_ptr(snd_pcm_t *pcm, snd_pcm_rbptr_t *pcm_rbptr,
+			     snd_pcm_t *slave, snd_pcm_rbptr_t *slave_rbptr)
+{
+	snd_pcm_t **a;
+	int idx;
+	
+	a = slave_rbptr->link_dst;
+	for (idx = 0; idx < slave_rbptr->link_dst_count; idx++)
+		if (a[idx] == NULL) {
+			a[idx] = pcm;
+			goto __found_free_place;
+		}
+	a = realloc(a, sizeof(snd_pcm_t *) * (slave_rbptr->link_dst_count + 1));
+	if (a == NULL) {
+		pcm_rbptr->ptr = NULL;
+		pcm_rbptr->fd = -1;
+		pcm_rbptr->offset = 0UL;
+		return;
+	}
+	a[slave_rbptr->link_dst_count++] = pcm;
+      __found_free_place:
+	pcm_rbptr->master = slave_rbptr->master ? slave_rbptr->master : slave;
+	pcm_rbptr->ptr = slave_rbptr->ptr;
+	pcm_rbptr->fd = slave_rbptr->fd;
+	pcm_rbptr->offset = slave_rbptr->offset;
+	slave_rbptr->link_dst = a;
+	if (pcm_rbptr->changed)
+		pcm_rbptr->changed(pcm, slave);
+}
+
+static void snd_pcm_unlink_ptr(snd_pcm_t *pcm, snd_pcm_rbptr_t *pcm_rbptr,
+			       snd_pcm_t *slave, snd_pcm_rbptr_t *slave_rbptr)
+{
+	snd_pcm_t **a;
+	int idx;
+
+	a = slave_rbptr->link_dst;
+	for (idx = 0; idx < slave_rbptr->link_dst_count; idx++) {
+		if (a[idx] == pcm) {
+			a[idx] = NULL;
+			goto __found;
+		}
+	}
+	/* assert(0); */
+	return;
+
+      __found:
+      	pcm_rbptr->master = NULL;
+	pcm_rbptr->ptr = NULL;
+	pcm_rbptr->fd = -1;
+	pcm_rbptr->offset = 0UL;
+	if (pcm_rbptr->changed)
+		pcm_rbptr->changed(pcm, slave);
+}
+
+void snd_pcm_link_hw_ptr(snd_pcm_t *pcm, snd_pcm_t *slave)
+{
+	assert(pcm);
+	assert(slave);
+	snd_pcm_link_ptr(pcm, &pcm->hw, slave, &slave->hw);
+}
+
+void snd_pcm_link_appl_ptr(snd_pcm_t *pcm, snd_pcm_t *slave)
+{
+	assert(pcm);
+	assert(slave);
+	snd_pcm_link_ptr(pcm, &pcm->appl, slave, &slave->appl);
+}
+
+void snd_pcm_unlink_hw_ptr(snd_pcm_t *pcm, snd_pcm_t *slave)
+{
+	assert(pcm);
+	assert(slave);
+	snd_pcm_unlink_ptr(pcm, &pcm->hw, slave, &slave->hw);
+}
+
+void snd_pcm_unlink_appl_ptr(snd_pcm_t *pcm, snd_pcm_t *slave)
+{
+	assert(pcm);
+	assert(slave);
+	snd_pcm_unlink_ptr(pcm, &pcm->appl, slave, &slave->appl);
+}
+
+#endif /* DOC_HIDDEN */
+
+/*
+ *
+ */
+
+#ifndef DOC_HIDDEN
+
+#ifdef USE_VERSIONED_SYMBOLS
+
+#define OBSOLETE1(name, what, new) \
+  default_symbol_version(__##name, name, new); \
+  symbol_version(__old_##name, name, what);
+
+#else
+
+#define OBSOLETE1(name, what, new) \
+  use_default_symbol_version(__##name, name, new);
+
+#endif /* USE_VERSIONED_SYMBOLS */
+
+#define __P_OLD_GET(pfx, name, val_type, ret_type) \
+ret_type pfx##name(const snd_pcm_hw_params_t *params) \
+{ \
+	val_type val; \
+	if (INTERNAL(name)(params, &val) < 0) \
+		return 0; \
+	return (ret_type)val; \
+}
+
+#define __P_OLD_GET1(pfx, name, val_type, ret_type) \
+ret_type pfx##name(const snd_pcm_hw_params_t *params, int *dir) \
+{ \
+	val_type val; \
+	if (INTERNAL(name)(params, &val, dir) < 0) \
+		return 0; \
+	return (ret_type)val; \
+}
+
+#define __OLD_GET(name, val_type, ret_type) __P_OLD_GET(__old_, name, val_type, ret_type)
+#define __OLD_GET1(name, val_type, ret_type) __P_OLD_GET1(__old_, name, val_type, ret_type)
+
+__OLD_GET(snd_pcm_hw_params_get_access, snd_pcm_access_t, int);
+__OLD_GET(snd_pcm_hw_params_get_format, snd_pcm_format_t, int);
+__OLD_GET(snd_pcm_hw_params_get_subformat, snd_pcm_subformat_t, int);
+__OLD_GET(snd_pcm_hw_params_get_channels, unsigned int, int);
+__OLD_GET1(snd_pcm_hw_params_get_rate, unsigned int, int);
+__OLD_GET1(snd_pcm_hw_params_get_period_time, unsigned int, int);
+__OLD_GET1(snd_pcm_hw_params_get_period_size, snd_pcm_uframes_t, snd_pcm_sframes_t);
+__OLD_GET1(snd_pcm_hw_params_get_periods, unsigned int, int);
+__OLD_GET1(snd_pcm_hw_params_get_buffer_time, unsigned int, int);
+__OLD_GET(snd_pcm_hw_params_get_buffer_size, snd_pcm_uframes_t, snd_pcm_sframes_t);
+__OLD_GET1(snd_pcm_hw_params_get_tick_time, unsigned int, int);
+
+__OLD_GET(snd_pcm_hw_params_get_channels_min, unsigned int, unsigned int);
+__OLD_GET1(snd_pcm_hw_params_get_rate_min, unsigned int, unsigned int);
+__OLD_GET1(snd_pcm_hw_params_get_period_time_min, unsigned int, unsigned int);
+__OLD_GET1(snd_pcm_hw_params_get_period_size_min, snd_pcm_uframes_t, snd_pcm_uframes_t);
+__OLD_GET1(snd_pcm_hw_params_get_periods_min, unsigned int, unsigned int);
+__OLD_GET1(snd_pcm_hw_params_get_buffer_time_min, unsigned int, unsigned int);
+__OLD_GET(snd_pcm_hw_params_get_buffer_size_min, snd_pcm_uframes_t, snd_pcm_uframes_t);
+__OLD_GET1(snd_pcm_hw_params_get_tick_time_min, unsigned int, unsigned int);
+
+__OLD_GET(snd_pcm_hw_params_get_channels_max, unsigned int, unsigned int);
+__OLD_GET1(snd_pcm_hw_params_get_rate_max, unsigned int, unsigned int);
+__OLD_GET1(snd_pcm_hw_params_get_period_time_max, unsigned int, unsigned int);
+__OLD_GET1(snd_pcm_hw_params_get_period_size_max, snd_pcm_uframes_t, snd_pcm_uframes_t);
+__OLD_GET1(snd_pcm_hw_params_get_periods_max, unsigned int, unsigned int);
+__OLD_GET1(snd_pcm_hw_params_get_buffer_time_max, unsigned int, unsigned int);
+__OLD_GET(snd_pcm_hw_params_get_buffer_size_max, snd_pcm_uframes_t, snd_pcm_uframes_t);
+__OLD_GET1(snd_pcm_hw_params_get_tick_time_max, unsigned int, unsigned int);
+
+#define __P_OLD_NEAR(pfx, name, ret_type) \
+ret_type pfx##name(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, ret_type val) \
+{ \
+	if (INTERNAL(name)(pcm, params, &val) < 0) \
+		return 0; \
+	return (ret_type)val; \
+}
+
+#define __P_OLD_NEAR1(pfx, name, ret_type) \
+ret_type pfx##name(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, ret_type val, int *dir) \
+{ \
+	if (INTERNAL(name)(pcm, params, &val, dir) < 0) \
+		return 0; \
+	return (ret_type)val; \
+}
+
+#define __OLD_NEAR(name, ret_type) __P_OLD_NEAR(__old_, name, ret_type)
+#define __OLD_NEAR1(name, ret_type) __P_OLD_NEAR1(__old_, name, ret_type)
+
+__OLD_NEAR(snd_pcm_hw_params_set_channels_near, unsigned int);
+__OLD_NEAR1(snd_pcm_hw_params_set_rate_near, unsigned int);
+__OLD_NEAR1(snd_pcm_hw_params_set_period_time_near, unsigned int);
+__OLD_NEAR1(snd_pcm_hw_params_set_period_size_near, snd_pcm_uframes_t);
+__OLD_NEAR1(snd_pcm_hw_params_set_periods_near, unsigned int);
+__OLD_NEAR1(snd_pcm_hw_params_set_buffer_time_near, unsigned int);
+__OLD_NEAR(snd_pcm_hw_params_set_buffer_size_near, snd_pcm_uframes_t);
+__OLD_NEAR1(snd_pcm_hw_params_set_tick_time_near, unsigned int);
+
+#define __P_OLD_SET_FL(pfx, name, ret_type) \
+ret_type pfx##name(snd_pcm_t *pcm, snd_pcm_hw_params_t *params) \
+{ \
+	ret_type val; \
+	if (INTERNAL(name)(pcm, params, &val) < 0) \
+		return 0; \
+	return (ret_type)val; \
+}
+
+#define __P_OLD_SET_FL1(pfx, name, ret_type) \
+ret_type pfx##name(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, int *dir) \
+{ \
+	ret_type val; \
+	if (INTERNAL(name)(pcm, params, &val, dir) < 0) \
+		return 0; \
+	return (ret_type)val; \
+}
+
+#define __OLD_SET_FL(name, ret_type) __P_OLD_SET_FL(__old_, name, ret_type)
+#define __OLD_SET_FL1(name, ret_type) __P_OLD_SET_FL1(__old_, name, ret_type)
+
+__OLD_SET_FL(snd_pcm_hw_params_set_access_first, snd_pcm_access_t);
+__OLD_SET_FL(snd_pcm_hw_params_set_format_first, snd_pcm_format_t);
+__OLD_SET_FL(snd_pcm_hw_params_set_subformat_first, snd_pcm_subformat_t);
+__OLD_SET_FL(snd_pcm_hw_params_set_channels_first, unsigned int);
+__OLD_SET_FL1(snd_pcm_hw_params_set_rate_first, unsigned int);
+__OLD_SET_FL1(snd_pcm_hw_params_set_period_time_first, unsigned int);
+__OLD_SET_FL1(snd_pcm_hw_params_set_period_size_first, snd_pcm_uframes_t);
+__OLD_SET_FL1(snd_pcm_hw_params_set_periods_first, unsigned int);
+__OLD_SET_FL1(snd_pcm_hw_params_set_buffer_time_first, unsigned int);
+__OLD_SET_FL(snd_pcm_hw_params_set_buffer_size_first, snd_pcm_uframes_t);
+__OLD_SET_FL1(snd_pcm_hw_params_set_tick_time_first, unsigned int);
+
+__OLD_SET_FL(snd_pcm_hw_params_set_access_last, snd_pcm_access_t);
+__OLD_SET_FL(snd_pcm_hw_params_set_format_last, snd_pcm_format_t);
+__OLD_SET_FL(snd_pcm_hw_params_set_subformat_last, snd_pcm_subformat_t);
+__OLD_SET_FL(snd_pcm_hw_params_set_channels_last, unsigned int);
+__OLD_SET_FL1(snd_pcm_hw_params_set_rate_last, unsigned int);
+__OLD_SET_FL1(snd_pcm_hw_params_set_period_time_last, unsigned int);
+__OLD_SET_FL1(snd_pcm_hw_params_set_period_size_last, snd_pcm_uframes_t);
+__OLD_SET_FL1(snd_pcm_hw_params_set_periods_last, unsigned int);
+__OLD_SET_FL1(snd_pcm_hw_params_set_buffer_time_last, unsigned int);
+__OLD_SET_FL(snd_pcm_hw_params_set_buffer_size_last, snd_pcm_uframes_t);
+__OLD_SET_FL1(snd_pcm_hw_params_set_tick_time_last, unsigned int);
+
+#define __P_OLD_GET_SW(pfx, name, ret_type) \
+ret_type pfx##name(snd_pcm_sw_params_t *params) \
+{ \
+	ret_type val; \
+	if (INTERNAL(name)(params, &val) < 0) \
+		return 0; \
+	return (ret_type)val; \
+}
+
+#define __OLD_GET_SW(name, ret_type) __P_OLD_GET_SW(__old_, name, ret_type)
+
+__OLD_GET_SW(snd_pcm_sw_params_get_tstamp_mode, snd_pcm_tstamp_t);
+__OLD_GET_SW(snd_pcm_sw_params_get_sleep_min, unsigned int);
+__OLD_GET_SW(snd_pcm_sw_params_get_avail_min, snd_pcm_uframes_t);
+__OLD_GET_SW(snd_pcm_sw_params_get_xfer_align, snd_pcm_uframes_t);
+__OLD_GET_SW(snd_pcm_sw_params_get_start_threshold, snd_pcm_uframes_t);
+__OLD_GET_SW(snd_pcm_sw_params_get_stop_threshold, snd_pcm_uframes_t);
+__OLD_GET_SW(snd_pcm_sw_params_get_silence_threshold, snd_pcm_uframes_t);
+__OLD_GET_SW(snd_pcm_sw_params_get_silence_size, snd_pcm_uframes_t);
+
+OBSOLETE1(snd_pcm_hw_params_get_access, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_set_access_first, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_set_access_last, ALSA_0.9, ALSA_0.9.0rc4);
+
+OBSOLETE1(snd_pcm_hw_params_get_format, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_set_format_first, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_set_format_last, ALSA_0.9, ALSA_0.9.0rc4);
+
+OBSOLETE1(snd_pcm_hw_params_get_subformat, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_set_subformat_first, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_set_subformat_last, ALSA_0.9, ALSA_0.9.0rc4);
+
+OBSOLETE1(snd_pcm_hw_params_get_channels, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_get_channels_min, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_get_channels_max, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_set_channels_near, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_set_channels_first, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_set_channels_last, ALSA_0.9, ALSA_0.9.0rc4);
+
+OBSOLETE1(snd_pcm_hw_params_get_rate, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_get_rate_min, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_get_rate_max, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_set_rate_near, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_set_rate_first, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_set_rate_last, ALSA_0.9, ALSA_0.9.0rc4);
+
+OBSOLETE1(snd_pcm_hw_params_get_period_time, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_get_period_time_min, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_get_period_time_max, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_set_period_time_near, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_set_period_time_first, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_set_period_time_last, ALSA_0.9, ALSA_0.9.0rc4);
+
+OBSOLETE1(snd_pcm_hw_params_get_period_size, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_get_period_size_min, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_get_period_size_max, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_set_period_size_near, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_set_period_size_first, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_set_period_size_last, ALSA_0.9, ALSA_0.9.0rc4);
+
+OBSOLETE1(snd_pcm_hw_params_get_periods, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_get_periods_min, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_get_periods_max, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_set_periods_near, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_set_periods_first, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_set_periods_last, ALSA_0.9, ALSA_0.9.0rc4);
+
+OBSOLETE1(snd_pcm_hw_params_get_buffer_time, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_get_buffer_time_min, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_get_buffer_time_max, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_set_buffer_time_near, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_set_buffer_time_first, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_set_buffer_time_last, ALSA_0.9, ALSA_0.9.0rc4);
+
+OBSOLETE1(snd_pcm_hw_params_get_buffer_size, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_get_buffer_size_min, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_get_buffer_size_max, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_set_buffer_size_near, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_set_buffer_size_first, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_set_buffer_size_last, ALSA_0.9, ALSA_0.9.0rc4);
+
+OBSOLETE1(snd_pcm_hw_params_get_tick_time, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_get_tick_time_min, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_get_tick_time_max, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_set_tick_time_near, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_set_tick_time_first, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_hw_params_set_tick_time_last, ALSA_0.9, ALSA_0.9.0rc4);
+
+OBSOLETE1(snd_pcm_sw_params_get_tstamp_mode, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_sw_params_get_sleep_min, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_sw_params_get_avail_min, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_sw_params_get_xfer_align, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_sw_params_get_start_threshold, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_sw_params_get_stop_threshold, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_sw_params_get_silence_threshold, ALSA_0.9, ALSA_0.9.0rc4);
+OBSOLETE1(snd_pcm_sw_params_get_silence_size, ALSA_0.9, ALSA_0.9.0rc4);
+
+#endif /* DOC_HIDDEN */
+
+/*
+ * basic helpers
+ */
+ 
+ 
+/**
+ * \brief Recover the stream state from an error or suspend
+ * \param pcm PCM handle
+ * \param err error number
+ * \param silent do not print error reason
+ * \return 0 when error code was handled successfuly, otherwise a negative error code
+ *
+ * This a high-level helper function building on other functions.
+ *
+ * This functions handles -EINTR (interrupted system call),
+ * -EPIPE (overrun or underrun) and -ESTRPIPE (stream is suspended)
+ * error codes trying to prepare given stream for next I/O.
+ *
+ * Note that this function returs the original error code when it is not
+ * handled inside this function (for example -EAGAIN is returned back).
+ */
+int snd_pcm_recover(snd_pcm_t *pcm, int err, int silent)
+{
+        if (err > 0)
+                err = -err;
+        if (err == -EINTR)	/* nothing to do, continue */
+                return 0;
+        if (err == -EPIPE) {
+                const char *s;
+                if (snd_pcm_stream(pcm) == SND_PCM_STREAM_PLAYBACK)
+                        s = "underrun";
+                else
+                        s = "overrun";
+                if (!silent)
+                        SNDERR("%s occurred", s);
+                err = snd_pcm_prepare(pcm);
+                if (err < 0) {
+                        SNDERR("cannot recovery from %s, prepare failed: %s", s, snd_strerror(err));
+                        return err;
+                }
+                return 0;
+        }
+        if (err == -ESTRPIPE) {
+                while ((err = snd_pcm_resume(pcm)) == -EAGAIN)
+                        /* wait until suspend flag is released */
+                        poll(NULL, 0, 1000);
+                if (err < 0) {
+                        err = snd_pcm_prepare(pcm);
+                        if (err < 0) {
+                                SNDERR("cannot recovery from suspend, prepare failed: %s", snd_strerror(err));
+                                return err;
+                        }
+                }
+                return 0;
+        }
+        return err;
+}
+
+/**
+ * \brief Set the hardware and software parameters in a simple way
+ * \param pcm PCM handle
+ * \param format required PCM format
+ * \param access required PCM access
+ * \param channels required PCM channels
+ * \param rate required sample rate in Hz
+ * \param soft_resample 0 = disallow alsa-lib resample stream, 1 = allow resampling
+ * \param latency required overall latency in us
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_pcm_set_params(snd_pcm_t *pcm,
+                       snd_pcm_format_t format,
+                       snd_pcm_access_t access,
+                       unsigned int channels,
+                       unsigned int rate,
+                       int soft_resample,
+                       unsigned int latency)
+{
+        snd_pcm_hw_params_t *params;
+        snd_pcm_sw_params_t *swparams;
+        const char *s = snd_pcm_stream_name(snd_pcm_stream(pcm));
+        snd_pcm_uframes_t buffer_size, period_size;
+        unsigned int rrate, period_time;
+        int err;
+
+        snd_pcm_hw_params_alloca(&params);
+        snd_pcm_sw_params_alloca(&swparams);
+
+	assert(pcm);
+	/* choose all parameters */
+	err = snd_pcm_hw_params_any(pcm, params);
+	if (err < 0) {
+	        SNDERR("Broken configuration for %s: no configurations available", s);
+	        return err;
+        }
+        /* set software resampling */
+        err = snd_pcm_hw_params_set_rate_resample(pcm, params, soft_resample);
+        if (err < 0) {
+                SNDERR("Resampling setup failed for %s: %s", s, snd_strerror(err));
+                return err;
+        }
+	/* set the selected read/write format */
+	err = snd_pcm_hw_params_set_access(pcm, params, access);
+	if (err < 0) {
+		SNDERR("Access type not available for %s: %s", s, snd_strerror(err));
+		return err;
+	}
+	/* set the sample format */
+	err = snd_pcm_hw_params_set_format(pcm, params, format);
+	if (err < 0) {
+		SNDERR("Sample format not available for %s: %s", s, snd_strerror(err));
+		return err;
+	}
+	/* set the count of channels */
+	err = snd_pcm_hw_params_set_channels(pcm, params, channels);
+	if (err < 0) {
+		SNDERR("Channels count (%i) not available for %s: %s", channels, s, snd_strerror(err));
+		return err;
+	}
+	/* set the stream rate */
+	rrate = rate;
+	err = INTERNAL(snd_pcm_hw_params_set_rate_near)(pcm, params, &rrate, 0);
+	if (err < 0) {
+		SNDERR("Rate %iHz not available for playback: %s", rate, snd_strerror(err));
+		return err;
+	}
+	if (rrate != rate) {
+		SNDERR("Rate doesn't match (requested %iHz, get %iHz)", rate, err);
+		return -EINVAL;
+	}
+	/* set the buffer time */
+	err = INTERNAL(snd_pcm_hw_params_set_buffer_time_near)(pcm, params, &latency, NULL);
+	if (err < 0) {
+	        /* error path -> set period size as first */
+        	/* set the period time */
+        	period_time = latency / 4;
+        	err = INTERNAL(snd_pcm_hw_params_set_period_time_near)(pcm, params, &period_time, NULL);
+        	if (err < 0) {
+        		SNDERR("Unable to set period time %i for %s: %s", period_time, s, snd_strerror(err));
+        		return err;
+        	}
+                err = INTERNAL(snd_pcm_hw_params_get_period_size)(params, &period_size, NULL);
+                if (err < 0) {
+                	SNDERR("Unable to get period size for %s: %s", s, snd_strerror(err));
+                	return err;
+        	}
+        	buffer_size = period_size * 4;
+        	err = INTERNAL(snd_pcm_hw_params_set_buffer_size_near)(pcm, params, &buffer_size);
+                if (err < 0) {
+                	SNDERR("Unable to set buffer size %lu %s: %s", buffer_size, s, snd_strerror(err));
+                	return err;
+        	}
+        	err = INTERNAL(snd_pcm_hw_params_get_buffer_size)(params, &buffer_size);
+        	if (err < 0) {
+        		SNDERR("Unable to get buffer size for %s: %s", s, snd_strerror(err));
+        		return err;
+        	}
+	} else {
+	        /* standard configuration buffer_time -> periods */
+        	err = INTERNAL(snd_pcm_hw_params_get_buffer_size)(params, &buffer_size);
+        	if (err < 0) {
+        		SNDERR("Unable to get buffer size for %s: %s", s, snd_strerror(err));
+        		return err;
+        	}
+        	err = INTERNAL(snd_pcm_hw_params_get_buffer_time)(params, &latency, NULL);
+        	if (err < 0) {
+        		SNDERR("Unable to get buffer time (latency) for %s: %s", s, snd_strerror(err));
+        		return err;
+        	}
+        	/* set the period time */
+        	period_time = latency / 4;
+        	err = INTERNAL(snd_pcm_hw_params_set_period_time_near)(pcm, params, &period_time, NULL);
+        	if (err < 0) {
+        		SNDERR("Unable to set period time %i for %s: %s", period_time, s, snd_strerror(err));
+        		return err;
+        	}
+                err = INTERNAL(snd_pcm_hw_params_get_period_size)(params, &period_size, NULL);
+                if (err < 0) {
+                	SNDERR("Unable to get period size for %s: %s", s, snd_strerror(err));
+                	return err;
+        	}
+        }
+	/* write the parameters to device */
+	err = snd_pcm_hw_params(pcm, params);
+	if (err < 0) {
+		SNDERR("Unable to set hw params for %s: %s", s, snd_strerror(err));
+		return err;
+	}
+
+	/* get the current swparams */
+	err = snd_pcm_sw_params_current(pcm, swparams);
+	if (err < 0) {
+		SNDERR("Unable to determine current swparams for %s: %s", s, snd_strerror(err));
+		return err;
+	}
+	/* start the transfer when the buffer is almost full: */
+	/* (buffer_size / avail_min) * avail_min */
+	err = snd_pcm_sw_params_set_start_threshold(pcm, swparams, (buffer_size / period_size) * period_size);
+	if (err < 0) {
+		SNDERR("Unable to set start threshold mode for %s: %s", s, snd_strerror(err));
+		return err;
+	}
+	/* allow the transfer when at least period_size samples can be processed */
+	err = snd_pcm_sw_params_set_avail_min(pcm, swparams, period_size);
+	if (err < 0) {
+		SNDERR("Unable to set avail min for %s: %s", s, snd_strerror(err));
+		return err;
+	}
+	/* write the parameters to the playback device */
+	err = snd_pcm_sw_params(pcm, swparams);
+	if (err < 0) {
+		SNDERR("Unable to set sw params for %s: %s", s, snd_strerror(err));
+		return err;
+	}
+	return 0;
+}
+
+/**
+ * \brief Get the transfer size parameters in a simple way
+ * \param pcm PCM handle
+ * \param buffer_size PCM ring buffer size in frames
+ * \param period_size PCM period size in frames
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_pcm_get_params(snd_pcm_t *pcm,
+                       snd_pcm_uframes_t *buffer_size,
+                       snd_pcm_uframes_t *period_size)
+{
+	snd_pcm_hw_params_t *hw;
+	int err;
+
+	assert(pcm);
+	snd_pcm_hw_params_alloca(&hw);
+	err = snd_pcm_hw_params_current(pcm, hw);
+	if (err < 0)
+	        return err;
+        err = INTERNAL(snd_pcm_hw_params_get_buffer_size)(hw, buffer_size);
+        if (err < 0)
+                return err;
+        err = INTERNAL(snd_pcm_hw_params_get_period_size)(hw, period_size, NULL);
+        if (err < 0)
+                return err;
+	return 0;
+}
diff --git a/src/pcm/pcm_adpcm.c b/src/pcm/pcm_adpcm.c
new file mode 100644
index 0000000..b68007f
--- /dev/null
+++ b/src/pcm/pcm_adpcm.c
@@ -0,0 +1,679 @@
+/**
+ * \file pcm/pcm_adpcm.c
+ * \ingroup PCM_Plugins
+ * \brief PCM Ima-ADPCM Conversion Plugin Interface
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \author Uros Bizjak <uros@kss-loka.si>
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \date 2000-2001
+ */
+/*
+ *  PCM - Ima-ADPCM conversion
+ *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *  Copyright (c) 1999 by Uros Bizjak <uros@kss-loka.si>
+ *                        Jaroslav Kysela <perex@perex.cz>
+ *
+ *  Based on Version 1.2, 18-Dec-92 implementation of Intel/DVI ADPCM code
+ *  by Jack Jansen, CWI, Amsterdam <Jack.Jansen@cwi.nl>, Copyright 1992
+ *  by Stichting Mathematisch Centrum, Amsterdam, The Netherlands.
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+/*
+These routines convert 16 bit linear PCM samples to 4 bit ADPCM code
+and vice versa. The ADPCM code used is the Intel/DVI ADPCM code which
+is being recommended by the IMA Digital Audio Technical Working Group.
+
+The algorithm for this coder was taken from:
+Proposal for Standardized Audio Interstreamge Formats,
+IMA compatibility project proceedings, Vol 2, Issue 2, May 1992.
+
+- No, this is *not* a G.721 coder/decoder. The algorithm used by G.721
+  is very complicated, requiring oodles of floating-point ops per
+  sample (resulting in very poor performance). I have not done any
+  tests myself but various people have assured my that 721 quality is
+  actually lower than DVI quality.
+
+- No, it probably isn't a RIFF ADPCM decoder either. Trying to decode
+  RIFF ADPCM with these routines seems to result in something
+  recognizable but very distorted.
+
+- No, it is not a CDROM-XA coder either, as far as I know. I haven't
+  come across a good description of XA yet.
+ */
+
+#include <byteswap.h>
+#include "pcm_local.h"
+#include "pcm_plugin.h"
+
+#include "plugin_ops.h"
+
+#ifndef PIC
+/* entry for static linking */
+const char *_snd_module_pcm_adpcm = "";
+#endif
+
+#ifndef DOC_HIDDEN
+
+typedef void (*adpcm_f)(const snd_pcm_channel_area_t *dst_areas,
+			snd_pcm_uframes_t dst_offset,
+			const snd_pcm_channel_area_t *src_areas,
+			snd_pcm_uframes_t src_offset,
+			unsigned int channels, snd_pcm_uframes_t frames,
+			unsigned int getputidx,
+			snd_pcm_adpcm_state_t *states);
+
+typedef struct {
+	/* This field need to be the first */
+	snd_pcm_plugin_t plug;
+	unsigned int getput_idx;
+	adpcm_f func;
+	snd_pcm_format_t sformat;
+	snd_pcm_adpcm_state_t *states;
+} snd_pcm_adpcm_t;
+
+#endif
+
+/* First table lookup for Ima-ADPCM quantizer */
+static const char IndexAdjust[8] = { -1, -1, -1, -1, 2, 4, 6, 8 };
+
+/* Second table lookup for Ima-ADPCM quantizer */
+static const short StepSize[89] = {
+	7, 8, 9, 10, 11, 12, 13, 14, 16, 17,
+	19, 21, 23, 25, 28, 31, 34, 37, 41, 45,
+	50, 55, 60, 66, 73, 80, 88, 97, 107, 118,
+	130, 143, 157, 173, 190, 209, 230, 253, 279, 307,
+	337, 371, 408, 449, 494, 544, 598, 658, 724, 796,
+	876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066,
+	2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358,
+	5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
+	15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767
+};
+
+static char adpcm_encoder(int sl, snd_pcm_adpcm_state_t * state)
+{
+	short diff;		/* Difference between sl and predicted sample */
+	short pred_diff;	/* Predicted difference to next sample */
+
+	unsigned char sign;	/* sign of diff */
+	short step;		/* holds previous StepSize value */
+	unsigned char adjust_idx;	/* Index to IndexAdjust lookup table */
+
+	int i;
+
+	/* Compute difference to previous predicted value */
+	diff = sl - state->pred_val;
+	sign = (diff < 0) ? 0x8 : 0x0;
+	if (sign) {
+		diff = -diff;
+	}
+
+	/*
+	 * This code *approximately* computes:
+	 *    adjust_idx = diff * 4 / step;
+	 *    pred_diff = (adjust_idx + 0.5) * step / 4;
+	 *
+	 * But in shift step bits are dropped. The net result of this is
+	 * that even if you have fast mul/div hardware you cannot put it to
+	 * good use since the fix-up would be too expensive.
+	 */
+
+	step = StepSize[state->step_idx];
+
+	/* Divide and clamp */
+	pred_diff = step >> 3;
+	for (adjust_idx = 0, i = 0x4; i; i >>= 1, step >>= 1) {
+		if (diff >= step) {
+			adjust_idx |= i;
+			diff -= step;
+			pred_diff += step;
+		}
+	}
+
+	/* Update and clamp previous predicted value */
+	state->pred_val += sign ? -pred_diff : pred_diff;
+
+	if (state->pred_val > 32767) {
+		state->pred_val = 32767;
+	} else if (state->pred_val < -32768) {
+		state->pred_val = -32768;
+	}
+
+	/* Update and clamp StepSize lookup table index */
+	state->step_idx += IndexAdjust[adjust_idx];
+
+	if (state->step_idx < 0) {
+		state->step_idx = 0;
+	} else if (state->step_idx > 88) {
+		state->step_idx = 88;
+	}
+	return (sign | adjust_idx);
+}
+
+
+static int adpcm_decoder(unsigned char code, snd_pcm_adpcm_state_t * state)
+{
+	short pred_diff;	/* Predicted difference to next sample */
+	short step;		/* holds previous StepSize value */
+	char sign;
+
+	int i;
+
+	/* Separate sign and magnitude */
+	sign = code & 0x8;
+	code &= 0x7;
+
+	/*
+	 * Computes pred_diff = (code + 0.5) * step / 4,
+	 * but see comment in adpcm_coder.
+	 */
+
+	step = StepSize[state->step_idx];
+
+	/* Compute difference and new predicted value */
+	pred_diff = step >> 3;
+	for (i = 0x4; i; i >>= 1, step >>= 1) {
+		if (code & i) {
+			pred_diff += step;
+		}
+	}
+	state->pred_val += (sign) ? -pred_diff : pred_diff;
+
+	/* Clamp output value */
+	if (state->pred_val > 32767) {
+		state->pred_val = 32767;
+	} else if (state->pred_val < -32768) {
+		state->pred_val = -32768;
+	}
+
+	/* Find new StepSize index value */
+	state->step_idx += IndexAdjust[code];
+
+	if (state->step_idx < 0) {
+		state->step_idx = 0;
+	} else if (state->step_idx > 88) {
+		state->step_idx = 88;
+	}
+	return (state->pred_val);
+}
+
+#ifndef DOC_HIDDEN
+
+void snd_pcm_adpcm_decode(const snd_pcm_channel_area_t *dst_areas,
+			  snd_pcm_uframes_t dst_offset,
+			  const snd_pcm_channel_area_t *src_areas,
+			  snd_pcm_uframes_t src_offset,
+			  unsigned int channels, snd_pcm_uframes_t frames,
+			  unsigned int putidx,
+			  snd_pcm_adpcm_state_t *states)
+{
+#define PUT16_LABELS
+#include "plugin_ops.h"
+#undef PUT16_LABELS
+	void *put = put16_labels[putidx];
+	unsigned int channel;
+	for (channel = 0; channel < channels; ++channel, ++states) {
+		const char *src;
+		int srcbit;
+		char *dst;
+		int src_step, srcbit_step, dst_step;
+		snd_pcm_uframes_t frames1;
+		const snd_pcm_channel_area_t *src_area = &src_areas[channel];
+		const snd_pcm_channel_area_t *dst_area = &dst_areas[channel];
+		srcbit = src_area->first + src_area->step * src_offset;
+		src = (const char *) src_area->addr + srcbit / 8;
+		srcbit %= 8;
+		src_step = src_area->step / 8;
+		srcbit_step = src_area->step % 8;
+		dst = snd_pcm_channel_area_addr(dst_area, dst_offset);
+		dst_step = snd_pcm_channel_area_step(dst_area);
+		frames1 = frames;
+		while (frames1-- > 0) {
+			int16_t sample;
+			unsigned char v;
+			if (srcbit)
+				v = *src & 0x0f;
+			else
+				v = (*src >> 4) & 0x0f;
+			sample = adpcm_decoder(v, states);
+			goto *put;
+#define PUT16_END after
+#include "plugin_ops.h"
+#undef PUT16_END
+		after:
+			src += src_step;
+			srcbit += srcbit_step;
+			if (srcbit == 8) {
+				src++;
+				srcbit = 0;
+			}
+			dst += dst_step;
+		}
+	}
+}
+
+void snd_pcm_adpcm_encode(const snd_pcm_channel_area_t *dst_areas,
+			  snd_pcm_uframes_t dst_offset,
+			  const snd_pcm_channel_area_t *src_areas,
+			  snd_pcm_uframes_t src_offset,
+			  unsigned int channels, snd_pcm_uframes_t frames,
+			  unsigned int getidx,
+			  snd_pcm_adpcm_state_t *states)
+{
+#define GET16_LABELS
+#include "plugin_ops.h"
+#undef GET16_LABELS
+	void *get = get16_labels[getidx];
+	unsigned int channel;
+	int16_t sample = 0;
+	for (channel = 0; channel < channels; ++channel, ++states) {
+		const char *src;
+		char *dst;
+		int dstbit;
+		int src_step, dst_step, dstbit_step;
+		snd_pcm_uframes_t frames1;
+		const snd_pcm_channel_area_t *src_area = &src_areas[channel];
+		const snd_pcm_channel_area_t *dst_area = &dst_areas[channel];
+		src = snd_pcm_channel_area_addr(src_area, src_offset);
+		src_step = snd_pcm_channel_area_step(src_area);
+		dstbit = dst_area->first + dst_area->step * dst_offset;
+		dst = (char *) dst_area->addr + dstbit / 8;
+		dstbit %= 8;
+		dst_step = dst_area->step / 8;
+		dstbit_step = dst_area->step % 8;
+		frames1 = frames;
+		while (frames1-- > 0) {
+			int v;
+			goto *get;
+#define GET16_END after
+#include "plugin_ops.h"
+#undef GET16_END
+		after:
+			v = adpcm_encoder(sample, states);
+			if (dstbit)
+				*dst = (*dst & 0xf0) | v;
+			else
+				*dst = (*dst & 0x0f) | (v << 4);
+			src += src_step;
+			dst += dst_step;
+			dstbit += dstbit_step;
+			if (dstbit == 8) {
+				dst++;
+				dstbit = 0;
+			}
+		}
+	}
+}
+
+#endif
+
+static int snd_pcm_adpcm_hw_refine_cprepare(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	snd_pcm_adpcm_t *adpcm = pcm->private_data;
+	int err;
+	snd_pcm_access_mask_t access_mask = { SND_PCM_ACCBIT_SHM };
+	err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_ACCESS,
+					 &access_mask);
+	if (err < 0)
+		return err;
+	if (adpcm->sformat == SND_PCM_FORMAT_IMA_ADPCM) {
+		snd_pcm_format_mask_t format_mask = { SND_PCM_FMTBIT_LINEAR };
+		err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_FORMAT,
+						 &format_mask);
+	} else {
+		err = _snd_pcm_hw_params_set_format(params,
+						   SND_PCM_FORMAT_IMA_ADPCM);
+	}
+	if (err < 0)
+		return err;
+	err = _snd_pcm_hw_params_set_subformat(params,
+					       SND_PCM_SUBFORMAT_STD);
+	if (err < 0)
+		return err;
+	params->info &= ~(SND_PCM_INFO_MMAP | SND_PCM_INFO_MMAP_VALID);
+	return 0;
+}
+
+static int snd_pcm_adpcm_hw_refine_sprepare(snd_pcm_t *pcm, snd_pcm_hw_params_t *sparams)
+{
+	snd_pcm_adpcm_t *adpcm = pcm->private_data;
+	snd_pcm_access_mask_t saccess_mask = { SND_PCM_ACCBIT_MMAP };
+	_snd_pcm_hw_params_any(sparams);
+	_snd_pcm_hw_param_set_mask(sparams, SND_PCM_HW_PARAM_ACCESS,
+				   &saccess_mask);
+	_snd_pcm_hw_params_set_format(sparams, adpcm->sformat);
+	_snd_pcm_hw_params_set_subformat(sparams, SND_PCM_SUBFORMAT_STD);
+	return 0;
+}
+
+static int snd_pcm_adpcm_hw_refine_schange(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params,
+					    snd_pcm_hw_params_t *sparams)
+{
+	int err;
+	unsigned int links = (SND_PCM_HW_PARBIT_CHANNELS |
+			      SND_PCM_HW_PARBIT_RATE |
+			      SND_PCM_HW_PARBIT_PERIOD_SIZE |
+			      SND_PCM_HW_PARBIT_BUFFER_SIZE |
+			      SND_PCM_HW_PARBIT_PERIODS |
+			      SND_PCM_HW_PARBIT_PERIOD_TIME |
+			      SND_PCM_HW_PARBIT_BUFFER_TIME |
+			      SND_PCM_HW_PARBIT_TICK_TIME);
+	err = _snd_pcm_hw_params_refine(sparams, links, params);
+	if (err < 0)
+		return err;
+	return 0;
+}
+	
+static int snd_pcm_adpcm_hw_refine_cchange(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params,
+					    snd_pcm_hw_params_t *sparams)
+{
+	int err;
+	unsigned int links = (SND_PCM_HW_PARBIT_CHANNELS |
+			      SND_PCM_HW_PARBIT_RATE |
+			      SND_PCM_HW_PARBIT_PERIOD_SIZE |
+			      SND_PCM_HW_PARBIT_BUFFER_SIZE |
+			      SND_PCM_HW_PARBIT_PERIODS |
+			      SND_PCM_HW_PARBIT_PERIOD_TIME |
+			      SND_PCM_HW_PARBIT_BUFFER_TIME |
+			      SND_PCM_HW_PARBIT_TICK_TIME);
+	err = _snd_pcm_hw_params_refine(params, links, sparams);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static int snd_pcm_adpcm_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	return snd_pcm_hw_refine_slave(pcm, params,
+				       snd_pcm_adpcm_hw_refine_cprepare,
+				       snd_pcm_adpcm_hw_refine_cchange,
+				       snd_pcm_adpcm_hw_refine_sprepare,
+				       snd_pcm_adpcm_hw_refine_schange,
+				       snd_pcm_generic_hw_refine);
+}
+
+static int snd_pcm_adpcm_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t * params)
+{
+	snd_pcm_adpcm_t *adpcm = pcm->private_data;
+	snd_pcm_format_t format;
+	int err = snd_pcm_hw_params_slave(pcm, params,
+					  snd_pcm_adpcm_hw_refine_cchange,
+					  snd_pcm_adpcm_hw_refine_sprepare,
+					  snd_pcm_adpcm_hw_refine_schange,
+					  snd_pcm_generic_hw_params);
+	if (err < 0)
+		return err;
+
+	err = INTERNAL(snd_pcm_hw_params_get_format)(params, &format);
+	if (err < 0)
+		return err;
+
+	if (pcm->stream == SND_PCM_STREAM_PLAYBACK) {
+		if (adpcm->sformat == SND_PCM_FORMAT_IMA_ADPCM) {
+			adpcm->getput_idx = snd_pcm_linear_get_index(format, SND_PCM_FORMAT_S16);
+			adpcm->func = snd_pcm_adpcm_encode;
+		} else {
+			adpcm->getput_idx = snd_pcm_linear_put_index(SND_PCM_FORMAT_S16, adpcm->sformat);
+			adpcm->func = snd_pcm_adpcm_decode;
+		}
+	} else {
+		if (adpcm->sformat == SND_PCM_FORMAT_IMA_ADPCM) {
+			adpcm->getput_idx = snd_pcm_linear_put_index(SND_PCM_FORMAT_S16, format);
+			adpcm->func = snd_pcm_adpcm_decode;
+		} else {
+			adpcm->getput_idx = snd_pcm_linear_get_index(adpcm->sformat, SND_PCM_FORMAT_S16);
+			adpcm->func = snd_pcm_adpcm_encode;
+		}
+	}
+	assert(!adpcm->states);
+	adpcm->states = malloc(adpcm->plug.gen.slave->channels * sizeof(*adpcm->states));
+	if (adpcm->states == NULL)
+		return -ENOMEM;
+	return 0;
+}
+
+static int snd_pcm_adpcm_hw_free(snd_pcm_t *pcm)
+{
+	snd_pcm_adpcm_t *adpcm = pcm->private_data;
+	free(adpcm->states);
+	adpcm->states = NULL;
+	return snd_pcm_hw_free(adpcm->plug.gen.slave);
+}
+
+static int snd_pcm_adpcm_init(snd_pcm_t *pcm)
+{
+	snd_pcm_adpcm_t *adpcm = pcm->private_data;
+	unsigned int k;
+	for (k = 0; k < pcm->channels; ++k) {
+		adpcm->states[k].pred_val = 0;
+		adpcm->states[k].step_idx = 0;
+	}
+	return 0;
+}
+
+static snd_pcm_uframes_t
+snd_pcm_adpcm_write_areas(snd_pcm_t *pcm,
+			  const snd_pcm_channel_area_t *areas,
+			  snd_pcm_uframes_t offset,
+			  snd_pcm_uframes_t size,
+			  const snd_pcm_channel_area_t *slave_areas,
+			  snd_pcm_uframes_t slave_offset,
+			  snd_pcm_uframes_t *slave_sizep)
+{
+	snd_pcm_adpcm_t *adpcm = pcm->private_data;
+	if (size > *slave_sizep)
+		size = *slave_sizep;
+	adpcm->func(slave_areas, slave_offset,
+		    areas, offset, 
+		    pcm->channels, size,
+		    adpcm->getput_idx, adpcm->states);
+	*slave_sizep = size;
+	return size;
+}
+
+static snd_pcm_uframes_t
+snd_pcm_adpcm_read_areas(snd_pcm_t *pcm,
+			 const snd_pcm_channel_area_t *areas,
+			 snd_pcm_uframes_t offset,
+			 snd_pcm_uframes_t size,
+			 const snd_pcm_channel_area_t *slave_areas,
+			 snd_pcm_uframes_t slave_offset,
+			 snd_pcm_uframes_t *slave_sizep)
+{
+	snd_pcm_adpcm_t *adpcm = pcm->private_data;
+	if (size > *slave_sizep)
+		size = *slave_sizep;
+	adpcm->func(areas, offset, 
+		    slave_areas, slave_offset,
+		    pcm->channels, size,
+		    adpcm->getput_idx, adpcm->states);
+	*slave_sizep = size;
+	return size;
+}
+
+static void snd_pcm_adpcm_dump(snd_pcm_t *pcm, snd_output_t *out)
+{
+	snd_pcm_adpcm_t *adpcm = pcm->private_data;
+	snd_output_printf(out, "Ima-ADPCM conversion PCM (%s)\n", 
+		snd_pcm_format_name(adpcm->sformat));
+	if (pcm->setup) {
+		snd_output_printf(out, "Its setup is:\n");
+		snd_pcm_dump_setup(pcm, out);
+	}
+	snd_output_printf(out, "Slave: ");
+	snd_pcm_dump(adpcm->plug.gen.slave, out);
+}
+
+static const snd_pcm_ops_t snd_pcm_adpcm_ops = {
+	.close = snd_pcm_generic_close,
+	.info = snd_pcm_generic_info,
+	.hw_refine = snd_pcm_adpcm_hw_refine,
+	.hw_params = snd_pcm_adpcm_hw_params,
+	.hw_free = snd_pcm_adpcm_hw_free,
+	.sw_params = snd_pcm_generic_sw_params,
+	.channel_info = snd_pcm_generic_channel_info,
+	.dump = snd_pcm_adpcm_dump,
+	.nonblock = snd_pcm_generic_nonblock,
+	.async = snd_pcm_generic_async,
+	.mmap = snd_pcm_generic_mmap,
+	.munmap = snd_pcm_generic_munmap,
+};
+
+/**
+ * \brief Creates a new Ima-ADPCM conversion PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param sformat Slave (destination) format
+ * \param slave Slave PCM handle
+ * \param close_slave When set, the slave PCM handle is closed with copy PCM
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int snd_pcm_adpcm_open(snd_pcm_t **pcmp, const char *name, snd_pcm_format_t sformat, snd_pcm_t *slave, int close_slave)
+{
+	snd_pcm_t *pcm;
+	snd_pcm_adpcm_t *adpcm;
+	int err;
+	assert(pcmp && slave);
+	if (snd_pcm_format_linear(sformat) != 1 &&
+	    sformat != SND_PCM_FORMAT_IMA_ADPCM)
+		return -EINVAL;
+	adpcm = calloc(1, sizeof(snd_pcm_adpcm_t));
+	if (!adpcm) {
+		return -ENOMEM;
+	}
+	adpcm->sformat = sformat;
+	snd_pcm_plugin_init(&adpcm->plug);
+	adpcm->plug.read = snd_pcm_adpcm_read_areas;
+	adpcm->plug.write = snd_pcm_adpcm_write_areas;
+	adpcm->plug.init = snd_pcm_adpcm_init;
+	adpcm->plug.gen.slave = slave;
+	adpcm->plug.gen.close_slave = close_slave;
+
+	err = snd_pcm_new(&pcm, SND_PCM_TYPE_ADPCM, name, slave->stream, slave->mode);
+	if (err < 0) {
+		free(adpcm);
+		return err;
+	}
+	pcm->ops = &snd_pcm_adpcm_ops;
+	pcm->fast_ops = &snd_pcm_plugin_fast_ops;
+	pcm->private_data = adpcm;
+	pcm->poll_fd = slave->poll_fd;
+	pcm->poll_events = slave->poll_events;
+	pcm->monotonic = slave->monotonic;
+	snd_pcm_set_hw_ptr(pcm, &adpcm->plug.hw_ptr, -1, 0);
+	snd_pcm_set_appl_ptr(pcm, &adpcm->plug.appl_ptr, -1, 0);
+	*pcmp = pcm;
+
+	return 0;
+}
+
+/*! \page pcm_plugins
+
+\section pcm_plugins_adpcm Plugin: Ima-ADPCM
+
+This plugin converts Ima-ADPCM samples to linear or linear to Ima-ADPCM samples
+from master Ima-ADPCM conversion PCM to given slave PCM. The channel count,
+format and rate must match for both of them.
+
+\code
+pcm.name {
+        type adpcm              # Ima-ADPCM conversion PCM
+        slave STR               # Slave name
+        # or
+        slave {                 # Slave definition
+                pcm STR         # Slave PCM name
+                # or
+                pcm { }         # Slave PCM definition
+                format STR      # Slave format
+        }
+}
+\endcode
+
+\subsection pcm_plugins_adpcm_funcref Function reference
+
+<UL>
+  <LI>snd_pcm_adpcm_open()
+  <LI>_snd_pcm_adpcm_open()
+</UL>
+
+*/
+
+/**
+ * \brief Creates a new Ima-ADPCM conversion PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param root Root configuration node
+ * \param conf Configuration node with copy PCM description
+ * \param stream Stream type
+ * \param mode Stream mode
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int _snd_pcm_adpcm_open(snd_pcm_t **pcmp, const char *name,
+			snd_config_t *root, snd_config_t *conf, 
+			snd_pcm_stream_t stream, int mode)
+{
+	snd_config_iterator_t i, next;
+	int err;
+	snd_pcm_t *spcm;
+	snd_config_t *slave = NULL, *sconf;
+	snd_pcm_format_t sformat;
+	snd_config_for_each(i, next, conf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (snd_pcm_conf_generic_id(id))
+			continue;
+		if (strcmp(id, "slave") == 0) {
+			slave = n;
+			continue;
+		}
+		SNDERR("Unknown field %s", id);
+		return -EINVAL;
+	}
+	if (!slave) {
+		SNDERR("slave is not defined");
+		return -EINVAL;
+	}
+	err = snd_pcm_slave_conf(root, slave, &sconf, 1,
+				 SND_PCM_HW_PARAM_FORMAT, SCONF_MANDATORY, &sformat);
+	if (err < 0)
+		return err;
+	if (snd_pcm_format_linear(sformat) != 1 &&
+	    sformat != SND_PCM_FORMAT_IMA_ADPCM) {
+	    	snd_config_delete(sconf);
+		SNDERR("invalid slave format");
+		return -EINVAL;
+	}
+	err = snd_pcm_open_slave(&spcm, root, sconf, stream, mode, conf);
+	snd_config_delete(sconf);
+	if (err < 0)
+		return err;
+	err = snd_pcm_adpcm_open(pcmp, name, sformat, spcm, 1);
+	if (err < 0)
+		snd_pcm_close(spcm);
+	return err;
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(_snd_pcm_adpcm_open, SND_PCM_DLSYM_VERSION);
+#endif
diff --git a/src/pcm/pcm_alaw.c b/src/pcm/pcm_alaw.c
new file mode 100644
index 0000000..09ad481
--- /dev/null
+++ b/src/pcm/pcm_alaw.c
@@ -0,0 +1,553 @@
+/**
+ * \file pcm/pcm_alaw.c
+ * \ingroup PCM_Plugins
+ * \brief PCM A-Law Conversion Plugin Interface
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \date 2000-2001
+ */
+/*
+ *  PCM - A-Law conversion
+ *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+#include <byteswap.h>
+#include "pcm_local.h"
+#include "pcm_plugin.h"
+
+#include "plugin_ops.h"
+
+#ifndef PIC
+/* entry for static linking */
+const char *_snd_module_pcm_alaw = "";
+#endif
+
+#ifndef DOC_HIDDEN
+
+typedef void (*alaw_f)(const snd_pcm_channel_area_t *dst_areas,
+		       snd_pcm_uframes_t dst_offset,
+		       const snd_pcm_channel_area_t *src_areas,
+		       snd_pcm_uframes_t src_offset,
+		       unsigned int channels, snd_pcm_uframes_t frames,
+		       unsigned int getputidx);
+
+typedef struct {
+	/* This field need to be the first */
+	snd_pcm_plugin_t plug;
+	unsigned int getput_idx;
+	alaw_f func;
+	snd_pcm_format_t sformat;
+} snd_pcm_alaw_t;
+
+#endif
+
+static inline int val_seg(int val)
+{
+	int r = 1;
+	val >>= 8;
+	if (val & 0xf0) {
+		val >>= 4;
+		r += 4;
+	}
+	if (val & 0x0c) {
+		val >>= 2;
+		r += 2;
+	}
+	if (val & 0x02)
+		r += 1;
+	return r;
+}
+
+/*
+ * s16_to_alaw() - Convert a 16-bit linear PCM value to 8-bit A-law
+ *
+ * s16_to_alaw() accepts an 16-bit integer and encodes it as A-law data.
+ *
+ *		Linear Input Code	Compressed Code
+ *	------------------------	---------------
+ *	0000000wxyza			000wxyz
+ *	0000001wxyza			001wxyz
+ *	000001wxyzab			010wxyz
+ *	00001wxyzabc			011wxyz
+ *	0001wxyzabcd			100wxyz
+ *	001wxyzabcde			101wxyz
+ *	01wxyzabcdef			110wxyz
+ *	1wxyzabcdefg			111wxyz
+ *
+ * For further information see John C. Bellamy's Digital Telephony, 1982,
+ * John Wiley & Sons, pps 98-111 and 472-476.
+ */
+
+static unsigned char s16_to_alaw(int pcm_val)
+{
+	int		mask;
+	int		seg;
+	unsigned char	aval;
+
+	if (pcm_val >= 0) {
+		mask = 0xD5;
+	} else {
+		mask = 0x55;
+		pcm_val = -pcm_val;
+		if (pcm_val > 0x7fff)
+			pcm_val = 0x7fff;
+	}
+
+	if (pcm_val < 256)
+		aval = pcm_val >> 4;
+	else {
+		/* Convert the scaled magnitude to segment number. */
+		seg = val_seg(pcm_val);
+		aval = (seg << 4) | ((pcm_val >> (seg + 3)) & 0x0f);
+	}
+	return aval ^ mask;
+}
+
+/*
+ * alaw_to_s16() - Convert an A-law value to 16-bit linear PCM
+ *
+ */
+static int alaw_to_s16(unsigned char a_val)
+{
+	int		t;
+	int		seg;
+
+	a_val ^= 0x55;
+	t = a_val & 0x7f;
+	if (t < 16)
+		t = (t << 4) + 8;
+	else {
+		seg = (t >> 4) & 0x07;
+		t = ((t & 0x0f) << 4) + 0x108;
+		t <<= seg -1;
+	}
+	return ((a_val & 0x80) ? t : -t);
+}
+
+#ifndef DOC_HIDDEN
+
+void snd_pcm_alaw_decode(const snd_pcm_channel_area_t *dst_areas,
+			 snd_pcm_uframes_t dst_offset,
+			 const snd_pcm_channel_area_t *src_areas,
+			 snd_pcm_uframes_t src_offset,
+			 unsigned int channels, snd_pcm_uframes_t frames,
+			 unsigned int putidx)
+{
+#define PUT16_LABELS
+#include "plugin_ops.h"
+#undef PUT16_LABELS
+	void *put = put16_labels[putidx];
+	unsigned int channel;
+	for (channel = 0; channel < channels; ++channel) {
+		const unsigned char *src;
+		char *dst;
+		int src_step, dst_step;
+		snd_pcm_uframes_t frames1;
+		const snd_pcm_channel_area_t *src_area = &src_areas[channel];
+		const snd_pcm_channel_area_t *dst_area = &dst_areas[channel];
+		src = snd_pcm_channel_area_addr(src_area, src_offset);
+		dst = snd_pcm_channel_area_addr(dst_area, dst_offset);
+		src_step = snd_pcm_channel_area_step(src_area);
+		dst_step = snd_pcm_channel_area_step(dst_area);
+		frames1 = frames;
+		while (frames1-- > 0) {
+			int16_t sample = alaw_to_s16(*src);
+			goto *put;
+#define PUT16_END after
+#include "plugin_ops.h"
+#undef PUT16_END
+		after:
+			src += src_step;
+			dst += dst_step;
+		}
+	}
+}
+
+void snd_pcm_alaw_encode(const snd_pcm_channel_area_t *dst_areas,
+			 snd_pcm_uframes_t dst_offset,
+			 const snd_pcm_channel_area_t *src_areas,
+			 snd_pcm_uframes_t src_offset,
+			 unsigned int channels, snd_pcm_uframes_t frames,
+			 unsigned int getidx)
+{
+#define GET16_LABELS
+#include "plugin_ops.h"
+#undef GET16_LABELS
+	void *get = get16_labels[getidx];
+	unsigned int channel;
+	int16_t sample = 0;
+	for (channel = 0; channel < channels; ++channel) {
+		const char *src;
+		char *dst;
+		int src_step, dst_step;
+		snd_pcm_uframes_t frames1;
+		const snd_pcm_channel_area_t *src_area = &src_areas[channel];
+		const snd_pcm_channel_area_t *dst_area = &dst_areas[channel];
+		src = snd_pcm_channel_area_addr(src_area, src_offset);
+		dst = snd_pcm_channel_area_addr(dst_area, dst_offset);
+		src_step = snd_pcm_channel_area_step(src_area);
+		dst_step = snd_pcm_channel_area_step(dst_area);
+		frames1 = frames;
+		while (frames1-- > 0) {
+			goto *get;
+#define GET16_END after
+#include "plugin_ops.h"
+#undef GET16_END
+		after:
+			*dst = s16_to_alaw(sample);
+			src += src_step;
+			dst += dst_step;
+		}
+	}
+}
+
+#endif /* DOC_HIDDEN */
+
+static int snd_pcm_alaw_hw_refine_cprepare(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	snd_pcm_alaw_t *alaw = pcm->private_data;
+	int err;
+	snd_pcm_access_mask_t access_mask = { SND_PCM_ACCBIT_SHM };
+	err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_ACCESS,
+					 &access_mask);
+	if (err < 0)
+		return err;
+	if (alaw->sformat == SND_PCM_FORMAT_A_LAW) {
+		snd_pcm_format_mask_t format_mask = { SND_PCM_FMTBIT_LINEAR };
+		err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_FORMAT,
+						 &format_mask);
+	} else {
+		err = _snd_pcm_hw_params_set_format(params, 
+						   SND_PCM_FORMAT_A_LAW);
+	}
+	if (err < 0)
+		return err;
+	err = _snd_pcm_hw_params_set_subformat(params, SND_PCM_SUBFORMAT_STD);
+	if (err < 0)
+		return err;
+	params->info &= ~(SND_PCM_INFO_MMAP | SND_PCM_INFO_MMAP_VALID);
+	return 0;
+}
+
+static int snd_pcm_alaw_hw_refine_sprepare(snd_pcm_t *pcm, snd_pcm_hw_params_t *sparams)
+{
+	snd_pcm_alaw_t *alaw = pcm->private_data;
+	snd_pcm_access_mask_t saccess_mask = { SND_PCM_ACCBIT_MMAP };
+	_snd_pcm_hw_params_any(sparams);
+	_snd_pcm_hw_param_set_mask(sparams, SND_PCM_HW_PARAM_ACCESS,
+				   &saccess_mask);
+	_snd_pcm_hw_params_set_format(sparams, alaw->sformat);
+	_snd_pcm_hw_params_set_subformat(sparams, SND_PCM_SUBFORMAT_STD);
+	return 0;
+}
+
+static int snd_pcm_alaw_hw_refine_schange(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params,
+					    snd_pcm_hw_params_t *sparams)
+{
+	int err;
+	unsigned int links = (SND_PCM_HW_PARBIT_CHANNELS |
+			      SND_PCM_HW_PARBIT_RATE |
+			      SND_PCM_HW_PARBIT_PERIOD_SIZE |
+			      SND_PCM_HW_PARBIT_BUFFER_SIZE |
+			      SND_PCM_HW_PARBIT_PERIODS |
+			      SND_PCM_HW_PARBIT_PERIOD_TIME |
+			      SND_PCM_HW_PARBIT_BUFFER_TIME |
+			      SND_PCM_HW_PARBIT_TICK_TIME);
+	err = _snd_pcm_hw_params_refine(sparams, links, params);
+	if (err < 0)
+		return err;
+	return 0;
+}
+	
+static int snd_pcm_alaw_hw_refine_cchange(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params,
+					    snd_pcm_hw_params_t *sparams)
+{
+	int err;
+	unsigned int links = (SND_PCM_HW_PARBIT_CHANNELS |
+			      SND_PCM_HW_PARBIT_RATE |
+			      SND_PCM_HW_PARBIT_PERIOD_SIZE |
+			      SND_PCM_HW_PARBIT_BUFFER_SIZE |
+			      SND_PCM_HW_PARBIT_PERIODS |
+			      SND_PCM_HW_PARBIT_PERIOD_TIME |
+			      SND_PCM_HW_PARBIT_BUFFER_TIME |
+			      SND_PCM_HW_PARBIT_TICK_TIME);
+	err = _snd_pcm_hw_params_refine(params, links, sparams);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static int snd_pcm_alaw_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	return snd_pcm_hw_refine_slave(pcm, params,
+				       snd_pcm_alaw_hw_refine_cprepare,
+				       snd_pcm_alaw_hw_refine_cchange,
+				       snd_pcm_alaw_hw_refine_sprepare,
+				       snd_pcm_alaw_hw_refine_schange,
+				       snd_pcm_generic_hw_refine);
+}
+
+static int snd_pcm_alaw_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t * params)
+{
+	snd_pcm_alaw_t *alaw = pcm->private_data;
+	snd_pcm_format_t format;
+	int err = snd_pcm_hw_params_slave(pcm, params,
+					  snd_pcm_alaw_hw_refine_cchange,
+					  snd_pcm_alaw_hw_refine_sprepare,
+					  snd_pcm_alaw_hw_refine_schange,
+					  snd_pcm_generic_hw_params);
+	if (err < 0)
+		return err;
+
+	err = INTERNAL(snd_pcm_hw_params_get_format)(params, &format);
+	if (err < 0)
+		return err;
+		
+	if (pcm->stream == SND_PCM_STREAM_PLAYBACK) {
+		if (alaw->sformat == SND_PCM_FORMAT_A_LAW) {
+			alaw->getput_idx = snd_pcm_linear_get_index(format, SND_PCM_FORMAT_S16);
+			alaw->func = snd_pcm_alaw_encode;
+		} else {
+			alaw->getput_idx = snd_pcm_linear_put_index(SND_PCM_FORMAT_S16, alaw->sformat);
+			alaw->func = snd_pcm_alaw_decode;
+		}
+	} else {
+		if (alaw->sformat == SND_PCM_FORMAT_A_LAW) {
+			alaw->getput_idx = snd_pcm_linear_put_index(SND_PCM_FORMAT_S16, format);
+			alaw->func = snd_pcm_alaw_decode;
+		} else {
+			alaw->getput_idx = snd_pcm_linear_get_index(alaw->sformat, SND_PCM_FORMAT_S16);
+			alaw->func = snd_pcm_alaw_encode;
+		}
+	}
+	return 0;
+}
+
+static snd_pcm_uframes_t
+snd_pcm_alaw_write_areas(snd_pcm_t *pcm,
+			 const snd_pcm_channel_area_t *areas,
+			 snd_pcm_uframes_t offset,
+			 snd_pcm_uframes_t size,
+			 const snd_pcm_channel_area_t *slave_areas,
+			 snd_pcm_uframes_t slave_offset,
+			 snd_pcm_uframes_t *slave_sizep)
+{
+	snd_pcm_alaw_t *alaw = pcm->private_data;
+	if (size > *slave_sizep)
+		size = *slave_sizep;
+	alaw->func(slave_areas, slave_offset,
+		   areas, offset, 
+		   pcm->channels, size,
+		   alaw->getput_idx);
+	*slave_sizep = size;
+	return size;
+}
+
+static snd_pcm_uframes_t
+snd_pcm_alaw_read_areas(snd_pcm_t *pcm,
+			const snd_pcm_channel_area_t *areas,
+			snd_pcm_uframes_t offset,
+			snd_pcm_uframes_t size,
+			const snd_pcm_channel_area_t *slave_areas,
+			snd_pcm_uframes_t slave_offset,
+			snd_pcm_uframes_t *slave_sizep)
+{
+	snd_pcm_alaw_t *alaw = pcm->private_data;
+	if (size > *slave_sizep)
+		size = *slave_sizep;
+	alaw->func(areas, offset, 
+		   slave_areas, slave_offset,
+		   pcm->channels, size,
+		   alaw->getput_idx);
+	*slave_sizep = size;
+	return size;
+}
+
+static void snd_pcm_alaw_dump(snd_pcm_t *pcm, snd_output_t *out)
+{
+	snd_pcm_alaw_t *alaw = pcm->private_data;
+	snd_output_printf(out, "A-Law conversion PCM (%s)\n", 
+		snd_pcm_format_name(alaw->sformat));
+	if (pcm->setup) {
+		snd_output_printf(out, "Its setup is:\n");
+		snd_pcm_dump_setup(pcm, out);
+	}
+	snd_output_printf(out, "Slave: ");
+	snd_pcm_dump(alaw->plug.gen.slave, out);
+}
+
+static const snd_pcm_ops_t snd_pcm_alaw_ops = {
+	.close = snd_pcm_generic_close,
+	.info = snd_pcm_generic_info,
+	.hw_refine = snd_pcm_alaw_hw_refine,
+	.hw_params = snd_pcm_alaw_hw_params,
+	.hw_free = snd_pcm_generic_hw_free,
+	.sw_params = snd_pcm_generic_sw_params,
+	.channel_info = snd_pcm_generic_channel_info,
+	.dump = snd_pcm_alaw_dump,
+	.nonblock = snd_pcm_generic_nonblock,
+	.async = snd_pcm_generic_async,
+	.mmap = snd_pcm_generic_mmap,
+	.munmap = snd_pcm_generic_munmap,
+};
+
+/**
+ * \brief Creates a new A-Law conversion PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param sformat Slave (destination) format
+ * \param slave Slave PCM handle
+ * \param close_slave When set, the slave PCM handle is closed with copy PCM
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */           
+int snd_pcm_alaw_open(snd_pcm_t **pcmp, const char *name, snd_pcm_format_t sformat, snd_pcm_t *slave, int close_slave)
+{
+	snd_pcm_t *pcm;
+	snd_pcm_alaw_t *alaw;
+	int err;
+	assert(pcmp && slave);
+	if (snd_pcm_format_linear(sformat) != 1 &&
+	    sformat != SND_PCM_FORMAT_A_LAW)
+		return -EINVAL;
+	alaw = calloc(1, sizeof(snd_pcm_alaw_t));
+	if (!alaw) {
+		return -ENOMEM;
+	}
+	snd_pcm_plugin_init(&alaw->plug);
+	alaw->sformat = sformat;
+	alaw->plug.read = snd_pcm_alaw_read_areas;
+	alaw->plug.write = snd_pcm_alaw_write_areas;
+	alaw->plug.undo_read = snd_pcm_plugin_undo_read_generic;
+	alaw->plug.undo_write = snd_pcm_plugin_undo_write_generic;
+	alaw->plug.gen.slave = slave;
+	alaw->plug.gen.close_slave = close_slave;
+
+	err = snd_pcm_new(&pcm, SND_PCM_TYPE_ALAW, name, slave->stream, slave->mode);
+	if (err < 0) {
+		free(alaw);
+		return err;
+	}
+	pcm->ops = &snd_pcm_alaw_ops;
+	pcm->fast_ops = &snd_pcm_plugin_fast_ops;
+	pcm->private_data = alaw;
+	pcm->poll_fd = slave->poll_fd;
+	pcm->poll_events = slave->poll_events;
+	pcm->monotonic = slave->monotonic;
+	snd_pcm_set_hw_ptr(pcm, &alaw->plug.hw_ptr, -1, 0);
+	snd_pcm_set_appl_ptr(pcm, &alaw->plug.appl_ptr, -1, 0);
+	*pcmp = pcm;
+
+	return 0;
+}
+
+/*! \page pcm_plugins
+
+\section pcm_plugins_alaw Plugin: A-Law
+
+This plugin converts A-Law samples to linear or linear to A-Law samples
+from master A-Law conversion PCM to given slave PCM. The channel count,
+format and rate must match for both of them.
+
+\code
+pcm.name {
+        type alaw               # A-Law conversion PCM
+        slave STR               # Slave name
+        # or
+        slave {                 # Slave definition
+                pcm STR         # Slave PCM name
+                # or
+                pcm { }         # Slave PCM definition
+                format STR      # Slave format
+        }
+}
+\endcode
+
+\subsection pcm_plugins_alaw_funcref Function reference
+
+<UL>
+  <LI>snd_pcm_alaw_open()
+  <LI>_snd_pcm_alaw_open()
+</UL>
+
+*/
+
+/**
+ * \brief Creates a new A-Law conversion PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param root Root configuration node
+ * \param conf Configuration node with copy PCM description
+ * \param stream Stream type
+ * \param mode Stream mode
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int _snd_pcm_alaw_open(snd_pcm_t **pcmp, const char *name,
+		       snd_config_t *root, snd_config_t *conf, 
+		       snd_pcm_stream_t stream, int mode)
+{
+	snd_config_iterator_t i, next;
+	int err;
+	snd_pcm_t *spcm;
+	snd_config_t *slave = NULL, *sconf;
+	snd_pcm_format_t sformat;
+	snd_config_for_each(i, next, conf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (snd_pcm_conf_generic_id(id))
+			continue;
+		if (strcmp(id, "slave") == 0) {
+			slave = n;
+			continue;
+		}
+		SNDERR("Unknown field %s", id);
+		return -EINVAL;
+	}
+	if (!slave) {
+		SNDERR("slave is not defined");
+		return -EINVAL;
+	}
+	err = snd_pcm_slave_conf(root, slave, &sconf, 1,
+				 SND_PCM_HW_PARAM_FORMAT, SCONF_MANDATORY, &sformat);
+	if (err < 0)
+		return err;
+	if (snd_pcm_format_linear(sformat) != 1 &&
+	    sformat != SND_PCM_FORMAT_A_LAW) {
+	    	snd_config_delete(sconf);
+		SNDERR("invalid slave format");
+		return -EINVAL;
+	}
+	err = snd_pcm_open_slave(&spcm, root, sconf, stream, mode, conf);
+	snd_config_delete(sconf);
+	if (err < 0)
+		return err;
+	err = snd_pcm_alaw_open(pcmp, name, sformat, spcm, 1);
+	if (err < 0)
+		snd_pcm_close(spcm);
+	return err;
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(_snd_pcm_alaw_open, SND_PCM_DLSYM_VERSION);
+#endif
diff --git a/src/pcm/pcm_asym.c b/src/pcm/pcm_asym.c
new file mode 100644
index 0000000..9c32b1b
--- /dev/null
+++ b/src/pcm/pcm_asym.c
@@ -0,0 +1,119 @@
+/**
+ * \file pcm/pcm_asym.c
+ * \ingroup PCM_Plugins
+ * \brief PCM Asymmetrical Plugin Interface
+ * \author Takashi Iwai <tiwai@suse.de>
+ * \date 2003
+ */
+
+#include "pcm_local.h"
+
+#ifndef PIC
+/* entry for static linking */
+const char *_snd_module_pcm_asym = "";
+#endif
+
+/*! \page pcm_plugins
+
+\section pcm_plugins_asym Plugin: asym
+
+This plugin is a combination of playback and capture PCM streams.
+Slave PCMs can be defined asymmetrically for both directions.
+
+\code
+pcm.name {
+        type asym               # Asym PCM
+        playback STR            # Playback slave name
+        # or
+        playback {              # Playback slave definition
+                pcm STR         # Slave PCM name
+                # or
+                pcm { }         # Slave PCM definition
+        }
+        capture STR             # Capture slave name
+        # or
+        capture {               # Capture slave definition
+                pcm STR         # Slave PCM name
+                # or
+                pcm { }         # Slave PCM definition
+        }
+}
+\endcode
+
+For example, you can combine a dmix plugin and a dsnoop plugin as
+as a single PCM for playback and capture directions, respectively.
+\code
+pcm.duplex {
+	type asym
+	playback.pcm "dmix"
+	capture.pcm "dsnoop"
+}
+\endcode
+
+By defining only a single direction, the resultant PCM becomes
+half-duplex.
+
+\subsection pcm_plugins_asym_funcref Function reference
+
+<UL>
+  <LI>_snd_pcm_asym_open()
+</UL>
+
+*/
+
+/**
+ * \brief Creates a new asym stream PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param root Root configuration node
+ * \param conf Configuration node with copy PCM description
+ * \param stream Stream type
+ * \param mode Stream mode
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int _snd_pcm_asym_open(snd_pcm_t **pcmp, const char *name ATTRIBUTE_UNUSED,
+			 snd_config_t *root, snd_config_t *conf,
+			 snd_pcm_stream_t stream, int mode)
+{
+	snd_config_iterator_t i, next;
+	int err;
+	snd_config_t *slave = NULL, *sconf;
+	snd_config_for_each(i, next, conf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (snd_pcm_conf_generic_id(id))
+			continue;
+		if (strcmp(id, "playback") == 0) {
+			if (stream == SND_PCM_STREAM_PLAYBACK)
+				slave = n;
+			continue;
+		}
+		if (strcmp(id, "capture") == 0) {
+			if (stream == SND_PCM_STREAM_CAPTURE)
+				slave = n;
+			continue;
+		}
+		SNDERR("Unknown field %s", id);
+		return -EINVAL;
+	}
+	if (! slave) {
+		SNDERR("%s slave is not defined",
+		       stream == SND_PCM_STREAM_PLAYBACK ? "playback" : "capture");
+		return -EINVAL;
+	}
+	err = snd_pcm_slave_conf(root, slave, &sconf, 0);
+	if (err < 0)
+		return err;
+	err = snd_pcm_open_named_slave(pcmp, name, root, sconf, stream,
+				       mode, conf);
+	snd_config_delete(sconf);
+	return err;
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(_snd_pcm_asym_open, SND_PCM_DLSYM_VERSION);
+#endif
diff --git a/src/pcm/pcm_copy.c b/src/pcm/pcm_copy.c
new file mode 100644
index 0000000..072bb12
--- /dev/null
+++ b/src/pcm/pcm_copy.c
@@ -0,0 +1,299 @@
+/**
+ * \file pcm/pcm_copy.c
+ * \ingroup PCM_Plugins
+ * \brief PCM Copy Plugin Interface
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \date 2000-2001
+ */
+/*
+ *  PCM - Copy conversion
+ *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+#include <byteswap.h>
+#include "pcm_local.h"
+#include "pcm_plugin.h"
+
+#ifndef PIC
+/* entry for static linking */
+const char *_snd_module_pcm_copy = "";
+#endif
+
+#ifndef DOC_HIDDEN
+typedef struct {
+	/* This field need to be the first */
+	snd_pcm_plugin_t plug;
+} snd_pcm_copy_t;
+#endif
+
+static int snd_pcm_copy_hw_refine_cprepare(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params)
+{
+	int err;
+	snd_pcm_access_mask_t access_mask = { SND_PCM_ACCBIT_SHM };
+	err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_ACCESS,
+					 &access_mask);
+	if (err < 0)
+		return err;
+	params->info &= ~(SND_PCM_INFO_MMAP | SND_PCM_INFO_MMAP_VALID);
+	return 0;
+}
+
+static int snd_pcm_copy_hw_refine_sprepare(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *sparams)
+{
+	snd_pcm_access_mask_t saccess_mask = { SND_PCM_ACCBIT_MMAP };
+	_snd_pcm_hw_params_any(sparams);
+	_snd_pcm_hw_param_set_mask(sparams, SND_PCM_HW_PARAM_ACCESS,
+				   &saccess_mask);
+	return 0;
+}
+
+static int snd_pcm_copy_hw_refine_schange(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params,
+					  snd_pcm_hw_params_t *sparams)
+{
+	int err;
+	unsigned int links = ~SND_PCM_HW_PARBIT_ACCESS;
+	err = _snd_pcm_hw_params_refine(sparams, links, params);
+	if (err < 0)
+		return err;
+	return 0;
+}
+	
+static int snd_pcm_copy_hw_refine_cchange(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params,
+					  snd_pcm_hw_params_t *sparams)
+{
+	int err;
+	unsigned int links = ~SND_PCM_HW_PARBIT_ACCESS;
+	err = _snd_pcm_hw_params_refine(params, links, sparams);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static int snd_pcm_copy_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	return snd_pcm_hw_refine_slave(pcm, params,
+				       snd_pcm_copy_hw_refine_cprepare,
+				       snd_pcm_copy_hw_refine_cchange,
+				       snd_pcm_copy_hw_refine_sprepare,
+				       snd_pcm_copy_hw_refine_schange,
+				       snd_pcm_generic_hw_refine);
+}
+
+static int snd_pcm_copy_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	return snd_pcm_hw_params_slave(pcm, params,
+				       snd_pcm_copy_hw_refine_cchange,
+				       snd_pcm_copy_hw_refine_sprepare,
+				       snd_pcm_copy_hw_refine_schange,
+				       snd_pcm_generic_hw_params);
+}
+
+static snd_pcm_uframes_t
+snd_pcm_copy_write_areas(snd_pcm_t *pcm,
+			 const snd_pcm_channel_area_t *areas,
+			 snd_pcm_uframes_t offset,
+			 snd_pcm_uframes_t size,
+			 const snd_pcm_channel_area_t *slave_areas,
+			 snd_pcm_uframes_t slave_offset,
+			 snd_pcm_uframes_t *slave_sizep)
+{
+	if (size > *slave_sizep)
+		size = *slave_sizep;
+	snd_pcm_areas_copy(slave_areas, slave_offset,
+			   areas, offset,
+			   pcm->channels, size, pcm->format);
+	*slave_sizep = size;
+	return size;
+}
+
+static snd_pcm_uframes_t
+snd_pcm_copy_read_areas(snd_pcm_t *pcm,
+			const snd_pcm_channel_area_t *areas,
+			snd_pcm_uframes_t offset,
+			snd_pcm_uframes_t size,
+			const snd_pcm_channel_area_t *slave_areas,
+			snd_pcm_uframes_t slave_offset,
+			snd_pcm_uframes_t *slave_sizep)
+{
+	if (size > *slave_sizep)
+		size = *slave_sizep;
+	snd_pcm_areas_copy(areas, offset, 
+			   slave_areas, slave_offset,
+			   pcm->channels, size, pcm->format);
+	*slave_sizep = size;
+	return size;
+}
+
+static void snd_pcm_copy_dump(snd_pcm_t *pcm, snd_output_t *out)
+{
+	snd_pcm_copy_t *copy = pcm->private_data;
+	snd_output_printf(out, "Copy conversion PCM\n");
+	if (pcm->setup) {
+		snd_output_printf(out, "Its setup is:\n");
+		snd_pcm_dump_setup(pcm, out);
+	}
+	snd_output_printf(out, "Slave: ");
+	snd_pcm_dump(copy->plug.gen.slave, out);
+}
+
+static const snd_pcm_ops_t snd_pcm_copy_ops = {
+	.close = snd_pcm_generic_close,
+	.info = snd_pcm_generic_info,
+	.hw_refine = snd_pcm_copy_hw_refine,
+	.hw_params = snd_pcm_copy_hw_params,
+	.hw_free = snd_pcm_generic_hw_free,
+	.sw_params = snd_pcm_generic_sw_params,
+	.channel_info = snd_pcm_generic_channel_info,
+	.dump = snd_pcm_copy_dump,
+	.nonblock = snd_pcm_generic_nonblock,
+	.async = snd_pcm_generic_async,
+	.mmap = snd_pcm_generic_mmap,
+	.munmap = snd_pcm_generic_munmap,
+};
+
+/**
+ * \brief Creates a new copy PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param slave Slave PCM handle
+ * \param close_slave When set, the slave PCM handle is closed with copy PCM
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int snd_pcm_copy_open(snd_pcm_t **pcmp, const char *name, snd_pcm_t *slave, int close_slave)
+{
+	snd_pcm_t *pcm;
+	snd_pcm_copy_t *copy;
+	int err;
+	assert(pcmp && slave);
+	copy = calloc(1, sizeof(snd_pcm_copy_t));
+	if (!copy) {
+		return -ENOMEM;
+	}
+	snd_pcm_plugin_init(&copy->plug);
+	copy->plug.read = snd_pcm_copy_read_areas;
+	copy->plug.write = snd_pcm_copy_write_areas;
+	copy->plug.undo_read = snd_pcm_plugin_undo_read_generic;
+	copy->plug.undo_write = snd_pcm_plugin_undo_write_generic;
+	copy->plug.gen.slave = slave;
+	copy->plug.gen.close_slave = close_slave;
+
+	err = snd_pcm_new(&pcm, SND_PCM_TYPE_COPY, name, slave->stream, slave->mode);
+	if (err < 0) {
+		free(copy);
+		return err;
+	}
+	pcm->ops = &snd_pcm_copy_ops;
+	pcm->fast_ops = &snd_pcm_plugin_fast_ops;
+	pcm->private_data = copy;
+	pcm->poll_fd = slave->poll_fd;
+	pcm->poll_events = slave->poll_events;
+	pcm->monotonic = slave->monotonic;
+	snd_pcm_set_hw_ptr(pcm, &copy->plug.hw_ptr, -1, 0);
+	snd_pcm_set_appl_ptr(pcm, &copy->plug.appl_ptr, -1, 0);
+	*pcmp = pcm;
+
+	return 0;
+}
+
+/*! \page pcm_plugins
+
+\section pcm_plugins_copy Plugin: copy
+
+This plugin copies samples from master copy PCM to given slave PCM.
+The channel count, format and rate must match for both of them. 
+
+\code
+pcm.name {
+	type copy		# Copy PCM
+	slave STR		# Slave name
+	# or
+	slave {			# Slave definition
+		pcm STR		# Slave PCM name
+		# or
+		pcm { }		# Slave PCM definition
+	}
+}
+\endcode
+
+\subsection pcm_plugins_copy_funcref Function reference
+
+<UL>
+  <LI>snd_pcm_copy_open()
+  <LI>_snd_pcm_copy_open()
+</UL>
+
+*/
+
+/**
+ * \brief Creates a new copy PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param root Root configuration node
+ * \param conf Configuration node with copy PCM description
+ * \param stream Stream type
+ * \param mode Stream mode
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int _snd_pcm_copy_open(snd_pcm_t **pcmp, const char *name,
+		       snd_config_t *root, snd_config_t *conf, 
+		       snd_pcm_stream_t stream, int mode)
+{
+	snd_config_iterator_t i, next;
+	int err;
+	snd_pcm_t *spcm;
+	snd_config_t *slave = NULL, *sconf;
+	snd_config_for_each(i, next, conf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (snd_pcm_conf_generic_id(id))
+			continue;
+		if (strcmp(id, "slave") == 0) {
+			slave = n;
+			continue;
+		}
+		SNDERR("Unknown field %s", id);
+		return -EINVAL;
+	}
+	if (!slave) {
+		SNDERR("slave is not defined");
+		return -EINVAL;
+	}
+	err = snd_pcm_slave_conf(root, slave, &sconf, 0);
+	if (err < 0)
+		return err;
+	err = snd_pcm_open_slave(&spcm, root, sconf, stream, mode, conf);
+	snd_config_delete(sconf);
+	if (err < 0)
+		return err;
+	err = snd_pcm_copy_open(pcmp, name, spcm, 1);
+	if (err < 0)
+		snd_pcm_close(spcm);
+	return err;
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(_snd_pcm_copy_open, SND_PCM_DLSYM_VERSION);
+#endif
diff --git a/src/pcm/pcm_direct.c b/src/pcm/pcm_direct.c
new file mode 100644
index 0000000..0a9047d
--- /dev/null
+++ b/src/pcm/pcm_direct.c
@@ -0,0 +1,1677 @@
+/*
+ *  PCM - Direct Stream Mixing
+ *  Copyright (c) 2003 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <grp.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/poll.h>
+#include <sys/shm.h>
+#include <sys/sem.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+#include <sys/mman.h>
+#include "pcm_direct.h"
+
+/*
+ *
+ */
+ 
+union semun {
+	int              val;    /* Value for SETVAL */
+	struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
+	unsigned short  *array;  /* Array for GETALL, SETALL */
+	struct seminfo  *__buf;  /* Buffer for IPC_INFO (Linux specific) */
+};
+ 
+/*
+ * FIXME:
+ *  add possibility to use futexes here
+ */
+
+int snd_pcm_direct_semaphore_create_or_connect(snd_pcm_direct_t *dmix)
+{
+	union semun s;
+	struct semid_ds buf;
+	int i;
+
+	dmix->semid = semget(dmix->ipc_key, DIRECT_IPC_SEMS,
+			     IPC_CREAT | dmix->ipc_perm);
+	if (dmix->semid < 0)
+		return -errno;
+	if (dmix->ipc_gid < 0)
+		return 0;
+	for (i = 0; i < DIRECT_IPC_SEMS; i++) {
+		s.buf = &buf;
+		if (semctl(dmix->semid, i, IPC_STAT, s) < 0) {
+			int err = -errno;
+			snd_pcm_direct_semaphore_discard(dmix);
+			return err;
+		}
+		buf.sem_perm.gid = dmix->ipc_gid;
+		s.buf = &buf;
+		semctl(dmix->semid, i, IPC_SET, s);
+	}
+	return 0;
+}
+
+#define SND_PCM_DIRECT_MAGIC	(0xa15ad300 + sizeof(snd_pcm_direct_share_t))
+
+/*
+ *  global shared memory area 
+ */
+
+int snd_pcm_direct_shm_create_or_connect(snd_pcm_direct_t *dmix)
+{
+	struct shmid_ds buf;
+	int tmpid, err;
+	
+retryget:
+	dmix->shmid = shmget(dmix->ipc_key, sizeof(snd_pcm_direct_share_t),
+			     IPC_CREAT | dmix->ipc_perm);
+	err = -errno;
+	if (dmix->shmid < 0){
+		if (errno == EINVAL)
+		if ((tmpid = shmget(dmix->ipc_key, 0, dmix->ipc_perm)) != -1)
+		if (!shmctl(tmpid, IPC_STAT, &buf))
+		if (!buf.shm_nattch)
+	    	/* no users so destroy the segment */
+		if (!shmctl(tmpid, IPC_RMID, NULL))
+		    goto retryget;
+		return err;
+	}
+	dmix->shmptr = shmat(dmix->shmid, 0, 0);
+	if (dmix->shmptr == (void *) -1) {
+		err = -errno;
+		snd_pcm_direct_shm_discard(dmix);
+		return err;
+	}
+	mlock(dmix->shmptr, sizeof(snd_pcm_direct_share_t));
+	if (shmctl(dmix->shmid, IPC_STAT, &buf) < 0) {
+		err = -errno;
+		snd_pcm_direct_shm_discard(dmix);
+		return err;
+	}
+	if (buf.shm_nattch == 1) {	/* we're the first user, clear the segment */
+		memset(dmix->shmptr, 0, sizeof(snd_pcm_direct_share_t));
+		if (dmix->ipc_gid >= 0) {
+			buf.shm_perm.gid = dmix->ipc_gid;
+			shmctl(dmix->shmid, IPC_SET, &buf);
+		}
+		dmix->shmptr->magic = SND_PCM_DIRECT_MAGIC;
+		return 1;
+	} else {
+		if (dmix->shmptr->magic != SND_PCM_DIRECT_MAGIC) {
+			snd_pcm_direct_shm_discard(dmix);
+			return -EINVAL;
+		}
+	}
+	return 0;
+}
+
+/* discard shared memory */
+/*
+ * Define snd_* functions to be used in server.
+ * Since objects referred in a plugin can be released dynamically, a forked
+ * server should have statically linked functions.
+ * (e.g. Novell bugzilla #105772)
+ */
+static int _snd_pcm_direct_shm_discard(snd_pcm_direct_t *dmix)
+{
+	struct shmid_ds buf;
+	int ret = 0;
+
+	if (dmix->shmid < 0)
+		return -EINVAL;
+	if (dmix->shmptr != (void *) -1 && shmdt(dmix->shmptr) < 0)
+		return -errno;
+	dmix->shmptr = (void *) -1;
+	if (shmctl(dmix->shmid, IPC_STAT, &buf) < 0)
+		return -errno;
+	if (buf.shm_nattch == 0) {	/* we're the last user, destroy the segment */
+		if (shmctl(dmix->shmid, IPC_RMID, NULL) < 0)
+			return -errno;
+		ret = 1;
+	}
+	dmix->shmid = -1;
+	return ret;
+}
+
+/* ... and an exported version */
+int snd_pcm_direct_shm_discard(snd_pcm_direct_t *dmix)
+{
+	return _snd_pcm_direct_shm_discard(dmix);
+}
+
+/*
+ *  server side
+ */
+
+static int get_tmp_name(char *filename, size_t size)
+{
+	struct timeval tv;
+
+	gettimeofday(&tv, NULL);
+	snprintf(filename, size, TMPDIR "/alsa-dmix-%i-%li-%li", (int)getpid(), (long)tv.tv_sec, (long)tv.tv_usec);
+	filename[size-1] = '\0';
+	return 0;
+}
+
+static int make_local_socket(const char *filename, int server, mode_t ipc_perm, int ipc_gid)
+{
+	size_t l = strlen(filename);
+	size_t size = offsetof(struct sockaddr_un, sun_path) + l;
+	struct sockaddr_un *addr = alloca(size);
+	int sock;
+
+	sock = socket(PF_LOCAL, SOCK_STREAM, 0);
+	if (sock < 0) {
+		int result = -errno;
+		SYSERR("socket failed");
+		return result;
+	}
+
+	if (server)
+		unlink(filename);
+	memset(addr, 0, size); /* make valgrind happy */
+	addr->sun_family = AF_LOCAL;
+	memcpy(addr->sun_path, filename, l);
+	
+	if (server) {
+		if (bind(sock, (struct sockaddr *) addr, size) < 0) {
+			int result = -errno;
+			SYSERR("bind failed: %s", filename);
+			close(sock);
+			return result;
+		} else {
+			if (chmod(filename, ipc_perm) < 0) {
+				int result = -errno;
+				SYSERR("chmod failed: %s", filename);
+				close(sock);
+				unlink(filename);
+				return result;
+			}
+			if (chown(filename, -1, ipc_gid) < 0) {
+#if 0 /* it's not fatal */
+				int result = -errno;
+				SYSERR("chown failed: %s", filename);
+				close(sock);
+				unlink(filename);
+				return result;
+#endif
+			}
+		}
+	} else {
+		if (connect(sock, (struct sockaddr *) addr, size) < 0) {
+			int result = -errno;
+			SYSERR("connect failed: %s", filename);
+			close(sock);
+			return result;
+		}
+	}
+	return sock;
+}
+
+#if 0
+#define SERVER_JOB_DEBUG
+#define server_printf(fmt, args...) printf(fmt, ##args)
+#else
+#undef SERVER_JOB_DEBUG
+#define server_printf(fmt, args...) /* nothing */
+#endif
+
+static snd_pcm_direct_t *server_job_dmix;
+
+static void server_cleanup(snd_pcm_direct_t *dmix)
+{
+	close(dmix->server_fd);
+	close(dmix->hw_fd);
+	if (dmix->server_free)
+		dmix->server_free(dmix);
+	unlink(dmix->shmptr->socket_name);
+	_snd_pcm_direct_shm_discard(dmix);
+	snd_pcm_direct_semaphore_discard(dmix);
+}
+
+static void server_job_signal(int sig ATTRIBUTE_UNUSED)
+{
+	snd_pcm_direct_semaphore_down(server_job_dmix, DIRECT_IPC_SEM_CLIENT);
+	server_cleanup(server_job_dmix);
+	server_printf("DIRECT SERVER EXIT - SIGNAL\n");
+	_exit(EXIT_SUCCESS);
+}
+
+/* This is a copy from ../socket.c, provided here only for a server job
+ * (see the comment above)
+ */
+static int _snd_send_fd(int sock, void *data, size_t len, int fd)
+{
+	int ret;
+	size_t cmsg_len = CMSG_LEN(sizeof(int));
+	struct cmsghdr *cmsg = alloca(cmsg_len);
+	int *fds = (int *) CMSG_DATA(cmsg);
+	struct msghdr msghdr;
+	struct iovec vec;
+
+	vec.iov_base = (void *)&data;
+	vec.iov_len = len;
+
+	cmsg->cmsg_len = cmsg_len;
+	cmsg->cmsg_level = SOL_SOCKET;
+	cmsg->cmsg_type = SCM_RIGHTS;
+	*fds = fd;
+
+	msghdr.msg_name = NULL;
+	msghdr.msg_namelen = 0;
+	msghdr.msg_iov = &vec;
+ 	msghdr.msg_iovlen = 1;
+	msghdr.msg_control = cmsg;
+	msghdr.msg_controllen = cmsg_len;
+	msghdr.msg_flags = 0;
+
+	ret = sendmsg(sock, &msghdr, 0 );
+	if (ret < 0)
+		return -errno;
+	return ret;
+}
+
+static void server_job(snd_pcm_direct_t *dmix)
+{
+	int ret, sck, i;
+	int max = 128, current = 0;
+	struct pollfd pfds[max + 1];
+
+	server_job_dmix = dmix;
+	/* don't allow to be killed */
+	signal(SIGHUP, server_job_signal);
+	signal(SIGQUIT, server_job_signal);
+	signal(SIGTERM, server_job_signal);
+	signal(SIGKILL, server_job_signal);
+	/* close all files to free resources */
+	i = sysconf(_SC_OPEN_MAX);
+#ifdef SERVER_JOB_DEBUG
+	while (--i >= 3) {
+#else
+	while (--i >= 0) {
+#endif
+		if (i != dmix->server_fd && i != dmix->hw_fd)
+			close(i);
+	}
+	
+	/* detach from parent */
+	setsid();
+
+	pfds[0].fd = dmix->server_fd;
+	pfds[0].events = POLLIN | POLLERR | POLLHUP;
+
+	server_printf("DIRECT SERVER STARTED\n");
+	while (1) {
+		ret = poll(pfds, current + 1, 500);
+		server_printf("DIRECT SERVER: poll ret = %i, revents[0] = 0x%x, errno = %i\n", ret, pfds[0].revents, errno);
+		if (ret < 0) {
+			if (errno == EINTR)
+				continue;
+			/* some error */
+			break;
+		}
+		if (ret == 0 || (pfds[0].revents & (POLLERR | POLLHUP))) {	/* timeout or error? */
+			struct shmid_ds buf;
+			snd_pcm_direct_semaphore_down(dmix, DIRECT_IPC_SEM_CLIENT);
+			if (shmctl(dmix->shmid, IPC_STAT, &buf) < 0) {
+				_snd_pcm_direct_shm_discard(dmix);
+				snd_pcm_direct_semaphore_up(dmix, DIRECT_IPC_SEM_CLIENT);
+				continue;
+			}
+			server_printf("DIRECT SERVER: nattch = %i\n", (int)buf.shm_nattch);
+			if (buf.shm_nattch == 1)	/* server is the last user, exit */
+				break;
+			snd_pcm_direct_semaphore_up(dmix, DIRECT_IPC_SEM_CLIENT);
+			continue;
+		}
+		if (pfds[0].revents & POLLIN) {
+			ret--;
+			sck = accept(dmix->server_fd, 0, 0);
+			if (sck >= 0) {
+				server_printf("DIRECT SERVER: new connection %i\n", sck);
+				if (current == max) {
+					close(sck);
+				} else {
+					unsigned char buf = 'A';
+					pfds[current+1].fd = sck;
+					pfds[current+1].events = POLLIN | POLLERR | POLLHUP;
+					_snd_send_fd(sck, &buf, 1, dmix->hw_fd);
+					server_printf("DIRECT SERVER: fd sent ok\n");
+					current++;
+				}
+			}
+		}
+		for (i = 0; i < current && ret > 0; i++) {
+			struct pollfd *pfd = &pfds[i+1];
+			unsigned char cmd;
+			server_printf("client %i revents = 0x%x\n", pfd->fd, pfd->revents);
+			if (pfd->revents & (POLLERR | POLLHUP)) {
+				ret--;
+				close(pfd->fd);
+				pfd->fd = -1;
+				continue;
+			}
+			if (!(pfd->revents & POLLIN))
+				continue;
+			ret--;
+			if (read(pfd->fd, &cmd, 1) == 1)
+				cmd = 0 /*process command */;
+		}
+		for (i = 0; i < current; i++) {
+			if (pfds[i+1].fd < 0) {
+				if (i + 1 != max)
+					memcpy(&pfds[i+1], &pfds[i+2], sizeof(struct pollfd) * (max - i - 1));
+				current--;
+			}
+		}
+	}
+	server_cleanup(dmix);
+	server_printf("DIRECT SERVER EXIT\n");
+#ifdef SERVER_JOB_DEBUG
+	close(0); close(1); close(2);
+#endif
+	_exit(EXIT_SUCCESS);
+}
+
+int snd_pcm_direct_server_create(snd_pcm_direct_t *dmix)
+{
+	int ret;
+
+	dmix->server_fd = -1;
+
+	ret = get_tmp_name(dmix->shmptr->socket_name, sizeof(dmix->shmptr->socket_name));
+	if (ret < 0)
+		return ret;
+	
+	ret = make_local_socket(dmix->shmptr->socket_name, 1, dmix->ipc_perm, dmix->ipc_gid);
+	if (ret < 0)
+		return ret;
+	dmix->server_fd = ret;
+
+	ret = listen(dmix->server_fd, 4);
+	if (ret < 0) {
+		close(dmix->server_fd);
+		return ret;
+	}
+	
+	ret = fork();
+	if (ret < 0) {
+		close(dmix->server_fd);
+		return ret;
+	} else if (ret == 0) {
+		ret = fork();
+		if (ret == 0)
+			server_job(dmix);
+		_exit(EXIT_SUCCESS);
+	} else {
+		waitpid(ret, NULL, 0);
+	}
+	dmix->server_pid = ret;
+	dmix->server = 1;
+	return 0;
+}
+
+int snd_pcm_direct_server_discard(snd_pcm_direct_t *dmix)
+{
+	if (dmix->server) {
+		//kill(dmix->server_pid, SIGTERM);
+		//waitpid(dmix->server_pid, NULL, 0);
+		dmix->server_pid = (pid_t)-1;
+	}
+	if (dmix->server_fd > 0) {
+		close(dmix->server_fd);
+		dmix->server_fd = -1;
+	}
+	dmix->server = 0;
+	return 0;
+}
+
+/*
+ *  client side
+ */
+
+int snd_pcm_direct_client_connect(snd_pcm_direct_t *dmix)
+{
+	int ret;
+	unsigned char buf;
+
+	ret = make_local_socket(dmix->shmptr->socket_name, 0, -1, -1);
+	if (ret < 0)
+		return ret;
+	dmix->comm_fd = ret;
+
+	ret = snd_receive_fd(dmix->comm_fd, &buf, 1, &dmix->hw_fd);
+	if (ret < 1 || buf != 'A') {
+		close(dmix->comm_fd);
+		dmix->comm_fd = -1;
+		return ret;
+	}
+
+	dmix->client = 1;
+	return 0;
+}
+
+int snd_pcm_direct_client_discard(snd_pcm_direct_t *dmix)
+{
+	if (dmix->client) {
+		close(dmix->comm_fd);
+		dmix->comm_fd = -1;
+	}
+	return 0;
+}
+
+/*
+ *  plugin helpers
+ */
+
+int snd_pcm_direct_nonblock(snd_pcm_t *pcm ATTRIBUTE_UNUSED, int nonblock ATTRIBUTE_UNUSED)
+{
+	/* value is cached for us in pcm->mode (SND_PCM_NONBLOCK flag) */
+	return 0;
+}
+
+int snd_pcm_direct_async(snd_pcm_t *pcm, int sig, pid_t pid)
+{
+	snd_pcm_direct_t *dmix = pcm->private_data;
+	return snd_timer_async(dmix->timer, sig, pid);
+}
+
+/* empty the timer read queue */
+void snd_pcm_direct_clear_timer_queue(snd_pcm_direct_t *dmix)
+{
+	if (dmix->timer_need_poll) {
+		while (poll(&dmix->timer_fd, 1, 0) > 0) {
+			/* we don't need the value */
+			if (dmix->tread) {
+				snd_timer_tread_t rbuf[4];
+				snd_timer_read(dmix->timer, rbuf, sizeof(rbuf));
+			} else {
+				snd_timer_read_t rbuf;
+				snd_timer_read(dmix->timer, &rbuf, sizeof(rbuf));
+			}
+		}
+	} else {
+		if (dmix->tread) {
+			snd_timer_tread_t rbuf[4];
+			int len;
+			while ((len = snd_timer_read(dmix->timer, rbuf,
+						     sizeof(rbuf))) > 0 &&
+			       len != sizeof(rbuf[0]))
+				;
+		} else {
+			snd_timer_read_t rbuf;
+			while (snd_timer_read(dmix->timer, &rbuf, sizeof(rbuf)) > 0)
+				;
+		}
+	}
+}
+
+int snd_pcm_direct_timer_stop(snd_pcm_direct_t *dmix)
+{
+	snd_timer_stop(dmix->timer);
+	return 0;
+}
+
+int snd_pcm_direct_poll_revents(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int nfds, unsigned short *revents)
+{
+	snd_pcm_direct_t *dmix = pcm->private_data;
+	unsigned short events;
+	int empty = 0;
+
+	assert(pfds && nfds == 1 && revents);
+	events = pfds[0].revents;
+	if (events & POLLIN) {
+		snd_pcm_uframes_t avail;
+		snd_pcm_avail_update(pcm);
+		if (pcm->stream == SND_PCM_STREAM_PLAYBACK) {
+			events |= POLLOUT;
+			events &= ~POLLIN;
+			avail = snd_pcm_mmap_playback_avail(pcm);
+		} else {
+			avail = snd_pcm_mmap_capture_avail(pcm);
+		}
+		empty = avail < pcm->avail_min;
+	}
+	switch (snd_pcm_state(dmix->spcm)) {
+	case SND_PCM_STATE_XRUN:
+	case SND_PCM_STATE_SUSPENDED:
+	case SND_PCM_STATE_SETUP:
+		events |= POLLERR;
+		break;
+	default:
+		if (empty) {
+			snd_pcm_direct_clear_timer_queue(dmix);
+			events &= ~(POLLOUT|POLLIN);
+			/* additional check */
+			switch (snd_pcm_state(pcm)) {
+			case SND_PCM_STATE_XRUN:
+			case SND_PCM_STATE_SUSPENDED:
+			case SND_PCM_STATE_SETUP:
+				events |= POLLERR;
+				break;
+			default:
+				break;
+			}
+		}
+		break;
+	}
+	*revents = events;
+	return 0;
+}
+
+int snd_pcm_direct_info(snd_pcm_t *pcm, snd_pcm_info_t * info)
+{
+	snd_pcm_direct_t *dmix = pcm->private_data;
+
+	if (dmix->spcm && !dmix->shmptr->use_server)
+		return snd_pcm_info(dmix->spcm, info);
+
+	memset(info, 0, sizeof(*info));
+	info->stream = pcm->stream;
+	info->card = -1;
+	/* FIXME: fill this with something more useful: we know the hardware name */
+	if (pcm->name) {
+		strncpy((char *)info->id, pcm->name, sizeof(info->id));
+		strncpy((char *)info->name, pcm->name, sizeof(info->name));
+		strncpy((char *)info->subname, pcm->name, sizeof(info->subname));
+	}
+	info->subdevices_count = 1;
+	return 0;
+}
+
+static inline snd_mask_t *hw_param_mask(snd_pcm_hw_params_t *params,
+					snd_pcm_hw_param_t var)
+{
+	return &params->masks[var - SND_PCM_HW_PARAM_FIRST_MASK];
+}
+
+static inline snd_interval_t *hw_param_interval(snd_pcm_hw_params_t *params,
+						snd_pcm_hw_param_t var)
+{
+	return &params->intervals[var - SND_PCM_HW_PARAM_FIRST_INTERVAL];
+}
+
+static int hw_param_interval_refine_one(snd_pcm_hw_params_t *params,
+					snd_pcm_hw_param_t var,
+					snd_interval_t *src)
+{
+	snd_interval_t *i;
+
+	if (!(params->rmask & (1<<var)))	/* nothing to do? */
+		return 0;
+	i = hw_param_interval(params, var);
+	if (snd_interval_empty(i)) {
+		SNDERR("dshare interval %i empty?", (int)var);
+		return -EINVAL;
+	}
+	if (snd_interval_refine(i, src))
+		params->cmask |= 1<<var;
+	return 0;
+}
+
+static int hw_param_interval_refine_minmax(snd_pcm_hw_params_t *params,
+					   snd_pcm_hw_param_t var,
+					   unsigned int imin,
+					   unsigned int imax)
+{
+	snd_interval_t t;
+
+	memset(&t, 0, sizeof(t));
+	snd_interval_set_minmax(&t, imin, imax);
+	t.integer = 1;
+	return hw_param_interval_refine_one(params, var, &t);
+}
+
+#undef REFINE_DEBUG
+
+int snd_pcm_direct_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	snd_pcm_direct_t *dshare = pcm->private_data;
+	static const snd_mask_t access = { .bits = { 
+					(1<<SNDRV_PCM_ACCESS_MMAP_INTERLEAVED) |
+					(1<<SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED) |
+					(1<<SNDRV_PCM_ACCESS_RW_INTERLEAVED) |
+					(1<<SNDRV_PCM_ACCESS_RW_NONINTERLEAVED),
+					0, 0, 0 } };
+	int err;
+
+#ifdef REFINE_DEBUG
+	snd_output_t *log;
+	snd_output_stdio_attach(&log, stderr, 0);
+	snd_output_puts(log, "DMIX REFINE (begin):\n");
+	snd_pcm_hw_params_dump(params, log);
+#endif
+	if (params->rmask & (1<<SND_PCM_HW_PARAM_ACCESS)) {
+		if (snd_mask_empty(hw_param_mask(params, SND_PCM_HW_PARAM_ACCESS))) {
+			SNDERR("dshare access mask empty?");
+			return -EINVAL;
+		}
+		if (snd_mask_refine(hw_param_mask(params, SND_PCM_HW_PARAM_ACCESS), &access))
+			params->cmask |= 1<<SND_PCM_HW_PARAM_ACCESS;
+	}
+	if (params->rmask & (1<<SND_PCM_HW_PARAM_FORMAT)) {
+		if (snd_mask_empty(hw_param_mask(params, SND_PCM_HW_PARAM_FORMAT))) {
+			SNDERR("dshare format mask empty?");
+			return -EINVAL;
+		}
+		if (snd_mask_refine_set(hw_param_mask(params, SND_PCM_HW_PARAM_FORMAT),
+					dshare->shmptr->hw.format))
+			params->cmask |= 1<<SND_PCM_HW_PARAM_FORMAT;
+	}
+	//snd_mask_none(hw_param_mask(params, SND_PCM_HW_PARAM_SUBFORMAT));
+	if (params->rmask & (1<<SND_PCM_HW_PARAM_CHANNELS)) {
+		if (snd_interval_empty(hw_param_interval(params, SND_PCM_HW_PARAM_CHANNELS))) {
+			SNDERR("dshare channels mask empty?");
+			return -EINVAL;
+		}
+		err = snd_interval_refine_set(hw_param_interval(params, SND_PCM_HW_PARAM_CHANNELS), dshare->channels);
+		if (err < 0)
+			return err;
+	}
+	err = hw_param_interval_refine_one(params, SND_PCM_HW_PARAM_RATE,
+					   &dshare->shmptr->hw.rate);
+	if (err < 0)
+		return err;
+	err = hw_param_interval_refine_one(params, SND_PCM_HW_PARAM_PERIOD_SIZE,
+					   &dshare->shmptr->hw.period_size);
+	if (err < 0)
+		return err;
+	err = hw_param_interval_refine_one(params, SND_PCM_HW_PARAM_PERIOD_TIME,
+					   &dshare->shmptr->hw.period_time);
+	if (err < 0)
+		return err;
+	if (dshare->max_periods < 0) {
+		err = hw_param_interval_refine_one(params, SND_PCM_HW_PARAM_BUFFER_SIZE,
+						   &dshare->shmptr->hw.buffer_size);
+		if (err < 0)
+			return err;
+		err = hw_param_interval_refine_one(params, SND_PCM_HW_PARAM_BUFFER_TIME,
+						   &dshare->shmptr->hw.buffer_time);
+		if (err < 0)
+			return err;
+	} else if (params->rmask & ((1<<SND_PCM_HW_PARAM_PERIODS)|
+				    (1<<SND_PCM_HW_PARAM_BUFFER_BYTES)|
+				    (1<<SND_PCM_HW_PARAM_BUFFER_SIZE)|
+				    (1<<SND_PCM_HW_PARAM_BUFFER_TIME))) {
+		int changed;
+		unsigned int max_periods = dshare->max_periods;
+		if (max_periods < 2)
+			max_periods = dshare->slave_buffer_size / dshare->slave_period_size;
+		do {
+			changed = 0;
+			err = hw_param_interval_refine_minmax(params, SND_PCM_HW_PARAM_PERIODS,
+							      2, max_periods);
+			if (err < 0)
+				return err;
+			changed |= err;
+			err = snd_pcm_hw_refine_soft(pcm, params);
+			if (err < 0)
+				return err;
+			changed |= err;
+		} while (changed);
+	}
+	params->info = dshare->shmptr->s.info;
+#ifdef REFINE_DEBUG
+	snd_output_puts(log, "DMIX REFINE (end):\n");
+	snd_pcm_hw_params_dump(params, log);
+	snd_output_close(log);
+#endif
+	return 0;
+}
+
+int snd_pcm_direct_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t * params)
+{
+	snd_pcm_direct_t *dmix = pcm->private_data;
+
+	params->info = dmix->shmptr->s.info;
+	params->rate_num = dmix->shmptr->s.rate;
+	params->rate_den = 1;
+	params->fifo_size = 0;
+	params->msbits = dmix->shmptr->s.msbits;
+	return 0;
+}
+
+int snd_pcm_direct_hw_free(snd_pcm_t *pcm ATTRIBUTE_UNUSED)
+{
+	/* values are cached in the pcm structure */
+	return 0;
+}
+
+int snd_pcm_direct_sw_params(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_sw_params_t * params ATTRIBUTE_UNUSED)
+{
+	/* values are cached in the pcm structure */
+	return 0;
+}
+
+int snd_pcm_direct_channel_info(snd_pcm_t *pcm, snd_pcm_channel_info_t * info)
+{
+        return snd_pcm_channel_info_shm(pcm, info, -1);
+}
+
+int snd_pcm_direct_mmap(snd_pcm_t *pcm ATTRIBUTE_UNUSED)
+{
+	return 0;
+}
+        
+int snd_pcm_direct_munmap(snd_pcm_t *pcm ATTRIBUTE_UNUSED)
+{
+	return 0;
+}
+
+int snd_pcm_direct_resume(snd_pcm_t *pcm)
+{
+	snd_pcm_direct_t *dmix = pcm->private_data;
+	int err;
+	
+	snd_pcm_direct_semaphore_down(dmix, DIRECT_IPC_SEM_CLIENT);
+	err = snd_pcm_resume(dmix->spcm);
+	if (err == -ENOSYS) {
+		/* FIXME: error handling? */
+		snd_pcm_prepare(dmix->spcm);
+		snd_pcm_start(dmix->spcm);
+		err = 0;
+	}
+	snd_pcm_direct_semaphore_up(dmix, DIRECT_IPC_SEM_CLIENT);
+	return err;
+}
+
+#define COPY_SLAVE(field) (dmix->shmptr->s.field = spcm->field)
+
+/* copy the slave setting */
+static void save_slave_setting(snd_pcm_direct_t *dmix, snd_pcm_t *spcm)
+{
+	spcm->info &= ~SND_PCM_INFO_PAUSE;
+
+	COPY_SLAVE(access);
+	COPY_SLAVE(format);
+	COPY_SLAVE(subformat);
+	COPY_SLAVE(channels);
+	COPY_SLAVE(rate);
+	COPY_SLAVE(period_size);
+	COPY_SLAVE(period_time);
+	COPY_SLAVE(periods);
+	COPY_SLAVE(tstamp_mode);
+	COPY_SLAVE(period_step);
+	COPY_SLAVE(avail_min);
+	COPY_SLAVE(start_threshold);
+	COPY_SLAVE(stop_threshold);
+	COPY_SLAVE(silence_threshold);
+	COPY_SLAVE(silence_size);
+	COPY_SLAVE(boundary);
+	COPY_SLAVE(info);
+	COPY_SLAVE(msbits);
+	COPY_SLAVE(rate_num);
+	COPY_SLAVE(rate_den);
+	COPY_SLAVE(hw_flags);
+	COPY_SLAVE(fifo_size);
+	COPY_SLAVE(buffer_size);
+	COPY_SLAVE(buffer_time);
+	COPY_SLAVE(sample_bits);
+	COPY_SLAVE(frame_bits);
+}
+
+#undef COPY_SLAVE
+
+/*
+ * this function initializes hardware and starts playback operation with
+ * no stop threshold (it operates all time without xrun checking)
+ * also, the driver silences the unused ring buffer areas for us
+ */
+int snd_pcm_direct_initialize_slave(snd_pcm_direct_t *dmix, snd_pcm_t *spcm, struct slave_params *params)
+{
+	snd_pcm_hw_params_t *hw_params;
+	snd_pcm_sw_params_t *sw_params;
+	int ret, buffer_is_not_initialized;
+	snd_pcm_uframes_t boundary;
+	struct pollfd fd;
+	int loops = 10;
+
+	snd_pcm_hw_params_alloca(&hw_params);
+	snd_pcm_sw_params_alloca(&sw_params);
+
+      __again:
+      	if (loops-- <= 0) {
+      		SNDERR("unable to find a valid configuration for slave");
+      		return -EINVAL;
+      	}
+	ret = snd_pcm_hw_params_any(spcm, hw_params);
+	if (ret < 0) {
+		SNDERR("snd_pcm_hw_params_any failed");
+		return ret;
+	}
+	ret = snd_pcm_hw_params_set_access(spcm, hw_params, SND_PCM_ACCESS_MMAP_INTERLEAVED);
+	if (ret < 0) {
+		ret = snd_pcm_hw_params_set_access(spcm, hw_params, SND_PCM_ACCESS_MMAP_NONINTERLEAVED);
+		if (ret < 0) {
+			SNDERR("slave plugin does not support mmap interleaved or mmap noninterleaved access");
+			return ret;
+		}
+	}
+	if (params->format == SND_PCM_FORMAT_UNKNOWN)
+		ret = -EINVAL;
+	else
+		ret = snd_pcm_hw_params_set_format(spcm, hw_params,
+						   params->format);
+	if (ret < 0) {
+		static const snd_pcm_format_t dmix_formats[] = {
+			SND_PCM_FORMAT_S32,
+			SND_PCM_FORMAT_S32 ^ SND_PCM_FORMAT_S32_LE ^ SND_PCM_FORMAT_S32_BE,
+			SND_PCM_FORMAT_S16,
+			SND_PCM_FORMAT_S16 ^ SND_PCM_FORMAT_S16_LE ^ SND_PCM_FORMAT_S16_BE,
+			SND_PCM_FORMAT_S24_LE,
+			SND_PCM_FORMAT_S24_3LE,
+			SND_PCM_FORMAT_U8,
+		};
+		snd_pcm_format_t format;
+		unsigned int i;
+
+		for (i = 0; i < sizeof dmix_formats / sizeof dmix_formats[0]; ++i) {
+			format = dmix_formats[i];
+			ret = snd_pcm_hw_params_set_format(spcm, hw_params, format);
+			if (ret >= 0)
+				break;
+		}
+		if (ret < 0 && dmix->type != SND_PCM_TYPE_DMIX) {
+			/* TODO: try to choose a good format */
+			ret = INTERNAL(snd_pcm_hw_params_set_format_first)(spcm, hw_params, &format);
+		}
+		if (ret < 0) {
+			SNDERR("requested or auto-format is not available");
+			return ret;
+		}
+		params->format = format;
+	}
+	ret = INTERNAL(snd_pcm_hw_params_set_channels_near)(spcm, hw_params, (unsigned int *)&params->channels);
+	if (ret < 0) {
+		SNDERR("requested count of channels is not available");
+		return ret;
+	}
+	ret = INTERNAL(snd_pcm_hw_params_set_rate_near)(spcm, hw_params, (unsigned int *)&params->rate, 0);
+	if (ret < 0) {
+		SNDERR("requested rate is not available");
+		return ret;
+	}
+
+	buffer_is_not_initialized = 0;
+	if (params->buffer_time > 0) {
+		ret = INTERNAL(snd_pcm_hw_params_set_buffer_time_near)(spcm, hw_params, (unsigned int *)&params->buffer_time, 0);
+		if (ret < 0) {
+			SNDERR("unable to set buffer time");
+			return ret;
+		}
+	} else if (params->buffer_size > 0) {
+		ret = INTERNAL(snd_pcm_hw_params_set_buffer_size_near)(spcm, hw_params, (snd_pcm_uframes_t *)&params->buffer_size);
+		if (ret < 0) {
+			SNDERR("unable to set buffer size");
+			return ret;
+		}
+	} else {
+		buffer_is_not_initialized = 1;
+	}
+
+	if (params->period_time > 0) {
+		ret = INTERNAL(snd_pcm_hw_params_set_period_time_near)(spcm, hw_params, (unsigned int *)&params->period_time, 0);
+		if (ret < 0) {
+			SNDERR("unable to set period_time");
+			return ret;
+		}
+	} else if (params->period_size > 0) {
+		ret = INTERNAL(snd_pcm_hw_params_set_period_size_near)(spcm, hw_params, (snd_pcm_uframes_t *)&params->period_size, 0);
+		if (ret < 0) {
+			SNDERR("unable to set period_size");
+			return ret;
+		}
+	}		
+	
+	if (buffer_is_not_initialized && params->periods > 0) {
+		unsigned int periods = params->periods;
+		ret = INTERNAL(snd_pcm_hw_params_set_periods_near)(spcm, hw_params, &params->periods, 0);
+		if (ret < 0) {
+			SNDERR("unable to set requested periods");
+			return ret;
+		}
+		if (params->periods == 1) {
+			params->periods = periods;
+			if (params->period_time > 0) {
+				params->period_time /= 2;
+				goto __again;
+			} else if (params->period_size > 0) {
+				params->period_size /= 2;
+				goto __again;
+			}
+			SNDERR("unable to use stream with periods == 1");
+			return ret;
+		}
+	}
+	
+	ret = snd_pcm_hw_params(spcm, hw_params);
+	if (ret < 0) {
+		SNDERR("unable to install hw params");
+		return ret;
+	}
+
+	/* store some hw_params values to shared info */
+	dmix->shmptr->hw.format = snd_mask_value(hw_param_mask(hw_params, SND_PCM_HW_PARAM_FORMAT));
+	dmix->shmptr->hw.rate = *hw_param_interval(hw_params, SND_PCM_HW_PARAM_RATE);
+	dmix->shmptr->hw.buffer_size = *hw_param_interval(hw_params, SND_PCM_HW_PARAM_BUFFER_SIZE);
+	dmix->shmptr->hw.buffer_time = *hw_param_interval(hw_params, SND_PCM_HW_PARAM_BUFFER_TIME);
+	dmix->shmptr->hw.period_size = *hw_param_interval(hw_params, SND_PCM_HW_PARAM_PERIOD_SIZE);
+	dmix->shmptr->hw.period_time = *hw_param_interval(hw_params, SND_PCM_HW_PARAM_PERIOD_TIME);
+	dmix->shmptr->hw.periods = *hw_param_interval(hw_params, SND_PCM_HW_PARAM_PERIODS);
+
+
+	ret = snd_pcm_sw_params_current(spcm, sw_params);
+	if (ret < 0) {
+		SNDERR("unable to get current sw_params");
+		return ret;
+	}
+
+	ret = snd_pcm_sw_params_get_boundary(sw_params, &boundary);
+	if (ret < 0) {
+		SNDERR("unable to get boundary");
+		return ret;
+	}
+	ret = snd_pcm_sw_params_set_stop_threshold(spcm, sw_params, boundary);
+	if (ret < 0) {
+		SNDERR("unable to set stop threshold");
+		return ret;
+	}
+
+	/* set timestamp mode to MMAP
+	 * the slave timestamp is copied appropriately in dsnoop/dmix/dshare
+	 * based on the tstamp_mode of each client
+	 */
+	ret = snd_pcm_sw_params_set_tstamp_mode(spcm, sw_params,
+						SND_PCM_TSTAMP_ENABLE);
+	if (ret < 0) {
+		SNDERR("unable to tstamp mode MMAP");
+		return ret;
+	}
+
+	if (dmix->type != SND_PCM_TYPE_DMIX)
+		goto __skip_silencing;
+
+	ret = snd_pcm_sw_params_set_silence_threshold(spcm, sw_params, 0);
+	if (ret < 0) {
+		SNDERR("unable to set silence threshold");
+		return ret;
+	}
+	ret = snd_pcm_sw_params_set_silence_size(spcm, sw_params, boundary);
+	if (ret < 0) {
+		SNDERR("unable to set silence threshold (please upgrade to 0.9.0rc8+ driver)");
+		return ret;
+	}
+
+      __skip_silencing:
+
+	ret = snd_pcm_sw_params(spcm, sw_params);
+	if (ret < 0) {
+		SNDERR("unable to install sw params (please upgrade to 0.9.0rc8+ driver)");
+		return ret;
+	}
+
+	if (dmix->type == SND_PCM_TYPE_DSHARE) {
+		const snd_pcm_channel_area_t *dst_areas;
+		dst_areas = snd_pcm_mmap_areas(spcm);
+		snd_pcm_areas_silence(dst_areas, 0, spcm->channels, spcm->buffer_size, spcm->format);
+	}
+	
+	ret = snd_pcm_start(spcm);
+	if (ret < 0) {
+		SNDERR("unable to start PCM stream");
+		return ret;
+	}
+
+	if (snd_pcm_poll_descriptors_count(spcm) != 1) {
+		SNDERR("unable to use hardware pcm with fd more than one!!!");
+		return ret;
+	}
+	snd_pcm_poll_descriptors(spcm, &fd, 1);
+	dmix->hw_fd = fd.fd;
+	
+	save_slave_setting(dmix, spcm);
+
+	/* Currently, we assume that each dmix client has the same
+	 * hw_params setting.
+	 * If the arbitrary hw_parmas is supported in future,
+	 * boundary has to be taken from the slave config but
+	 * recalculated for the native boundary size (for 32bit
+	 * emulation on 64bit arch).
+	 */
+	dmix->slave_buffer_size = spcm->buffer_size;
+	dmix->slave_period_size = spcm->period_size;
+	dmix->slave_boundary = spcm->boundary;
+
+	spcm->donot_close = 1;
+
+	{
+		int ver = 0;
+		ioctl(spcm->poll_fd, SNDRV_PCM_IOCTL_PVERSION, &ver);
+		if (ver < SNDRV_PROTOCOL_VERSION(2, 0, 8))
+			dmix->shmptr->use_server = 1;
+	}
+
+	return 0;
+}
+
+/*
+ * the trick is used here; we cannot use effectively the hardware handle because
+ * we cannot drive multiple accesses to appl_ptr; so we use slave timer of given
+ * PCM hardware handle; it's not this easy and cheap?
+ */
+int snd_pcm_direct_initialize_poll_fd(snd_pcm_direct_t *dmix)
+{
+	int ret;
+	snd_pcm_info_t *info;
+	char name[128];
+	int capture = dmix->type == SND_PCM_TYPE_DSNOOP ? 1 : 0;
+
+	dmix->tread = 1;
+	dmix->timer_need_poll = 0;
+	snd_pcm_info_alloca(&info);
+	ret = snd_pcm_info(dmix->spcm, info);
+	if (ret < 0) {
+		SNDERR("unable to info for slave pcm");
+		return ret;
+	}
+	sprintf(name, "hw:CLASS=%i,SCLASS=0,CARD=%i,DEV=%i,SUBDEV=%i",
+				(int)SND_TIMER_CLASS_PCM, 
+				snd_pcm_info_get_card(info),
+				snd_pcm_info_get_device(info),
+				snd_pcm_info_get_subdevice(info) * 2 + capture);
+	ret = snd_timer_open(&dmix->timer, name, SND_TIMER_OPEN_NONBLOCK | SND_TIMER_OPEN_TREAD);
+	if (ret < 0) {
+		dmix->tread = 0;
+		ret = snd_timer_open(&dmix->timer, name, SND_TIMER_OPEN_NONBLOCK);
+		if (ret < 0) {
+			SNDERR("unable to open timer '%s'", name);
+			return ret;
+		}
+	}
+
+	if (snd_timer_poll_descriptors_count(dmix->timer) != 1) {
+		SNDERR("unable to use timer '%s' with more than one fd!", name);
+		return ret;
+	}
+	snd_timer_poll_descriptors(dmix->timer, &dmix->timer_fd, 1);
+	dmix->poll_fd = dmix->timer_fd.fd;
+
+	dmix->timer_events = (1<<SND_TIMER_EVENT_MSUSPEND) |
+			     (1<<SND_TIMER_EVENT_MRESUME) |
+			     (1<<SND_TIMER_EVENT_STOP);
+
+	/*
+	 * Some hacks for older kernel drivers
+	 */
+	{
+		int ver = 0;
+		ioctl(dmix->poll_fd, SNDRV_TIMER_IOCTL_PVERSION, &ver);
+		/* In older versions, check via poll before read() is needed
+		 * because of the confliction between TIMER_START and
+		 * FIONBIO ioctls.
+		 */
+		if (ver < SNDRV_PROTOCOL_VERSION(2, 0, 4))
+			dmix->timer_need_poll = 1;
+		/*
+		 * In older versions, timer uses pause events instead
+		 * suspend/resume events.
+		 */
+		if (ver < SNDRV_PROTOCOL_VERSION(2, 0, 5)) {
+			dmix->timer_events &= ~((1<<SND_TIMER_EVENT_MSUSPEND) |
+						(1<<SND_TIMER_EVENT_MRESUME));
+			dmix->timer_events |= (1<<SND_TIMER_EVENT_MPAUSE) |
+					      (1<<SND_TIMER_EVENT_MCONTINUE);
+		}
+		/* In older versions, use SND_TIMER_EVENT_START too.
+		 */
+		if (ver < SNDRV_PROTOCOL_VERSION(2, 0, 6))
+			dmix->timer_events |= 1<<SND_TIMER_EVENT_START;
+	}
+	return 0;
+}
+
+static snd_pcm_uframes_t recalc_boundary_size(unsigned long long bsize, snd_pcm_uframes_t buffer_size)
+{
+	if (bsize > LONG_MAX) {
+		bsize = buffer_size;
+		while (bsize * 2 <= LONG_MAX - buffer_size)
+			bsize *= 2;
+	}
+	return (snd_pcm_uframes_t)bsize;
+}
+
+#define COPY_SLAVE(field) (spcm->field = dmix->shmptr->s.field)
+
+/* copy the slave setting */
+static void copy_slave_setting(snd_pcm_direct_t *dmix, snd_pcm_t *spcm)
+{
+	COPY_SLAVE(access);
+	COPY_SLAVE(format);
+	COPY_SLAVE(subformat);
+	COPY_SLAVE(channels);
+	COPY_SLAVE(rate);
+	COPY_SLAVE(period_size);
+	COPY_SLAVE(period_time);
+	COPY_SLAVE(periods);
+	COPY_SLAVE(tstamp_mode);
+	COPY_SLAVE(period_step);
+	COPY_SLAVE(avail_min);
+	COPY_SLAVE(start_threshold);
+	COPY_SLAVE(stop_threshold);
+	COPY_SLAVE(silence_threshold);
+	COPY_SLAVE(silence_size);
+	COPY_SLAVE(boundary);
+	COPY_SLAVE(info);
+	COPY_SLAVE(msbits);
+	COPY_SLAVE(rate_num);
+	COPY_SLAVE(rate_den);
+	COPY_SLAVE(hw_flags);
+	COPY_SLAVE(fifo_size);
+	COPY_SLAVE(buffer_size);
+	COPY_SLAVE(buffer_time);
+	COPY_SLAVE(sample_bits);
+	COPY_SLAVE(frame_bits);
+
+	spcm->info &= ~SND_PCM_INFO_PAUSE;
+	spcm->boundary = recalc_boundary_size(dmix->shmptr->s.boundary, spcm->buffer_size);
+}
+
+#undef COPY_SLAVE
+
+
+/*
+ * open a slave PCM as secondary client (dup'ed fd)
+ */
+int snd_pcm_direct_open_secondary_client(snd_pcm_t **spcmp, snd_pcm_direct_t *dmix, const char *client_name)
+{
+	int ret;
+	snd_pcm_t *spcm;
+
+	ret = snd_pcm_hw_open_fd(spcmp, client_name, dmix->hw_fd, 0, 0);
+	if (ret < 0) {
+		SNDERR("unable to open hardware");
+		return ret;
+	}
+		
+	spcm = *spcmp;
+	spcm->donot_close = 1;
+	spcm->setup = 1;
+
+	copy_slave_setting(dmix, spcm);
+
+	/* Use the slave setting as SPCM, so far */
+	dmix->slave_buffer_size = spcm->buffer_size;
+	dmix->slave_period_size = dmix->shmptr->s.period_size;
+	dmix->slave_boundary = spcm->boundary;
+
+	ret = snd_pcm_mmap(spcm);
+	if (ret < 0) {
+		SNDERR("unable to mmap channels");
+		return ret;
+	}
+	return 0;
+}
+
+/*
+ * open a slave PCM as secondary client (dup'ed fd)
+ */
+int snd_pcm_direct_initialize_secondary_slave(snd_pcm_direct_t *dmix,
+					      snd_pcm_t *spcm,
+					      struct slave_params *params ATTRIBUTE_UNUSED)
+{
+	int ret;
+
+	spcm->donot_close = 1;
+	spcm->setup = 1;
+
+	copy_slave_setting(dmix, spcm);
+
+	/* Use the slave setting as SPCM, so far */
+	dmix->slave_buffer_size = spcm->buffer_size;
+	dmix->slave_period_size = dmix->shmptr->s.period_size;
+	dmix->slave_boundary = spcm->boundary;
+
+	ret = snd_pcm_mmap(spcm);
+	if (ret < 0) {
+		SNDERR("unable to mmap channels");
+		return ret;
+	}
+	return 0;
+}
+
+int snd_pcm_direct_set_timer_params(snd_pcm_direct_t *dmix)
+{
+	snd_timer_params_t *params;
+	unsigned int filter;
+	int ret;
+
+	snd_timer_params_alloca(&params);
+	snd_timer_params_set_auto_start(params, 1);
+	if (dmix->type != SND_PCM_TYPE_DSNOOP)
+		snd_timer_params_set_early_event(params, 1);
+	snd_timer_params_set_ticks(params, 1);
+	if (dmix->tread) {
+		filter = (1<<SND_TIMER_EVENT_TICK) |
+			 dmix->timer_events;
+		snd_timer_params_set_filter(params, filter);
+	}
+	ret = snd_timer_params(dmix->timer, params);
+	if (ret < 0) {
+		SNDERR("unable to set timer parameters");
+                return ret;
+	}
+	return 0;
+}
+
+/*
+ *  ring buffer operation
+ */
+int snd_pcm_direct_check_interleave(snd_pcm_direct_t *dmix, snd_pcm_t *pcm)
+{
+	unsigned int chn, channels;
+	int bits, interleaved = 1;
+	const snd_pcm_channel_area_t *dst_areas;
+	const snd_pcm_channel_area_t *src_areas;
+
+	bits = snd_pcm_format_physical_width(pcm->format);
+	if ((bits % 8) != 0)
+		interleaved = 0;
+	channels = dmix->channels;
+	dst_areas = snd_pcm_mmap_areas(dmix->spcm);
+	src_areas = snd_pcm_mmap_areas(pcm);
+	for (chn = 1; chn < channels; chn++) {
+		if (dst_areas[chn-1].addr != dst_areas[chn].addr) {
+			interleaved = 0;
+			break;
+		}
+		if (src_areas[chn-1].addr != src_areas[chn].addr) {
+			interleaved = 0;
+			break;
+		}
+	}
+	for (chn = 0; chn < channels; chn++) {
+		if (dmix->bindings && dmix->bindings[chn] != chn) {
+			interleaved = 0;
+			break;
+		}
+		if (dst_areas[chn].first != chn * bits ||
+		    dst_areas[chn].step != channels * bits) {
+			interleaved = 0;
+			break;
+		}
+		if (src_areas[chn].first != chn * bits ||
+		    src_areas[chn].step != channels * bits) {
+			interleaved = 0;
+			break;
+		}
+	}
+	return dmix->interleaved = interleaved;
+}
+
+/*
+ * parse the channel map
+ * id == client channel
+ * value == slave's channel
+ */
+int snd_pcm_direct_parse_bindings(snd_pcm_direct_t *dmix,
+				  struct slave_params *params,
+				  snd_config_t *cfg)
+{
+	snd_config_iterator_t i, next;
+	unsigned int chn, chn1, count = 0;
+	unsigned int *bindings;
+	int err;
+
+	dmix->channels = UINT_MAX;
+	if (cfg == NULL)
+		return 0;
+	if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
+		SNDERR("invalid type for bindings");
+		return -EINVAL;
+	}
+	snd_config_for_each(i, next, cfg) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		long cchannel;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		err = safe_strtol(id, &cchannel);
+		if (err < 0 || cchannel < 0) {
+			SNDERR("invalid client channel in binding: %s\n", id);
+			return -EINVAL;
+		}
+		if ((unsigned)cchannel >= count)
+			count = cchannel + 1;
+	}
+	if (count == 0)
+		return 0;
+	if (count > 1024) {
+		SNDERR("client channel out of range");
+		return -EINVAL;
+	}
+	bindings = malloc(count * sizeof(unsigned int));
+	if (bindings == NULL)
+		return -ENOMEM;
+	for (chn = 0; chn < count; chn++)
+		bindings[chn] = UINT_MAX;		/* don't route */
+	snd_config_for_each(i, next, cfg) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		long cchannel, schannel;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		safe_strtol(id, &cchannel);
+		if (snd_config_get_integer(n, &schannel) < 0) {
+			SNDERR("unable to get slave channel (should be integer type) in binding: %s\n", id);
+			free(bindings);
+			return -EINVAL;
+		}
+		if (schannel < 0 || schannel >= params->channels) {
+			SNDERR("invalid slave channel number %ld in binding to %ld",
+			       schannel, cchannel);
+			free(bindings);
+			return -EINVAL;
+		}
+		bindings[cchannel] = schannel;
+	}
+	if (dmix->type == SND_PCM_TYPE_DSNOOP ||
+	    ! dmix->bindings)
+		goto __skip_same_dst;
+	for (chn = 0; chn < count; chn++) {
+		for (chn1 = 0; chn1 < count; chn1++) {
+			if (chn == chn1)
+				continue;
+			if (bindings[chn] == dmix->bindings[chn1]) {
+				SNDERR("unable to route channels %d,%d to same destination %d", chn, chn1, bindings[chn]);
+				free(bindings);
+				return -EINVAL;
+			}
+		}
+	}
+      __skip_same_dst:
+	dmix->bindings = bindings;
+	dmix->channels = count;
+	return 0;
+}
+
+/*
+ * parse slave config and calculate the ipc_key offset
+ */
+
+static int _snd_pcm_direct_get_slave_ipc_offset(snd_config_t *root,
+						snd_config_t *sconf,
+						int direction,
+						int hop)
+{
+	snd_config_iterator_t i, next;
+	snd_config_t *pcm_conf;
+	int err;
+	long card = 0, device = 0, subdevice = 0;
+	const char *str;
+
+	if (snd_config_get_string(sconf, &str) >= 0) {
+		if (hop > SND_CONF_MAX_HOPS) {
+			SNDERR("Too many definition levels (looped?)");
+			return -EINVAL;
+		}
+		err = snd_config_search_definition(root, "pcm", str, &pcm_conf);
+		if (err < 0) {
+			SNDERR("Unknown slave PCM %s", str);
+			return err;
+		}
+		err = _snd_pcm_direct_get_slave_ipc_offset(root, pcm_conf,
+							   direction,
+							   hop + 1);
+		snd_config_delete(pcm_conf);
+		return err;
+	}
+
+#if 0	/* for debug purposes */
+	{
+		snd_output_t *out;
+		snd_output_stdio_attach(&out, stderr, 0);
+		snd_config_save(sconf, out);
+		snd_output_close(out);
+	}
+#endif
+
+	if (snd_config_search(sconf, "slave", &pcm_conf) >= 0 &&
+	    (snd_config_search(pcm_conf, "pcm", &pcm_conf) >= 0 ||
+	    (snd_config_get_string(pcm_conf, &str) >= 0 &&
+	    snd_config_search_definition(root, "pcm_slave", str, &pcm_conf) >= 0 &&
+	    snd_config_search(pcm_conf, "pcm", &pcm_conf) >= 0)))
+		return _snd_pcm_direct_get_slave_ipc_offset(root, pcm_conf,
+							    direction,
+							    hop + 1);
+
+	snd_config_for_each(i, next, sconf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id, *str;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (strcmp(id, "type") == 0) {
+			err = snd_config_get_string(n, &str);
+			if (err < 0) {
+				SNDERR("Invalid value for PCM type definition\n");
+				return -EINVAL;
+			}
+			if (strcmp(str, "hw")) {
+				SNDERR("Invalid type '%s' for slave PCM\n", str);
+				return -EINVAL;
+			}
+			continue;
+		}
+		if (strcmp(id, "card") == 0) {
+			err = snd_config_get_integer(n, &card);
+			if (err < 0) {
+				err = snd_config_get_string(n, &str);
+				if (err < 0) {
+					SNDERR("Invalid type for %s", id);
+					return -EINVAL;
+				}
+				card = snd_card_get_index(str);
+				if (card < 0) {
+					SNDERR("Invalid value for %s", id);
+					return card;
+				}
+			}
+			continue;
+		}
+		if (strcmp(id, "device") == 0) {
+			err = snd_config_get_integer(n, &device);
+			if (err < 0) {
+				SNDERR("Invalid type for %s", id);
+				return err;
+			}
+			continue;
+		}
+		if (strcmp(id, "subdevice") == 0) {
+			err = snd_config_get_integer(n, &subdevice);
+			if (err < 0) {
+				SNDERR("Invalid type for %s", id);
+				return err;
+			}
+			continue;
+		}
+	}
+	if (card < 0)
+		card = 0;
+	if (device < 0)
+		device = 0;
+	if (subdevice < 0)
+		subdevice = 0;
+	return (direction << 1) + (device << 2) + (subdevice << 8) + (card << 12);
+}
+
+static int snd_pcm_direct_get_slave_ipc_offset(snd_config_t *root,
+					snd_config_t *sconf,
+					int direction)
+{
+	return _snd_pcm_direct_get_slave_ipc_offset(root, sconf, direction, 0);
+}
+
+int snd_pcm_direct_parse_open_conf(snd_config_t *root, snd_config_t *conf,
+				   int stream, struct snd_pcm_direct_open_conf *rec)
+{
+	snd_config_iterator_t i, next;
+	int ipc_key_add_uid = 0;
+	snd_config_t *n;
+	int err;
+
+	rec->slave = NULL;
+	rec->bindings = NULL;
+	rec->ipc_key = 0;
+	rec->ipc_perm = 0600;
+	rec->ipc_gid = -1;
+	rec->slowptr = 1;
+	rec->max_periods = 0;
+
+	/* read defaults */
+	if (snd_config_search(root, "defaults.pcm.dmix_max_periods", &n) >= 0) {
+		long val;
+		err = snd_config_get_integer(n, &val);
+		if (err >= 0)
+			rec->max_periods = val;
+	}
+
+	snd_config_for_each(i, next, conf) {
+		const char *id;
+		n = snd_config_iterator_entry(i);
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (snd_pcm_conf_generic_id(id))
+			continue;
+		if (strcmp(id, "ipc_key") == 0) {
+			long key;
+			err = snd_config_get_integer(n, &key);
+			if (err < 0) {
+				SNDERR("The field ipc_key must be an integer type");
+
+				return err;
+			}
+			rec->ipc_key = key;
+			continue;
+		}
+		if (strcmp(id, "ipc_perm") == 0) {
+			long perm;
+			err = snd_config_get_integer(n, &perm);
+			if (err < 0) {
+				SNDERR("Invalid type for %s", id);
+				return err;
+			}
+			if ((perm & ~0777) != 0) {
+				SNDERR("The field ipc_perm must be a valid file permission");
+				return -EINVAL;
+			}
+			rec->ipc_perm = perm;
+			continue;
+		}
+		if (strcmp(id, "ipc_gid") == 0) {
+			char *group;
+			char *endp;
+			err = snd_config_get_ascii(n, &group);
+			if (err < 0) {
+				SNDERR("The field ipc_gid must be a valid group");
+				return err;
+			}
+			if (! *group) {
+				rec->ipc_gid = -1;
+				free(group);
+				continue;
+			}
+			if (isdigit(*group) == 0) {
+				struct group *grp = getgrnam(group);
+				if (grp == NULL) {
+					SNDERR("The field ipc_gid must be a valid group (create group %s)", group);
+					free(group);
+					return -EINVAL;
+				}
+				rec->ipc_gid = grp->gr_gid;
+			} else {
+				rec->ipc_gid = strtol(group, &endp, 10);
+			}
+			free(group);
+			continue;
+		}
+		if (strcmp(id, "ipc_key_add_uid") == 0) {
+			if ((err = snd_config_get_bool(n)) < 0) {
+				SNDERR("The field ipc_key_add_uid must be a boolean type");
+				return err;
+			}
+			ipc_key_add_uid = err;
+			continue;
+		}
+		if (strcmp(id, "slave") == 0) {
+			rec->slave = n;
+			continue;
+		}
+		if (strcmp(id, "bindings") == 0) {
+			rec->bindings = n;
+			continue;
+		}
+		if (strcmp(id, "slowptr") == 0) {
+			err = snd_config_get_bool(n);
+			if (err < 0)
+				return err;
+			rec->slowptr = err;
+			continue;
+		}
+		if (strcmp(id, "max_periods") == 0) {
+			long val;
+			err = snd_config_get_integer(n, &val);
+			if (err < 0)
+				return err;
+			rec->max_periods = val;
+			continue;
+		}
+		SNDERR("Unknown field %s", id);
+		return -EINVAL;
+	}
+	if (!rec->slave) {
+		SNDERR("slave is not defined");
+		return -EINVAL;
+	}
+	if (!rec->ipc_key) {
+		SNDERR("Unique IPC key is not defined");
+		return -EINVAL;
+	}
+	if (ipc_key_add_uid)
+		rec->ipc_key += getuid();
+	err = snd_pcm_direct_get_slave_ipc_offset(root, conf, stream);
+	if (err < 0)
+		return err;
+	rec->ipc_key += err;
+
+	return 0;
+}
diff --git a/src/pcm/pcm_direct.h b/src/pcm/pcm_direct.h
new file mode 100644
index 0000000..7f16481
--- /dev/null
+++ b/src/pcm/pcm_direct.h
@@ -0,0 +1,306 @@
+/*
+ *  PCM - Direct Stream Mixing
+ *  Copyright (c) 2003 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include "pcm_local.h"  
+
+#define DIRECT_IPC_SEMS         1
+#define DIRECT_IPC_SEM_CLIENT   0
+
+typedef void (mix_areas_t)(unsigned int size,
+			   volatile void *dst, void *src,
+			   volatile signed int *sum, size_t dst_step,
+			   size_t src_step, size_t sum_step);
+
+typedef void (mix_areas_16_t)(unsigned int size,
+			      volatile signed short *dst, signed short *src,
+			      volatile signed int *sum, size_t dst_step,
+			      size_t src_step, size_t sum_step);
+
+typedef void (mix_areas_32_t)(unsigned int size,
+			      volatile signed int *dst, signed int *src,
+			      volatile signed int *sum, size_t dst_step,
+			      size_t src_step, size_t sum_step);
+
+typedef void (mix_areas_24_t)(unsigned int size,
+			      volatile unsigned char *dst, unsigned char *src,
+			      volatile signed int *sum, size_t dst_step,
+			      size_t src_step, size_t sum_step);
+
+typedef void (mix_areas_u8_t)(unsigned int size,
+			      volatile unsigned char *dst, unsigned char *src,
+			      volatile signed int *sum, size_t dst_step,
+			      size_t src_step, size_t sum_step);
+
+struct slave_params {
+	snd_pcm_format_t format;
+	int rate;
+	int channels;
+	int period_time;
+	int buffer_time;
+	snd_pcm_sframes_t period_size;
+	snd_pcm_sframes_t buffer_size;
+	unsigned int periods;
+};
+
+/* shared among direct plugin clients - be careful to be 32/64bit compatible! */
+typedef struct {
+	unsigned int magic;			/* magic number */
+	char socket_name[256];			/* name of communication socket */
+	snd_pcm_type_t type;			/* PCM type (currently only hw) */
+	int use_server;
+	struct {
+		unsigned int format;
+		snd_interval_t rate;
+		snd_interval_t buffer_size;
+		snd_interval_t buffer_time;
+		snd_interval_t period_size;
+		snd_interval_t period_time;
+		snd_interval_t periods;
+	} hw;
+	struct {
+		/* copied to slave PCMs */
+		snd_pcm_access_t access;
+		snd_pcm_format_t format;
+		snd_pcm_subformat_t subformat;
+		unsigned int channels;
+		unsigned int rate;
+		unsigned int period_size;
+		unsigned int period_time;
+		snd_interval_t periods;
+		unsigned int monotonic;
+		snd_pcm_tstamp_t tstamp_mode;
+		unsigned int period_step;
+		unsigned int sleep_min; /* not used */
+		unsigned int avail_min;
+		unsigned int start_threshold;	
+		unsigned int stop_threshold;	
+		unsigned int silence_threshold;
+		unsigned int silence_size;
+		unsigned int xfer_align; /* not used */
+		unsigned long long boundary;
+		unsigned int info;
+		unsigned int msbits;
+		unsigned int rate_num;
+		unsigned int rate_den;
+		unsigned int hw_flags;
+		unsigned int fifo_size;
+		unsigned int buffer_size;
+		snd_interval_t buffer_time;
+		unsigned int sample_bits;
+		unsigned int frame_bits;
+	} s;
+	union {
+		struct {
+			unsigned long long chn_mask;
+		} dshare;
+	} u;
+} snd_pcm_direct_share_t;
+
+typedef struct snd_pcm_direct snd_pcm_direct_t;
+
+struct snd_pcm_direct {
+	snd_pcm_type_t type;		/* type (dmix, dsnoop, dshare) */
+	key_t ipc_key;			/* IPC key for semaphore and memory */
+	mode_t ipc_perm;		/* IPC socket permissions */
+	int ipc_gid;			/* IPC socket gid */
+	int semid;			/* IPC global semaphore identification */
+	int shmid;			/* IPC global shared memory identification */
+	snd_pcm_direct_share_t *shmptr;	/* pointer to shared memory area */
+	snd_pcm_t *spcm; 		/* slave PCM handle */
+	snd_pcm_uframes_t appl_ptr;
+	snd_pcm_uframes_t last_appl_ptr;
+	snd_pcm_uframes_t hw_ptr;
+	snd_pcm_uframes_t avail_max;
+	snd_pcm_uframes_t slave_appl_ptr;
+	snd_pcm_uframes_t slave_hw_ptr;
+	snd_pcm_uframes_t slave_period_size;
+	snd_pcm_uframes_t slave_buffer_size;
+	snd_pcm_uframes_t slave_boundary;
+	int (*sync_ptr)(snd_pcm_t *pcm);
+	snd_pcm_state_t state;
+	snd_htimestamp_t trigger_tstamp;
+	snd_htimestamp_t update_tstamp;
+	int server, client;
+	int comm_fd;			/* communication file descriptor (socket) */
+	int hw_fd;			/* hardware file descriptor */
+	struct pollfd timer_fd;
+	int poll_fd;
+	int tread: 1;
+	int timer_need_poll: 1;
+	unsigned int timer_events;
+	int server_fd;
+	pid_t server_pid;
+	snd_timer_t *timer; 		/* timer used as poll_fd */
+	int interleaved;	 	/* we have interleaved buffer */
+	int slowptr;			/* use slow but more precise ptr updates */
+	int max_periods;		/* max periods (-1 = fixed periods, 0 = max buffer size) */
+	unsigned int channels;		/* client's channels */
+	unsigned int *bindings;
+	union {
+		struct {
+			int shmid_sum;			/* IPC global sum ring buffer memory identification */
+			signed int *sum_buffer;		/* shared sum buffer */
+			mix_areas_16_t *mix_areas_16;
+			mix_areas_32_t *mix_areas_32;
+			mix_areas_24_t *mix_areas_24;
+			mix_areas_u8_t *mix_areas_u8;
+			mix_areas_16_t *remix_areas_16;
+			mix_areas_32_t *remix_areas_32;
+			mix_areas_24_t *remix_areas_24;
+			mix_areas_u8_t *remix_areas_u8;
+		} dmix;
+		struct {
+		} dsnoop;
+		struct {
+			unsigned long long chn_mask;
+		} dshare;
+	} u;
+	void (*server_free)(snd_pcm_direct_t *direct);
+};
+
+/* make local functions really local */
+#define snd_pcm_direct_semaphore_create_or_connect \
+	snd1_pcm_direct_semaphore_create_or_connect
+#define snd_pcm_direct_shm_create_or_connect \
+	snd1_pcm_direct_shm_create_or_connect
+#define snd_pcm_direct_shm_discard \
+	snd1_pcm_direct_shm_discard
+#define snd_pcm_direct_server_create \
+	snd1_pcm_direct_server_create
+#define snd_pcm_direct_server_discard \
+	snd1_pcm_direct_server_discard
+#define snd_pcm_direct_client_connect \
+	snd1_pcm_direct_client_connect
+#define snd_pcm_direct_client_discard \
+	snd1_pcm_direct_client_discard
+#define snd_pcm_direct_initialize_slave \
+	snd1_pcm_direct_initialize_slave
+#define snd_pcm_direct_initialize_secondary_slave \
+	snd1_pcm_direct_initialize_secondary_slave
+#define snd_pcm_direct_initialize_poll_fd \
+	snd1_pcm_direct_initialize_poll_fd
+#define snd_pcm_direct_check_interleave \
+	snd1_pcm_direct_check_interleave
+#define snd_pcm_direct_parse_bindings \
+	snd1_pcm_direct_parse_bindings
+#define snd_pcm_direct_nonblock \
+	snd1_pcm_direct_nonblock
+#define snd_pcm_direct_async \
+	snd1_pcm_direct_async
+#define snd_pcm_direct_poll_revents \
+	snd1_pcm_direct_poll_revents
+#define snd_pcm_direct_info \
+	snd1_pcm_direct_info
+#define snd_pcm_direct_hw_refine \
+	snd1_pcm_direct_hw_refine
+#define snd_pcm_direct_hw_params \
+	snd1_pcm_direct_hw_params
+#define snd_pcm_direct_hw_free \
+	snd1_pcm_direct_hw_free
+#define snd_pcm_direct_sw_params \
+	snd1_pcm_direct_sw_params
+#define snd_pcm_direct_channel_info \
+	snd1_pcm_direct_channel_info
+#define snd_pcm_direct_mmap \
+	snd1_pcm_direct_mmap
+#define snd_pcm_direct_munmap \
+	snd1_pcm_direct_munmap
+#define snd_pcm_direct_resume \
+	snd1_pcm_direct_resume
+#define snd_pcm_direct_timer_stop \
+	snd1_pcm_direct_timer_stop
+#define snd_pcm_direct_clear_timer_queue \
+	snd1_pcm_direct_clear_timer_queue
+#define snd_pcm_direct_set_timer_params \
+	snd1_pcm_direct_set_timer_params
+#define snd_pcm_direct_open_secondary_client \
+	snd1_pcm_direct_open_secondary_client
+#define snd_pcm_direct_parse_open_conf \
+	snd1_pcm_direct_parse_open_conf
+
+int snd_pcm_direct_semaphore_create_or_connect(snd_pcm_direct_t *dmix);
+
+static inline int snd_pcm_direct_semaphore_discard(snd_pcm_direct_t *dmix)
+{
+	if (dmix->semid >= 0) {
+		if (semctl(dmix->semid, 0, IPC_RMID, NULL) < 0)
+			return -errno;
+		dmix->semid = -1;
+	}
+	return 0;
+}
+
+static inline int snd_pcm_direct_semaphore_down(snd_pcm_direct_t *dmix, int sem_num)
+{
+	struct sembuf op[2] = { { sem_num, 0, 0 }, { sem_num, 1, SEM_UNDO } };
+	return semop(dmix->semid, op, 2);
+}
+
+static inline int snd_pcm_direct_semaphore_up(snd_pcm_direct_t *dmix, int sem_num)
+{
+	struct sembuf op = { sem_num, -1, SEM_UNDO | IPC_NOWAIT };
+	return semop(dmix->semid, &op, 1);
+}
+
+int snd_pcm_direct_shm_create_or_connect(snd_pcm_direct_t *dmix);
+int snd_pcm_direct_shm_discard(snd_pcm_direct_t *dmix);
+int snd_pcm_direct_server_create(snd_pcm_direct_t *dmix);
+int snd_pcm_direct_server_discard(snd_pcm_direct_t *dmix);
+int snd_pcm_direct_client_connect(snd_pcm_direct_t *dmix);
+int snd_pcm_direct_client_discard(snd_pcm_direct_t *dmix);
+int snd_pcm_direct_initialize_slave(snd_pcm_direct_t *dmix, snd_pcm_t *spcm, struct slave_params *params);
+int snd_pcm_direct_initialize_secondary_slave(snd_pcm_direct_t *dmix, snd_pcm_t *spcm, struct slave_params *params);
+int snd_pcm_direct_initialize_poll_fd(snd_pcm_direct_t *dmix);
+int snd_pcm_direct_check_interleave(snd_pcm_direct_t *dmix, snd_pcm_t *pcm);
+int snd_pcm_direct_parse_bindings(snd_pcm_direct_t *dmix,
+				  struct slave_params *params,
+				  snd_config_t *cfg);
+int snd_pcm_direct_nonblock(snd_pcm_t *pcm, int nonblock);
+int snd_pcm_direct_async(snd_pcm_t *pcm, int sig, pid_t pid);
+int snd_pcm_direct_poll_revents(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int nfds, unsigned short *revents);
+int snd_pcm_direct_info(snd_pcm_t *pcm, snd_pcm_info_t * info);
+int snd_pcm_direct_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params);
+int snd_pcm_direct_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t * params);
+int snd_pcm_direct_hw_free(snd_pcm_t *pcm);
+int snd_pcm_direct_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t * params);
+int snd_pcm_direct_channel_info(snd_pcm_t *pcm, snd_pcm_channel_info_t * info);
+int snd_pcm_direct_mmap(snd_pcm_t *pcm);
+int snd_pcm_direct_munmap(snd_pcm_t *pcm);
+int snd_pcm_direct_resume(snd_pcm_t *pcm);
+int snd_pcm_direct_timer_stop(snd_pcm_direct_t *dmix);
+void snd_pcm_direct_clear_timer_queue(snd_pcm_direct_t *dmix);
+int snd_pcm_direct_set_timer_params(snd_pcm_direct_t *dmix);
+int snd_pcm_direct_open_secondary_client(snd_pcm_t **spcmp, snd_pcm_direct_t *dmix, const char *client_name);
+
+int snd_timer_async(snd_timer_t *timer, int sig, pid_t pid);
+struct timespec snd_pcm_hw_fast_tstamp(snd_pcm_t *pcm);
+
+struct snd_pcm_direct_open_conf {
+	key_t ipc_key;
+	mode_t ipc_perm;
+	int ipc_gid;
+	int slowptr;
+	int max_periods;
+	snd_config_t *slave;
+	snd_config_t *bindings;
+};
+
+int snd_pcm_direct_parse_open_conf(snd_config_t *root, snd_config_t *conf, int stream, struct snd_pcm_direct_open_conf *rec);
diff --git a/src/pcm/pcm_dmix.c b/src/pcm/pcm_dmix.c
new file mode 100644
index 0000000..434fc65
--- /dev/null
+++ b/src/pcm/pcm_dmix.c
@@ -0,0 +1,1326 @@
+/**
+ * \file pcm/pcm_dmix.c
+ * \ingroup PCM_Plugins
+ * \brief PCM Direct Stream Mixing (dmix) Plugin Interface
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \date 2003
+ */
+/*
+ *  PCM - Direct Stream Mixing
+ *  Copyright (c) 2003 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <grp.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/shm.h>
+#include <sys/sem.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/mman.h>
+#include "pcm_direct.h"
+
+#ifndef PIC
+/* entry for static linking */
+const char *_snd_module_pcm_dmix = "";
+#endif
+
+#ifndef DOC_HIDDEN
+/* start is pending - this state happens when rate plugin does a delayed commit */
+#define STATE_RUN_PENDING	1024
+#endif
+
+/*
+ *
+ */
+
+static int shm_sum_discard(snd_pcm_direct_t *dmix);
+
+/*
+ *  sum ring buffer shared memory area 
+ */
+static int shm_sum_create_or_connect(snd_pcm_direct_t *dmix)
+{
+	struct shmid_ds buf;
+	int tmpid, err;
+	size_t size;
+
+	size = dmix->shmptr->s.channels *
+	       dmix->shmptr->s.buffer_size *
+	       sizeof(signed int);	
+retryshm:
+	dmix->u.dmix.shmid_sum = shmget(dmix->ipc_key + 1, size,
+					IPC_CREAT | dmix->ipc_perm);
+	err = -errno;
+	if (dmix->u.dmix.shmid_sum < 0) {
+		if (errno == EINVAL)
+		if ((tmpid = shmget(dmix->ipc_key + 1, 0, dmix->ipc_perm)) != -1)
+		if (!shmctl(tmpid, IPC_STAT, &buf))
+	    	if (!buf.shm_nattch) 
+		/* no users so destroy the segment */
+		if (!shmctl(tmpid, IPC_RMID, NULL))
+		    goto retryshm;
+		return err;
+	}
+	if (shmctl(dmix->u.dmix.shmid_sum, IPC_STAT, &buf) < 0) {
+		err = -errno;
+		shm_sum_discard(dmix);
+		return err;
+	}
+	if (dmix->ipc_gid >= 0) {
+		buf.shm_perm.gid = dmix->ipc_gid;
+		shmctl(dmix->u.dmix.shmid_sum, IPC_SET, &buf); 
+	}
+	dmix->u.dmix.sum_buffer = shmat(dmix->u.dmix.shmid_sum, 0, 0);
+	if (dmix->u.dmix.sum_buffer == (void *) -1) {
+		err = -errno;
+		shm_sum_discard(dmix);
+		return err;
+	}
+	mlock(dmix->u.dmix.sum_buffer, size);
+	return 0;
+}
+
+static int shm_sum_discard(snd_pcm_direct_t *dmix)
+{
+	struct shmid_ds buf;
+	int ret = 0;
+
+	if (dmix->u.dmix.shmid_sum < 0)
+		return -EINVAL;
+	if (dmix->u.dmix.sum_buffer != (void *) -1 && shmdt(dmix->u.dmix.sum_buffer) < 0)
+		return -errno;
+	dmix->u.dmix.sum_buffer = (void *) -1;
+	if (shmctl(dmix->u.dmix.shmid_sum, IPC_STAT, &buf) < 0)
+		return -errno;
+	if (buf.shm_nattch == 0) {	/* we're the last user, destroy the segment */
+		if (shmctl(dmix->u.dmix.shmid_sum, IPC_RMID, NULL) < 0)
+			return -errno;
+		ret = 1;
+	}
+	dmix->u.dmix.shmid_sum = -1;
+	return ret;
+}
+
+static void dmix_server_free(snd_pcm_direct_t *dmix)
+{
+	/* remove the memory region */
+	shm_sum_create_or_connect(dmix);
+	shm_sum_discard(dmix);
+}
+
+/*
+ *  the main function of this plugin: mixing
+ *  FIXME: optimize it for different architectures
+ */
+
+#include "pcm_dmix_generic.c"
+#if defined(__i386__)
+#include "pcm_dmix_i386.c"
+#elif defined(__x86_64__)
+#include "pcm_dmix_x86_64.c"
+#else
+#ifndef DOC_HIDDEN
+#define mix_select_callbacks(x)	generic_mix_select_callbacks(x)
+#define dmix_supported_format generic_dmix_supported_format
+#endif
+#endif
+
+static void mix_areas(snd_pcm_direct_t *dmix,
+		      const snd_pcm_channel_area_t *src_areas,
+		      const snd_pcm_channel_area_t *dst_areas,
+		      snd_pcm_uframes_t src_ofs,
+		      snd_pcm_uframes_t dst_ofs,
+		      snd_pcm_uframes_t size)
+{
+	unsigned int src_step, dst_step;
+	unsigned int chn, dchn, channels, sample_size;
+	mix_areas_t *do_mix_areas;
+	
+	channels = dmix->channels;
+	switch (dmix->shmptr->s.format) {
+	case SND_PCM_FORMAT_S16_LE:
+	case SND_PCM_FORMAT_S16_BE:
+		sample_size = 2;
+		do_mix_areas = (mix_areas_t *)dmix->u.dmix.mix_areas_16;
+		break;
+	case SND_PCM_FORMAT_S32_LE:
+	case SND_PCM_FORMAT_S32_BE:
+		sample_size = 4;
+		do_mix_areas = (mix_areas_t *)dmix->u.dmix.mix_areas_32;
+		break;
+	case SND_PCM_FORMAT_S24_LE:
+		sample_size = 4;
+		do_mix_areas = (mix_areas_t *)dmix->u.dmix.mix_areas_24;
+		break;
+	case SND_PCM_FORMAT_S24_3LE:
+		sample_size = 3;
+		do_mix_areas = (mix_areas_t *)dmix->u.dmix.mix_areas_24;
+		break;
+	case SND_PCM_FORMAT_U8:
+		sample_size = 1;
+		do_mix_areas = (mix_areas_t *)dmix->u.dmix.mix_areas_u8;
+		break;
+	default:
+		return;
+	}
+	if (dmix->interleaved) {
+		/*
+		 * process all areas in one loop
+		 * it optimizes the memory accesses for this case
+		 */
+		do_mix_areas(size * channels,
+			     (unsigned char *)dst_areas[0].addr + sample_size * dst_ofs * channels,
+			     (unsigned char *)src_areas[0].addr + sample_size * src_ofs * channels,
+			     dmix->u.dmix.sum_buffer + dst_ofs * channels,
+			     sample_size,
+			     sample_size,
+			     sizeof(signed int));
+		return;
+	}
+	for (chn = 0; chn < channels; chn++) {
+		dchn = dmix->bindings ? dmix->bindings[chn] : chn;
+		if (dchn >= dmix->shmptr->s.channels)
+			continue;
+		src_step = src_areas[chn].step / 8;
+		dst_step = dst_areas[dchn].step / 8;
+		do_mix_areas(size,
+			     ((unsigned char *)dst_areas[dchn].addr + dst_areas[dchn].first / 8) + dst_ofs * dst_step,
+			     ((unsigned char *)src_areas[chn].addr + src_areas[chn].first / 8) + src_ofs * src_step,
+			     dmix->u.dmix.sum_buffer + channels * dst_ofs + chn,
+			     dst_step,
+			     src_step,
+			     channels * sizeof(signed int));
+	}
+}
+
+static void remix_areas(snd_pcm_direct_t *dmix,
+			const snd_pcm_channel_area_t *src_areas,
+			const snd_pcm_channel_area_t *dst_areas,
+			snd_pcm_uframes_t src_ofs,
+			snd_pcm_uframes_t dst_ofs,
+			snd_pcm_uframes_t size)
+{
+	unsigned int src_step, dst_step;
+	unsigned int chn, dchn, channels, sample_size;
+	mix_areas_t *do_remix_areas;
+	
+	channels = dmix->channels;
+	switch (dmix->shmptr->s.format) {
+	case SND_PCM_FORMAT_S16_LE:
+	case SND_PCM_FORMAT_S16_BE:
+		sample_size = 2;
+		do_remix_areas = (mix_areas_t *)dmix->u.dmix.remix_areas_16;
+		break;
+	case SND_PCM_FORMAT_S32_LE:
+	case SND_PCM_FORMAT_S32_BE:
+		sample_size = 4;
+		do_remix_areas = (mix_areas_t *)dmix->u.dmix.remix_areas_32;
+		break;
+	case SND_PCM_FORMAT_S24_LE:
+		sample_size = 4;
+		do_remix_areas = (mix_areas_t *)dmix->u.dmix.remix_areas_24;
+		break;
+	case SND_PCM_FORMAT_S24_3LE:
+		sample_size = 3;
+		do_remix_areas = (mix_areas_t *)dmix->u.dmix.remix_areas_24;
+		break;
+	case SND_PCM_FORMAT_U8:
+		sample_size = 1;
+		do_remix_areas = (mix_areas_t *)dmix->u.dmix.remix_areas_u8;
+		break;
+	default:
+		return;
+	}
+	if (dmix->interleaved) {
+		/*
+		 * process all areas in one loop
+		 * it optimizes the memory accesses for this case
+		 */
+		do_remix_areas(size * channels,
+			       (unsigned char *)dst_areas[0].addr + sample_size * dst_ofs * channels,
+			       (unsigned char *)src_areas[0].addr + sample_size * src_ofs * channels,
+			       dmix->u.dmix.sum_buffer + dst_ofs * channels,
+			       sample_size,
+			       sample_size,
+			       sizeof(signed int));
+		return;
+	}
+	for (chn = 0; chn < channels; chn++) {
+		dchn = dmix->bindings ? dmix->bindings[chn] : chn;
+		if (dchn >= dmix->shmptr->s.channels)
+			continue;
+		src_step = src_areas[chn].step / 8;
+		dst_step = dst_areas[dchn].step / 8;
+		do_remix_areas(size,
+			       ((unsigned char *)dst_areas[dchn].addr + dst_areas[dchn].first / 8) + dst_ofs * dst_step,
+			       ((unsigned char *)src_areas[chn].addr + src_areas[chn].first / 8) + src_ofs * src_step,
+			       dmix->u.dmix.sum_buffer + channels * dst_ofs + chn,
+			       dst_step,
+			       src_step,
+			       channels * sizeof(signed int));
+	}
+}
+
+/*
+ * if no concurrent access is allowed in the mixing routines, we need to protect
+ * the area via semaphore
+ */
+#ifndef DOC_HIDDEN
+#ifdef NO_CONCURRENT_ACCESS
+#define dmix_down_sem(dmix) snd_pcm_direct_semaphore_down(dmix, DIRECT_IPC_SEM_CLIENT)
+#define dmix_up_sem(dmix) snd_pcm_direct_semaphore_up(dmix, DIRECT_IPC_SEM_CLIENT)
+#else
+#define dmix_down_sem(dmix)
+#define dmix_up_sem(dmix)
+#endif
+#endif
+
+/*
+ *  synchronize shm ring buffer with hardware
+ */
+static void snd_pcm_dmix_sync_area(snd_pcm_t *pcm)
+{
+	snd_pcm_direct_t *dmix = pcm->private_data;
+	snd_pcm_uframes_t slave_hw_ptr, slave_appl_ptr, slave_size;
+	snd_pcm_uframes_t appl_ptr, size, transfer;
+	const snd_pcm_channel_area_t *src_areas, *dst_areas;
+	
+	/* calculate the size to transfer */
+	/* check the available size in the local buffer
+	 * last_appl_ptr keeps the last updated position
+	 */
+	size = dmix->appl_ptr - dmix->last_appl_ptr;
+	if (! size)
+		return;
+	if (size >= pcm->boundary / 2)
+		size = pcm->boundary - size;
+
+	/* the slave_app_ptr can be far behind the slave_hw_ptr */
+	/* reduce mixing and errors here - just skip not catched writes */
+	if (dmix->slave_hw_ptr <= dmix->slave_appl_ptr)
+		slave_size = dmix->slave_appl_ptr - dmix->slave_hw_ptr;
+	else
+		slave_size = dmix->slave_appl_ptr + (dmix->slave_boundary - dmix->slave_hw_ptr);
+	if (slave_size > dmix->slave_buffer_size) {
+		transfer = dmix->slave_buffer_size - slave_size;
+		if (transfer > size)
+			transfer = size;
+		dmix->last_appl_ptr += transfer;
+		dmix->last_appl_ptr %= pcm->boundary;
+		dmix->slave_appl_ptr += transfer;
+		dmix->slave_appl_ptr %= dmix->slave_boundary;
+		size = dmix->appl_ptr - dmix->last_appl_ptr;
+		if (! size)
+			return;
+		if (size >= pcm->boundary / 2)
+			size = pcm->boundary - size;
+	}
+
+	/* check the available size in the slave PCM buffer */
+	slave_hw_ptr = dmix->slave_hw_ptr;
+	/* don't write on the last active period - this area may be cleared
+	 * by the driver during mix operation...
+	 */
+	slave_hw_ptr -= slave_hw_ptr % dmix->slave_period_size;
+	slave_hw_ptr += dmix->slave_buffer_size;
+	if (slave_hw_ptr >= dmix->slave_boundary)
+		slave_hw_ptr -= dmix->slave_boundary;
+	if (slave_hw_ptr < dmix->slave_appl_ptr)
+		slave_size = slave_hw_ptr + (dmix->slave_boundary - dmix->slave_appl_ptr);
+	else
+		slave_size = slave_hw_ptr - dmix->slave_appl_ptr;
+	if (slave_size < size)
+		size = slave_size;
+	if (! size)
+		return;
+
+	/* add sample areas here */
+	src_areas = snd_pcm_mmap_areas(pcm);
+	dst_areas = snd_pcm_mmap_areas(dmix->spcm);
+	appl_ptr = dmix->last_appl_ptr % pcm->buffer_size;
+	dmix->last_appl_ptr += size;
+	dmix->last_appl_ptr %= pcm->boundary;
+	slave_appl_ptr = dmix->slave_appl_ptr % dmix->slave_buffer_size;
+	dmix->slave_appl_ptr += size;
+	dmix->slave_appl_ptr %= dmix->slave_boundary;
+	dmix_down_sem(dmix);
+	for (;;) {
+		transfer = size;
+		if (appl_ptr + transfer > pcm->buffer_size)
+			transfer = pcm->buffer_size - appl_ptr;
+		if (slave_appl_ptr + transfer > dmix->slave_buffer_size)
+			transfer = dmix->slave_buffer_size - slave_appl_ptr;
+		mix_areas(dmix, src_areas, dst_areas, appl_ptr, slave_appl_ptr, transfer);
+		size -= transfer;
+		if (! size)
+			break;
+		slave_appl_ptr += transfer;
+		slave_appl_ptr %= dmix->slave_buffer_size;
+		appl_ptr += transfer;
+		appl_ptr %= pcm->buffer_size;
+	}
+	dmix_up_sem(dmix);
+}
+
+/*
+ *  synchronize hardware pointer (hw_ptr) with ours
+ */
+static int snd_pcm_dmix_sync_ptr(snd_pcm_t *pcm)
+{
+	snd_pcm_direct_t *dmix = pcm->private_data;
+	snd_pcm_uframes_t slave_hw_ptr, old_slave_hw_ptr, avail;
+	snd_pcm_sframes_t diff;
+	
+	switch (snd_pcm_state(dmix->spcm)) {
+	case SND_PCM_STATE_DISCONNECTED:
+		dmix->state = SND_PCM_STATE_DISCONNECTED;
+		return -ENODEV;
+	default:
+		break;
+	}
+	if (dmix->slowptr)
+		snd_pcm_hwsync(dmix->spcm);
+	old_slave_hw_ptr = dmix->slave_hw_ptr;
+	slave_hw_ptr = dmix->slave_hw_ptr = *dmix->spcm->hw.ptr;
+	diff = slave_hw_ptr - old_slave_hw_ptr;
+	if (diff == 0)		/* fast path */
+		return 0;
+	if (dmix->state != SND_PCM_STATE_RUNNING &&
+	    dmix->state != SND_PCM_STATE_DRAINING)
+		/* not really started yet - don't update hw_ptr */
+		return 0;
+	if (diff < 0) {
+		slave_hw_ptr += dmix->slave_boundary;
+		diff = slave_hw_ptr - old_slave_hw_ptr;
+	}
+	dmix->hw_ptr += diff;
+	dmix->hw_ptr %= pcm->boundary;
+	if (pcm->stop_threshold >= pcm->boundary)	/* don't care */
+		return 0;
+	avail = snd_pcm_mmap_playback_avail(pcm);
+	if (avail > dmix->avail_max)
+		dmix->avail_max = avail;
+	if (avail >= pcm->stop_threshold) {
+		snd_timer_stop(dmix->timer);
+		gettimestamp(&dmix->trigger_tstamp, pcm->monotonic);
+		if (dmix->state == SND_PCM_STATE_RUNNING) {
+			dmix->state = SND_PCM_STATE_XRUN;
+			return -EPIPE;
+		}
+		dmix->state = SND_PCM_STATE_SETUP;
+		/* clear queue to remove pending poll events */
+		snd_pcm_direct_clear_timer_queue(dmix);
+	}
+	return 0;
+}
+
+/*
+ *  plugin implementation
+ */
+
+static snd_pcm_state_t snd_pcm_dmix_state(snd_pcm_t *pcm)
+{
+	snd_pcm_direct_t *dmix = pcm->private_data;
+	snd_pcm_state_t state;
+	state = snd_pcm_state(dmix->spcm);
+	switch (state) {
+	case SND_PCM_STATE_SUSPENDED:
+		return state;
+	case SND_PCM_STATE_DISCONNECTED:
+		return state;
+	default:
+		break;
+	}
+	if (dmix->state == STATE_RUN_PENDING)
+		return SNDRV_PCM_STATE_RUNNING;
+	return dmix->state;
+}
+
+static int snd_pcm_dmix_status(snd_pcm_t *pcm, snd_pcm_status_t * status)
+{
+	snd_pcm_direct_t *dmix = pcm->private_data;
+
+	switch (dmix->state) {
+	case SNDRV_PCM_STATE_DRAINING:
+	case SNDRV_PCM_STATE_RUNNING:
+		snd_pcm_dmix_sync_ptr(pcm);
+		break;
+	default:
+		break;
+	}
+	memset(status, 0, sizeof(*status));
+	status->state = snd_pcm_dmix_state(pcm);
+	status->trigger_tstamp = dmix->trigger_tstamp;
+	gettimestamp(&status->tstamp, pcm->monotonic);
+	status->avail = snd_pcm_mmap_playback_avail(pcm);
+	status->avail_max = status->avail > dmix->avail_max ? status->avail : dmix->avail_max;
+	dmix->avail_max = 0;
+	return 0;
+}
+
+static int snd_pcm_dmix_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp)
+{
+	snd_pcm_direct_t *dmix = pcm->private_data;
+	int err;
+	
+	switch(dmix->state) {
+	case SNDRV_PCM_STATE_DRAINING:
+	case SNDRV_PCM_STATE_RUNNING:
+		err = snd_pcm_dmix_sync_ptr(pcm);
+		if (err < 0)
+			return err;
+		/* fallthru */
+	case SNDRV_PCM_STATE_PREPARED:
+	case SNDRV_PCM_STATE_SUSPENDED:
+	case STATE_RUN_PENDING:
+		*delayp = snd_pcm_mmap_playback_hw_avail(pcm);
+		return 0;
+	case SNDRV_PCM_STATE_XRUN:
+		return -EPIPE;
+	case SNDRV_PCM_STATE_DISCONNECTED:
+		return -ENODEV;
+	default:
+		return -EBADFD;
+	}
+}
+
+static int snd_pcm_dmix_hwsync(snd_pcm_t *pcm)
+{
+	snd_pcm_direct_t *dmix = pcm->private_data;
+
+	switch(dmix->state) {
+	case SNDRV_PCM_STATE_DRAINING:
+	case SNDRV_PCM_STATE_RUNNING:
+		/* sync slave PCM */
+		return snd_pcm_dmix_sync_ptr(pcm);
+	case SNDRV_PCM_STATE_PREPARED:
+	case SNDRV_PCM_STATE_SUSPENDED:
+	case STATE_RUN_PENDING:
+		return 0;
+	case SNDRV_PCM_STATE_XRUN:
+		return -EPIPE;
+	case SNDRV_PCM_STATE_DISCONNECTED:
+		return -ENODEV;
+	default:
+		return -EBADFD;
+	}
+}
+
+static int snd_pcm_dmix_prepare(snd_pcm_t *pcm)
+{
+	snd_pcm_direct_t *dmix = pcm->private_data;
+
+	snd_pcm_direct_check_interleave(dmix, pcm);
+	dmix->state = SND_PCM_STATE_PREPARED;
+	dmix->appl_ptr = dmix->last_appl_ptr = 0;
+	dmix->hw_ptr = 0;
+	return snd_pcm_direct_set_timer_params(dmix);
+}
+
+static void reset_slave_ptr(snd_pcm_t *pcm, snd_pcm_direct_t *dmix)
+{
+	dmix->slave_appl_ptr = dmix->slave_hw_ptr = *dmix->spcm->hw.ptr;
+	if (pcm->buffer_size > pcm->period_size * 2)
+		return;
+	/* If we have too litte periods, better to align the start position
+	 * to the period boundary so that the interrupt can be handled properly
+	 * at the right time.
+	 */
+	dmix->slave_appl_ptr = ((dmix->slave_appl_ptr + dmix->slave_period_size - 1)
+				/ dmix->slave_period_size) * dmix->slave_period_size;
+}
+
+static int snd_pcm_dmix_reset(snd_pcm_t *pcm)
+{
+	snd_pcm_direct_t *dmix = pcm->private_data;
+	dmix->hw_ptr %= pcm->period_size;
+	dmix->appl_ptr = dmix->last_appl_ptr = dmix->hw_ptr;
+	reset_slave_ptr(pcm, dmix);
+	return 0;
+}
+
+static int snd_pcm_dmix_start_timer(snd_pcm_t *pcm, snd_pcm_direct_t *dmix)
+{
+	int err;
+
+	snd_pcm_hwsync(dmix->spcm);
+	reset_slave_ptr(pcm, dmix);
+	err = snd_timer_start(dmix->timer);
+	if (err < 0)
+		return err;
+	dmix->state = SND_PCM_STATE_RUNNING;
+	return 0;
+}
+
+static int snd_pcm_dmix_start(snd_pcm_t *pcm)
+{
+	snd_pcm_direct_t *dmix = pcm->private_data;
+	snd_pcm_sframes_t avail;
+	int err;
+	
+	if (dmix->state != SND_PCM_STATE_PREPARED)
+		return -EBADFD;
+	avail = snd_pcm_mmap_playback_hw_avail(pcm);
+	if (avail == 0)
+		dmix->state = STATE_RUN_PENDING;
+	else if (avail < 0)
+		return 0;
+	else {
+		if ((err = snd_pcm_dmix_start_timer(pcm, dmix)) < 0)
+			return err;
+		snd_pcm_dmix_sync_area(pcm);
+	}
+	gettimestamp(&dmix->trigger_tstamp, pcm->monotonic);
+	return 0;
+}
+
+static int snd_pcm_dmix_drop(snd_pcm_t *pcm)
+{
+	snd_pcm_direct_t *dmix = pcm->private_data;
+	if (dmix->state == SND_PCM_STATE_OPEN)
+		return -EBADFD;
+	dmix->state = SND_PCM_STATE_SETUP;
+	snd_pcm_direct_timer_stop(dmix);
+	return 0;
+}
+
+static int snd_pcm_dmix_drain(snd_pcm_t *pcm)
+{
+	snd_pcm_direct_t *dmix = pcm->private_data;
+	snd_pcm_uframes_t stop_threshold;
+	int err;
+
+	if (dmix->state == SND_PCM_STATE_OPEN)
+		return -EBADFD;
+	if (pcm->mode & SND_PCM_NONBLOCK)
+		return -EAGAIN;
+	if (dmix->state == SND_PCM_STATE_PREPARED) {
+		if (snd_pcm_mmap_playback_hw_avail(pcm) > 0)
+			snd_pcm_dmix_start(pcm);
+		else {
+			snd_pcm_dmix_drop(pcm);
+			return 0;
+		}
+	}
+
+	if (dmix->state == SND_PCM_STATE_XRUN) {
+		snd_pcm_dmix_drop(pcm);
+		return 0;
+	}
+
+	stop_threshold = pcm->stop_threshold;
+	if (pcm->stop_threshold > pcm->buffer_size)
+		pcm->stop_threshold = pcm->buffer_size;
+	dmix->state = SND_PCM_STATE_DRAINING;
+	do {
+		err = snd_pcm_dmix_sync_ptr(pcm);
+		if (err < 0) {
+			snd_pcm_dmix_drop(pcm);
+			return err;
+		}
+		if (dmix->state == SND_PCM_STATE_DRAINING) {
+			snd_pcm_dmix_sync_area(pcm);
+			snd_pcm_wait_nocheck(pcm, -1);
+			snd_pcm_direct_clear_timer_queue(dmix); /* force poll to wait */
+		}
+	} while (dmix->state == SND_PCM_STATE_DRAINING);
+	pcm->stop_threshold = stop_threshold;
+	return 0;
+}
+
+static int snd_pcm_dmix_pause(snd_pcm_t *pcm ATTRIBUTE_UNUSED, int enable ATTRIBUTE_UNUSED)
+{
+	return -EIO;
+}
+
+static snd_pcm_sframes_t snd_pcm_dmix_rewindable(snd_pcm_t *pcm)
+{
+	return snd_pcm_mmap_hw_avail(pcm);
+}
+
+static snd_pcm_sframes_t snd_pcm_dmix_rewind(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
+{
+	snd_pcm_direct_t *dmix = pcm->private_data;
+	snd_pcm_uframes_t slave_appl_ptr, slave_size;
+	snd_pcm_uframes_t appl_ptr, size, transfer, result;
+	const snd_pcm_channel_area_t *src_areas, *dst_areas;
+
+	if (dmix->state == SND_PCM_STATE_RUNNING ||
+	    dmix->state == SND_PCM_STATE_DRAINING)
+	    	return snd_pcm_dmix_hwsync(pcm);
+
+	if (dmix->last_appl_ptr < dmix->appl_ptr)
+		size = dmix->appl_ptr - dmix->last_appl_ptr;
+	else
+		size = dmix->appl_ptr + (pcm->boundary - dmix->last_appl_ptr);
+	if (frames < size)
+		size = frames;
+	snd_pcm_mmap_appl_backward(pcm, size);
+	frames -= size;
+	if (!frames)
+		return size;
+	result = size;
+
+	if (dmix->hw_ptr < dmix->appl_ptr)
+		size = dmix->appl_ptr - dmix->hw_ptr;
+	else
+		size = dmix->appl_ptr + (pcm->boundary - dmix->hw_ptr);
+	if (size > frames)
+		size = frames;
+	if (dmix->slave_hw_ptr < dmix->slave_appl_ptr)
+		slave_size = dmix->slave_appl_ptr - dmix->slave_hw_ptr;
+	else
+		slave_size = dmix->slave_appl_ptr + (pcm->boundary - dmix->slave_hw_ptr);
+	if (slave_size < size)
+		size = slave_size;
+	frames -= size;
+	result += size;
+		
+	/* add sample areas here */
+	src_areas = snd_pcm_mmap_areas(pcm);
+	dst_areas = snd_pcm_mmap_areas(dmix->spcm);
+	dmix->last_appl_ptr -= size;
+	dmix->last_appl_ptr %= pcm->boundary;
+	appl_ptr = dmix->last_appl_ptr % pcm->buffer_size;
+	dmix->slave_appl_ptr -= size;
+	dmix->slave_appl_ptr %= dmix->slave_boundary;
+	slave_appl_ptr = dmix->slave_appl_ptr % dmix->slave_buffer_size;
+	dmix_down_sem(dmix);
+	for (;;) {
+		transfer = size;
+		if (appl_ptr + transfer > pcm->buffer_size)
+			transfer = pcm->buffer_size - appl_ptr;
+		if (slave_appl_ptr + transfer > dmix->slave_buffer_size)
+			transfer = dmix->slave_buffer_size - slave_appl_ptr;
+		remix_areas(dmix, src_areas, dst_areas, appl_ptr, slave_appl_ptr, transfer);
+		size -= transfer;
+		if (! size)
+			break;
+		slave_appl_ptr += transfer;
+		slave_appl_ptr %= dmix->slave_buffer_size;
+		appl_ptr += transfer;
+		appl_ptr %= pcm->buffer_size;
+	}
+	dmix->last_appl_ptr -= frames;
+	dmix->last_appl_ptr %= pcm->boundary;
+	dmix->slave_appl_ptr -= frames;
+	dmix->slave_appl_ptr %= dmix->slave_boundary;
+	dmix_up_sem(dmix);
+
+	snd_pcm_mmap_appl_backward(pcm, frames);
+
+	return result + frames;
+}
+
+static snd_pcm_sframes_t snd_pcm_dmix_forwardable(snd_pcm_t *pcm)
+{
+	return snd_pcm_mmap_avail(pcm);
+}
+
+static snd_pcm_sframes_t snd_pcm_dmix_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
+{
+	snd_pcm_sframes_t avail;
+
+	avail = snd_pcm_mmap_playback_avail(pcm);
+	if (avail < 0)
+		return 0;
+	if (frames > (snd_pcm_uframes_t)avail)
+		frames = avail;
+	snd_pcm_mmap_appl_forward(pcm, frames);
+	return frames;
+}
+
+static snd_pcm_sframes_t snd_pcm_dmix_readi(snd_pcm_t *pcm ATTRIBUTE_UNUSED, void *buffer ATTRIBUTE_UNUSED, snd_pcm_uframes_t size ATTRIBUTE_UNUSED)
+{
+	return -ENODEV;
+}
+
+static snd_pcm_sframes_t snd_pcm_dmix_readn(snd_pcm_t *pcm ATTRIBUTE_UNUSED, void **bufs ATTRIBUTE_UNUSED, snd_pcm_uframes_t size ATTRIBUTE_UNUSED)
+{
+	return -ENODEV;
+}
+
+static int snd_pcm_dmix_close(snd_pcm_t *pcm)
+{
+	snd_pcm_direct_t *dmix = pcm->private_data;
+
+	if (dmix->timer)
+		snd_timer_close(dmix->timer);
+	snd_pcm_direct_semaphore_down(dmix, DIRECT_IPC_SEM_CLIENT);
+	snd_pcm_close(dmix->spcm);
+ 	if (dmix->server)
+ 		snd_pcm_direct_server_discard(dmix);
+ 	if (dmix->client)
+ 		snd_pcm_direct_client_discard(dmix);
+ 	shm_sum_discard(dmix);
+	if (snd_pcm_direct_shm_discard(dmix)) {
+		if (snd_pcm_direct_semaphore_discard(dmix))
+			snd_pcm_direct_semaphore_up(dmix, DIRECT_IPC_SEM_CLIENT);
+	} else
+		snd_pcm_direct_semaphore_up(dmix, DIRECT_IPC_SEM_CLIENT);
+	free(dmix->bindings);
+	pcm->private_data = NULL;
+	free(dmix);
+	return 0;
+}
+
+static snd_pcm_sframes_t snd_pcm_dmix_mmap_commit(snd_pcm_t *pcm,
+						  snd_pcm_uframes_t offset ATTRIBUTE_UNUSED,
+						  snd_pcm_uframes_t size)
+{
+	snd_pcm_direct_t *dmix = pcm->private_data;
+	int err;
+
+	switch (snd_pcm_state(dmix->spcm)) {
+	case SND_PCM_STATE_XRUN:
+		return -EPIPE;
+	case SND_PCM_STATE_SUSPENDED:
+		return -ESTRPIPE;
+	default:
+		break;
+	}
+	if (! size)
+		return 0;
+	snd_pcm_mmap_appl_forward(pcm, size);
+	if (dmix->state == STATE_RUN_PENDING) {
+		if ((err = snd_pcm_dmix_start_timer(pcm, dmix)) < 0)
+			return err;
+	} else if (dmix->state == SND_PCM_STATE_RUNNING ||
+		   dmix->state == SND_PCM_STATE_DRAINING)
+		snd_pcm_dmix_sync_ptr(pcm);
+	if (dmix->state == SND_PCM_STATE_RUNNING ||
+	    dmix->state == SND_PCM_STATE_DRAINING) {
+		/* ok, we commit the changes after the validation of area */
+		/* it's intended, although the result might be crappy */
+		snd_pcm_dmix_sync_area(pcm);
+		/* clear timer queue to avoid a bogus return from poll */
+		if (snd_pcm_mmap_playback_avail(pcm) < pcm->avail_min)
+			snd_pcm_direct_clear_timer_queue(dmix);
+	}
+	return size;
+}
+
+static snd_pcm_sframes_t snd_pcm_dmix_avail_update(snd_pcm_t *pcm)
+{
+	snd_pcm_direct_t *dmix = pcm->private_data;
+	
+	if (dmix->state == SND_PCM_STATE_RUNNING ||
+	    dmix->state == SND_PCM_STATE_DRAINING)
+		snd_pcm_dmix_sync_ptr(pcm);
+	return snd_pcm_mmap_playback_avail(pcm);
+}
+
+static int snd_pcm_dmix_htimestamp(snd_pcm_t *pcm,
+				   snd_pcm_uframes_t *avail,
+				   snd_htimestamp_t *tstamp)
+{
+	snd_pcm_direct_t *dmix = pcm->private_data;
+	snd_pcm_uframes_t avail1;
+	int ok = 0;
+	
+	while (1) {
+		if (dmix->state == SND_PCM_STATE_RUNNING ||
+		    dmix->state == SND_PCM_STATE_DRAINING)
+			snd_pcm_dmix_sync_ptr(pcm);
+		avail1 = snd_pcm_mmap_playback_avail(pcm);
+		if (ok && *avail == avail1)
+			break;
+		*avail = avail1;
+		*tstamp = snd_pcm_hw_fast_tstamp(dmix->spcm);
+	}
+	return 0;
+}
+
+static int snd_pcm_dmix_poll_revents(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int nfds, unsigned short *revents)
+{
+	snd_pcm_direct_t *dmix = pcm->private_data;
+	if (dmix->state == SND_PCM_STATE_RUNNING)
+		snd_pcm_dmix_sync_area(pcm);
+	return snd_pcm_direct_poll_revents(pcm, pfds, nfds, revents);
+}
+
+
+static void snd_pcm_dmix_dump(snd_pcm_t *pcm, snd_output_t *out)
+{
+	snd_pcm_direct_t *dmix = pcm->private_data;
+
+	snd_output_printf(out, "Direct Stream Mixing PCM\n");
+	if (pcm->setup) {
+		snd_output_printf(out, "Its setup is:\n");
+		snd_pcm_dump_setup(pcm, out);
+	}
+	if (dmix->spcm)
+		snd_pcm_dump(dmix->spcm, out);
+}
+
+static const snd_pcm_ops_t snd_pcm_dmix_ops = {
+	.close = snd_pcm_dmix_close,
+	.info = snd_pcm_direct_info,
+	.hw_refine = snd_pcm_direct_hw_refine,
+	.hw_params = snd_pcm_direct_hw_params,
+	.hw_free = snd_pcm_direct_hw_free,
+	.sw_params = snd_pcm_direct_sw_params,
+	.channel_info = snd_pcm_direct_channel_info,
+	.dump = snd_pcm_dmix_dump,
+	.nonblock = snd_pcm_direct_nonblock,
+	.async = snd_pcm_direct_async,
+	.mmap = snd_pcm_direct_mmap,
+	.munmap = snd_pcm_direct_munmap,
+};
+
+static const snd_pcm_fast_ops_t snd_pcm_dmix_fast_ops = {
+	.status = snd_pcm_dmix_status,
+	.state = snd_pcm_dmix_state,
+	.hwsync = snd_pcm_dmix_hwsync,
+	.delay = snd_pcm_dmix_delay,
+	.prepare = snd_pcm_dmix_prepare,
+	.reset = snd_pcm_dmix_reset,
+	.start = snd_pcm_dmix_start,
+	.drop = snd_pcm_dmix_drop,
+	.drain = snd_pcm_dmix_drain,
+	.pause = snd_pcm_dmix_pause,
+	.rewindable = snd_pcm_dmix_rewindable,
+	.rewind = snd_pcm_dmix_rewind,
+	.forwardable = snd_pcm_dmix_forwardable,
+	.forward = snd_pcm_dmix_forward,
+	.resume = snd_pcm_direct_resume,
+	.link = NULL,
+	.link_slaves = NULL,
+	.unlink = NULL,
+	.writei = snd_pcm_mmap_writei,
+	.writen = snd_pcm_mmap_writen,
+	.readi = snd_pcm_dmix_readi,
+	.readn = snd_pcm_dmix_readn,
+	.avail_update = snd_pcm_dmix_avail_update,
+	.mmap_commit = snd_pcm_dmix_mmap_commit,
+	.htimestamp = snd_pcm_dmix_htimestamp,
+	.poll_descriptors = NULL,
+	.poll_descriptors_count = NULL,
+	.poll_revents = snd_pcm_dmix_poll_revents,
+};
+
+/**
+ * \brief Creates a new dmix PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param opts Direct PCM configurations
+ * \param params Parameters for slave
+ * \param root Configuration root
+ * \param sconf Slave configuration
+ * \param stream PCM Direction (stream)
+ * \param mode PCM Mode
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int snd_pcm_dmix_open(snd_pcm_t **pcmp, const char *name,
+		      struct snd_pcm_direct_open_conf *opts,
+		      struct slave_params *params,
+		      snd_config_t *root, snd_config_t *sconf,
+		      snd_pcm_stream_t stream, int mode)
+{
+	snd_pcm_t *pcm = NULL, *spcm = NULL;
+	snd_pcm_direct_t *dmix = NULL;
+	int ret, first_instance;
+	int fail_sem_loop = 10;
+
+	assert(pcmp);
+
+	if (stream != SND_PCM_STREAM_PLAYBACK) {
+		SNDERR("The dmix plugin supports only playback stream");
+		return -EINVAL;
+	}
+
+	dmix = calloc(1, sizeof(snd_pcm_direct_t));
+	if (!dmix) {
+		ret = -ENOMEM;
+		goto _err_nosem;
+	}
+	
+	ret = snd_pcm_direct_parse_bindings(dmix, params, opts->bindings);
+	if (ret < 0)
+		goto _err_nosem;
+	
+	dmix->ipc_key = opts->ipc_key;
+	dmix->ipc_perm = opts->ipc_perm;
+	dmix->ipc_gid = opts->ipc_gid;
+	dmix->semid = -1;
+	dmix->shmid = -1;
+
+	ret = snd_pcm_new(&pcm, dmix->type = SND_PCM_TYPE_DMIX, name, stream, mode);
+	if (ret < 0)
+		goto _err;
+
+	
+	while (1) {
+		ret = snd_pcm_direct_semaphore_create_or_connect(dmix);
+		if (ret < 0) {
+			SNDERR("unable to create IPC semaphore");
+			goto _err_nosem;
+		}
+		ret = snd_pcm_direct_semaphore_down(dmix, DIRECT_IPC_SEM_CLIENT);
+		if (ret < 0) {
+			snd_pcm_direct_semaphore_discard(dmix);
+			if (--fail_sem_loop <= 0)
+				goto _err_nosem;
+			continue;
+		}
+		break;
+	}
+		
+	first_instance = ret = snd_pcm_direct_shm_create_or_connect(dmix);
+	if (ret < 0) {
+		SNDERR("unable to create IPC shm instance");
+		goto _err;
+	}
+		
+	pcm->ops = &snd_pcm_dmix_ops;
+	pcm->fast_ops = &snd_pcm_dmix_fast_ops;
+	pcm->private_data = dmix;
+	dmix->state = SND_PCM_STATE_OPEN;
+	dmix->slowptr = opts->slowptr;
+	dmix->max_periods = opts->max_periods;
+	dmix->sync_ptr = snd_pcm_dmix_sync_ptr;
+
+	if (first_instance) {
+		/* recursion is already checked in
+		   snd_pcm_direct_get_slave_ipc_offset() */
+		ret = snd_pcm_open_slave(&spcm, root, sconf, stream,
+					 mode | SND_PCM_NONBLOCK, NULL);
+		if (ret < 0) {
+			SNDERR("unable to open slave");
+			goto _err;
+		}
+	
+		if (snd_pcm_type(spcm) != SND_PCM_TYPE_HW) {
+			SNDERR("dmix plugin can be only connected to hw plugin");
+			ret = -EINVAL;
+			goto _err;
+		}
+		
+		ret = snd_pcm_direct_initialize_slave(dmix, spcm, params);
+		if (ret < 0) {
+			SNDERR("unable to initialize slave");
+			goto _err;
+		}
+
+		dmix->spcm = spcm;
+
+		if (dmix->shmptr->use_server) {
+			dmix->server_free = dmix_server_free;
+		
+			ret = snd_pcm_direct_server_create(dmix);
+			if (ret < 0) {
+				SNDERR("unable to create server");
+				goto _err;
+			}
+		}
+
+		dmix->shmptr->type = spcm->type;
+	} else {
+		if (dmix->shmptr->use_server) {
+			/* up semaphore to avoid deadlock */
+			snd_pcm_direct_semaphore_up(dmix, DIRECT_IPC_SEM_CLIENT);
+			ret = snd_pcm_direct_client_connect(dmix);
+			if (ret < 0) {
+				SNDERR("unable to connect client");
+				goto _err_nosem;
+			}
+			
+			snd_pcm_direct_semaphore_down(dmix, DIRECT_IPC_SEM_CLIENT);
+			ret = snd_pcm_direct_open_secondary_client(&spcm, dmix, "dmix_client");
+			if (ret < 0)
+				goto _err;
+		} else {
+
+			ret = snd_pcm_open_slave(&spcm, root, sconf, stream,
+						 mode | SND_PCM_NONBLOCK |
+						 SND_PCM_APPEND,
+						 NULL);
+			if (ret < 0) {
+				SNDERR("unable to open slave");
+				goto _err;
+			}
+			if (snd_pcm_type(spcm) != SND_PCM_TYPE_HW) {
+				SNDERR("dmix plugin can be only connected to hw plugin");
+				ret = -EINVAL;
+				goto _err;
+			}
+		
+			ret = snd_pcm_direct_initialize_secondary_slave(dmix, spcm, params);
+			if (ret < 0) {
+				SNDERR("unable to initialize slave");
+				goto _err;
+			}
+		}
+
+		dmix->spcm = spcm;
+	}
+
+	ret = shm_sum_create_or_connect(dmix);
+	if (ret < 0) {
+		SNDERR("unable to initialize sum ring buffer");
+		goto _err;
+	}
+
+	ret = snd_pcm_direct_initialize_poll_fd(dmix);
+	if (ret < 0) {
+		SNDERR("unable to initialize poll_fd");
+		goto _err;
+	}
+
+	mix_select_callbacks(dmix);
+		
+	pcm->poll_fd = dmix->poll_fd;
+	pcm->poll_events = POLLIN;	/* it's different than other plugins */
+		
+	pcm->mmap_rw = 1;
+	snd_pcm_set_hw_ptr(pcm, &dmix->hw_ptr, -1, 0);
+	snd_pcm_set_appl_ptr(pcm, &dmix->appl_ptr, -1, 0);
+	
+	if (dmix->channels == UINT_MAX)
+		dmix->channels = dmix->shmptr->s.channels;
+
+	snd_pcm_direct_semaphore_up(dmix, DIRECT_IPC_SEM_CLIENT);
+
+	*pcmp = pcm;
+	return 0;
+	
+ _err:
+	if (dmix->timer)
+		snd_timer_close(dmix->timer);
+	if (dmix->server)
+		snd_pcm_direct_server_discard(dmix);
+	if (dmix->client)
+		snd_pcm_direct_client_discard(dmix);
+	if (spcm)
+		snd_pcm_close(spcm);
+	if (dmix->u.dmix.shmid_sum >= 0)
+		shm_sum_discard(dmix);
+	if (dmix->shmid >= 0)
+		snd_pcm_direct_shm_discard(dmix);
+	if (snd_pcm_direct_semaphore_discard(dmix) < 0)
+		snd_pcm_direct_semaphore_up(dmix, DIRECT_IPC_SEM_CLIENT);
+ _err_nosem:
+	if (dmix) {
+		free(dmix->bindings);
+		free(dmix);
+	}
+	if (pcm)
+		snd_pcm_free(pcm);
+	return ret;
+}
+
+/*! \page pcm_plugins
+
+\section pcm_plugins_dmix Plugin: dmix
+
+This plugin provides direct mixing of multiple streams. The resolution
+for 32-bit mixing is only 24-bit. The low significant byte is filled with
+zeros. The extra 8 bits are used for the saturation.
+
+\code
+pcm.name {
+	type dmix		# Direct mix
+	ipc_key INT		# unique IPC key
+	ipc_key_add_uid BOOL	# add current uid to unique IPC key
+	ipc_perm INT		# IPC permissions (octal, default 0600)
+	slave STR
+	# or
+	slave {			# Slave definition
+		pcm STR		# slave PCM name
+		# or
+		pcm { }		# slave PCM definition
+		format STR	# format definition
+		rate INT	# rate definition
+		channels INT
+		period_time INT	# in usec
+		# or
+		period_size INT	# in bytes
+		buffer_time INT	# in usec
+		# or
+		buffer_size INT # in bytes
+		periods INT	# when buffer_size or buffer_time is not specified
+	}
+	bindings {		# note: this is client independent!!!
+		N INT		# maps slave channel to client channel N
+	}
+	slowptr BOOL		# slow but more precise pointer updates
+}
+\endcode
+
+<code>ipc_key</code> specfies the unique IPC key in integer.
+This number must be unique for each different dmix definition,
+since the shared memory is created with this key number.
+When <code>ipc_key_add_uid</code> is set true, the uid value is
+added to the value set in <code>ipc_key</code>.  This will
+avoid the confliction of the same IPC key with different users
+concurrently.
+
+Note that the dmix plugin itself supports only a single configuration.
+That is, it supports only the fixed rate (default 48000), format
+(\c S16), channels (2), and period_time (125000).
+For using other configuration, you have to set the value explicitly
+in the slave PCM definition.  The rate, format and channels can be
+covered by an additional \ref pcm_plugins_dmix "plug plugin",
+but there is only one base configuration, anyway.
+
+An example configuration for setting 44100 Hz, \c S32_LE format
+as the slave PCM of "hw:0" is like below:
+\code
+pcm.dmix_44 {
+	type dmix
+	ipc_key 321456	# any unique value
+	ipc_key_add_uid true
+	slave {
+		pcm "hw:0"
+		format S32_LE
+		rate 44100
+	}
+}
+\endcode
+You can hear 48000 Hz samples still using this dmix pcm via plug plugin
+like:
+\code
+% aplay -Dplug:dmix_44 foo_48k.wav
+\endcode
+
+For using the dmix plugin for OSS emulation device, you have to set
+the period and the buffer sizes in power of two.  For example,
+\code
+pcm.dmixoss {
+	type dmix
+	ipc_key 321456	# any unique value
+	ipc_key_add_uid true
+	slave {
+		pcm "hw:0"
+		period_time 0
+		period_size 1024  # must be power of 2
+		buffer_size 8192  # ditto
+	}
+}
+\endcode
+<code>period_time 0</code> must be set, too, for resetting the
+default value.  In the case of soundcards with multi-channel IO,
+adding the bindings would help
+\code
+pcm.dmixoss {
+	...
+	bindings {
+		0 0   # map from 0 to 0
+		1 1   # map from 1 to 1
+	}
+}
+\endcode
+so that only the first two channels are used by dmix.
+Also, note that ICE1712 have the limited buffer size, 5513 frames
+(corresponding to 640 kB).  In this case, reduce the buffer_size
+to 4096.
+
+\subsection pcm_plugins_dmix_funcref Function reference
+
+<UL>
+  <LI>snd_pcm_dmix_open()
+  <LI>_snd_pcm_dmix_open()
+</UL>
+
+*/
+
+/**
+ * \brief Creates a new dmix PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param root Root configuration node
+ * \param conf Configuration node with dmix PCM description
+ * \param stream PCM Stream
+ * \param mode PCM Mode
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int _snd_pcm_dmix_open(snd_pcm_t **pcmp, const char *name,
+		       snd_config_t *root, snd_config_t *conf,
+		       snd_pcm_stream_t stream, int mode)
+{
+	snd_config_t *sconf;
+	struct slave_params params;
+	struct snd_pcm_direct_open_conf dopen;
+	int bsize, psize;
+	int err;
+
+	err = snd_pcm_direct_parse_open_conf(root, conf, stream, &dopen);
+	if (err < 0)
+		return err;
+
+	/* the default settings, it might be invalid for some hardware */
+	params.format = SND_PCM_FORMAT_S16;
+	params.rate = 48000;
+	params.channels = 2;
+	params.period_time = -1;
+	params.buffer_time = -1;
+	bsize = psize = -1;
+	params.periods = 3;
+
+	err = snd_pcm_slave_conf(root, dopen.slave, &sconf, 8,
+				 SND_PCM_HW_PARAM_FORMAT, SCONF_UNCHANGED, &params.format,
+				 SND_PCM_HW_PARAM_RATE, 0, &params.rate,
+				 SND_PCM_HW_PARAM_CHANNELS, 0, &params.channels,
+				 SND_PCM_HW_PARAM_PERIOD_TIME, 0, &params.period_time,
+				 SND_PCM_HW_PARAM_BUFFER_TIME, 0, &params.buffer_time,
+				 SND_PCM_HW_PARAM_PERIOD_SIZE, 0, &psize,
+				 SND_PCM_HW_PARAM_BUFFER_SIZE, 0, &bsize,
+				 SND_PCM_HW_PARAM_PERIODS, 0, &params.periods);
+	if (err < 0)
+		return err;
+
+	/* set a reasonable default */  
+	if (psize == -1 && params.period_time == -1)
+		params.period_time = 125000;    /* 0.125 seconds */
+
+	if (params.format == -2)
+		params.format = SND_PCM_FORMAT_UNKNOWN;
+	else if (!(dmix_supported_format & (1ULL << params.format))) {
+		/* sorry, limited features */
+		SNDERR("Unsupported format");
+		snd_config_delete(sconf);
+		return -EINVAL;
+	}
+
+	params.period_size = psize;
+	params.buffer_size = bsize;
+
+	err = snd_pcm_dmix_open(pcmp, name, &dopen, &params,
+				root, sconf, stream, mode);
+	snd_config_delete(sconf);
+	return err;
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(_snd_pcm_dmix_open, SND_PCM_DLSYM_VERSION);
+#endif
diff --git a/src/pcm/pcm_dmix_generic.c b/src/pcm/pcm_dmix_generic.c
new file mode 100644
index 0000000..9e9d3c3
--- /dev/null
+++ b/src/pcm/pcm_dmix_generic.c
@@ -0,0 +1,535 @@
+#if 0
+//#if defined(__i386__) || defined(__x86_64__)
+#define LOCK_PREFIX "lock ; "
+#define ARCH_ADD(p,a)					\
+	__asm__ __volatile__(LOCK_PREFIX "addl %1,%0"	\
+			     :"=m" (*p)			\
+			     :"ir" (a), "m" (*p))
+struct __xchg_dummy { unsigned long a[100]; };
+#define __xg(x) ((struct __xchg_dummy *)(x))
+static inline unsigned long __cmpxchg(volatile void *ptr, unsigned long old,
+				      unsigned long new, int size)
+{
+	unsigned long prev;
+	switch (size) {
+	case 1:
+		__asm__ __volatile__(LOCK_PREFIX "cmpxchgb %b1,%2"
+				     : "=a"(prev)
+				     : "q"(new), "m"(*__xg(ptr)), "0"(old)
+				     : "memory");
+		return prev;
+	case 2:
+		__asm__ __volatile__(LOCK_PREFIX "cmpxchgw %w1,%2"
+				     : "=a"(prev)
+				     : "q"(new), "m"(*__xg(ptr)), "0"(old)
+				     : "memory");
+		return prev;
+	case 4:
+		__asm__ __volatile__(LOCK_PREFIX "cmpxchgl %1,%2"
+				     : "=a"(prev)
+				     : "q"(new), "m"(*__xg(ptr)), "0"(old)
+				     : "memory");
+		return prev;
+	}
+	return old;
+}
+
+#define ARCH_CMPXCHG(ptr,o,n)\
+	((__typeof__(*(ptr)))__cmpxchg((ptr),(unsigned long)(o),\
+					(unsigned long)(n),sizeof(*(ptr))))
+#define IS_CONCURRENT	1	/* check race */
+#endif
+
+#ifndef ARCH_ADD
+#define ARCH_ADD(p,a) (*(p) += (a))
+#define ARCH_CMPXCHG(p,a,b) (*(p)) /* fake */
+#define NO_CONCURRENT_ACCESS	/* use semaphore to avoid race */
+#define IS_CONCURRENT	0	/* no race check */
+#endif
+
+#if IS_CONCURRENT
+static void mix_areas_16(unsigned int size,
+			 volatile signed short *dst, signed short *src,
+			 volatile signed int *sum, size_t dst_step,
+			 size_t src_step, size_t sum_step)
+{
+	register signed int sample, old_sample;
+
+	for (;;) {
+		sample = *src;
+		old_sample = *sum;
+		if (ARCH_CMPXCHG(dst, 0, 1) == 0)
+			sample -= old_sample;
+		ARCH_ADD(sum, sample);
+		do {
+			old_sample = *sum;
+			if (old_sample > 0x7fff)
+				sample = 0x7fff;
+			else if (old_sample < -0x8000)
+				sample = -0x8000;
+			else
+				sample = old_sample;
+			*dst = sample;
+		} while (IS_CONCURRENT && *sum != old_sample);
+		if (!--size)
+			return;
+		src = (signed short *) ((char *)src + src_step);
+		dst = (signed short *) ((char *)dst + dst_step);
+		sum = (signed int *)   ((char *)sum + sum_step);
+	}
+}
+
+static void mix_areas_32(unsigned int size,
+			 volatile signed int *dst, signed int *src,
+			 volatile signed int *sum, size_t dst_step,
+			 size_t src_step, size_t sum_step)
+{
+	register signed int sample, old_sample;
+
+	for (;;) {
+		sample = *src >> 8;
+		old_sample = *sum;
+		if (ARCH_CMPXCHG(dst, 0, 1) == 0)
+			sample -= old_sample;
+		ARCH_ADD(sum, sample);
+		do {
+			old_sample = *sum;
+			if (old_sample > 0x7fffff)
+				sample = 0x7fffffff;
+			else if (old_sample < -0x800000)
+				sample = -0x80000000;
+			else
+				sample = old_sample * 256;
+			*dst = sample;
+		} while (IS_CONCURRENT && *sum != old_sample);
+		if (!--size)
+			return;
+		src = (signed int *) ((char *)src + src_step);
+		dst = (signed int *) ((char *)dst + dst_step);
+		sum = (signed int *) ((char *)sum + sum_step);
+	}
+}
+
+static void mix_select_callbacks(snd_pcm_direct_t *dmix)
+{
+	dmix->u.dmix.mix_areas_16 = mix_areas_16;
+	dmix->u.dmix.mix_areas_32 = mix_areas_32;
+}
+
+#else
+
+/* non-concurrent version, supporting both endians */
+#define generic_dmix_supported_format \
+	((1ULL << SND_PCM_FORMAT_S16_LE) | (1ULL << SND_PCM_FORMAT_S32_LE) |\
+	 (1ULL << SND_PCM_FORMAT_S16_BE) | (1ULL << SND_PCM_FORMAT_S32_BE) |\
+	 (1ULL << SND_PCM_FORMAT_S24_LE) | (1ULL << SND_PCM_FORMAT_S24_3LE) | \
+	 (1ULL << SND_PCM_FORMAT_U8))
+
+#include <byteswap.h>
+
+static void generic_mix_areas_16_native(unsigned int size,
+					volatile signed short *dst,
+					signed short *src,
+					volatile signed int *sum,
+					size_t dst_step,
+					size_t src_step,
+					size_t sum_step)
+{
+	register signed int sample;
+
+	for (;;) {
+		sample = *src;
+		if (! *dst) {
+			*sum = sample;
+			*dst = *src;
+		} else {
+			sample += *sum;
+			*sum = sample;
+			if (sample > 0x7fff)
+				sample = 0x7fff;
+			else if (sample < -0x8000)
+				sample = -0x8000;
+			*dst = sample;
+		}
+		if (!--size)
+			return;
+		src = (signed short *) ((char *)src + src_step);
+		dst = (signed short *) ((char *)dst + dst_step);
+		sum = (signed int *)   ((char *)sum + sum_step);
+	}
+}
+
+static void generic_remix_areas_16_native(unsigned int size,
+					  volatile signed short *dst,
+					  signed short *src,
+					  volatile signed int *sum,
+					  size_t dst_step,
+					  size_t src_step,
+					  size_t sum_step)
+{
+	register signed int sample;
+
+	for (;;) {
+		sample = *src;
+		if (! *dst) {
+			*sum = -sample;
+			*dst = -sample;
+		} else {
+			*sum = sample = *sum - sample;
+			if (sample > 0x7fff)
+				sample = 0x7fff;
+			else if (sample < -0x8000)
+				sample = -0x8000;
+			*dst = sample;
+		}
+		if (!--size)
+			return;
+		src = (signed short *) ((char *)src + src_step);
+		dst = (signed short *) ((char *)dst + dst_step);
+		sum = (signed int *)   ((char *)sum + sum_step);
+	}
+}
+
+static void generic_mix_areas_32_native(unsigned int size,
+					volatile signed int *dst,
+					signed int *src,
+					volatile signed int *sum,
+					size_t dst_step,
+					size_t src_step,
+					size_t sum_step)
+{
+	register signed int sample;
+
+	for (;;) {
+		sample = *src >> 8;
+		if (! *dst) {
+			*sum = sample;
+			*dst = *src;
+		} else {
+			sample += *sum;
+			*sum = sample;
+			if (sample > 0x7fffff)
+				sample = 0x7fffffff;
+			else if (sample < -0x800000)
+				sample = -0x80000000;
+			else
+				sample *= 256;
+			*dst = sample;
+		}
+		if (!--size)
+			return;
+		src = (signed int *) ((char *)src + src_step);
+		dst = (signed int *) ((char *)dst + dst_step);
+		sum = (signed int *) ((char *)sum + sum_step);
+	}
+}
+
+static void generic_remix_areas_32_native(unsigned int size,
+					  volatile signed int *dst,
+					  signed int *src,
+					  volatile signed int *sum,
+					  size_t dst_step,
+					  size_t src_step,
+					  size_t sum_step)
+{
+	register signed int sample;
+
+	for (;;) {
+		sample = *src >> 8;
+		if (! *dst) {
+			*sum = -sample;
+			*dst = -*src;
+		} else {
+			*sum = sample = *sum - sample;
+			if (sample > 0x7fffff)
+				sample = 0x7fffffff;
+			else if (sample < -0x800000)
+				sample = -0x80000000;
+			else
+				sample *= 256;
+			*dst = sample;
+		}
+		if (!--size)
+			return;
+		src = (signed int *) ((char *)src + src_step);
+		dst = (signed int *) ((char *)dst + dst_step);
+		sum = (signed int *) ((char *)sum + sum_step);
+	}
+}
+
+static void generic_mix_areas_16_swap(unsigned int size,
+				      volatile signed short *dst,
+				      signed short *src,
+				      volatile signed int *sum,
+				      size_t dst_step,
+				      size_t src_step,
+				      size_t sum_step)
+{
+	register signed int sample;
+
+	for (;;) {
+		sample = (signed short) bswap_16(*src);
+		if (! *dst) {
+			*sum = sample;
+			*dst = *src;
+		} else {
+			sample += *sum;
+			*sum = sample;
+			if (sample > 0x7fff)
+				sample = 0x7fff;
+			else if (sample < -0x8000)
+				sample = -0x8000;
+			*dst = (signed short) bswap_16((signed short) sample);
+		}
+		if (!--size)
+			return;
+		src = (signed short *) ((char *)src + src_step);
+		dst = (signed short *) ((char *)dst + dst_step);
+		sum = (signed int *)   ((char *)sum + sum_step);
+	}
+}
+
+static void generic_remix_areas_16_swap(unsigned int size,
+				        volatile signed short *dst,
+				        signed short *src,
+				        volatile signed int *sum,
+				        size_t dst_step,
+				        size_t src_step,
+				        size_t sum_step)
+{
+	register signed int sample;
+
+	for (;;) {
+		sample = (signed short) bswap_16(*src);
+		if (! *dst) {
+			*sum = -sample;
+			*dst = (signed short) bswap_16((signed short) -sample);
+		} else {
+			*sum = sample = *sum - sample;
+			if (sample > 0x7fff)
+				sample = 0x7fff;
+			else if (sample < -0x8000)
+				sample = -0x8000;
+			*dst = (signed short) bswap_16((signed short) sample);
+		}
+		if (!--size)
+			return;
+		src = (signed short *) ((char *)src + src_step);
+		dst = (signed short *) ((char *)dst + dst_step);
+		sum = (signed int *)   ((char *)sum + sum_step);
+	}
+}
+
+static void generic_mix_areas_32_swap(unsigned int size,
+				      volatile signed int *dst,
+				      signed int *src,
+				      volatile signed int *sum,
+				      size_t dst_step,
+				      size_t src_step,
+				      size_t sum_step)
+{
+	register signed int sample;
+
+	for (;;) {
+		sample = bswap_32(*src) >> 8;
+		if (! *dst) {
+			*sum = sample;
+			*dst = *src;
+		} else {
+			sample += *sum;
+			*sum = sample;
+			if (sample > 0x7fffff)
+				sample = 0x7fffffff;
+			else if (sample < -0x800000)
+				sample = -0x80000000;
+			else
+				sample *= 256;
+			*dst = bswap_32(sample);
+		}
+		if (!--size)
+			return;
+		src = (signed int *) ((char *)src + src_step);
+		dst = (signed int *) ((char *)dst + dst_step);
+		sum = (signed int *) ((char *)sum + sum_step);
+	}
+}
+
+static void generic_remix_areas_32_swap(unsigned int size,
+				        volatile signed int *dst,
+				        signed int *src,
+				        volatile signed int *sum,
+				        size_t dst_step,
+				        size_t src_step,
+				        size_t sum_step)
+{
+	register signed int sample;
+
+	for (;;) {
+		sample = bswap_32(*src) >> 8;
+		if (! *dst) {
+			*sum = -sample;
+			*dst = bswap_32(-sample);
+		} else {
+			*sum = sample = *sum - sample;
+			if (sample > 0x7fffff)
+				sample = 0x7fffffff;
+			else if (sample < -0x800000)
+				sample = -0x80000000;
+			else
+				sample *= 256;
+			*dst = bswap_32(sample);
+		}
+		if (!--size)
+			return;
+		src = (signed int *) ((char *)src + src_step);
+		dst = (signed int *) ((char *)dst + dst_step);
+		sum = (signed int *) ((char *)sum + sum_step);
+	}
+}
+
+/* always little endian */
+static void generic_mix_areas_24(unsigned int size,
+				 volatile unsigned char *dst,
+				 unsigned char *src,
+				 volatile signed int *sum,
+				 size_t dst_step,
+				 size_t src_step,
+				 size_t sum_step)
+{
+	register signed int sample;
+
+	for (;;) {
+		sample = src[0] | (src[1] << 8) | (((signed char *)src)[2] << 16);
+		if (!(dst[0] | dst[1] | dst[2])) {
+			*sum = sample;
+		} else {
+			sample += *sum;
+			*sum = sample;
+			if (sample > 0x7fffff)
+				sample = 0x7fffff;
+			else if (sample < -0x800000)
+				sample = -0x800000;
+		}
+		dst[0] = sample;
+		dst[1] = sample >> 8;
+		dst[2] = sample >> 16;
+		if (!--size)
+			return;
+		dst += dst_step;
+		src += src_step;
+		sum = (signed int *) ((char *)sum + sum_step);
+	}
+}
+
+static void generic_remix_areas_24(unsigned int size,
+				   volatile unsigned char *dst,
+				   unsigned char *src,
+				   volatile signed int *sum,
+				   size_t dst_step,
+				   size_t src_step,
+				   size_t sum_step)
+{
+	register signed int sample;
+
+	for (;;) {
+		sample = src[0] | (src[1] << 8) | (((signed char *)src)[2] << 16);
+		if (!(dst[0] | dst[1] | dst[2])) {
+			sample = -sample;
+			*sum = sample;
+		} else {
+			*sum = sample = *sum - sample;
+			if (sample > 0x7fffff)
+				sample = 0x7fffff;
+			else if (sample < -0x800000)
+				sample = -0x800000;
+		}
+		dst[0] = sample;
+		dst[1] = sample >> 8;
+		dst[2] = sample >> 16;
+		if (!--size)
+			return;
+		dst += dst_step;
+		src += src_step;
+		sum = (signed int *) ((char *)sum + sum_step);
+	}
+}
+
+static void generic_mix_areas_u8(unsigned int size,
+				 volatile unsigned char *dst,
+				 unsigned char *src,
+				 volatile signed int *sum,
+				 size_t dst_step,
+				 size_t src_step,
+				 size_t sum_step)
+{
+	for (;;) {
+		register int sample = *src - 0x80;
+		if (*dst == 0x80) {
+			*sum = sample;
+		} else {
+			sample += *sum;
+			*sum = sample;
+			if (sample > 0x7f)
+				sample = 0x7f;
+			else if (sample < -0x80)
+				sample = -0x80;
+		}
+		*dst = sample + 0x80;
+		if (!--size)
+			return;
+		dst += dst_step;
+		src += src_step;
+		sum = (signed int *) ((char *)sum + sum_step);
+	}
+}
+
+static void generic_remix_areas_u8(unsigned int size,
+				   volatile unsigned char *dst,
+				   unsigned char *src,
+				   volatile signed int *sum,
+				   size_t dst_step,
+				   size_t src_step,
+				   size_t sum_step)
+{
+	for (;;) {
+		register int sample = *src - 0x80;
+		if (*dst == 0x80) {
+			sample = -sample;
+			*sum = sample;
+		} else {
+			*sum = sample = *sum - sample;
+			if (sample > 0x7f)
+				sample = 0x7f;
+			else if (sample < -0x80)
+				sample = -0x80;
+		}
+		*dst = sample + 0x80;
+		if (!--size)
+			return;
+		dst += dst_step;
+		src += src_step;
+		sum = (signed int *) ((char *)sum + sum_step);
+	}
+}
+
+
+static void generic_mix_select_callbacks(snd_pcm_direct_t *dmix)
+{
+	if (snd_pcm_format_cpu_endian(dmix->shmptr->s.format)) {
+		dmix->u.dmix.mix_areas_16 = generic_mix_areas_16_native;
+		dmix->u.dmix.mix_areas_32 = generic_mix_areas_32_native;
+		dmix->u.dmix.remix_areas_16 = generic_remix_areas_16_native;
+		dmix->u.dmix.remix_areas_32 = generic_remix_areas_32_native;
+	} else {
+		dmix->u.dmix.mix_areas_16 = generic_mix_areas_16_swap;
+		dmix->u.dmix.mix_areas_32 = generic_mix_areas_32_swap;
+		dmix->u.dmix.remix_areas_16 = generic_remix_areas_16_swap;
+		dmix->u.dmix.remix_areas_32 = generic_remix_areas_32_swap;
+	}
+	dmix->u.dmix.mix_areas_24 = generic_mix_areas_24;
+	dmix->u.dmix.mix_areas_u8 = generic_mix_areas_u8;
+	dmix->u.dmix.remix_areas_24 = generic_remix_areas_24;
+	dmix->u.dmix.remix_areas_u8 = generic_remix_areas_u8;
+}
+
+#endif
diff --git a/src/pcm/pcm_dmix_i386.c b/src/pcm/pcm_dmix_i386.c
new file mode 100644
index 0000000..dcc6b9a
--- /dev/null
+++ b/src/pcm/pcm_dmix_i386.c
@@ -0,0 +1,133 @@
+/*
+ * optimized mixing code for i386
+ */
+
+#define MIX_AREAS_16 mix_areas_16
+#define MIX_AREAS_16_MMX mix_areas_16_mmx
+#define MIX_AREAS_32 mix_areas_32
+#define MIX_AREAS_24 mix_areas_24
+#define MIX_AREAS_24_CMOV mix_areas_24_cmov
+#define LOCK_PREFIX ""
+#define XADD "addl"
+#define XSUB "subl"
+#include "pcm_dmix_i386.h"
+#undef MIX_AREAS_16
+#undef MIX_AREAS_16_MMX
+#undef MIX_AREAS_32
+#undef MIX_AREAS_24
+#undef MIX_AREAS_24_CMOV
+#undef LOCK_PREFIX
+#undef XADD
+#undef XSUB
+
+#define MIX_AREAS_16 remix_areas_16
+#define MIX_AREAS_16_MMX remix_areas_16_mmx
+#define MIX_AREAS_32 remix_areas_32
+#define MIX_AREAS_24 remix_areas_24
+#define MIX_AREAS_24_CMOV remix_areas_24_cmov
+#define LOCK_PREFIX ""
+#define XADD "subl"
+#define XSUB "addl"
+#include "pcm_dmix_i386.h"
+#undef MIX_AREAS_16
+#undef MIX_AREAS_16_MMX
+#undef MIX_AREAS_32
+#undef MIX_AREAS_24
+#undef MIX_AREAS_24_CMOV
+#undef LOCK_PREFIX
+#undef XADD
+#undef XSUB
+
+#define MIX_AREAS_16 mix_areas_16_smp
+#define MIX_AREAS_16_MMX mix_areas_16_smp_mmx
+#define MIX_AREAS_32 mix_areas_32_smp
+#define MIX_AREAS_24 mix_areas_24_smp
+#define MIX_AREAS_24_CMOV mix_areas_24_smp_cmov
+#define LOCK_PREFIX "lock ; "
+#define XADD "addl"
+#define XSUB "subl"
+#include "pcm_dmix_i386.h"
+#undef MIX_AREAS_16
+#undef MIX_AREAS_16_MMX
+#undef MIX_AREAS_32
+#undef MIX_AREAS_24
+#undef MIX_AREAS_24_CMOV
+#undef LOCK_PREFIX
+#undef XADD
+#undef XSUB
+ 
+#define MIX_AREAS_16 remix_areas_16_smp
+#define MIX_AREAS_16_MMX remix_areas_16_smp_mmx
+#define MIX_AREAS_32 remix_areas_32_smp
+#define MIX_AREAS_24 remix_areas_24_smp
+#define MIX_AREAS_24_CMOV remix_areas_24_smp_cmov
+#define LOCK_PREFIX "lock ; "
+#define XADD "subl"
+#define XSUB "addl"
+#include "pcm_dmix_i386.h"
+#undef MIX_AREAS_16
+#undef MIX_AREAS_16_MMX
+#undef MIX_AREAS_32
+#undef MIX_AREAS_24
+#undef MIX_AREAS_24_CMOV
+#undef LOCK_PREFIX
+#undef XADD
+#undef XSUB
+ 
+#define i386_dmix_supported_format \
+	((1ULL << SND_PCM_FORMAT_S16_LE) |\
+	 (1ULL << SND_PCM_FORMAT_S32_LE) |\
+	 (1ULL << SND_PCM_FORMAT_S24_LE) |\
+	 (1ULL << SND_PCM_FORMAT_S24_3LE))
+
+#define dmix_supported_format \
+	(i386_dmix_supported_format | generic_dmix_supported_format)
+
+static void mix_select_callbacks(snd_pcm_direct_t *dmix)
+{
+	static int smp = 0, mmx = 0, cmov = 0;
+
+	if (!((1ULL<< dmix->shmptr->s.format) & i386_dmix_supported_format)) {
+		generic_mix_select_callbacks(dmix);
+		return;
+	}
+
+	if (!smp) {
+		FILE *in;
+		char line[255];
+	
+		/* try to determine the capabilities of the CPU */
+		in = fopen("/proc/cpuinfo", "r");
+		if (in) {
+			while (!feof(in)) {
+				fgets(line, sizeof(line), in);
+				if (!strncmp(line, "processor", 9))
+					smp++;
+				else if (!strncmp(line, "flags", 5)) {
+					if (strstr(line, " mmx"))
+						mmx = 1;
+					if (strstr(line, " cmov"))
+						cmov = 1;
+				}
+			}
+			fclose(in);
+		}
+	}
+	
+	if (mmx) {
+		dmix->u.dmix.mix_areas_16 = smp > 1 ? mix_areas_16_smp_mmx : mix_areas_16_mmx;
+		dmix->u.dmix.remix_areas_16 = smp > 1 ? remix_areas_16_smp_mmx : remix_areas_16_mmx;
+	} else {
+		dmix->u.dmix.mix_areas_16 = smp > 1 ? mix_areas_16_smp : mix_areas_16;
+		dmix->u.dmix.remix_areas_16 = smp > 1 ? remix_areas_16_smp : remix_areas_16;
+	}
+	dmix->u.dmix.mix_areas_32 = smp > 1 ? mix_areas_32_smp : mix_areas_32;
+	dmix->u.dmix.remix_areas_32 = smp > 1 ? remix_areas_32_smp : remix_areas_32;
+	if (cmov) {
+		dmix->u.dmix.mix_areas_24 = smp > 1 ? mix_areas_24_smp_cmov : mix_areas_24_cmov;
+		dmix->u.dmix.remix_areas_24 = smp > 1 ? remix_areas_24_smp_cmov : remix_areas_24_cmov;
+	} else {
+		dmix->u.dmix.mix_areas_24 = smp > 1 ? mix_areas_24_smp: mix_areas_24;
+		dmix->u.dmix.remix_areas_24 = smp > 1 ? remix_areas_24_smp: remix_areas_24;
+	}
+}
diff --git a/src/pcm/pcm_dmix_i386.h b/src/pcm/pcm_dmix_i386.h
new file mode 100644
index 0000000..462371a
--- /dev/null
+++ b/src/pcm/pcm_dmix_i386.h
@@ -0,0 +1,559 @@
+/**
+ * \file pcm/pcm_dmix_i386.h
+ * \ingroup PCM_Plugins
+ * \brief PCM Direct Stream Mixing (dmix) Plugin Interface - I386 assembler code
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \date 2003
+ */
+/*
+ *  PCM - Direct Stream Mixing
+ *  Copyright (c) 2003 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+/*
+ *  for plain i386
+ */
+static void MIX_AREAS_16(unsigned int size,
+			 volatile signed short *dst, signed short *src,
+			 volatile signed int *sum, size_t dst_step,
+			 size_t src_step, size_t sum_step)
+{
+	unsigned int old_ebx;
+
+	/*
+	 *  ESI - src
+	 *  EDI - dst
+	 *  EBX - sum
+	 *  ECX - old sample
+	 *  EAX - sample / temporary
+	 *  EDX - temporary
+	 */
+	__asm__ __volatile__ (
+		"\n"
+
+		"\tmovl %%ebx, %7\n"	/* ebx is GOT pointer (-fPIC) */
+		/*
+		 *  initialization, load ESI, EDI, EBX registers
+		 */
+		"\tmovl %1, %%edi\n"
+		"\tmovl %2, %%esi\n"
+		"\tmovl %3, %%ebx\n"
+		"\tcmpl $0, %0\n"
+		"\tjnz 2f\n"
+		"\tjmp 7f\n"
+
+
+		/*
+		 * for (;;)
+		 */
+		"\t.p2align 4,,15\n"
+		"1:"
+		"\tadd %4, %%edi\n"
+		"\tadd %5, %%esi\n"
+		"\tadd %6, %%ebx\n"
+
+		/*
+		 *   sample = *src;
+		 *   sum_sample = *sum;
+		 *   if (cmpxchg(*dst, 0, 1) == 0)
+		 *     sample -= sum_sample;
+		 *   xadd(*sum, sample);
+		 */
+
+		"2:"
+		"\tmovw $0, %%ax\n"
+		"\tmovw $1, %%cx\n"
+		"\tmovl (%%ebx), %%edx\n"
+		"\t" LOCK_PREFIX "cmpxchgw %%cx, (%%edi)\n"
+		"\tmovswl (%%esi), %%ecx\n"
+		"\tjnz 3f\n"
+		"\t" XSUB " %%edx, %%ecx\n"
+		"3:"
+		"\t" LOCK_PREFIX XADD " %%ecx, (%%ebx)\n"
+
+		/*
+		 *   do {
+		 *     sample = old_sample = *sum;
+		 *     saturate(v);
+		 *     *dst = sample;
+		 *   } while (v != *sum);
+		 */
+
+		"4:"
+		"\tmovl (%%ebx), %%ecx\n"
+		"\tcmpl $0x7fff,%%ecx\n"
+		"\tjg 5f\n"
+		"\tcmpl $-0x8000,%%ecx\n"
+		"\tjl 6f\n"
+		"\tmovw %%cx, (%%edi)\n"
+		"\tcmpl %%ecx, (%%ebx)\n"
+		"\tjnz 4b\n"
+
+		/*
+		 * while (size-- > 0)
+		 */
+		"\tdecl %0\n"
+		"\tjnz 1b\n"
+		"\tjmp 7f\n"
+
+		/*
+		 *  sample > 0x7fff
+		 */
+
+		"\t.p2align 4,,15\n"
+
+		"5:"
+		"\tmovw $0x7fff, (%%edi)\n"
+		"\tcmpl %%ecx,(%%ebx)\n"
+		"\tjnz 4b\n"
+		"\tdecl %0\n"
+		"\tjnz 1b\n"
+		"\tjmp 7f\n"
+
+		/*
+		 *  sample < -0x8000
+		 */
+
+		"\t.p2align 4,,15\n"
+
+		"6:"
+		"\tmovw $-0x8000, (%%edi)\n"
+		"\tcmpl %%ecx, (%%ebx)\n"
+		"\tjnz 4b\n"
+		"\tdecl %0\n"
+		"\tjnz 1b\n"
+		
+		"7:"
+		"\tmovl %7, %%ebx\n"	/* ebx is GOT pointer (-fPIC) */
+
+		: /* no output regs */
+		: "m" (size), "m" (dst), "m" (src),
+		  "m" (sum), "m" (dst_step), "m" (src_step),
+		  "m" (sum_step), "m" (old_ebx)
+		: "esi", "edi", "edx", "ecx", "eax"
+	);
+}
+
+/*
+ *  MMX optimized
+ */
+static void MIX_AREAS_16_MMX(unsigned int size,
+			     volatile signed short *dst, signed short *src,
+			     volatile signed int *sum, size_t dst_step,
+			     size_t src_step, size_t sum_step)
+{
+	unsigned int old_ebx;
+
+	/*
+	 *  ESI - src
+	 *  EDI - dst
+	 *  EBX - sum
+	 *  ECX - old sample
+	 *  EAX - sample / temporary
+	 *  EDX - temporary
+	 */
+	__asm__ __volatile__ (
+		"\n"
+
+		"\tmovl %%ebx, %7\n"	/* ebx is GOT pointer (-fPIC) */
+		/*
+		 *  initialization, load ESI, EDI, EBX registers
+		 */
+		"\tmovl %1, %%edi\n"
+		"\tmovl %2, %%esi\n"
+		"\tmovl %3, %%ebx\n"
+		"\tcmpl $0, %0\n"
+		"\tjnz 2f\n"
+		"\tjmp 5f\n"
+
+		"\t.p2align 4,,15\n"
+		"1:"
+		"\tadd %4, %%edi\n"
+		"\tadd %5, %%esi\n"
+		"\tadd %6, %%ebx\n"
+
+		"2:"
+		/*
+		 *   sample = *src;
+		 *   sum_sample = *sum;
+		 *   if (cmpxchg(*dst, 0, 1) == 0)
+		 *     sample -= sum_sample;
+		 *   xadd(*sum, sample);
+		 */
+		"\tmovw $0, %%ax\n"
+		"\tmovw $1, %%cx\n"
+		"\tmovl (%%ebx), %%edx\n"
+		"\t" LOCK_PREFIX "cmpxchgw %%cx, (%%edi)\n"
+		"\tmovswl (%%esi), %%ecx\n"
+		"\tjnz 3f\n"
+		"\t" XSUB " %%edx, %%ecx\n"
+		"3:"
+		"\t" LOCK_PREFIX XADD " %%ecx, (%%ebx)\n"
+
+		/*
+		 *   do {
+		 *     sample = old_sample = *sum;
+		 *     saturate(v);
+		 *     *dst = sample;
+		 *   } while (v != *sum);
+		 */
+
+		"4:"
+		"\tmovl (%%ebx), %%ecx\n"
+		"\tmovd %%ecx, %%mm0\n"
+		"\tpackssdw %%mm1, %%mm0\n"
+		"\tmovd %%mm0, %%eax\n"
+		"\tmovw %%ax, (%%edi)\n"
+		"\tcmpl %%ecx, (%%ebx)\n"
+		"\tjnz 4b\n"
+
+		/*
+		 * while (size-- > 0)
+		 */
+		"\tdecl %0\n"
+		"\tjnz 1b\n"
+		"\temms\n"
+                "5:"
+		"\tmovl %7, %%ebx\n"	/* ebx is GOT pointer (-fPIC) */
+
+		: /* no output regs */
+		: "m" (size), "m" (dst), "m" (src),
+		  "m" (sum), "m" (dst_step), "m" (src_step),
+		  "m" (sum_step), "m" (old_ebx)
+		: "esi", "edi", "edx", "ecx", "eax"
+	);
+}
+
+/*
+ *  for plain i386, 32-bit version (24-bit resolution)
+ */
+static void MIX_AREAS_32(unsigned int size,
+			 volatile signed int *dst, signed int *src,
+			 volatile signed int *sum, size_t dst_step,
+			 size_t src_step, size_t sum_step)
+{
+	unsigned int old_ebx;
+
+	/*
+	 *  ESI - src
+	 *  EDI - dst
+	 *  EBX - sum
+	 *  ECX - old sample
+	 *  EAX - sample / temporary
+	 *  EDX - temporary
+	 */
+	__asm__ __volatile__ (
+		"\n"
+
+		"\tmovl %%ebx, %7\n"	/* ebx is GOT pointer (-fPIC) */
+		/*
+		 *  initialization, load ESI, EDI, EBX registers
+		 */
+		"\tmovl %1, %%edi\n"
+		"\tmovl %2, %%esi\n"
+		"\tmovl %3, %%ebx\n"
+		"\tcmpl $0, %0\n"
+		"\tjnz 1f\n"
+		"\tjmp 6f\n"
+
+		"\t.p2align 4,,15\n"
+
+		"1:"
+
+		/*
+		 *   sample = *src;
+		 *   sum_sample = *sum;
+		 *   if (cmpxchg(*dst, 0, 1) == 0)
+		 *     sample -= sum_sample;
+		 *   xadd(*sum, sample);
+		 */
+		"\tmovl $0, %%eax\n"
+		"\tmovl $1, %%ecx\n"
+		"\tmovl (%%ebx), %%edx\n"
+		"\t" LOCK_PREFIX "cmpxchgl %%ecx, (%%edi)\n"
+		"\tjnz 2f\n"
+		"\tmovl (%%esi), %%ecx\n"
+		/* sample >>= 8 */
+		"\tsarl $8, %%ecx\n"
+		"\t" XSUB " %%edx, %%ecx\n"
+		"\tjmp 21f\n"
+		"2:"
+		"\tmovl (%%esi), %%ecx\n"
+		/* sample >>= 8 */
+		"\tsarl $8, %%ecx\n"
+		"21:"
+		"\t" LOCK_PREFIX XADD " %%ecx, (%%ebx)\n"
+
+		/*
+		 *   do {
+		 *     sample = old_sample = *sum;
+		 *     saturate(v);
+		 *     *dst = sample;
+		 *   } while (v != *sum);
+		 */
+
+		"3:"
+		"\tmovl (%%ebx), %%ecx\n"
+		/*
+		 *  if (sample > 0x7fff00)
+		 */
+		"\tmovl $0x7fffff, %%eax\n"
+		"\tcmpl %%eax, %%ecx\n"
+		"\tjg 4f\n"
+		/*
+		 *  if (sample < -0x800000)
+		 */
+		"\tmovl $-0x800000, %%eax\n"
+		"\tcmpl %%eax, %%ecx\n"
+		"\tjl 4f\n"
+		"\tmovl %%ecx, %%eax\n"
+		"4:"
+		/*
+		 *  sample <<= 8;
+		 */
+		"\tsall $8, %%eax\n"
+		"\tmovl %%eax, (%%edi)\n"
+		"\tcmpl %%ecx, (%%ebx)\n"
+		"\tjnz 3b\n"
+
+		/*
+		 * while (size-- > 0)
+		 */
+		"\tdecl %0\n"
+		"\tjz 6f\n"
+		"\tadd %4, %%edi\n"
+		"\tadd %5, %%esi\n"
+		"\tadd %6, %%ebx\n"
+		"\tjmp 1b\n"
+		
+		"6:"
+		"\tmovl %7, %%ebx\n"	/* ebx is GOT pointer (-fPIC) */
+
+		: /* no output regs */
+		: "m" (size), "m" (dst), "m" (src),
+		  "m" (sum), "m" (dst_step), "m" (src_step),
+		  "m" (sum_step), "m" (old_ebx)
+		: "esi", "edi", "edx", "ecx", "eax"
+	);
+}
+
+/*
+ * 24-bit version for plain i386
+ */
+static void MIX_AREAS_24(unsigned int size,
+			 volatile unsigned char *dst, unsigned char *src,
+			 volatile signed int *sum, size_t dst_step,
+			 size_t src_step, size_t sum_step)
+{
+	unsigned int old_ebx;
+
+	/*
+	 *  ESI - src
+	 *  EDI - dst
+	 *  EBX - sum
+	 *  ECX - old sample
+	 *  EAX - sample / temporary
+	 *  EDX - temporary
+	 */
+	__asm__ __volatile__ (
+		"\n"
+
+		"\tmovl %%ebx, %7\n"	/* ebx is GOT pointer (-fPIC) */
+		/*
+		 *  initialization, load ESI, EDI, EBX registers
+		 */
+		"\tmovl %1, %%edi\n"
+		"\tmovl %2, %%esi\n"
+		"\tmovl %3, %%ebx\n"
+		"\tcmpl $0, %0\n"
+		"\tjnz 1f\n"
+		"\tjmp 6f\n"
+
+		"\t.p2align 4,,15\n"
+
+		"1:"
+
+		/*
+		 *   sample = *src;
+		 *   sum_sample = *sum;
+		 *   if (test_and_set_bit(0, dst) == 0)
+		 *     sample -= sum_sample;
+		 *   *sum += sample;
+		 */
+		"\tmovsbl 2(%%esi), %%eax\n"
+		"\tmovzwl (%%esi), %%ecx\n"
+		"\tmovl (%%ebx), %%edx\n"
+		"\tsall $16, %%eax\n"
+		"\torl %%eax, %%ecx\n"
+		"\t" LOCK_PREFIX "btsw $0, (%%edi)\n"
+		"\tjc 2f\n"
+		"\t" XSUB " %%edx, %%ecx\n"
+		"2:"
+		"\t" LOCK_PREFIX XADD " %%ecx, (%%ebx)\n"
+
+		/*
+		 *   do {
+		 *     sample = old_sample = *sum;
+		 *     saturate(sample);
+		 *     *dst = sample | 1;
+		 *   } while (old_sample != *sum);
+		 */
+
+		"3:"
+		"\tmovl (%%ebx), %%ecx\n"
+		/*
+		 *  if (sample > 0x7fffff)
+		 */
+		"\tmovl $0x7fffff, %%eax\n"
+		"\tcmpl %%eax, %%ecx\n"
+		"\tjg 4f\n"
+		/*
+		 *  if (sample < -0x7fffff)
+		 */
+		"\tmovl $-0x7fffff, %%eax\n"
+		"\tcmpl %%eax, %%ecx\n"
+		"\tjl 4f\n"
+		"\tmovl %%ecx, %%eax\n"
+		"\torl $1, %%eax\n"
+		"4:"
+		"\tmovw %%ax, (%%edi)\n"
+		"\tshrl $16, %%eax\n"
+		"\tmovb %%al, 2(%%edi)\n"
+		"\tcmpl %%ecx, (%%ebx)\n"
+		"\tjnz 3b\n"
+
+		/*
+		 * while (size-- > 0)
+		 */
+		"\tdecl %0\n"
+		"\tjz 6f\n"
+		"\tadd %4, %%edi\n"
+		"\tadd %5, %%esi\n"
+		"\tadd %6, %%ebx\n"
+		"\tjmp 1b\n"
+		
+		"6:"
+		"\tmovl %7, %%ebx\n"	/* ebx is GOT pointer (-fPIC) */
+
+		: /* no output regs */
+		: "m" (size), "m" (dst), "m" (src),
+		  "m" (sum), "m" (dst_step), "m" (src_step),
+		  "m" (sum_step), "m" (old_ebx)
+		: "esi", "edi", "edx", "ecx", "eax"
+	);
+}
+
+/*
+ * 24-bit version for Pentium Pro/II
+ */
+static void MIX_AREAS_24_CMOV(unsigned int size,
+			      volatile unsigned char *dst, unsigned char *src,
+			      volatile signed int *sum, size_t dst_step,
+			      size_t src_step, size_t sum_step)
+{
+	unsigned int old_ebx;
+
+	/*
+	 *  ESI - src
+	 *  EDI - dst
+	 *  EBX - sum
+	 *  ECX - old sample
+	 *  EAX - sample / temporary
+	 *  EDX - temporary
+	 */
+	__asm__ __volatile__ (
+		"\n"
+
+		"\tmovl %%ebx, %7\n"	/* ebx is GOT pointer (-fPIC) */
+		/*
+		 *  initialization, load ESI, EDI, EBX registers
+		 */
+		"\tmovl %1, %%edi\n"
+		"\tmovl %2, %%esi\n"
+		"\tmovl %3, %%ebx\n"
+		"\tcmpl $0, %0\n"
+		"\tjz 6f\n"
+
+		"\t.p2align 4,,15\n"
+
+		"1:"
+
+		/*
+		 *   sample = *src;
+		 *   sum_sample = *sum;
+		 *   if (test_and_set_bit(0, dst) == 0)
+		 *     sample -= sum_sample;
+		 *   *sum += sample;
+		 */
+		"\tmovsbl 2(%%esi), %%eax\n"
+		"\tmovzwl (%%esi), %%ecx\n"
+		"\tmovl (%%ebx), %%edx\n"
+		"\tsall $16, %%eax\n"
+		"\t" LOCK_PREFIX "btsw $0, (%%edi)\n"
+		"\tleal (%%ecx,%%eax,1), %%ecx\n"
+		"\tjc 2f\n"
+		"\t" XSUB " %%edx, %%ecx\n"
+		"2:"
+		"\t" LOCK_PREFIX XADD " %%ecx, (%%ebx)\n"
+
+		/*
+		 *   do {
+		 *     sample = old_sample = *sum;
+		 *     saturate(sample);
+		 *     *dst = sample | 1;
+		 *   } while (old_sample != *sum);
+		 */
+
+		"3:"
+		"\tmovl (%%ebx), %%ecx\n"
+
+		"\tmovl $0x7fffff, %%eax\n"
+		"\tmovl $-0x7fffff, %%edx\n"
+		"\tcmpl %%eax, %%ecx\n"
+		"\tcmovng %%ecx, %%eax\n"
+		"\tcmpl %%edx, %%ecx\n"
+		"\tcmovl %%edx, %%eax\n"
+
+		"\torl $1, %%eax\n"
+		"\tmovw %%ax, (%%edi)\n"
+		"\tshrl $16, %%eax\n"
+		"\tmovb %%al, 2(%%edi)\n"
+
+		"\tcmpl %%ecx, (%%ebx)\n"
+		"\tjnz 3b\n"
+
+		/*
+		 * while (size-- > 0)
+		 */
+		"\tadd %4, %%edi\n"
+		"\tadd %5, %%esi\n"
+		"\tadd %6, %%ebx\n"
+		"\tdecl %0\n"
+		"\tjnz 1b\n"
+		
+		"6:"
+		"\tmovl %7, %%ebx\n"	/* ebx is GOT pointer (-fPIC) */
+
+		: /* no output regs */
+		: "m" (size), "m" (dst), "m" (src),
+		  "m" (sum), "m" (dst_step), "m" (src_step),
+		  "m" (sum_step), "m" (old_ebx)
+		: "esi", "edi", "edx", "ecx", "eax"
+	);
+}
diff --git a/src/pcm/pcm_dmix_x86_64.c b/src/pcm/pcm_dmix_x86_64.c
new file mode 100644
index 0000000..831046d
--- /dev/null
+++ b/src/pcm/pcm_dmix_x86_64.c
@@ -0,0 +1,100 @@
+/*
+ * optimized mixing code for x86-64
+ */
+
+#define MIX_AREAS_16 mix_areas_16
+#define MIX_AREAS_32 mix_areas_32
+#define MIX_AREAS_24 mix_areas_24
+#define LOCK_PREFIX ""
+#define XADD "addl"
+#define XSUB "subl"
+#include "pcm_dmix_x86_64.h"
+#undef MIX_AREAS_16
+#undef MIX_AREAS_32
+#undef MIX_AREAS_24
+#undef LOCK_PREFIX
+#undef XADD
+#undef XSUB
+
+#define MIX_AREAS_16 remix_areas_16
+#define MIX_AREAS_32 remix_areas_32
+#define MIX_AREAS_24 remix_areas_24
+#define LOCK_PREFIX ""
+#define XADD "subl"
+#define XSUB "addl"
+#include "pcm_dmix_x86_64.h"
+#undef MIX_AREAS_16
+#undef MIX_AREAS_32
+#undef MIX_AREAS_24
+#undef LOCK_PREFIX
+#undef XADD
+#undef XSUB
+
+#define MIX_AREAS_16 mix_areas_16_smp
+#define MIX_AREAS_32 mix_areas_32_smp
+#define MIX_AREAS_24 mix_areas_24_smp
+#define LOCK_PREFIX "lock ; "
+#define XADD "addl"
+#define XSUB "subl"
+#include "pcm_dmix_x86_64.h"
+#undef MIX_AREAS_16
+#undef MIX_AREAS_32
+#undef MIX_AREAS_24
+#undef LOCK_PREFIX
+#undef XADD
+#undef XSUB
+ 
+#define MIX_AREAS_16 remix_areas_16_smp
+#define MIX_AREAS_32 remix_areas_32_smp
+#define MIX_AREAS_24 remix_areas_24_smp
+#define LOCK_PREFIX "lock ; "
+#define XADD "subl"
+#define XSUB "addl"
+#include "pcm_dmix_x86_64.h"
+#undef MIX_AREAS_16
+#undef MIX_AREAS_32
+#undef MIX_AREAS_24
+#undef LOCK_PREFIX
+#undef XADD
+#undef XSUB
+ 
+#define x86_64_dmix_supported_format \
+	((1ULL << SND_PCM_FORMAT_S16_LE) |\
+	 (1ULL << SND_PCM_FORMAT_S32_LE) |\
+	 (1ULL << SND_PCM_FORMAT_S24_3LE))
+
+#define dmix_supported_format \
+	(x86_64_dmix_supported_format | generic_dmix_supported_format)
+
+static void mix_select_callbacks(snd_pcm_direct_t *dmix)
+{
+	static int smp = 0;
+	
+	if (!((1ULL<< dmix->shmptr->s.format) & x86_64_dmix_supported_format)) {
+		generic_mix_select_callbacks(dmix);
+		return;
+	}
+
+	if (!smp) {
+		FILE *in;
+		char line[255];
+
+		/* try to determine, if we have SMP */
+		in = fopen("/proc/cpuinfo", "r");
+		if (in) {
+			while (!feof(in)) {
+				fgets(line, sizeof(line), in);
+				if (!strncmp(line, "processor", 9))
+					smp++;
+			}
+			fclose(in);
+		}
+	}
+	// printf("SMP: %i\n", smp);
+	dmix->u.dmix.mix_areas_16 = smp > 1 ? mix_areas_16_smp : mix_areas_16;
+	dmix->u.dmix.remix_areas_16 = smp > 1 ? remix_areas_16_smp : remix_areas_16;
+	dmix->u.dmix.mix_areas_32 = smp > 1 ? mix_areas_32_smp : mix_areas_32;
+	dmix->u.dmix.remix_areas_32 = smp > 1 ? remix_areas_32_smp : remix_areas_32;
+	dmix->u.dmix.mix_areas_24 = smp > 1 ? mix_areas_24_smp : mix_areas_24;
+	dmix->u.dmix.remix_areas_24 = smp > 1 ? remix_areas_24_smp : remix_areas_24;
+}
diff --git a/src/pcm/pcm_dmix_x86_64.h b/src/pcm/pcm_dmix_x86_64.h
new file mode 100644
index 0000000..ab40f50
--- /dev/null
+++ b/src/pcm/pcm_dmix_x86_64.h
@@ -0,0 +1,341 @@
+/**
+ * \file pcm/pcm_dmix_x86_64.h
+ * \ingroup PCM_Plugins
+ * \brief PCM Direct Stream Mixing (dmix) Plugin Interface - X86-64 assembler code
+ * \author Takashi Iwai <tiwai@suse.de>
+ * \date 2003
+ */
+/*
+ *  PCM - Direct Stream Mixing
+ *  Copyright (c) 2003 by Jaroslav Kysela <perex@perex.cz>
+ *                        Takashi Iwai <tiwai@suse.de>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+/*
+ *  MMX optimized
+ */
+static void MIX_AREAS_16(unsigned int size,
+			 volatile signed short *dst, signed short *src,
+			 volatile signed int *sum, size_t dst_step,
+			 size_t src_step, size_t sum_step)
+{
+	unsigned long long old_rbx;
+
+	/*
+	 *  RSI - src
+	 *  RDI - dst
+	 *  RBX - sum
+	 *  ECX - old sample
+	 *  EAX - sample / temporary
+	 *  EDX - temporary
+	 */
+	__asm__ __volatile__ (
+		"\n"
+
+		"\tmovq %%rbx, %7\n"
+		/*
+		 *  initialization, load RSI, RDI, RBX registers
+		 */
+		"\tmovq %1, %%rdi\n"
+		"\tmovq %2, %%rsi\n"
+		"\tmovq %3, %%rbx\n"
+
+		/*
+		 * while (size-- > 0) {
+		 */
+		"\tcmpl $0, %0\n"
+		"jz 6f\n"
+
+		"\t.p2align 4,,15\n"
+
+		"1:"
+
+		/*
+		 *   sample = *src;
+		 *   sum_sample = *sum;
+		 *   if (cmpxchg(*dst, 0, 1) == 0)
+		 *     sample -= sum_sample;
+		 *   xadd(*sum, sample);
+		 */
+		"\tmovw $0, %%ax\n"
+		"\tmovw $1, %%cx\n"
+		"\tmovl (%%rbx), %%edx\n"
+		"\t" LOCK_PREFIX "cmpxchgw %%cx, (%%rdi)\n"
+		"\tmovswl (%%rsi), %%ecx\n"
+		"\tjnz 2f\n"
+		"\t" XSUB " %%edx, %%ecx\n"
+		"2:"
+		"\t" LOCK_PREFIX XADD " %%ecx, (%%rbx)\n"
+
+		/*
+		 *   do {
+		 *     sample = old_sample = *sum;
+		 *     saturate(v);
+		 *     *dst = sample;
+		 *   } while (v != *sum);
+		 */
+
+		"3:"
+		"\tmovl (%%rbx), %%ecx\n"
+		"\tmovd %%ecx, %%mm0\n"
+		"\tpackssdw %%mm1, %%mm0\n"
+		"\tmovd %%mm0, %%eax\n"
+		"\tmovw %%ax, (%%rdi)\n"
+		"\tcmpl %%ecx, (%%rbx)\n"
+		"\tjnz 3b\n"
+
+		/*
+		 * while (size-- > 0)
+		 */
+		"\tadd %4, %%rdi\n"
+		"\tadd %5, %%rsi\n"
+		"\tadd %6, %%rbx\n"
+		"\tdecl %0\n"
+		"\tjnz 1b\n"
+
+		"6:"
+		
+		"\temms\n"
+		"\tmovq %7, %%rbx\n"
+
+		: /* no output regs */
+		: "m" (size), "m" (dst), "m" (src),
+		  "m" (sum), "m" (dst_step), "m" (src_step),
+		  "m" (sum_step), "m" (old_rbx)
+		: "rsi", "rdi", "edx", "ecx", "eax"
+	);
+}
+
+/*
+ *  32-bit version (24-bit resolution)
+ */
+static void MIX_AREAS_32(unsigned int size,
+			 volatile signed int *dst, signed int *src,
+			 volatile signed int *sum, size_t dst_step,
+			 size_t src_step, size_t sum_step)
+{
+	unsigned long long old_rbx;
+
+	/*
+	 *  RSI - src
+	 *  RDI - dst
+	 *  RBX - sum
+	 *  ECX - old sample
+	 *  EAX - sample / temporary
+	 *  EDX - temporary
+	 */
+	__asm__ __volatile__ (
+		"\n"
+
+		"\tmovq %%rbx, %7\n"
+		/*
+		 *  initialization, load ESI, EDI, EBX registers
+		 */
+		"\tmovq %1, %%rdi\n"
+		"\tmovq %2, %%rsi\n"
+		"\tmovq %3, %%rbx\n"
+
+		/*
+		 * while (size-- > 0) {
+		 */
+		"\tcmpl $0, %0\n"
+		"jz 6f\n"
+
+		"\t.p2align 4,,15\n"
+
+		"1:"
+
+		/*
+		 *   sample = *src;
+		 *   sum_sample = *sum;
+		 *   if (cmpxchg(*dst, 0, 1) == 0)
+		 *     sample -= sum_sample;
+		 *   xadd(*sum, sample);
+		 */
+		"\tmovl $0, %%eax\n"
+		"\tmovl $1, %%ecx\n"
+		"\tmovl (%%rbx), %%edx\n"
+		"\t" LOCK_PREFIX "cmpxchgl %%ecx, (%%rdi)\n"
+		"\tjnz 2f\n"
+		"\tmovl (%%rsi), %%ecx\n"
+		/* sample >>= 8 */
+		"\tsarl $8, %%ecx\n"
+		"\t" XSUB " %%edx, %%ecx\n"
+		"\tjmp 21f\n"
+		"2:"
+		"\tmovl (%%rsi), %%ecx\n"
+		/* sample >>= 8 */
+		"\tsarl $8, %%ecx\n"
+		"21:"
+		"\t" LOCK_PREFIX XADD " %%ecx, (%%rbx)\n"
+
+		/*
+		 *   do {
+		 *     sample = old_sample = *sum;
+		 *     saturate(v);
+		 *     *dst = sample;
+		 *   } while (v != *sum);
+		 */
+
+		"3:"
+		"\tmovl (%%rbx), %%ecx\n"
+		/*
+		 *  if (sample > 0x7fff00)
+		 */
+		"\tmovl $0x7fffff, %%eax\n"
+		"\tcmpl %%eax, %%ecx\n"
+		"\tjg 4f\n"
+		/*
+		 *  if (sample < -0x800000)
+		 */
+		"\tmovl $-0x800000, %%eax\n"
+		"\tcmpl %%eax, %%ecx\n"
+		"\tjl 4f\n"
+		"\tmovl %%ecx, %%eax\n"
+		"4:"
+		/*
+		 *  sample <<= 8;
+		 */
+		"\tsall $8, %%eax\n"
+		"\tmovl %%eax, (%%rdi)\n"
+		"\tcmpl %%ecx, (%%rbx)\n"
+		"\tjnz 3b\n"
+
+		/*
+		 * while (size-- > 0)
+		 */
+		"\tadd %4, %%rdi\n"
+		"\tadd %5, %%rsi\n"
+		"\tadd %6, %%rbx\n"
+		"\tdecl %0\n"
+		"\tjnz 1b\n"
+		
+		"6:"
+		"\tmovq %7, %%rbx\n"
+
+		: /* no output regs */
+		: "m" (size), "m" (dst), "m" (src),
+		  "m" (sum), "m" (dst_step), "m" (src_step),
+		  "m" (sum_step), "m" (old_rbx)
+		: "rsi", "rdi", "edx", "ecx", "eax"
+	);
+}
+
+/*
+ *  24-bit version
+ */
+static void MIX_AREAS_24(unsigned int size,
+			 volatile unsigned char *dst, unsigned char *src,
+			 volatile signed int *sum, size_t dst_step,
+			 size_t src_step, size_t sum_step)
+{
+	unsigned long long old_rbx;
+
+	/*
+	 *  RSI - src
+	 *  RDI - dst
+	 *  RBX - sum
+	 *  ECX - old sample
+	 *  EAX - sample / temporary
+	 *  EDX - temporary
+	 */
+	__asm__ __volatile__ (
+		"\n"
+
+		"\tmovq %%rbx, %7\n"
+		/*
+		 *  initialization, load ESI, EDI, EBX registers
+		 */
+		"\tmovq %1, %%rdi\n"
+		"\tmovq %2, %%rsi\n"
+		"\tmovq %3, %%rbx\n"
+
+		/*
+		 * while (size-- > 0) {
+		 */
+		"\tcmpl $0, %0\n"
+		"jz 6f\n"
+
+		"\t.p2align 4,,15\n"
+
+		"1:"
+
+		/*
+		 *   sample = *src;
+		 *   sum_sample = *sum;
+		 *   if (test_and_set_bit(0, dst) == 0)
+		 *     sample -= sum_sample;
+		 *   *sum += sample;
+		 */
+		"\tmovsbl 2(%%rsi), %%eax\n"
+		"\tmovzwl (%%rsi), %%ecx\n"
+		"\tmovl (%%rbx), %%edx\n"
+		"\tsall $16, %%eax\n"
+		"\torl %%eax, %%ecx\n"
+		"\t" LOCK_PREFIX "btsw $0, (%%rdi)\n"
+		"\tjc 2f\n"
+		"\t" XSUB " %%edx, %%ecx\n"
+		"2:"
+		"\t" LOCK_PREFIX XADD " %%ecx, (%%rbx)\n"
+
+		/*
+		 *   do {
+		 *     sample = old_sample = *sum;
+		 *     saturate(sample);
+		 *     *dst = sample | 1;
+		 *   } while (old_sample != *sum);
+		 */
+
+		"3:"
+		"\tmovl (%%rbx), %%ecx\n"
+
+		"\tmovl $0x7fffff, %%eax\n"
+		"\tmovl $-0x7fffff, %%edx\n"
+		"\tcmpl %%eax, %%ecx\n"
+		"\tcmovng %%ecx, %%eax\n"
+		"\tcmpl %%edx, %%ecx\n"
+		"\tcmovl %%edx, %%eax\n"
+
+		"\torl $1, %%eax\n"
+		"\tmovw %%ax, (%%rdi)\n"
+		"\tshrl $16, %%eax\n"
+		"\tmovb %%al, 2(%%rdi)\n"
+	
+		"\tcmpl %%ecx, (%%rbx)\n"
+		"\tjnz 3b\n"
+
+		/*
+		 * while (size-- > 0)
+		 */
+		"\tadd %4, %%rdi\n"
+		"\tadd %5, %%rsi\n"
+		"\tadd %6, %%rbx\n"
+		"\tdecl %0\n"
+		"\tjnz 1b\n"
+		
+		"6:"
+		"\tmovq %7, %%rbx\n"
+
+		: /* no output regs */
+		: "m" (size), "m" (dst), "m" (src),
+		  "m" (sum), "m" (dst_step), "m" (src_step),
+		  "m" (sum_step), "m" (old_rbx)
+		: "rsi", "rdi", "edx", "ecx", "eax"
+	);
+}
diff --git a/src/pcm/pcm_dshare.c b/src/pcm/pcm_dshare.c
new file mode 100644
index 0000000..77789a5
--- /dev/null
+++ b/src/pcm/pcm_dshare.c
@@ -0,0 +1,939 @@
+/**
+ * \file pcm/pcm_dshare.c
+ * \ingroup PCM_Plugins
+ * \brief PCM Direct Sharing of Channels Plugin Interface
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \date 2003
+ */
+/*
+ *  PCM - Direct Sharing of Channels
+ *  Copyright (c) 2003 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <grp.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/shm.h>
+#include <sys/sem.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/mman.h>
+#include "pcm_direct.h"
+
+#ifndef PIC
+/* entry for static linking */
+const char *_snd_module_pcm_dshare = "";
+#endif
+
+#ifndef DOC_HIDDEN
+/* start is pending - this state happens when rate plugin does a delayed commit */
+#define STATE_RUN_PENDING	1024
+#endif
+
+static void do_silence(snd_pcm_t *pcm)
+{
+	snd_pcm_direct_t *dshare = pcm->private_data;
+	const snd_pcm_channel_area_t *dst_areas;
+	unsigned int chn, dchn, channels;
+	snd_pcm_format_t format;
+
+	dst_areas = snd_pcm_mmap_areas(dshare->spcm);
+	channels = dshare->channels;
+	format = dshare->shmptr->s.format;
+	for (chn = 0; chn < channels; chn++) {
+		dchn = dshare->bindings ? dshare->bindings[chn] : chn;
+		snd_pcm_area_silence(&dst_areas[dchn], 0, dshare->shmptr->s.buffer_size, format);
+	}
+}
+
+static void share_areas(snd_pcm_direct_t *dshare,
+		      const snd_pcm_channel_area_t *src_areas,
+		      const snd_pcm_channel_area_t *dst_areas,
+		      snd_pcm_uframes_t src_ofs,
+		      snd_pcm_uframes_t dst_ofs,
+		      snd_pcm_uframes_t size)
+{
+	unsigned int chn, dchn, channels;
+	snd_pcm_format_t format;
+
+	channels = dshare->channels;
+	format = dshare->shmptr->s.format;
+	if (dshare->interleaved) {
+		unsigned int fbytes = snd_pcm_format_physical_width(format) / 8;
+		memcpy(((char *)dst_areas[0].addr) + (dst_ofs * channels * fbytes),
+		       ((char *)src_areas[0].addr) + (src_ofs * channels * fbytes),
+		       size * channels * fbytes);
+	} else {
+		for (chn = 0; chn < channels; chn++) {
+			dchn = dshare->bindings ? dshare->bindings[chn] : chn;
+			snd_pcm_area_copy(&dst_areas[dchn], dst_ofs, &src_areas[chn], src_ofs, size, format);
+
+		}
+	}
+}
+
+/*
+ *  synchronize shm ring buffer with hardware
+ */
+static void snd_pcm_dshare_sync_area(snd_pcm_t *pcm)
+{
+	snd_pcm_direct_t *dshare = pcm->private_data;
+	snd_pcm_uframes_t slave_hw_ptr, slave_appl_ptr, slave_size;
+	snd_pcm_uframes_t appl_ptr, size;
+	const snd_pcm_channel_area_t *src_areas, *dst_areas;
+	
+	/* calculate the size to transfer */
+	size = dshare->appl_ptr - dshare->last_appl_ptr;
+	if (! size)
+		return;
+	slave_hw_ptr = dshare->slave_hw_ptr;
+	/* don't write on the last active period - this area may be cleared
+	 * by the driver during write operation...
+	 */
+	slave_hw_ptr -= slave_hw_ptr % dshare->slave_period_size;
+	slave_hw_ptr += dshare->slave_buffer_size;
+	if (dshare->slave_hw_ptr > dshare->slave_boundary)
+		slave_hw_ptr -= dshare->slave_boundary;
+	if (slave_hw_ptr < dshare->slave_appl_ptr)
+		slave_size = slave_hw_ptr + (dshare->slave_boundary - dshare->slave_appl_ptr);
+	else
+		slave_size = slave_hw_ptr - dshare->slave_appl_ptr;
+	if (slave_size < size)
+		size = slave_size;
+	if (! size)
+		return;
+
+	/* add sample areas here */
+	src_areas = snd_pcm_mmap_areas(pcm);
+	dst_areas = snd_pcm_mmap_areas(dshare->spcm);
+	appl_ptr = dshare->last_appl_ptr % pcm->buffer_size;
+	dshare->last_appl_ptr += size;
+	dshare->last_appl_ptr %= pcm->boundary;
+	slave_appl_ptr = dshare->slave_appl_ptr % dshare->slave_buffer_size;
+	dshare->slave_appl_ptr += size;
+	dshare->slave_appl_ptr %= dshare->slave_boundary;
+	for (;;) {
+		snd_pcm_uframes_t transfer = size;
+		if (appl_ptr + transfer > pcm->buffer_size)
+			transfer = pcm->buffer_size - appl_ptr;
+		if (slave_appl_ptr + transfer > dshare->slave_buffer_size)
+			transfer = dshare->slave_buffer_size - slave_appl_ptr;
+		share_areas(dshare, src_areas, dst_areas, appl_ptr, slave_appl_ptr, transfer);
+		size -= transfer;
+		if (! size)
+			break;
+		slave_appl_ptr += transfer;
+		slave_appl_ptr %= dshare->slave_buffer_size;
+		appl_ptr += transfer;
+		appl_ptr %= pcm->buffer_size;
+	}
+}
+
+/*
+ *  synchronize hardware pointer (hw_ptr) with ours
+ */
+static int snd_pcm_dshare_sync_ptr(snd_pcm_t *pcm)
+{
+	snd_pcm_direct_t *dshare = pcm->private_data;
+	snd_pcm_uframes_t slave_hw_ptr, old_slave_hw_ptr, avail;
+	snd_pcm_sframes_t diff;
+	
+	switch (snd_pcm_state(dshare->spcm)) {
+	case SND_PCM_STATE_DISCONNECTED:
+		dshare->state = SNDRV_PCM_STATE_DISCONNECTED;
+		return -ENODEV;
+	default:
+		break;
+	}
+	if (dshare->slowptr)
+		snd_pcm_hwsync(dshare->spcm);
+	old_slave_hw_ptr = dshare->slave_hw_ptr;
+	slave_hw_ptr = dshare->slave_hw_ptr = *dshare->spcm->hw.ptr;
+	diff = slave_hw_ptr - old_slave_hw_ptr;
+	if (diff == 0)		/* fast path */
+		return 0;
+	if (dshare->state != SND_PCM_STATE_RUNNING &&
+	    dshare->state != SND_PCM_STATE_DRAINING)
+		/* not really started yet - don't update hw_ptr */
+		return 0;
+	if (diff < 0) {
+		slave_hw_ptr += dshare->slave_boundary;
+		diff = slave_hw_ptr - old_slave_hw_ptr;
+	}
+	dshare->hw_ptr += diff;
+	dshare->hw_ptr %= pcm->boundary;
+	// printf("sync ptr diff = %li\n", diff);
+	if (pcm->stop_threshold >= pcm->boundary)	/* don't care */
+		return 0;
+	avail = snd_pcm_mmap_playback_avail(pcm);
+	if (avail > dshare->avail_max)
+		dshare->avail_max = avail;
+	if (avail >= pcm->stop_threshold) {
+		snd_timer_stop(dshare->timer);
+		gettimestamp(&dshare->trigger_tstamp, pcm->monotonic);
+		if (dshare->state == SND_PCM_STATE_RUNNING) {
+			dshare->state = SND_PCM_STATE_XRUN;
+			return -EPIPE;
+		}
+		dshare->state = SND_PCM_STATE_SETUP;
+		/* clear queue to remove pending poll events */
+		snd_pcm_direct_clear_timer_queue(dshare);
+	}
+	return 0;
+}
+
+/*
+ *  plugin implementation
+ */
+
+static int snd_pcm_dshare_status(snd_pcm_t *pcm, snd_pcm_status_t * status)
+{
+	snd_pcm_direct_t *dshare = pcm->private_data;
+
+	switch (dshare->state) {
+	case SNDRV_PCM_STATE_DRAINING:
+	case SNDRV_PCM_STATE_RUNNING:
+		snd_pcm_dshare_sync_ptr(pcm);
+		break;
+	default:
+		break;
+	}
+	memset(status, 0, sizeof(*status));
+	status->state = snd_pcm_state(dshare->spcm);
+	status->trigger_tstamp = dshare->trigger_tstamp;
+	gettimestamp(&status->tstamp, pcm->monotonic);
+	status->avail = snd_pcm_mmap_playback_avail(pcm);
+	status->avail_max = status->avail > dshare->avail_max ? status->avail : dshare->avail_max;
+	dshare->avail_max = 0;
+	return 0;
+}
+
+static snd_pcm_state_t snd_pcm_dshare_state(snd_pcm_t *pcm)
+{
+	snd_pcm_direct_t *dshare = pcm->private_data;
+	switch (snd_pcm_state(dshare->spcm)) {
+	case SND_PCM_STATE_SUSPENDED:
+		return SND_PCM_STATE_SUSPENDED;
+	case SND_PCM_STATE_DISCONNECTED:
+		return SND_PCM_STATE_DISCONNECTED;
+	default:
+		break;
+	}
+	if (dshare->state == STATE_RUN_PENDING)
+		return SNDRV_PCM_STATE_RUNNING;
+	return dshare->state;
+}
+
+static int snd_pcm_dshare_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp)
+{
+	snd_pcm_direct_t *dshare = pcm->private_data;
+	int err;
+	
+	switch (dshare->state) {
+	case SNDRV_PCM_STATE_DRAINING:
+	case SNDRV_PCM_STATE_RUNNING:
+		err = snd_pcm_dshare_sync_ptr(pcm);
+		if (err < 0)
+			return err;
+		/* fallthru */
+	case SNDRV_PCM_STATE_PREPARED:
+	case SNDRV_PCM_STATE_SUSPENDED:
+	case STATE_RUN_PENDING:
+		*delayp = snd_pcm_mmap_playback_hw_avail(pcm);
+		return 0;
+	case SNDRV_PCM_STATE_XRUN:
+		return -EPIPE;
+	case SNDRV_PCM_STATE_DISCONNECTED:
+		return -ENODEV;
+	default:
+		return -EBADFD;
+	}
+}
+
+static int snd_pcm_dshare_hwsync(snd_pcm_t *pcm)
+{
+	snd_pcm_direct_t *dshare = pcm->private_data;
+
+	switch(dshare->state) {
+	case SNDRV_PCM_STATE_DRAINING:
+	case SNDRV_PCM_STATE_RUNNING:
+		return snd_pcm_dshare_sync_ptr(pcm);
+	case SNDRV_PCM_STATE_PREPARED:
+	case SNDRV_PCM_STATE_SUSPENDED:
+		return 0;
+	case SNDRV_PCM_STATE_XRUN:
+		return -EPIPE;
+	case SNDRV_PCM_STATE_DISCONNECTED:
+		return -ENODEV;
+	default:
+		return -EBADFD;
+	}
+}
+
+static int snd_pcm_dshare_prepare(snd_pcm_t *pcm)
+{
+	snd_pcm_direct_t *dshare = pcm->private_data;
+
+	snd_pcm_direct_check_interleave(dshare, pcm);
+	dshare->state = SND_PCM_STATE_PREPARED;
+	dshare->appl_ptr = dshare->last_appl_ptr = 0;
+	dshare->hw_ptr = 0;
+	return snd_pcm_direct_set_timer_params(dshare);
+}
+
+static int snd_pcm_dshare_reset(snd_pcm_t *pcm)
+{
+	snd_pcm_direct_t *dshare = pcm->private_data;
+	dshare->hw_ptr %= pcm->period_size;
+	dshare->appl_ptr = dshare->last_appl_ptr = dshare->hw_ptr;
+	dshare->slave_appl_ptr = dshare->slave_hw_ptr = *dshare->spcm->hw.ptr;
+	return 0;
+}
+
+static int snd_pcm_dshare_start_timer(snd_pcm_direct_t *dshare)
+{
+	int err;
+
+	snd_pcm_hwsync(dshare->spcm);
+	dshare->slave_appl_ptr = dshare->slave_hw_ptr = *dshare->spcm->hw.ptr;
+	err = snd_timer_start(dshare->timer);
+	if (err < 0)
+		return err;
+	dshare->state = SND_PCM_STATE_RUNNING;
+	return 0;
+}
+
+static int snd_pcm_dshare_start(snd_pcm_t *pcm)
+{
+	snd_pcm_direct_t *dshare = pcm->private_data;
+	snd_pcm_sframes_t avail;
+	int err;
+	
+	if (dshare->state != SND_PCM_STATE_PREPARED)
+		return -EBADFD;
+	avail = snd_pcm_mmap_playback_hw_avail(pcm);
+	if (avail == 0)
+		dshare->state = STATE_RUN_PENDING;
+	else if (avail < 0)
+		return 0;
+	else {
+		if ((err = snd_pcm_dshare_start_timer(dshare)) < 0)
+			return err;
+		snd_pcm_dshare_sync_area(pcm);
+	}
+	gettimestamp(&dshare->trigger_tstamp, pcm->monotonic);
+	return 0;
+}
+
+static int snd_pcm_dshare_drop(snd_pcm_t *pcm)
+{
+	snd_pcm_direct_t *dshare = pcm->private_data;
+	if (dshare->state == SND_PCM_STATE_OPEN)
+		return -EBADFD;
+	dshare->state = SND_PCM_STATE_SETUP;
+	snd_pcm_direct_timer_stop(dshare);
+	do_silence(pcm);
+	return 0;
+}
+
+static int snd_pcm_dshare_drain(snd_pcm_t *pcm)
+{
+	snd_pcm_direct_t *dshare = pcm->private_data;
+	snd_pcm_uframes_t stop_threshold;
+	int err;
+
+	if (dshare->state == SND_PCM_STATE_OPEN)
+		return -EBADFD;
+	if (pcm->mode & SND_PCM_NONBLOCK)
+		return -EAGAIN;
+	if (dshare->state == SND_PCM_STATE_PREPARED) {
+		if (snd_pcm_mmap_playback_hw_avail(pcm) > 0)
+			snd_pcm_dshare_start(pcm);
+		else {
+			snd_pcm_dshare_drop(pcm);
+			return 0;
+		}
+	}
+
+	if (dshare->state == SND_PCM_STATE_XRUN) {
+		snd_pcm_dshare_drop(pcm);
+		return 0;
+	}
+
+	stop_threshold = pcm->stop_threshold;
+	if (pcm->stop_threshold > pcm->buffer_size)
+		pcm->stop_threshold = pcm->buffer_size;
+	dshare->state = SND_PCM_STATE_DRAINING;
+	do {
+		err = snd_pcm_dshare_sync_ptr(pcm);
+		if (err < 0) {
+			snd_pcm_dshare_drop(pcm);
+			break;
+		}
+		if (dshare->state == SND_PCM_STATE_DRAINING) {
+			snd_pcm_dshare_sync_area(pcm);
+			snd_pcm_wait_nocheck(pcm, -1);
+			snd_pcm_direct_clear_timer_queue(dshare); /* force poll to wait */
+		}
+	} while (dshare->state == SND_PCM_STATE_DRAINING);
+	pcm->stop_threshold = stop_threshold;
+	return 0;
+}
+
+static int snd_pcm_dshare_pause(snd_pcm_t *pcm ATTRIBUTE_UNUSED, int enable ATTRIBUTE_UNUSED)
+{
+	return -EIO;
+}
+
+static snd_pcm_sframes_t snd_pcm_dshare_rewindable(snd_pcm_t *pcm)
+{
+	return snd_pcm_mmap_playback_hw_avail(pcm);
+}
+
+static snd_pcm_sframes_t snd_pcm_dshare_rewind(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
+{
+	snd_pcm_sframes_t avail;
+
+	avail = snd_pcm_mmap_playback_hw_avail(pcm);
+	if (avail < 0)
+		return 0;
+	if (frames > (snd_pcm_uframes_t)avail)
+		frames = avail;
+	snd_pcm_mmap_appl_backward(pcm, frames);
+	return frames;
+}
+
+static snd_pcm_sframes_t snd_pcm_dshare_forwardable(snd_pcm_t *pcm)
+{
+	return snd_pcm_mmap_playback_avail(pcm);
+}
+
+static snd_pcm_sframes_t snd_pcm_dshare_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
+{
+	snd_pcm_sframes_t avail;
+
+	avail = snd_pcm_mmap_playback_avail(pcm);
+	if (avail < 0)
+		return 0;
+	if (frames > (snd_pcm_uframes_t)avail)
+		frames = avail;
+	snd_pcm_mmap_appl_forward(pcm, frames);
+	return frames;
+}
+
+static snd_pcm_sframes_t snd_pcm_dshare_readi(snd_pcm_t *pcm ATTRIBUTE_UNUSED, void *buffer ATTRIBUTE_UNUSED, snd_pcm_uframes_t size ATTRIBUTE_UNUSED)
+{
+	return -ENODEV;
+}
+
+static snd_pcm_sframes_t snd_pcm_dshare_readn(snd_pcm_t *pcm ATTRIBUTE_UNUSED, void **bufs ATTRIBUTE_UNUSED, snd_pcm_uframes_t size ATTRIBUTE_UNUSED)
+{
+	return -ENODEV;
+}
+
+static int snd_pcm_dshare_close(snd_pcm_t *pcm)
+{
+	snd_pcm_direct_t *dshare = pcm->private_data;
+
+	if (dshare->timer)
+		snd_timer_close(dshare->timer);
+	do_silence(pcm);
+	snd_pcm_direct_semaphore_down(dshare, DIRECT_IPC_SEM_CLIENT);
+	dshare->shmptr->u.dshare.chn_mask &= ~dshare->u.dshare.chn_mask;
+	snd_pcm_close(dshare->spcm);
+ 	if (dshare->server)
+ 		snd_pcm_direct_server_discard(dshare);
+ 	if (dshare->client)
+ 		snd_pcm_direct_client_discard(dshare);
+	if (snd_pcm_direct_shm_discard(dshare))
+		snd_pcm_direct_semaphore_discard(dshare);
+	else
+		snd_pcm_direct_semaphore_up(dshare, DIRECT_IPC_SEM_CLIENT);
+	free(dshare->bindings);
+	pcm->private_data = NULL;
+	free(dshare);
+	return 0;
+}
+
+static snd_pcm_sframes_t snd_pcm_dshare_mmap_commit(snd_pcm_t *pcm,
+						  snd_pcm_uframes_t offset ATTRIBUTE_UNUSED,
+						  snd_pcm_uframes_t size)
+{
+	snd_pcm_direct_t *dshare = pcm->private_data;
+	int err;
+
+	switch (snd_pcm_state(dshare->spcm)) {
+	case SND_PCM_STATE_XRUN:
+		return -EPIPE;
+	case SND_PCM_STATE_SUSPENDED:
+		return -ESTRPIPE;
+	default:
+		break;
+	}
+	if (! size)
+		return 0;
+	snd_pcm_mmap_appl_forward(pcm, size);
+	if (dshare->state == STATE_RUN_PENDING) {
+		if ((err = snd_pcm_dshare_start_timer(dshare)) < 0)
+			return err;
+	} else if (dshare->state == SND_PCM_STATE_RUNNING ||
+		   dshare->state == SND_PCM_STATE_DRAINING)
+		snd_pcm_dshare_sync_ptr(pcm);
+	if (dshare->state == SND_PCM_STATE_RUNNING ||
+	    dshare->state == SND_PCM_STATE_DRAINING) {
+		/* ok, we commit the changes after the validation of area */
+		/* it's intended, although the result might be crappy */
+		snd_pcm_dshare_sync_area(pcm);
+		/* clear timer queue to avoid a bogus return from poll */
+		if (snd_pcm_mmap_playback_avail(pcm) < pcm->avail_min)
+			snd_pcm_direct_clear_timer_queue(dshare);
+	}
+	return size;
+}
+
+static snd_pcm_sframes_t snd_pcm_dshare_avail_update(snd_pcm_t *pcm)
+{
+	snd_pcm_direct_t *dshare = pcm->private_data;
+	
+	if (dshare->state == SND_PCM_STATE_RUNNING ||
+	    dshare->state == SND_PCM_STATE_DRAINING)
+		snd_pcm_dshare_sync_ptr(pcm);
+	return snd_pcm_mmap_playback_avail(pcm);
+}
+
+static int snd_pcm_dshare_htimestamp(snd_pcm_t *pcm,
+				     snd_pcm_uframes_t *avail,
+				     snd_htimestamp_t *tstamp)
+{
+	snd_pcm_direct_t *dshare = pcm->private_data;
+	snd_pcm_uframes_t avail1;
+	int ok = 0;
+	
+	while (1) {
+		if (dshare->state == SND_PCM_STATE_RUNNING ||
+		    dshare->state == SND_PCM_STATE_DRAINING)
+			snd_pcm_dshare_sync_ptr(pcm);
+		avail1 = snd_pcm_mmap_playback_avail(pcm);
+		if (ok && *avail == avail1)
+			break;
+		*avail = avail1;
+		*tstamp = snd_pcm_hw_fast_tstamp(dshare->spcm);
+	}
+	return 0;
+}
+
+static void snd_pcm_dshare_dump(snd_pcm_t *pcm, snd_output_t *out)
+{
+	snd_pcm_direct_t *dshare = pcm->private_data;
+
+	snd_output_printf(out, "Direct Share PCM\n");
+	if (pcm->setup) {
+		snd_output_printf(out, "Its setup is:\n");
+		snd_pcm_dump_setup(pcm, out);
+	}
+	if (dshare->spcm)
+		snd_pcm_dump(dshare->spcm, out);
+}
+
+static const snd_pcm_ops_t snd_pcm_dshare_ops = {
+	.close = snd_pcm_dshare_close,
+	.info = snd_pcm_direct_info,
+	.hw_refine = snd_pcm_direct_hw_refine,
+	.hw_params = snd_pcm_direct_hw_params,
+	.hw_free = snd_pcm_direct_hw_free,
+	.sw_params = snd_pcm_direct_sw_params,
+	.channel_info = snd_pcm_direct_channel_info,
+	.dump = snd_pcm_dshare_dump,
+	.nonblock = snd_pcm_direct_nonblock,
+	.async = snd_pcm_direct_async,
+	.mmap = snd_pcm_direct_mmap,
+	.munmap = snd_pcm_direct_munmap,
+};
+
+static const snd_pcm_fast_ops_t snd_pcm_dshare_fast_ops = {
+	.status = snd_pcm_dshare_status,
+	.state = snd_pcm_dshare_state,
+	.hwsync = snd_pcm_dshare_hwsync,
+	.delay = snd_pcm_dshare_delay,
+	.prepare = snd_pcm_dshare_prepare,
+	.reset = snd_pcm_dshare_reset,
+	.start = snd_pcm_dshare_start,
+	.drop = snd_pcm_dshare_drop,
+	.drain = snd_pcm_dshare_drain,
+	.pause = snd_pcm_dshare_pause,
+	.rewindable = snd_pcm_dshare_rewindable,
+	.rewind = snd_pcm_dshare_rewind,
+	.forwardable = snd_pcm_dshare_forwardable,
+	.forward = snd_pcm_dshare_forward,
+	.resume = snd_pcm_direct_resume,
+	.link = NULL,
+	.link_slaves = NULL,
+	.unlink = NULL,
+	.writei = snd_pcm_mmap_writei,
+	.writen = snd_pcm_mmap_writen,
+	.readi = snd_pcm_dshare_readi,
+	.readn = snd_pcm_dshare_readn,
+	.avail_update = snd_pcm_dshare_avail_update,
+	.mmap_commit = snd_pcm_dshare_mmap_commit,
+	.htimestamp = snd_pcm_dshare_htimestamp,
+	.poll_descriptors = NULL,
+	.poll_descriptors_count = NULL,
+	.poll_revents = snd_pcm_direct_poll_revents,
+};
+
+/**
+ * \brief Creates a new dshare PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param opts Direct PCM configurations
+ * \param params Parameters for slave
+ * \param root Configuration root
+ * \param sconf Slave configuration
+ * \param stream PCM Direction (stream)
+ * \param mode PCM Mode
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int snd_pcm_dshare_open(snd_pcm_t **pcmp, const char *name,
+			struct snd_pcm_direct_open_conf *opts,
+			struct slave_params *params,
+			snd_config_t *root, snd_config_t *sconf,
+			snd_pcm_stream_t stream, int mode)
+{
+	snd_pcm_t *pcm = NULL, *spcm = NULL;
+	snd_pcm_direct_t *dshare = NULL;
+	int ret, first_instance;
+	unsigned int chn;
+	int fail_sem_loop = 10;
+
+	assert(pcmp);
+
+	if (stream != SND_PCM_STREAM_PLAYBACK) {
+		SNDERR("The dshare plugin supports only playback stream");
+		return -EINVAL;
+	}
+
+	dshare = calloc(1, sizeof(snd_pcm_direct_t));
+	if (!dshare) {
+		ret = -ENOMEM;
+		goto _err_nosem;
+	}
+	
+	ret = snd_pcm_direct_parse_bindings(dshare, params, opts->bindings);
+	if (ret < 0)
+		goto _err_nosem;
+		
+	if (!dshare->bindings) {
+		SNDERR("dshare: specify bindings!!!");
+		ret = -EINVAL;
+		goto _err_nosem;
+	}
+	
+	dshare->ipc_key = opts->ipc_key;
+	dshare->ipc_perm = opts->ipc_perm;
+	dshare->ipc_gid = opts->ipc_gid;
+	dshare->semid = -1;
+	dshare->shmid = -1;
+
+	ret = snd_pcm_new(&pcm, dshare->type = SND_PCM_TYPE_DSHARE, name, stream, mode);
+	if (ret < 0)
+		goto _err_nosem;
+
+	while (1) {
+		ret = snd_pcm_direct_semaphore_create_or_connect(dshare);
+		if (ret < 0) {
+			SNDERR("unable to create IPC semaphore");
+			goto _err_nosem;
+		}
+	
+		ret = snd_pcm_direct_semaphore_down(dshare, DIRECT_IPC_SEM_CLIENT);
+		if (ret < 0) {
+			snd_pcm_direct_semaphore_discard(dshare);
+			if (--fail_sem_loop <= 0)
+				goto _err_nosem;
+			continue;
+		}
+		break;
+	}
+
+	first_instance = ret = snd_pcm_direct_shm_create_or_connect(dshare);
+	if (ret < 0) {
+		SNDERR("unable to create IPC shm instance");
+		goto _err;
+	}
+		
+	pcm->ops = &snd_pcm_dshare_ops;
+	pcm->fast_ops = &snd_pcm_dshare_fast_ops;
+	pcm->private_data = dshare;
+	dshare->state = SND_PCM_STATE_OPEN;
+	dshare->slowptr = opts->slowptr;
+	dshare->max_periods = opts->max_periods;
+	dshare->sync_ptr = snd_pcm_dshare_sync_ptr;
+
+	if (first_instance) {
+		/* recursion is already checked in
+		   snd_pcm_direct_get_slave_ipc_offset() */
+		ret = snd_pcm_open_slave(&spcm, root, sconf, stream,
+					 mode | SND_PCM_NONBLOCK, NULL);
+		if (ret < 0) {
+			SNDERR("unable to open slave");
+			goto _err;
+		}
+	
+		if (snd_pcm_type(spcm) != SND_PCM_TYPE_HW) {
+			SNDERR("dshare plugin can be only connected to hw plugin");
+			goto _err;
+		}
+		
+		ret = snd_pcm_direct_initialize_slave(dshare, spcm, params);
+		if (ret < 0) {
+			SNDERR("unable to initialize slave");
+			goto _err;
+		}
+
+		dshare->spcm = spcm;
+		
+		if (dshare->shmptr->use_server) {
+			ret = snd_pcm_direct_server_create(dshare);
+			if (ret < 0) {
+				SNDERR("unable to create server");
+				goto _err;
+			}
+		}
+
+		dshare->shmptr->type = spcm->type;
+	} else {
+		if (dshare->shmptr->use_server) {
+			/* up semaphore to avoid deadlock */
+			snd_pcm_direct_semaphore_up(dshare, DIRECT_IPC_SEM_CLIENT);
+			ret = snd_pcm_direct_client_connect(dshare);
+			if (ret < 0) {
+				SNDERR("unable to connect client");
+				goto _err_nosem;
+			}
+			
+			snd_pcm_direct_semaphore_down(dshare, DIRECT_IPC_SEM_CLIENT);
+			ret = snd_pcm_direct_open_secondary_client(&spcm, dshare, "dshare_client");
+			if (ret < 0)
+				goto _err;
+
+		} else {
+
+			ret = snd_pcm_open_slave(&spcm, root, sconf, stream,
+						 mode | SND_PCM_NONBLOCK |
+						 SND_PCM_APPEND,
+						 NULL);
+			if (ret < 0) {
+				SNDERR("unable to open slave");
+				goto _err;
+			}
+			if (snd_pcm_type(spcm) != SND_PCM_TYPE_HW) {
+				SNDERR("dshare plugin can be only connected to hw plugin");
+				ret = -EINVAL;
+				goto _err;
+			}
+		
+			ret = snd_pcm_direct_initialize_secondary_slave(dshare, spcm, params);
+			if (ret < 0) {
+				SNDERR("unable to initialize slave");
+				goto _err;
+			}
+		}
+
+		dshare->spcm = spcm;
+	}
+
+	for (chn = 0; chn < dshare->channels; chn++)
+		dshare->u.dshare.chn_mask |= (1ULL<<dshare->bindings[chn]);
+	if (dshare->shmptr->u.dshare.chn_mask & dshare->u.dshare.chn_mask) {
+		SNDERR("destination channel specified in bindings is already used");
+		dshare->u.dshare.chn_mask = 0;
+		ret = -EINVAL;
+		goto _err;
+	}
+	dshare->shmptr->u.dshare.chn_mask |= dshare->u.dshare.chn_mask;
+		
+	ret = snd_pcm_direct_initialize_poll_fd(dshare);
+	if (ret < 0) {
+		SNDERR("unable to initialize poll_fd");
+		goto _err;
+	}
+
+	pcm->poll_fd = dshare->poll_fd;
+	pcm->poll_events = POLLIN;	/* it's different than other plugins */
+		
+	pcm->mmap_rw = 1;
+	snd_pcm_set_hw_ptr(pcm, &dshare->hw_ptr, -1, 0);
+	snd_pcm_set_appl_ptr(pcm, &dshare->appl_ptr, -1, 0);
+	
+	snd_pcm_direct_semaphore_up(dshare, DIRECT_IPC_SEM_CLIENT);
+
+	*pcmp = pcm;
+	return 0;
+	
+ _err:
+	if (dshare->shmptr)
+		dshare->shmptr->u.dshare.chn_mask &= ~dshare->u.dshare.chn_mask;
+	if (dshare->timer)
+		snd_timer_close(dshare->timer);
+	if (dshare->server)
+		snd_pcm_direct_server_discard(dshare);
+	if (dshare->client)
+		snd_pcm_direct_client_discard(dshare);
+	if (spcm)
+		snd_pcm_close(spcm);
+	if (dshare->shmid >= 0)
+		snd_pcm_direct_shm_discard(dshare);
+	if (snd_pcm_direct_semaphore_discard(dshare) < 0)
+		snd_pcm_direct_semaphore_up(dshare, DIRECT_IPC_SEM_CLIENT);
+ _err_nosem:
+	if (dshare) {
+		free(dshare->bindings);
+		free(dshare);
+	}
+	if (pcm)
+		snd_pcm_free(pcm);
+	return ret;
+}
+
+/*! \page pcm_plugins
+
+\section pcm_plugins_dshare Plugin: dshare
+
+This plugin provides sharing channels.
+Unlike \ref pcm_plugins_share "share plugin", this plugin doesn't need
+the explicit server program but accesses the shared buffer concurrently
+from each client as well as \ref pcm_plugins_dmix "dmix" and
+\ref pcm_plugins_dsnoop "dsnoop" plugins do.
+The parameters below are almost identical with these plugins.
+
+\code
+pcm.name {
+	type dshare		# Direct sharing
+	ipc_key INT		# unique IPC key
+	ipc_key_add_uid BOOL	# add current uid to unique IPC key
+	ipc_perm INT		# IPC permissions (octal, default 0600)
+	slave STR
+	# or
+	slave {			# Slave definition
+		pcm STR		# slave PCM name
+		# or
+		pcm { }		# slave PCM definition
+		format STR	# format definition
+		rate INT	# rate definition
+		channels INT
+		period_time INT	# in usec
+		# or
+		period_size INT	# in bytes
+		buffer_time INT	# in usec
+		# or
+		buffer_size INT # in bytes
+		periods INT	# when buffer_size or buffer_time is not specified
+	}
+	bindings {		# note: this is client independent!!!
+		N INT		# maps slave channel to client channel N
+	}
+	slowptr BOOL		# slow but more precise pointer updates
+}
+\endcode
+
+\subsection pcm_plugins_dshare_funcref Function reference
+
+<UL>
+  <LI>snd_pcm_dshare_open()
+  <LI>_snd_pcm_dshare_open()
+</UL>
+
+*/
+
+/**
+ * \brief Creates a new dshare PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param root Root configuration node
+ * \param conf Configuration node with dshare PCM description
+ * \param stream PCM Stream
+ * \param mode PCM Mode
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int _snd_pcm_dshare_open(snd_pcm_t **pcmp, const char *name,
+		       snd_config_t *root, snd_config_t *conf,
+		       snd_pcm_stream_t stream, int mode)
+{
+	snd_config_t *sconf;
+	struct slave_params params;
+	struct snd_pcm_direct_open_conf dopen;
+	int bsize, psize;
+	int err;
+
+	err = snd_pcm_direct_parse_open_conf(root, conf, stream, &dopen);
+	if (err < 0)
+		return err;
+
+	/* the default settings, it might be invalid for some hardware */
+	params.format = SND_PCM_FORMAT_S16;
+	params.rate = 48000;
+	params.channels = 2;
+	params.period_time = -1;
+	params.buffer_time = -1;
+	bsize = psize = -1;
+	params.periods = 3;
+	err = snd_pcm_slave_conf(root, dopen.slave, &sconf, 8,
+				 SND_PCM_HW_PARAM_FORMAT, SCONF_UNCHANGED, &params.format,
+				 SND_PCM_HW_PARAM_RATE, 0, &params.rate,
+				 SND_PCM_HW_PARAM_CHANNELS, 0, &params.channels,
+				 SND_PCM_HW_PARAM_PERIOD_TIME, 0, &params.period_time,
+				 SND_PCM_HW_PARAM_BUFFER_TIME, 0, &params.buffer_time,
+				 SND_PCM_HW_PARAM_PERIOD_SIZE, 0, &psize,
+				 SND_PCM_HW_PARAM_BUFFER_SIZE, 0, &bsize,
+				 SND_PCM_HW_PARAM_PERIODS, 0, &params.periods);
+	if (err < 0)
+		return err;
+
+	/* set a reasonable default */
+	if (psize == -1 && params.period_time == -1)
+		params.period_time = 125000;	/* 0.125 seconds */
+
+	if (params.format == -2)
+		params.format = SND_PCM_FORMAT_UNKNOWN;
+
+	params.period_size = psize;
+	params.buffer_size = bsize;
+
+	err = snd_pcm_dshare_open(pcmp, name, &dopen, &params,
+				  root, sconf, stream, mode);
+	snd_config_delete(sconf);
+	return err;
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(_snd_pcm_dshare_open, SND_PCM_DLSYM_VERSION);
+#endif
diff --git a/src/pcm/pcm_dsnoop.c b/src/pcm/pcm_dsnoop.c
new file mode 100644
index 0000000..988f1f4
--- /dev/null
+++ b/src/pcm/pcm_dsnoop.c
@@ -0,0 +1,835 @@
+/**
+ * \file pcm/pcm_dsnoop.c
+ * \ingroup PCM_Plugins
+ * \brief PCM Capture Stream Snooping (dsnoop) Plugin Interface
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \date 2003
+ */
+/*
+ *  PCM - Capture Stream Snooping
+ *  Copyright (c) 2003 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <grp.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/shm.h>
+#include <sys/sem.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/mman.h>
+#include "pcm_direct.h"
+
+#ifndef PIC
+/* entry for static linking */
+const char *_snd_module_pcm_dsnoop = "";
+#endif
+
+/*
+ *
+ */
+
+static int snoop_timestamp(snd_pcm_t *pcm)
+{
+	snd_pcm_direct_t *dsnoop = pcm->private_data;
+	snd_pcm_uframes_t ptr1 = -2LL /* invalid value */, ptr2;
+
+	/* loop is required to sync hw.ptr with timestamp */
+	while (1) {
+		ptr2 = *dsnoop->spcm->hw.ptr;
+		if (ptr1 == ptr2)
+			break;
+		ptr1 = ptr2;
+		dsnoop->update_tstamp = snd_pcm_hw_fast_tstamp(dsnoop->spcm);
+	}
+	dsnoop->slave_hw_ptr = ptr1;
+	return 0;
+}
+
+static void snoop_areas(snd_pcm_direct_t *dsnoop,
+			const snd_pcm_channel_area_t *src_areas,
+			const snd_pcm_channel_area_t *dst_areas,
+			snd_pcm_uframes_t src_ofs,
+			snd_pcm_uframes_t dst_ofs,
+			snd_pcm_uframes_t size)
+{
+	unsigned int chn, schn, channels;
+	snd_pcm_format_t format;
+
+	channels = dsnoop->channels;
+	format = dsnoop->shmptr->s.format;
+	if (dsnoop->interleaved) {
+		unsigned int fbytes = snd_pcm_format_physical_width(format) / 8;
+		memcpy(((char *)dst_areas[0].addr) + (dst_ofs * channels * fbytes),
+		       ((char *)src_areas[0].addr) + (src_ofs * channels * fbytes),
+		       size * channels * fbytes);
+	} else {
+		for (chn = 0; chn < channels; chn++) {
+			schn = dsnoop->bindings ? dsnoop->bindings[chn] : chn;
+			snd_pcm_area_copy(&dst_areas[chn], dst_ofs, &src_areas[schn], src_ofs, size, format);
+		}
+	}
+}
+
+/*
+ *  synchronize shm ring buffer with hardware
+ */
+static void snd_pcm_dsnoop_sync_area(snd_pcm_t *pcm, snd_pcm_uframes_t slave_hw_ptr, snd_pcm_uframes_t size)
+{
+	snd_pcm_direct_t *dsnoop = pcm->private_data;
+	snd_pcm_uframes_t hw_ptr = dsnoop->hw_ptr;
+	snd_pcm_uframes_t transfer;
+	const snd_pcm_channel_area_t *src_areas, *dst_areas;
+	
+	/* add sample areas here */
+	dst_areas = snd_pcm_mmap_areas(pcm);
+	src_areas = snd_pcm_mmap_areas(dsnoop->spcm);
+	hw_ptr %= pcm->buffer_size;
+	slave_hw_ptr %= dsnoop->slave_buffer_size;
+	while (size > 0) {
+		transfer = hw_ptr + size > pcm->buffer_size ? pcm->buffer_size - hw_ptr : size;
+		transfer = slave_hw_ptr + transfer > dsnoop->slave_buffer_size ?
+			dsnoop->slave_buffer_size - slave_hw_ptr : transfer;
+		size -= transfer;
+		snoop_areas(dsnoop, src_areas, dst_areas, slave_hw_ptr, hw_ptr, transfer);
+		slave_hw_ptr += transfer;
+	 	slave_hw_ptr %= dsnoop->slave_buffer_size;
+		hw_ptr += transfer;
+		hw_ptr %= pcm->buffer_size;
+	}
+}
+
+/*
+ *  synchronize hardware pointer (hw_ptr) with ours
+ */
+static int snd_pcm_dsnoop_sync_ptr(snd_pcm_t *pcm)
+{
+	snd_pcm_direct_t *dsnoop = pcm->private_data;
+	snd_pcm_uframes_t slave_hw_ptr, old_slave_hw_ptr, avail;
+	snd_pcm_sframes_t diff;
+	
+	switch (snd_pcm_state(dsnoop->spcm)) {
+	case SND_PCM_STATE_DISCONNECTED:
+		dsnoop->state = SNDRV_PCM_STATE_DISCONNECTED;
+		return -ENODEV;
+	default:
+		break;
+	}
+	if (dsnoop->slowptr)
+		snd_pcm_hwsync(dsnoop->spcm);
+	old_slave_hw_ptr = dsnoop->slave_hw_ptr;
+	snoop_timestamp(pcm);
+	slave_hw_ptr = dsnoop->slave_hw_ptr;
+	diff = slave_hw_ptr - old_slave_hw_ptr;
+	if (diff == 0)		/* fast path */
+		return 0;
+	if (diff < 0) {
+		slave_hw_ptr += dsnoop->slave_boundary;
+		diff = slave_hw_ptr - old_slave_hw_ptr;
+	}
+	snd_pcm_dsnoop_sync_area(pcm, old_slave_hw_ptr, diff);
+	dsnoop->hw_ptr += diff;
+	dsnoop->hw_ptr %= pcm->boundary;
+	// printf("sync ptr diff = %li\n", diff);
+	if (pcm->stop_threshold >= pcm->boundary)	/* don't care */
+		return 0;
+	if ((avail = snd_pcm_mmap_capture_hw_avail(pcm)) >= pcm->stop_threshold) {
+		gettimestamp(&dsnoop->trigger_tstamp, pcm->monotonic);
+		dsnoop->state = SND_PCM_STATE_XRUN;
+		dsnoop->avail_max = avail;
+		return -EPIPE;
+	}
+	if (avail > dsnoop->avail_max)
+		dsnoop->avail_max = avail;
+	return 0;
+}
+
+/*
+ *  plugin implementation
+ */
+
+static int snd_pcm_dsnoop_status(snd_pcm_t *pcm, snd_pcm_status_t * status)
+{
+	snd_pcm_direct_t *dsnoop = pcm->private_data;
+	snd_pcm_state_t state;
+
+	switch(dsnoop->state) {
+	case SNDRV_PCM_STATE_DRAINING:
+	case SNDRV_PCM_STATE_RUNNING:
+		snd_pcm_dsnoop_sync_ptr(pcm);
+		break;
+	default:
+		break;
+	}
+	memset(status, 0, sizeof(*status));
+	state = snd_pcm_state(dsnoop->spcm);
+	status->state = state == SND_PCM_STATE_RUNNING ? dsnoop->state : state;
+	status->trigger_tstamp = dsnoop->trigger_tstamp;
+	status->tstamp = dsnoop->update_tstamp;
+	status->avail = snd_pcm_mmap_capture_avail(pcm);
+	status->avail_max = status->avail > dsnoop->avail_max ? status->avail : dsnoop->avail_max;
+	dsnoop->avail_max = 0;
+	return 0;
+}
+
+static snd_pcm_state_t snd_pcm_dsnoop_state(snd_pcm_t *pcm)
+{
+	snd_pcm_direct_t *dsnoop = pcm->private_data;
+	switch (snd_pcm_state(dsnoop->spcm)) {
+	case SND_PCM_STATE_SUSPENDED:
+		return SND_PCM_STATE_SUSPENDED;
+	case SND_PCM_STATE_DISCONNECTED:
+		dsnoop->state = SNDRV_PCM_STATE_DISCONNECTED;
+		return -ENODEV;
+	default:
+		break;
+	}
+	return dsnoop->state;
+}
+
+static int snd_pcm_dsnoop_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp)
+{
+	snd_pcm_direct_t *dsnoop = pcm->private_data;
+	int err;
+	
+	switch(dsnoop->state) {
+	case SNDRV_PCM_STATE_DRAINING:
+	case SNDRV_PCM_STATE_RUNNING:
+		err = snd_pcm_dsnoop_sync_ptr(pcm);
+		if (err < 0)
+			return err;
+	case SNDRV_PCM_STATE_PREPARED:
+	case SNDRV_PCM_STATE_SUSPENDED:
+		*delayp = snd_pcm_mmap_capture_hw_avail(pcm);
+		return 0;
+	case SNDRV_PCM_STATE_XRUN:
+		return -EPIPE;
+	case SNDRV_PCM_STATE_DISCONNECTED:
+		return -ENODEV;
+	default:
+		return -EBADFD;
+	}
+}
+
+static int snd_pcm_dsnoop_hwsync(snd_pcm_t *pcm)
+{
+	snd_pcm_direct_t *dsnoop = pcm->private_data;
+
+	switch(dsnoop->state) {
+	case SNDRV_PCM_STATE_DRAINING:
+	case SNDRV_PCM_STATE_RUNNING:
+		return snd_pcm_dsnoop_sync_ptr(pcm);
+	case SNDRV_PCM_STATE_PREPARED:
+	case SNDRV_PCM_STATE_SUSPENDED:
+		return 0;
+	case SNDRV_PCM_STATE_XRUN:
+		return -EPIPE;
+	case SNDRV_PCM_STATE_DISCONNECTED:
+		return -ENODEV;
+	default:
+		return -EBADFD;
+	}
+}
+
+static int snd_pcm_dsnoop_prepare(snd_pcm_t *pcm)
+{
+	snd_pcm_direct_t *dsnoop = pcm->private_data;
+
+	snd_pcm_direct_check_interleave(dsnoop, pcm);
+	dsnoop->state = SND_PCM_STATE_PREPARED;
+	dsnoop->appl_ptr = 0;
+	dsnoop->hw_ptr = 0;
+	return snd_pcm_direct_set_timer_params(dsnoop);
+}
+
+static int snd_pcm_dsnoop_reset(snd_pcm_t *pcm)
+{
+	snd_pcm_direct_t *dsnoop = pcm->private_data;
+	dsnoop->hw_ptr %= pcm->period_size;
+	dsnoop->appl_ptr = dsnoop->hw_ptr;
+	dsnoop->slave_appl_ptr = dsnoop->slave_hw_ptr;
+	return 0;
+}
+
+static int snd_pcm_dsnoop_start(snd_pcm_t *pcm)
+{
+	snd_pcm_direct_t *dsnoop = pcm->private_data;
+	int err;
+	
+	if (dsnoop->state != SND_PCM_STATE_PREPARED)
+		return -EBADFD;
+	snd_pcm_hwsync(dsnoop->spcm);
+	snoop_timestamp(pcm);
+	dsnoop->slave_appl_ptr = dsnoop->slave_hw_ptr;
+	err = snd_timer_start(dsnoop->timer);
+	if (err < 0)
+		return err;
+	dsnoop->state = SND_PCM_STATE_RUNNING;
+	dsnoop->trigger_tstamp = dsnoop->update_tstamp;
+	return 0;
+}
+
+static int snd_pcm_dsnoop_drop(snd_pcm_t *pcm)
+{
+	snd_pcm_direct_t *dsnoop = pcm->private_data;
+	if (dsnoop->state == SND_PCM_STATE_OPEN)
+		return -EBADFD;
+	dsnoop->state = SND_PCM_STATE_SETUP;
+	snd_timer_stop(dsnoop->timer);
+	return 0;
+}
+
+static int snd_pcm_dsnoop_drain(snd_pcm_t *pcm)
+{
+	snd_pcm_direct_t *dsnoop = pcm->private_data;
+	snd_pcm_uframes_t stop_threshold;
+	int err;
+
+	if (dsnoop->state == SND_PCM_STATE_OPEN)
+		return -EBADFD;
+	stop_threshold = pcm->stop_threshold;
+	if (pcm->stop_threshold > pcm->buffer_size)
+		pcm->stop_threshold = pcm->buffer_size;
+	while (dsnoop->state == SND_PCM_STATE_RUNNING) {
+		err = snd_pcm_dsnoop_sync_ptr(pcm);
+		if (err < 0)
+			break;
+		if (pcm->mode & SND_PCM_NONBLOCK)
+			return -EAGAIN;
+		snd_pcm_wait(pcm, -1);
+	}
+	pcm->stop_threshold = stop_threshold;
+	return snd_pcm_dsnoop_drop(pcm);
+}
+
+static int snd_pcm_dsnoop_pause(snd_pcm_t *pcm ATTRIBUTE_UNUSED, int enable ATTRIBUTE_UNUSED)
+{
+	return -EIO;
+}
+
+static snd_pcm_sframes_t snd_pcm_dsnoop_rewindable(snd_pcm_t *pcm)
+{
+	return snd_pcm_mmap_capture_avail(pcm);
+}
+
+static snd_pcm_sframes_t snd_pcm_dsnoop_rewind(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
+{
+	snd_pcm_sframes_t avail;
+
+	avail = snd_pcm_mmap_capture_avail(pcm);
+	if (avail < 0)
+		return 0;
+	if (frames > (snd_pcm_uframes_t)avail)
+		frames = avail;
+	snd_pcm_mmap_appl_backward(pcm, frames);
+	return frames;
+}
+
+static snd_pcm_sframes_t snd_pcm_dsnoop_forwardable(snd_pcm_t *pcm)
+{
+	return snd_pcm_mmap_capture_hw_avail(pcm);
+}
+
+static snd_pcm_sframes_t snd_pcm_dsnoop_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
+{
+	snd_pcm_sframes_t avail;
+
+	avail = snd_pcm_mmap_capture_hw_avail(pcm);
+	if (avail < 0)
+		return 0;
+	if (frames > (snd_pcm_uframes_t)avail)
+		frames = avail;
+	snd_pcm_mmap_appl_forward(pcm, frames);
+	return frames;
+}
+
+static snd_pcm_sframes_t snd_pcm_dsnoop_writei(snd_pcm_t *pcm ATTRIBUTE_UNUSED, const void *buffer ATTRIBUTE_UNUSED, snd_pcm_uframes_t size ATTRIBUTE_UNUSED)
+{
+	return -ENODEV;
+}
+
+static snd_pcm_sframes_t snd_pcm_dsnoop_writen(snd_pcm_t *pcm ATTRIBUTE_UNUSED, void **bufs ATTRIBUTE_UNUSED, snd_pcm_uframes_t size ATTRIBUTE_UNUSED)
+{
+	return -ENODEV;
+}
+
+static int snd_pcm_dsnoop_close(snd_pcm_t *pcm)
+{
+	snd_pcm_direct_t *dsnoop = pcm->private_data;
+
+	if (dsnoop->timer)
+		snd_timer_close(dsnoop->timer);
+	snd_pcm_direct_semaphore_down(dsnoop, DIRECT_IPC_SEM_CLIENT);
+	snd_pcm_close(dsnoop->spcm);
+ 	if (dsnoop->server)
+ 		snd_pcm_direct_server_discard(dsnoop);
+ 	if (dsnoop->client)
+ 		snd_pcm_direct_client_discard(dsnoop);
+	if (snd_pcm_direct_shm_discard(dsnoop))
+		snd_pcm_direct_semaphore_discard(dsnoop);
+	else
+		snd_pcm_direct_semaphore_up(dsnoop, DIRECT_IPC_SEM_CLIENT);
+	free(dsnoop->bindings);
+	pcm->private_data = NULL;
+	free(dsnoop);
+	return 0;
+}
+
+static snd_pcm_sframes_t snd_pcm_dsnoop_mmap_commit(snd_pcm_t *pcm,
+						    snd_pcm_uframes_t offset ATTRIBUTE_UNUSED,
+						    snd_pcm_uframes_t size)
+{
+	snd_pcm_direct_t *dsnoop = pcm->private_data;
+	int err;
+
+	switch (snd_pcm_state(dsnoop->spcm)) {
+	case SND_PCM_STATE_XRUN:
+		return -EPIPE;
+	case SND_PCM_STATE_SUSPENDED:
+		return -ESTRPIPE;
+	default:
+		break;
+	}
+	if (dsnoop->state == SND_PCM_STATE_RUNNING) {
+		err = snd_pcm_dsnoop_sync_ptr(pcm);
+		if (err < 0)
+			return err;
+	}
+	snd_pcm_mmap_appl_forward(pcm, size);
+	/* clear timer queue to avoid a bogus return from poll */
+	if (snd_pcm_mmap_capture_avail(pcm) < pcm->avail_min)
+		snd_pcm_direct_clear_timer_queue(dsnoop);
+	return size;
+}
+
+static snd_pcm_sframes_t snd_pcm_dsnoop_avail_update(snd_pcm_t *pcm)
+{
+	snd_pcm_direct_t *dsnoop = pcm->private_data;
+	int err;
+	
+	if (dsnoop->state == SND_PCM_STATE_RUNNING) {
+		err = snd_pcm_dsnoop_sync_ptr(pcm);
+		if (err < 0)
+			return err;
+	}
+	return snd_pcm_mmap_capture_avail(pcm);
+}
+
+static int snd_pcm_dsnoop_htimestamp(snd_pcm_t *pcm,
+				     snd_pcm_uframes_t *avail,
+				     snd_htimestamp_t *tstamp)
+{
+	snd_pcm_direct_t *dsnoop = pcm->private_data;
+	snd_pcm_uframes_t avail1;
+	int ok = 0;
+	
+	while (1) {
+		if (dsnoop->state == SND_PCM_STATE_RUNNING ||
+		    dsnoop->state == SND_PCM_STATE_DRAINING)
+			snd_pcm_dsnoop_sync_ptr(pcm);
+		avail1 = snd_pcm_mmap_capture_avail(pcm);
+		if (ok && *avail == avail1)
+			break;
+		*avail = avail1;
+		*tstamp = snd_pcm_hw_fast_tstamp(dsnoop->spcm);
+	}
+	return 0;
+}
+
+static void snd_pcm_dsnoop_dump(snd_pcm_t *pcm, snd_output_t *out)
+{
+	snd_pcm_direct_t *dsnoop = pcm->private_data;
+
+	snd_output_printf(out, "Direct Snoop PCM\n");
+	if (pcm->setup) {
+		snd_output_printf(out, "Its setup is:\n");
+		snd_pcm_dump_setup(pcm, out);
+	}
+	if (dsnoop->spcm)
+		snd_pcm_dump(dsnoop->spcm, out);
+}
+
+static const snd_pcm_ops_t snd_pcm_dsnoop_ops = {
+	.close = snd_pcm_dsnoop_close,
+	.info = snd_pcm_direct_info,
+	.hw_refine = snd_pcm_direct_hw_refine,
+	.hw_params = snd_pcm_direct_hw_params,
+	.hw_free = snd_pcm_direct_hw_free,
+	.sw_params = snd_pcm_direct_sw_params,
+	.channel_info = snd_pcm_direct_channel_info,
+	.dump = snd_pcm_dsnoop_dump,
+	.nonblock = snd_pcm_direct_nonblock,
+	.async = snd_pcm_direct_async,
+	.mmap = snd_pcm_direct_mmap,
+	.munmap = snd_pcm_direct_munmap,
+};
+
+static const snd_pcm_fast_ops_t snd_pcm_dsnoop_fast_ops = {
+	.status = snd_pcm_dsnoop_status,
+	.state = snd_pcm_dsnoop_state,
+	.hwsync = snd_pcm_dsnoop_hwsync,
+	.delay = snd_pcm_dsnoop_delay,
+	.prepare = snd_pcm_dsnoop_prepare,
+	.reset = snd_pcm_dsnoop_reset,
+	.start = snd_pcm_dsnoop_start,
+	.drop = snd_pcm_dsnoop_drop,
+	.drain = snd_pcm_dsnoop_drain,
+	.pause = snd_pcm_dsnoop_pause,
+	.rewindable = snd_pcm_dsnoop_rewindable,
+	.rewind = snd_pcm_dsnoop_rewind,
+	.forwardable = snd_pcm_dsnoop_forwardable,
+	.forward = snd_pcm_dsnoop_forward,
+	.resume = snd_pcm_direct_resume,
+	.link = NULL,
+	.link_slaves = NULL,
+	.unlink = NULL,
+	.writei = snd_pcm_dsnoop_writei,
+	.writen = snd_pcm_dsnoop_writen,
+	.readi = snd_pcm_mmap_readi,
+	.readn = snd_pcm_mmap_readn,
+	.avail_update = snd_pcm_dsnoop_avail_update,
+	.mmap_commit = snd_pcm_dsnoop_mmap_commit,
+	.htimestamp = snd_pcm_dsnoop_htimestamp,
+	.poll_descriptors = NULL,
+	.poll_descriptors_count = NULL,
+	.poll_revents = snd_pcm_direct_poll_revents,
+};
+
+/**
+ * \brief Creates a new dsnoop PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param opts Direct PCM configurations
+ * \param params Parameters for slave
+ * \param root Configuration root
+ * \param sconf Slave configuration
+ * \param stream PCM Direction (stream)
+ * \param mode PCM Mode
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int snd_pcm_dsnoop_open(snd_pcm_t **pcmp, const char *name,
+			struct snd_pcm_direct_open_conf *opts,
+			struct slave_params *params,
+			snd_config_t *root, snd_config_t *sconf,
+			snd_pcm_stream_t stream, int mode)
+{
+	snd_pcm_t *pcm = NULL, *spcm = NULL;
+	snd_pcm_direct_t *dsnoop = NULL;
+	int ret, first_instance, fail_sem_loop = 10;
+
+	assert(pcmp);
+
+	if (stream != SND_PCM_STREAM_CAPTURE) {
+		SNDERR("The dsnoop plugin supports only capture stream");
+		return -EINVAL;
+	}
+
+	dsnoop = calloc(1, sizeof(snd_pcm_direct_t));
+	if (!dsnoop) {
+		ret = -ENOMEM;
+		goto _err_nosem;
+	}
+	
+	ret = snd_pcm_direct_parse_bindings(dsnoop, params, opts->bindings);
+	if (ret < 0)
+		goto _err_nosem;
+	
+	dsnoop->ipc_key = opts->ipc_key;
+	dsnoop->ipc_perm = opts->ipc_perm;
+	dsnoop->ipc_gid = opts->ipc_gid;
+	dsnoop->semid = -1;
+	dsnoop->shmid = -1;
+
+	ret = snd_pcm_new(&pcm, dsnoop->type = SND_PCM_TYPE_DSNOOP, name, stream, mode);
+	if (ret < 0)
+		goto _err_nosem;
+
+	while (1) {
+		ret = snd_pcm_direct_semaphore_create_or_connect(dsnoop);
+		if (ret < 0) {
+			SNDERR("unable to create IPC semaphore");
+			goto _err_nosem;
+		}
+	
+		ret = snd_pcm_direct_semaphore_down(dsnoop, DIRECT_IPC_SEM_CLIENT);
+		if (ret < 0) {
+			snd_pcm_direct_semaphore_discard(dsnoop);
+			if (--fail_sem_loop <= 0)
+				goto _err_nosem;
+			continue;
+		}
+		break;
+	}
+		
+	first_instance = ret = snd_pcm_direct_shm_create_or_connect(dsnoop);
+	if (ret < 0) {
+		SNDERR("unable to create IPC shm instance");
+		goto _err;
+	}
+		
+	pcm->ops = &snd_pcm_dsnoop_ops;
+	pcm->fast_ops = &snd_pcm_dsnoop_fast_ops;
+	pcm->private_data = dsnoop;
+	dsnoop->state = SND_PCM_STATE_OPEN;
+	dsnoop->slowptr = opts->slowptr;
+	dsnoop->max_periods = opts->max_periods;
+	dsnoop->sync_ptr = snd_pcm_dsnoop_sync_ptr;
+
+	if (first_instance) {
+		/* recursion is already checked in
+		   snd_pcm_direct_get_slave_ipc_offset() */
+		ret = snd_pcm_open_slave(&spcm, root, sconf, stream,
+					 mode | SND_PCM_NONBLOCK, NULL);
+		if (ret < 0) {
+			SNDERR("unable to open slave");
+			goto _err;
+		}
+	
+		if (snd_pcm_type(spcm) != SND_PCM_TYPE_HW) {
+			SNDERR("dsnoop plugin can be only connected to hw plugin");
+			goto _err;
+		}
+		
+		ret = snd_pcm_direct_initialize_slave(dsnoop, spcm, params);
+		if (ret < 0) {
+			SNDERR("unable to initialize slave");
+			goto _err;
+		}
+
+		dsnoop->spcm = spcm;
+		
+		if (dsnoop->shmptr->use_server) {
+			ret = snd_pcm_direct_server_create(dsnoop);
+			if (ret < 0) {
+				SNDERR("unable to create server");
+				goto _err;
+			}
+		}
+
+		dsnoop->shmptr->type = spcm->type;
+	} else {
+		if (dsnoop->shmptr->use_server) {
+			/* up semaphore to avoid deadlock */
+			snd_pcm_direct_semaphore_up(dsnoop, DIRECT_IPC_SEM_CLIENT);
+			ret = snd_pcm_direct_client_connect(dsnoop);
+			if (ret < 0) {
+				SNDERR("unable to connect client");
+				goto _err_nosem;
+			}
+			
+			snd_pcm_direct_semaphore_down(dsnoop, DIRECT_IPC_SEM_CLIENT);
+
+			ret = snd_pcm_direct_open_secondary_client(&spcm, dsnoop, "dsnoop_client");
+			if (ret < 0)
+				goto _err;
+		} else {
+
+			ret = snd_pcm_open_slave(&spcm, root, sconf, stream,
+						 mode | SND_PCM_NONBLOCK |
+						 SND_PCM_APPEND,
+						 NULL);
+			if (ret < 0) {
+				SNDERR("unable to open slave");
+				goto _err;
+			}
+			if (snd_pcm_type(spcm) != SND_PCM_TYPE_HW) {
+				SNDERR("dsnoop plugin can be only connected to hw plugin");
+				ret = -EINVAL;
+				goto _err;
+			}
+		
+			ret = snd_pcm_direct_initialize_secondary_slave(dsnoop, spcm, params);
+			if (ret < 0) {
+				SNDERR("unable to initialize slave");
+				goto _err;
+			}
+		}
+
+		dsnoop->spcm = spcm;
+	}
+
+	ret = snd_pcm_direct_initialize_poll_fd(dsnoop);
+	if (ret < 0) {
+		SNDERR("unable to initialize poll_fd");
+		goto _err;
+	}
+
+	pcm->poll_fd = dsnoop->poll_fd;
+	pcm->poll_events = POLLIN;	/* it's different than other plugins */
+		
+	pcm->mmap_rw = 1;
+	snd_pcm_set_hw_ptr(pcm, &dsnoop->hw_ptr, -1, 0);
+	snd_pcm_set_appl_ptr(pcm, &dsnoop->appl_ptr, -1, 0);
+	
+	if (dsnoop->channels == UINT_MAX)
+		dsnoop->channels = dsnoop->shmptr->s.channels;
+	
+	snd_pcm_direct_semaphore_up(dsnoop, DIRECT_IPC_SEM_CLIENT);
+
+	*pcmp = pcm;
+	return 0;
+	
+ _err:
+ 	if (dsnoop->timer)
+		snd_timer_close(dsnoop->timer);
+	if (dsnoop->server)
+		snd_pcm_direct_server_discard(dsnoop);
+	if (dsnoop->client)
+		snd_pcm_direct_client_discard(dsnoop);
+	if (spcm)
+		snd_pcm_close(spcm);
+	if (dsnoop->shmid >= 0)
+		snd_pcm_direct_shm_discard(dsnoop);
+	if (snd_pcm_direct_semaphore_discard(dsnoop) < 0)
+		snd_pcm_direct_semaphore_up(dsnoop, DIRECT_IPC_SEM_CLIENT);
+ _err_nosem:
+	if (dsnoop) {
+		free(dsnoop->bindings);
+		free(dsnoop);
+	}
+	if (pcm)
+		snd_pcm_free(pcm);
+	return ret;
+}
+
+/*! \page pcm_plugins
+
+\section pcm_plugins_dsnoop Plugin: dsnoop
+
+This plugin splits one capture stream to more.
+It works the reverse way of \ref pcm_plugins_dmix "dmix plugin",
+reading the shared capture buffer from many clients concurrently.
+The meaning of parameters below are almost identical with
+dmix plugin.
+
+\code
+pcm.name {
+	type dsnoop		# Direct snoop
+	ipc_key INT		# unique IPC key
+	ipc_key_add_uid BOOL	# add current uid to unique IPC key
+	ipc_perm INT		# IPC permissions (octal, default 0600)
+	slave STR
+	# or
+	slave {			# Slave definition
+		pcm STR		# slave PCM name
+		# or
+		pcm { }		# slave PCM definition
+		format STR	# format definition
+		rate INT	# rate definition
+		channels INT
+		period_time INT	# in usec
+		# or
+		period_size INT	# in bytes
+		buffer_time INT	# in usec
+		# or
+		buffer_size INT # in bytes
+		periods INT	# when buffer_size or buffer_time is not specified
+	}
+	bindings {		# note: this is client independent!!!
+		N INT		# maps slave channel to client channel N
+	}
+	slowptr BOOL		# slow but more precise pointer updates
+}
+\endcode
+
+\subsection pcm_plugins_dsnoop_funcref Function reference
+
+<UL>
+  <LI>snd_pcm_dsnoop_open()
+  <LI>_snd_pcm_dsnoop_open()
+</UL>
+
+*/
+
+/**
+ * \brief Creates a new dsnoop PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param root Root configuration node
+ * \param conf Configuration node with dsnoop PCM description
+ * \param stream PCM Stream
+ * \param mode PCM Mode
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int _snd_pcm_dsnoop_open(snd_pcm_t **pcmp, const char *name,
+		       snd_config_t *root, snd_config_t *conf,
+		       snd_pcm_stream_t stream, int mode)
+{
+	snd_config_t *sconf;
+	struct slave_params params;
+	struct snd_pcm_direct_open_conf dopen;
+	int bsize, psize;
+	int err;
+
+	err = snd_pcm_direct_parse_open_conf(root, conf, stream, &dopen);
+	if (err < 0)
+		return err;
+
+	/* the default settings, it might be invalid for some hardware */
+	params.format = SND_PCM_FORMAT_S16;
+	params.rate = 48000;
+	params.channels = 2;
+	params.period_time = -1;
+	params.buffer_time = -1;
+	bsize = psize = -1;
+	params.periods = 3;
+	err = snd_pcm_slave_conf(root, dopen.slave, &sconf, 8,
+				 SND_PCM_HW_PARAM_FORMAT, SCONF_UNCHANGED, &params.format,
+				 SND_PCM_HW_PARAM_RATE, 0, &params.rate,
+				 SND_PCM_HW_PARAM_CHANNELS, 0, &params.channels,
+				 SND_PCM_HW_PARAM_PERIOD_TIME, 0, &params.period_time,
+				 SND_PCM_HW_PARAM_BUFFER_TIME, 0, &params.buffer_time,
+				 SND_PCM_HW_PARAM_PERIOD_SIZE, 0, &psize,
+				 SND_PCM_HW_PARAM_BUFFER_SIZE, 0, &bsize,
+				 SND_PCM_HW_PARAM_PERIODS, 0, &params.periods);
+	if (err < 0)
+		return err;
+
+	/* set a reasonable default */  
+	if (psize == -1 && params.period_time == -1)
+		params.period_time = 125000;    /* 0.125 seconds */
+
+	if (params.format == -2)
+		params.format = SND_PCM_FORMAT_UNKNOWN;
+
+	params.period_size = psize;
+	params.buffer_size = bsize;
+
+	err = snd_pcm_dsnoop_open(pcmp, name, &dopen, &params,
+				  root, sconf, stream, mode);
+	snd_config_delete(sconf);
+	return err;
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(_snd_pcm_dsnoop_open, SND_PCM_DLSYM_VERSION);
+#endif
diff --git a/src/pcm/pcm_empty.c b/src/pcm/pcm_empty.c
new file mode 100644
index 0000000..070850c
--- /dev/null
+++ b/src/pcm/pcm_empty.c
@@ -0,0 +1,110 @@
+/**
+ * \file pcm/pcm_empty.c
+ * \ingroup PCM_Plugins
+ * \brief PCM Null Plugin Interface
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \date 2006
+ */
+/*
+ *  PCM - Null plugin
+ *  Copyright (c) 2006 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+#include "pcm_local.h"
+#include "pcm_plugin.h"
+
+#ifndef PIC
+/* entry for static linking */
+const char *_snd_module_pcm_empty = "";
+#endif
+
+/*! \page pcm_plugins
+
+\section pcm_plugins_null Plugin: Null
+
+This plugin discards contents of a PCM stream or creates a stream with zero
+samples.
+
+Note: This implementation uses devices /dev/null (playback, must be writable)
+and /dev/full (capture, must be readable).
+
+\code
+pcm.name {
+        type null               # Null PCM
+}
+\endcode
+
+\subsection pcm_plugins_null_funcref Function reference
+
+<UL>
+  <LI>_snd_pcm_empty_open()
+</UL>
+
+*/
+
+/**
+ * \brief Creates a new Empty PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param root Root configuration node
+ * \param conf Configuration node with empty PCM description
+ * \param stream Stream type
+ * \param mode Stream mode
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int _snd_pcm_empty_open(snd_pcm_t **pcmp, const char *name ATTRIBUTE_UNUSED,
+		        snd_config_t *root, snd_config_t *conf, 
+		        snd_pcm_stream_t stream, int mode)
+{
+	snd_config_t *slave = NULL, *sconf;
+	snd_config_iterator_t i, next;
+	int err;
+
+	snd_config_for_each(i, next, conf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (snd_pcm_conf_generic_id(id))
+			continue;
+		if (strcmp(id, "slave") == 0) {
+			slave = n;
+			continue;
+		}
+		SNDERR("Unknown field %s", id);
+		return -EINVAL;
+	}
+	if (!slave) {
+		SNDERR("slave is not defined");
+		return -EINVAL;
+	}
+	err = snd_pcm_slave_conf(root, slave, &sconf, 0);
+	if (err < 0)
+		return err;
+	err = snd_pcm_open_named_slave(pcmp, name, root, sconf, stream,
+				       mode, conf);
+	snd_config_delete(sconf);
+	return err;
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(_snd_pcm_empty_open, SND_PCM_DLSYM_VERSION);
+#endif
diff --git a/src/pcm/pcm_ext_parm.h b/src/pcm/pcm_ext_parm.h
new file mode 100644
index 0000000..d25f2ab
--- /dev/null
+++ b/src/pcm/pcm_ext_parm.h
@@ -0,0 +1,41 @@
+/* hw_params */
+struct snd_ext_parm {
+	unsigned int min, max;
+	unsigned int num_list;
+	unsigned int *list;
+	unsigned int active: 1;
+	unsigned int integer: 1;
+};
+
+static inline snd_mask_t *hw_param_mask(snd_pcm_hw_params_t *params,
+					snd_pcm_hw_param_t var)
+{
+	return &params->masks[var - SND_PCM_HW_PARAM_FIRST_MASK];
+}
+
+static inline snd_interval_t *hw_param_interval(snd_pcm_hw_params_t *params,
+						snd_pcm_hw_param_t var)
+{
+	return &params->intervals[var - SND_PCM_HW_PARAM_FIRST_INTERVAL];
+}
+
+/* make local functions really local */
+#define snd_ext_parm_set_minmax \
+	snd1_ext_parm_set_minmax
+#define snd_ext_parm_set_list \
+	snd1_ext_parm_set_list
+#define snd_ext_parm_clear \
+	snd1_ext_parm_clear
+#define snd_interval_list \
+	snd1_interval_list
+#define snd_ext_parm_interval_refine \
+	snd1_ext_parm_interval_refine
+#define snd_ext_parm_mask_refine \
+	snd1_ext_parm_mask_refine
+
+int snd_ext_parm_set_minmax(struct snd_ext_parm *parm, unsigned int min, unsigned int max);
+int snd_ext_parm_set_list(struct snd_ext_parm *parm, unsigned int num_list, const unsigned int *list);
+void snd_ext_parm_clear(struct snd_ext_parm *parm);
+int snd_interval_list(snd_interval_t *ival, int num_list, unsigned int *list);
+int snd_ext_parm_interval_refine(snd_interval_t *ival, struct snd_ext_parm *parm, int type);
+int snd_ext_parm_mask_refine(snd_mask_t *mask, struct snd_ext_parm *parm, int type);
diff --git a/src/pcm/pcm_extplug.c b/src/pcm/pcm_extplug.c
new file mode 100644
index 0000000..a34706f
--- /dev/null
+++ b/src/pcm/pcm_extplug.c
@@ -0,0 +1,813 @@
+/**
+ * \file pcm/pcm_extplug.c
+ * \ingroup Plugin_SDK
+ * \brief External Filter Plugin SDK
+ * \author Takashi Iwai <tiwai@suse.de>
+ * \date 2005
+ */
+/*
+ *  PCM - External Filter Plugin SDK
+ *  Copyright (c) 2005 by Takashi Iwai <tiwai@suse.de>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+#include "pcm_local.h"
+#include "pcm_plugin.h"
+#include "pcm_extplug.h"
+#include "pcm_ext_parm.h"
+
+#ifndef PIC
+/* entry for static linking */
+const char *_snd_module_pcm_extplug = "";
+#endif
+
+#ifndef DOC_HIDDEN
+
+typedef struct snd_pcm_extplug_priv {
+	snd_pcm_plugin_t plug;
+	snd_pcm_extplug_t *data;
+	struct snd_ext_parm params[SND_PCM_EXTPLUG_HW_PARAMS];
+	struct snd_ext_parm sparams[SND_PCM_EXTPLUG_HW_PARAMS];
+} extplug_priv_t;
+
+static const int hw_params_type[SND_PCM_EXTPLUG_HW_PARAMS] = {
+	[SND_PCM_EXTPLUG_HW_FORMAT] = SND_PCM_HW_PARAM_FORMAT,
+	[SND_PCM_EXTPLUG_HW_CHANNELS] = SND_PCM_HW_PARAM_CHANNELS
+};
+
+#define is_mask_type(i) (hw_params_type[i] < SND_PCM_HW_PARAM_FIRST_INTERVAL)
+
+static const unsigned int excl_parbits[SND_PCM_EXTPLUG_HW_PARAMS] = {
+	[SND_PCM_EXTPLUG_HW_FORMAT] = (SND_PCM_HW_PARBIT_FORMAT|
+				       SND_PCM_HW_PARBIT_SUBFORMAT |
+				       SND_PCM_HW_PARBIT_SAMPLE_BITS),
+	[SND_PCM_EXTPLUG_HW_CHANNELS] = (SND_PCM_HW_PARBIT_CHANNELS|
+					 SND_PCM_HW_PARBIT_FRAME_BITS),
+};
+
+/*
+ * set min/max values for the given parameter
+ */
+int snd_ext_parm_set_minmax(struct snd_ext_parm *parm, unsigned int min, unsigned int max)
+{
+	parm->num_list = 0;
+	free(parm->list);
+	parm->list = NULL;
+	parm->min = min;
+	parm->max = max;
+	parm->active = 1;
+	return 0;
+}
+
+/*
+ * set the list of available values for the given parameter
+ */
+static int val_compar(const void *ap, const void *bp)
+{
+	return *(const unsigned int *)ap - *(const unsigned int *)bp;
+}
+
+int snd_ext_parm_set_list(struct snd_ext_parm *parm, unsigned int num_list, const unsigned int *list)
+{
+	unsigned int *new_list;
+
+	new_list = malloc(sizeof(*new_list) * num_list);
+	if (new_list == NULL)
+		return -ENOMEM;
+	memcpy(new_list, list, sizeof(*new_list) * num_list);
+	qsort(new_list, num_list, sizeof(*new_list), val_compar);
+
+	free(parm->list);
+	parm->num_list = num_list;
+	parm->list = new_list;
+	parm->active = 1;
+	return 0;
+}
+
+void snd_ext_parm_clear(struct snd_ext_parm *parm)
+{
+	free(parm->list);
+	memset(parm, 0, sizeof(*parm));
+}
+
+/*
+ * limit the interval to the given list
+ */
+int snd_interval_list(snd_interval_t *ival, int num_list, unsigned int *list)
+{
+	int imin, imax;
+	int changed = 0;
+
+	if (snd_interval_empty(ival))
+		return -ENOENT;
+	for (imin = 0; imin < num_list; imin++) {
+		if (ival->min == list[imin] && ! ival->openmin)
+			break;
+		if (ival->min <= list[imin]) {
+			ival->min = list[imin];
+			ival->openmin = 0;
+			changed = 1;
+			break;
+		}
+	}
+	if (imin >= num_list)
+		return -EINVAL;
+	for (imax = num_list - 1; imax >= imin; imax--) {
+		if (ival->max == list[imax] && ! ival->openmax)
+			break;
+		if (ival->max >= list[imax]) {
+			ival->max = list[imax];
+			ival->openmax = 0;
+			changed = 1;
+			break;
+		}
+	}
+	if (imax < imin)
+		return -EINVAL;
+	return changed;
+}
+
+/*
+ * refine the interval parameter
+ */
+int snd_ext_parm_interval_refine(snd_interval_t *ival, struct snd_ext_parm *parm, int type)
+{
+	parm += type;
+	if (! parm->active)
+		return 0;
+	ival->integer |= parm->integer;
+	if (parm->num_list) {
+		return snd_interval_list(ival, parm->num_list, parm->list);
+	} else if (parm->min || parm->max) {
+		snd_interval_t t;
+		memset(&t, 0, sizeof(t));
+		snd_interval_set_minmax(&t, parm->min, parm->max);
+		t.integer = ival->integer;
+		return snd_interval_refine(ival, &t);
+	}
+	return 0;
+}
+
+/*
+ * refine the mask parameter
+ */
+int snd_ext_parm_mask_refine(snd_mask_t *mask, struct snd_ext_parm *parm, int type)
+{
+	snd_mask_t bits;
+	unsigned int i;
+
+	parm += type;
+	memset(&bits, 0, sizeof(bits));
+	for (i = 0; i < parm->num_list; i++)
+		bits.bits[parm->list[i] / 32] |= 1U << (parm->list[i] % 32);
+	return snd_mask_refine(mask, &bits);
+}
+
+
+/*
+ * hw_refine callback
+ */
+static int extplug_hw_refine(snd_pcm_hw_params_t *hw_params,
+			     struct snd_ext_parm *parm)
+{
+	int i, err, change = 0;
+	for (i = 0; i < SND_PCM_EXTPLUG_HW_PARAMS; i++) {
+		int type = hw_params_type[i];
+		if (is_mask_type(i))
+			err = snd_ext_parm_mask_refine(hw_param_mask(hw_params, type),
+						       parm, i);
+		else
+			err = snd_ext_parm_interval_refine(hw_param_interval(hw_params, type),
+							   parm, i);
+		if (err < 0)
+			return err;
+		change |= err;
+	}
+	return change;
+}
+
+static int snd_pcm_extplug_hw_refine_cprepare(snd_pcm_t *pcm,
+					      snd_pcm_hw_params_t *params)
+{
+	extplug_priv_t *ext = pcm->private_data;
+	int err;
+	snd_pcm_access_mask_t access_mask = { SND_PCM_ACCBIT_SHM };
+	err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_ACCESS,
+					 &access_mask);
+	if (err < 0)
+		return err;
+	err = extplug_hw_refine(params, ext->params);
+	if (err < 0)
+		return err;
+	params->info &= ~(SND_PCM_INFO_MMAP | SND_PCM_INFO_MMAP_VALID);
+	return 0;
+}
+
+static int snd_pcm_extplug_hw_refine_sprepare(snd_pcm_t *pcm,
+					      snd_pcm_hw_params_t *sparams)
+{
+	extplug_priv_t *ext = pcm->private_data;
+	snd_pcm_access_mask_t saccess_mask = { SND_PCM_ACCBIT_MMAP };
+	_snd_pcm_hw_params_any(sparams);
+	_snd_pcm_hw_param_set_mask(sparams, SND_PCM_HW_PARAM_ACCESS,
+				   &saccess_mask);
+	extplug_hw_refine(sparams, ext->sparams);
+	return 0;
+}
+
+static unsigned int get_links(struct snd_ext_parm *params)
+{
+	int i;
+	unsigned int links = (SND_PCM_HW_PARBIT_FORMAT |
+			      SND_PCM_HW_PARBIT_SUBFORMAT |
+			      SND_PCM_HW_PARBIT_SAMPLE_BITS |
+			      SND_PCM_HW_PARBIT_CHANNELS |
+			      SND_PCM_HW_PARBIT_FRAME_BITS |
+			      SND_PCM_HW_PARBIT_RATE |
+			      SND_PCM_HW_PARBIT_PERIODS |
+			      SND_PCM_HW_PARBIT_PERIOD_SIZE |
+			      SND_PCM_HW_PARBIT_PERIOD_TIME |
+			      SND_PCM_HW_PARBIT_BUFFER_SIZE |
+			      SND_PCM_HW_PARBIT_BUFFER_TIME |
+			      SND_PCM_HW_PARBIT_TICK_TIME);
+
+	for (i = 0; i < SND_PCM_EXTPLUG_HW_PARAMS; i++) {
+		if (params[i].active)
+			links &= ~excl_parbits[i];
+	}
+	return links;
+}
+
+static int snd_pcm_extplug_hw_refine_schange(snd_pcm_t *pcm,
+					     snd_pcm_hw_params_t *params,
+					     snd_pcm_hw_params_t *sparams)
+{
+	extplug_priv_t *ext = pcm->private_data;
+	unsigned int links = get_links(ext->sparams);
+
+	return _snd_pcm_hw_params_refine(sparams, links, params);
+}
+	
+static int snd_pcm_extplug_hw_refine_cchange(snd_pcm_t *pcm,
+					     snd_pcm_hw_params_t *params,
+					     snd_pcm_hw_params_t *sparams)
+{
+	extplug_priv_t *ext = pcm->private_data;
+	unsigned int links = get_links(ext->params);
+
+	return _snd_pcm_hw_params_refine(params, links, sparams);
+}
+
+static int snd_pcm_extplug_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	int err = snd_pcm_hw_refine_slave(pcm, params,
+				       snd_pcm_extplug_hw_refine_cprepare,
+				       snd_pcm_extplug_hw_refine_cchange,
+				       snd_pcm_extplug_hw_refine_sprepare,
+				       snd_pcm_extplug_hw_refine_schange,
+				       snd_pcm_generic_hw_refine);
+	return err;
+}
+
+/*
+ * hw_params callback
+ */
+static int snd_pcm_extplug_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+
+	extplug_priv_t *ext = pcm->private_data;
+	snd_pcm_t *slave = ext->plug.gen.slave;
+	int err = snd_pcm_hw_params_slave(pcm, params,
+					  snd_pcm_extplug_hw_refine_cchange,
+					  snd_pcm_extplug_hw_refine_sprepare,
+					  snd_pcm_extplug_hw_refine_schange,
+					  snd_pcm_generic_hw_params);
+	if (err < 0)
+		return err;
+	ext->data->slave_format = slave->format;
+	ext->data->slave_subformat = slave->subformat;
+	ext->data->slave_channels = slave->channels;
+	ext->data->rate = slave->rate;
+	INTERNAL(snd_pcm_hw_params_get_format)(params, &ext->data->format);
+	INTERNAL(snd_pcm_hw_params_get_subformat)(params, &ext->data->subformat);
+	INTERNAL(snd_pcm_hw_params_get_channels)(params, &ext->data->channels);
+
+	if (ext->data->callback->hw_params) {
+		err = ext->data->callback->hw_params(ext->data, params);
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+/*
+ * hw_free callback
+ */
+static int snd_pcm_extplug_hw_free(snd_pcm_t *pcm)
+{
+	extplug_priv_t *ext = pcm->private_data;
+
+	snd_pcm_hw_free(ext->plug.gen.slave);
+	if (ext->data->callback->hw_free)
+		return ext->data->callback->hw_free(ext->data);
+	return 0;
+}
+
+/*
+ * write_areas skeleton - call transfer callback
+ */
+static snd_pcm_uframes_t
+snd_pcm_extplug_write_areas(snd_pcm_t *pcm,
+			    const snd_pcm_channel_area_t *areas,
+			    snd_pcm_uframes_t offset,
+			    snd_pcm_uframes_t size,
+			    const snd_pcm_channel_area_t *slave_areas,
+			    snd_pcm_uframes_t slave_offset,
+			    snd_pcm_uframes_t *slave_sizep)
+{
+	extplug_priv_t *ext = pcm->private_data;
+
+	if (size > *slave_sizep)
+		size = *slave_sizep;
+	size = ext->data->callback->transfer(ext->data, slave_areas, slave_offset,
+					     areas, offset, size);
+	*slave_sizep = size;
+	return size;
+}
+
+/*
+ * read_areas skeleton - call transfer callback
+ */
+static snd_pcm_uframes_t
+snd_pcm_extplug_read_areas(snd_pcm_t *pcm,
+			   const snd_pcm_channel_area_t *areas,
+			   snd_pcm_uframes_t offset,
+			   snd_pcm_uframes_t size,
+			   const snd_pcm_channel_area_t *slave_areas,
+			   snd_pcm_uframes_t slave_offset,
+			   snd_pcm_uframes_t *slave_sizep)
+{
+	extplug_priv_t *ext = pcm->private_data;
+
+	if (size > *slave_sizep)
+		size = *slave_sizep;
+	size = ext->data->callback->transfer(ext->data, areas, offset,
+					     slave_areas, slave_offset, size);
+	*slave_sizep = size;
+	return size;
+}
+
+/*
+ * call init callback
+ */
+static int snd_pcm_extplug_init(snd_pcm_t *pcm)
+{
+	extplug_priv_t *ext = pcm->private_data;
+	return ext->data->callback->init(ext->data);
+}
+
+/*
+ * dump setup
+ */
+static void snd_pcm_extplug_dump(snd_pcm_t *pcm, snd_output_t *out)
+{
+	extplug_priv_t *ext = pcm->private_data;
+
+	if (ext->data->callback->dump)
+		ext->data->callback->dump(ext->data, out);
+	else {
+		if (ext->data->name)
+			snd_output_printf(out, "%s\n", ext->data->name);
+		else
+			snd_output_printf(out, "External PCM Plugin\n");
+		if (pcm->setup) {
+			snd_output_printf(out, "Its setup is:\n");
+			snd_pcm_dump_setup(pcm, out);
+		}
+	}
+	snd_output_printf(out, "Slave: ");
+	snd_pcm_dump(ext->plug.gen.slave, out);
+}
+
+static void clear_ext_params(extplug_priv_t *ext)
+{
+	int i;
+	for (i = 0; i < SND_PCM_EXTPLUG_HW_PARAMS; i++) {
+		snd_ext_parm_clear(&ext->params[i]);
+		snd_ext_parm_clear(&ext->sparams[i]);
+	}
+}
+
+static int snd_pcm_extplug_close(snd_pcm_t *pcm)
+{
+	extplug_priv_t *ext = pcm->private_data;
+
+	snd_pcm_close(ext->plug.gen.slave);
+	clear_ext_params(ext);
+	if (ext->data->callback->close)
+		ext->data->callback->close(ext->data);
+	free(ext);
+	return 0;
+}
+
+static const snd_pcm_ops_t snd_pcm_extplug_ops = {
+	.close = snd_pcm_extplug_close,
+	.info = snd_pcm_generic_info,
+	.hw_refine = snd_pcm_extplug_hw_refine,
+	.hw_params = snd_pcm_extplug_hw_params,
+	.hw_free = snd_pcm_extplug_hw_free,
+	.sw_params = snd_pcm_generic_sw_params,
+	.channel_info = snd_pcm_generic_channel_info,
+	.dump = snd_pcm_extplug_dump,
+	.nonblock = snd_pcm_generic_nonblock,
+	.async = snd_pcm_generic_async,
+	.mmap = snd_pcm_generic_mmap,
+	.munmap = snd_pcm_generic_munmap,
+};
+
+#endif /* !DOC_HIDDEN */
+
+/*
+ * Exported functions
+ */
+
+/*! \page pcm_external_plugins PCM External Plugin SDK
+
+\section pcm_externals External Plugins
+
+The external plugins are implemented in a shared object file located
+at /usr/lib/alsa-lib (the exact location depends on the build option
+and asoundrc configuration).  It has to be the file like
+libasound_module_pcm_MYPLUGIN.so, where MYPLUGIN corresponds to your
+own plugin name.
+
+The entry point of the plugin is defined via
+#SND_PCM_PLUGIN_DEFINE_FUNC() macro.  This macro defines the function
+with a proper name to be referred from alsa-lib.  The function takes
+the following 6 arguments:
+\code
+int (snd_pcm_t **pcmp, const char *name, snd_config_t *root,
+	snd_config_t *conf, snd_pcm_stream_t stream, int mode)
+\endcode
+The first argument, pcmp, is the pointer to store the resultant PCM
+handle.  The arguments name, root, stream and mode are the parameters
+to be passed to the plugin constructor.  The conf is the configuration
+tree for the plugin.  The arguments above are defined in the macro
+itself, so don't use variables with the same names to shadow
+parameters.
+
+After parsing the configuration parameters in the given conf tree,
+usually you will call the external plugin API function,
+#snd_pcm_extplug_create() or #snd_pcm_ioplug_create(), depending
+on the plugin type.  The PCM handle must be filled *pcmp in return.
+Then this function must return either a value 0 when succeeded, or a
+negative value as the error code. 
+
+Finally, add #SND_PCM_PLUGIN_SYMBOL() with the name of your
+plugin as the argument at the end.  This defines the proper versioned
+symbol as the reference.
+
+The typical code would look like below:
+\code
+struct myplug_info {
+	snd_pcm_extplug_t ext;
+	int my_own_data;
+	...
+};
+
+SND_PCM_PLUGIN_DEFINE_FUNC(myplug)
+{
+	snd_config_iterator_t i, next;
+	snd_config_t *slave = NULL;
+	struct myplug_info *myplug;
+	int err;
+
+	snd_config_for_each(i, next, conf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (strcmp(id, "comment") == 0 || strcmp(id, "type") == 0)
+			continue;
+		if (strcmp(id, "slave") == 0) {
+			slave = n;
+			continue;
+		}
+		if (strcmp(id, "my_own_parameter") == 0) {
+			....
+			continue;
+		}
+		SNDERR("Unknown field %s", id);
+		return -EINVAL;
+	}
+
+	if (! slave) {
+		SNDERR("No slave defined for myplug");
+		return -EINVAL;
+	}
+
+	myplug = calloc(1, sizeof(*myplug));
+	if (myplug == NULL)
+		return -ENOMEM;
+
+	myplug->ext.version = SND_PCM_EXTPLUG_VERSION;
+	myplug->ext.name = "My Own Plugin";
+	myplug->ext.callback = &my_own_callback;
+	myplug->ext.private_data = myplug;
+	....
+
+	err = snd_pcm_extplug_create(&myplug->ext, name, root, conf, stream, mode);
+	if (err < 0) {
+		myplug_free(myplug);
+		return err;
+	}
+
+	*pcmp = myplug->ext.pcm;
+	return 0;
+}
+
+SND_PCM_PLUGIN_SYMBOL(myplug);
+\endcode
+
+Read the codes in alsa-plugins package for the real examples.
+
+
+\section pcm_extplug External Plugin: Filter-Type Plugin
+
+The filter-type plugin is a plugin to convert the PCM signals from the input
+and feeds to the output.  Thus, this plugin always needs a slave PCM as its output.
+
+The plugin can modify the format and the channels of the input/output PCM.
+It can <i>not</i> modify the sample rate (because of simplicity reason).
+
+The following fields have to be filled in extplug record before calling
+#snd_pcm_extplug_create() : version, name, callback.
+Otherfields are optional and should be initialized with zero.
+
+The constant #SND_PCM_EXTPLUG_VERSION must be passed to the version
+field for the version check in alsa-lib.  A non-NULL ASCII string
+has to be passed to the name field.  The callback field contains the 
+table of callback functions for this plugin (defined as
+#snd_pcm_extplug_callback_t).
+
+The driver can set an arbitrary value (pointer) to private_data
+field to refer its own data in the callbacks.
+
+The rest fields are filled by #snd_pcm_extplug_create().  The pcm field
+is the resultant PCM handle.  The others are the current status of the
+PCM.
+
+The callback functions in #snd_pcm_extplug_callback_t define the real
+behavior of the driver.
+At least, transfer callback must be given.  This callback is called
+at each time certain size of data block is transfered to the slave
+PCM.  Other callbacks are optional.  
+
+The close callback is called when the PCM is closed.  If the plugin
+allocates private resources, this is the place to release them
+again.  The hw_params and hw_free callbacks are called at
+#snd_pcm_hw_params() and #snd_pcm_hw_free() API calls,
+respectively.  The last, dump callback, is called for printing the
+information of the given plugin.
+
+The init callback is called when the PCM is at prepare state or any
+initialization is issued.  Use this callback to reset the PCM instance
+to a sane initial state.
+
+The hw_params constraints can be defined via either
+#snd_pcm_extplug_set_param_minmax() and #snd_pcm_extplug_set_param_list()
+functions after calling #snd_pcm_extplug_create().
+The former defines the minimal and maximal acceptable values for the
+given hw_params parameter (SND_PCM_EXTPLUG_HW_XXX).
+This function can't be used for the format parameter.  The latter
+function specifies the available parameter values as the list.
+As mentioned above, the rate can't be changed.  Only changeable
+parameters are sample format and channels.
+
+To define the constraints of the slave PCM configuration, use
+either #snd_pcm_extplug_set_slave_param_minmax() and
+#snd_pcm_extplug_set_slave_param_list().  The arguments are as same
+as former functions.
+
+To clear the parameter constraints, call #snd_pcm_extplug_params_reset()
+function. 
+
+*/
+
+/**
+ * \brief Create an extplug instance
+ * \param extplug the extplug handle
+ * \param name name of the PCM
+ * \param root configuration tree root
+ * \param slave_conf slave configuration root
+ * \param stream stream direction
+ * \param mode PCM open mode
+ * \return 0 if successful, or a negative error code
+ *
+ * Creates the extplug instance based on the given handle.
+ * The slave_conf argument is mandatory, and usually taken from the config tree of the
+ * PCM plugin as "slave" config value.
+ * name, root, stream and mode arguments are the values used for opening the PCM.
+ *
+ * The callback is the mandatory field of extplug handle.  At least, start, stop and
+ * pointer callbacks must be set before calling this function.
+ */
+int snd_pcm_extplug_create(snd_pcm_extplug_t *extplug, const char *name,
+			   snd_config_t *root, snd_config_t *slave_conf,
+			   snd_pcm_stream_t stream, int mode)
+{
+	extplug_priv_t *ext;
+	int err;
+	snd_pcm_t *spcm, *pcm;
+	snd_config_t *sconf;
+
+	assert(root);
+	assert(extplug && extplug->callback);
+	assert(extplug->callback->transfer);
+	assert(slave_conf);
+
+	if (extplug->version != SND_PCM_EXTPLUG_VERSION) {
+		SNDERR("extplug: Plugin version mismatch\n");
+		return -ENXIO;
+	}
+
+	err = snd_pcm_slave_conf(root, slave_conf, &sconf, 0);
+	if (err < 0)
+		return err;
+	err = snd_pcm_open_slave(&spcm, root, sconf, stream, mode, NULL);
+	snd_config_delete(sconf);
+	if (err < 0)
+		return err;
+
+	ext = calloc(1, sizeof(*ext));
+	if (! ext)
+		return -ENOMEM;
+
+	ext->data = extplug;
+	extplug->stream = stream;
+
+	snd_pcm_plugin_init(&ext->plug);
+	ext->plug.read = snd_pcm_extplug_read_areas;
+	ext->plug.write = snd_pcm_extplug_write_areas;
+	ext->plug.undo_read = snd_pcm_plugin_undo_read_generic;
+	ext->plug.undo_write = snd_pcm_plugin_undo_write_generic;
+	ext->plug.gen.slave = spcm;
+	ext->plug.gen.close_slave = 1;
+	if (extplug->callback->init)
+		ext->plug.init = snd_pcm_extplug_init;
+
+	err = snd_pcm_new(&pcm, SND_PCM_TYPE_EXTPLUG, name, stream, mode);
+	if (err < 0) {
+		free(ext);
+		return err;
+	}
+
+	extplug->pcm = pcm;
+	pcm->ops = &snd_pcm_extplug_ops;
+	pcm->fast_ops = &snd_pcm_plugin_fast_ops;
+	pcm->private_data = ext;
+	pcm->poll_fd = spcm->poll_fd;
+	pcm->poll_events = spcm->poll_events;
+	snd_pcm_set_hw_ptr(pcm, &ext->plug.hw_ptr, -1, 0);
+	snd_pcm_set_appl_ptr(pcm, &ext->plug.appl_ptr, -1, 0);
+
+	return 0;
+}
+
+/**
+ * \brief Delete the extplug instance
+ * \param extplug the extplug handle to delete
+ * \return 0 if successful, or a negative error code
+ *
+ * The destructor of extplug instance.
+ * Closes the PCM and deletes the associated resources.
+ */
+int snd_pcm_extplug_delete(snd_pcm_extplug_t *extplug)
+{
+	return snd_pcm_close(extplug->pcm);
+}
+
+
+/**
+ * \brief Reset extplug parameters
+ * \param extplug the extplug handle
+ *
+ * Resets the all parameters for the given extplug handle.
+ */
+void snd_pcm_extplug_params_reset(snd_pcm_extplug_t *extplug)
+{
+	extplug_priv_t *ext = extplug->pcm->private_data;
+	clear_ext_params(ext);
+}
+
+/**
+ * \brief Set slave parameter as the list
+ * \param extplug the extplug handle
+ * \param type parameter type
+ * \param num_list number of available values
+ * \param list the list of available values
+ * \return 0 if successful, or a negative error code
+ *
+ * Sets the slave parameter as the list.
+ * The available values of the given parameter type of the slave PCM is restricted
+ * to the ones of the given list.
+ */
+int snd_pcm_extplug_set_slave_param_list(snd_pcm_extplug_t *extplug, int type, unsigned int num_list, const unsigned int *list)
+{
+	extplug_priv_t *ext = extplug->pcm->private_data;
+	if (type < 0 && type >= SND_PCM_EXTPLUG_HW_PARAMS) {
+		SNDERR("EXTPLUG: invalid parameter type %d", type);
+		return -EINVAL;
+	}
+	return snd_ext_parm_set_list(&ext->sparams[type], num_list, list);
+}
+
+/**
+ * \brief Set slave parameter as the min/max values
+ * \param extplug the extplug handle
+ * \param type parameter type
+ * \param min the minimum value
+ * \param max the maximum value
+ * \return 0 if successful, or a negative error code
+ *
+ * Sets the slave parameter as the min/max values.
+ * The available values of the given parameter type of the slave PCM is restricted
+ * between the given minimum and maximum values.
+ */
+int snd_pcm_extplug_set_slave_param_minmax(snd_pcm_extplug_t *extplug, int type, unsigned int min, unsigned int max)
+{
+	extplug_priv_t *ext = extplug->pcm->private_data;
+	if (type < 0 && type >= SND_PCM_EXTPLUG_HW_PARAMS) {
+		SNDERR("EXTPLUG: invalid parameter type %d", type);
+		return -EINVAL;
+	}
+	if (is_mask_type(type)) {
+		SNDERR("EXTPLUG: invalid parameter type %d", type);
+		return -EINVAL;
+	}
+	return snd_ext_parm_set_minmax(&ext->sparams[type], min, max);
+}
+
+/**
+ * \brief Set master parameter as the list
+ * \param extplug the extplug handle
+ * \param type parameter type
+ * \param num_list number of available values
+ * \param list the list of available values
+ * \return 0 if successful, or a negative error code
+ *
+ * Sets the master parameter as the list.
+ * The available values of the given parameter type of this PCM (as input) is restricted
+ * to the ones of the given list.
+ */
+int snd_pcm_extplug_set_param_list(snd_pcm_extplug_t *extplug, int type, unsigned int num_list, const unsigned int *list)
+{
+	extplug_priv_t *ext = extplug->pcm->private_data;
+	if (type < 0 && type >= SND_PCM_EXTPLUG_HW_PARAMS) {
+		SNDERR("EXTPLUG: invalid parameter type %d", type);
+		return -EINVAL;
+	}
+	return snd_ext_parm_set_list(&ext->params[type], num_list, list);
+}
+
+/**
+ * \brief Set master parameter as the min/max values
+ * \param extplug the extplug handle
+ * \param type parameter type
+ * \param min the minimum value
+ * \param max the maximum value
+ * \return 0 if successful, or a negative error code
+ *
+ * Sets the master parameter as the min/max values.
+ * The available values of the given parameter type of this PCM (as input) is restricted
+ * between the given minimum and maximum values.
+ */
+int snd_pcm_extplug_set_param_minmax(snd_pcm_extplug_t *extplug, int type, unsigned int min, unsigned int max)
+{
+	extplug_priv_t *ext = extplug->pcm->private_data;
+	if (type < 0 && type >= SND_PCM_EXTPLUG_HW_PARAMS) {
+		SNDERR("EXTPLUG: invalid parameter type %d", type);
+		return -EINVAL;
+	}
+	if (is_mask_type(type)) {
+		SNDERR("EXTPLUG: invalid parameter type %d", type);
+		return -EINVAL;
+	}
+	return snd_ext_parm_set_minmax(&ext->params[type], min, max);
+}
+
diff --git a/src/pcm/pcm_file.c b/src/pcm/pcm_file.c
new file mode 100644
index 0000000..bfa1cc8
--- /dev/null
+++ b/src/pcm/pcm_file.c
@@ -0,0 +1,965 @@
+/**
+ * \file pcm/pcm_file.c
+ * \ingroup PCM_Plugins
+ * \brief PCM File Plugin Interface
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \date 2000-2001
+ */
+/*
+ *  PCM - File plugin
+ *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+#include <endian.h>
+#include <byteswap.h>
+#include <ctype.h>
+#include <string.h>
+#include "pcm_local.h"
+#include "pcm_plugin.h"
+
+#ifndef PIC
+/* entry for static linking */
+const char *_snd_module_pcm_file = "";
+#endif
+
+#ifndef DOC_HIDDEN
+
+/* keys to be replaced by real values in the filename */
+#define LEADING_KEY	'%'	/* i.e. %r, %c, %b ... */
+#define RATE_KEY	'r'
+#define CHANNELS_KEY	'c'
+#define BWIDTH_KEY	'b'
+#define FORMAT_KEY	'f'
+
+/* maximum length of a value */
+#define VALUE_MAXLEN	64
+
+typedef enum _snd_pcm_file_format {
+	SND_PCM_FILE_FORMAT_RAW,
+	SND_PCM_FILE_FORMAT_WAV
+} snd_pcm_file_format_t;
+
+/* WAV format chunk */
+struct wav_fmt {
+	short fmt;
+	short chan;
+	int rate;
+	int bps;
+	short bwidth;
+	short bits;
+};
+
+typedef struct {
+	snd_pcm_generic_t gen;
+	char *fname;
+	char *final_fname;
+	int trunc;
+	int perm;
+	int fd;
+	char *ifname;
+	int ifd;
+	int format;
+	snd_pcm_uframes_t appl_ptr;
+	snd_pcm_uframes_t file_ptr_bytes;
+	snd_pcm_uframes_t wbuf_size;
+	size_t wbuf_size_bytes;
+	size_t wbuf_used_bytes;
+	char *wbuf;
+	size_t rbuf_size_bytes;
+	size_t rbuf_used_bytes;
+	char *rbuf;
+	snd_pcm_channel_area_t *wbuf_areas;
+	size_t buffer_bytes;
+	struct wav_fmt wav_header;
+	size_t filelen;
+} snd_pcm_file_t;
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+#define TO_LE32(x)	(x)
+#define TO_LE16(x)	(x)
+#else
+#define TO_LE32(x)	bswap_32(x)
+#define TO_LE16(x)	bswap_16(x)
+#endif
+
+static int snd_pcm_file_append_value(char **string_p, char **index_ch_p,
+		int *len_p, const char *value)
+{
+	char *string, *index_ch;
+	int index, len, value_len;
+	/* input pointer values */
+	len = *(len_p);
+	string = *(string_p);
+	index_ch = *(index_ch_p);
+
+	value_len = strlen(value);
+	/* reallocation to accommodate the value */
+	index = index_ch - string;
+	len += value_len;
+	string = realloc(string, len + 1);
+	if (!string)
+		return -ENOMEM;
+	index_ch = string + index;
+	/* concatenating the new value */
+	strcpy(index_ch, value);
+	index_ch += value_len;
+	/* return values */
+	*(len_p) = len;
+	*(string_p) = string;
+	*(index_ch_p) = index_ch;
+	return 0;
+}
+
+static int snd_pcm_file_replace_fname(snd_pcm_file_t *file, char **new_fname_p)
+{
+	char value[VALUE_MAXLEN];
+	char *fname = file->fname;
+	char *new_fname = NULL;
+	char *old_last_ch, *old_index_ch, *new_index_ch;
+	int old_len, new_len, err;
+
+	snd_pcm_t *pcm = file->gen.slave;
+
+	/* we want to keep fname, const */
+	old_len = new_len = strlen(fname);
+	old_last_ch = fname + old_len - 1;
+	new_fname = malloc(new_len + 1);
+	if (!new_fname)
+		return -ENOMEM;
+
+	old_index_ch = fname;	/* first character of the old name */
+	new_index_ch = new_fname;	/* first char of the new name */
+
+	while (old_index_ch <= old_last_ch) {
+		if (*(old_index_ch) == LEADING_KEY &&
+				old_index_ch != old_last_ch) {
+			/* is %, not last char, skipping and checking
+			 next char */
+			switch (*(++old_index_ch)) {
+			case RATE_KEY:
+				snprintf(value, sizeof(value), "%d",
+						pcm->rate);
+				err = snd_pcm_file_append_value(&new_fname,
+					&new_index_ch, &new_len, value);
+				if (err < 0)
+					return err;
+				break;
+
+			case CHANNELS_KEY:
+				snprintf(value, sizeof(value), "%d",
+						pcm->channels);
+				err = snd_pcm_file_append_value(&new_fname,
+					&new_index_ch, &new_len, value);
+				if (err < 0)
+					return err;
+				break;
+
+			case BWIDTH_KEY:
+				snprintf(value, sizeof(value), "%d",
+					pcm->frame_bits/pcm->channels);
+				err = snd_pcm_file_append_value(&new_fname,
+						&new_index_ch, &new_len, value);
+				if (err < 0)
+					return err;
+				break;
+
+			case FORMAT_KEY:
+				err = snd_pcm_file_append_value(&new_fname,
+					&new_index_ch, &new_len,
+					snd_pcm_format_name(pcm->format));
+				if (err < 0)
+					return err;
+				break;
+
+			default:
+				/* non-key char, just copying */
+				*(new_index_ch++) = *(old_index_ch);
+			}
+			/* next old char */
+			old_index_ch++;
+		} else {
+			/* plain copying, shifting both strings to next chars */
+			*(new_index_ch++) = *(old_index_ch++);
+		}
+	}
+	/* closing the new string */
+	*(new_index_ch) = '\0';
+	*(new_fname_p) = new_fname;
+	return 0;
+
+}
+
+static int snd_pcm_file_open_output_file(snd_pcm_file_t *file)
+{
+	int err, fd;
+
+	/* fname can contain keys, generating final_fname */
+	err = snd_pcm_file_replace_fname(file, &(file->final_fname));
+	if (err < 0)
+		return err;
+	/*printf("DEBUG - original fname: %s, final fname: %s\n",
+	  file->fname, file->final_fname);*/
+
+	if (file->final_fname[0] == '|') {
+		/* pipe mode */
+		FILE *pipe;
+		/* clearing */
+		pipe = popen(file->final_fname + 1, "w");
+		if (!pipe) {
+			SYSERR("running %s for writing failed",
+					file->final_fname);
+			return -errno;
+		}
+		fd = fileno(pipe);
+	} else {
+		if (file->trunc)
+			fd = open(file->final_fname, O_WRONLY|O_CREAT|O_TRUNC,
+					file->perm);
+		else {
+			fd = open(file->final_fname, O_WRONLY|O_CREAT|O_EXCL,
+					file->perm);
+			if (fd < 0) {
+				char *tmpfname = NULL;
+				int idx, len;
+				len = strlen(file->final_fname) + 6;
+				tmpfname = malloc(len);
+				if (!tmpfname)
+					return -ENOMEM;
+				for (idx = 1; idx < 10000; idx++) {
+					snprintf(tmpfname, len,
+						"%s.%04d", file->final_fname,
+						idx);
+					fd = open(tmpfname,
+							O_WRONLY|O_CREAT|O_EXCL,
+							file->perm);
+					if (fd >= 0) {
+						free(file->final_fname);
+						file->final_fname = tmpfname;
+						break;
+					}
+				}
+				if (fd < 0) {
+					SYSERR("open %s for writing failed",
+							file->final_fname);
+					free(tmpfname);
+					return -errno;
+				}
+			}
+		}
+	}
+	file->fd = fd;
+	return 0;
+}
+
+static void setup_wav_header(snd_pcm_t *pcm, struct wav_fmt *fmt)
+{
+	fmt->fmt = TO_LE16(0x01);
+	fmt->chan = TO_LE16(pcm->channels);
+	fmt->rate = TO_LE32(pcm->rate);
+	fmt->bwidth = pcm->frame_bits / 8;
+	fmt->bps = fmt->bwidth * pcm->rate;
+	fmt->bits = snd_pcm_format_width(pcm->format);
+	fmt->bps = TO_LE32(fmt->bps);
+	fmt->bwidth = TO_LE16(fmt->bwidth);
+	fmt->bits = TO_LE16(fmt->bits);
+}
+
+static int write_wav_header(snd_pcm_t *pcm)
+{
+	snd_pcm_file_t *file = pcm->private_data;
+	static const char header[] = {
+		'R', 'I', 'F', 'F',
+		0x24, 0, 0, 0,
+		'W', 'A', 'V', 'E',
+		'f', 'm', 't', ' ',
+		0x10, 0, 0, 0,
+	};
+	static const char header2[] = {
+		'd', 'a', 't', 'a',
+		0, 0, 0, 0
+	};
+	
+	setup_wav_header(pcm, &file->wav_header);
+
+	if (write(file->fd, header, sizeof(header)) != sizeof(header) ||
+	    write(file->fd, &file->wav_header, sizeof(file->wav_header)) !=
+	    sizeof(file->wav_header) ||
+	    write(file->fd, header2, sizeof(header2)) != sizeof(header2)) {
+		int err = errno;
+		SYSERR("Write error.\n");
+		return -err;
+	}
+	return 0;
+}
+
+/* fix up the length fields in WAV header */
+static void fixup_wav_header(snd_pcm_t *pcm)
+{
+	snd_pcm_file_t *file = pcm->private_data;
+	int len, ret;
+
+	/* RIFF length */
+	if (lseek(file->fd, 4, SEEK_SET) == 4) {
+		len = (file->filelen + 0x24) > 0x7fffffff ?
+			0x7fffffff : (int)(file->filelen + 0x24);
+		len = TO_LE32(len);
+		ret = write(file->fd, &len, 4);
+		if (ret < 0)
+			return;
+	}
+	/* data length */
+	if (lseek(file->fd, 0x28, SEEK_SET) == 0x28) {
+		len = file->filelen > 0x7fffffff ?
+			0x7fffffff : (int)file->filelen;
+		len = TO_LE32(len);
+		ret = write(file->fd, &len, 4);
+		if (ret < 0)
+			return;
+	}
+}
+#endif /* DOC_HIDDEN */
+
+
+
+static void snd_pcm_file_write_bytes(snd_pcm_t *pcm, size_t bytes)
+{
+	snd_pcm_file_t *file = pcm->private_data;
+	assert(bytes <= file->wbuf_used_bytes);
+
+	if (file->format == SND_PCM_FILE_FORMAT_WAV &&
+	    !file->wav_header.fmt) {
+		if (write_wav_header(pcm) < 0)
+			return;
+	}
+
+	while (bytes > 0) {
+		snd_pcm_sframes_t err;
+		size_t n = bytes;
+		size_t cont = file->wbuf_size_bytes - file->file_ptr_bytes;
+		if (n > cont)
+			n = cont;
+		err = write(file->fd, file->wbuf + file->file_ptr_bytes, n);
+		if (err < 0) {
+			SYSERR("write failed");
+			break;
+		}
+		bytes -= err;
+		file->wbuf_used_bytes -= err;
+		file->file_ptr_bytes += err;
+		if (file->file_ptr_bytes == file->wbuf_size_bytes)
+			file->file_ptr_bytes = 0;
+		file->filelen += err;
+		if ((snd_pcm_uframes_t)err != n)
+			break;
+	}
+}
+
+static void snd_pcm_file_add_frames(snd_pcm_t *pcm, 
+				    const snd_pcm_channel_area_t *areas,
+				    snd_pcm_uframes_t offset,
+				    snd_pcm_uframes_t frames)
+{
+	snd_pcm_file_t *file = pcm->private_data;
+	while (frames > 0) {
+		snd_pcm_uframes_t n = frames;
+		snd_pcm_uframes_t cont = file->wbuf_size - file->appl_ptr;
+		snd_pcm_uframes_t avail = file->wbuf_size - snd_pcm_bytes_to_frames(pcm, file->wbuf_used_bytes);
+		if (n > cont)
+			n = cont;
+		if (n > avail)
+			n = avail;
+		snd_pcm_areas_copy(file->wbuf_areas, file->appl_ptr, 
+				   areas, offset,
+				   pcm->channels, n, pcm->format);
+		frames -= n;
+		offset += n;
+		file->appl_ptr += n;
+		if (file->appl_ptr == file->wbuf_size)
+			file->appl_ptr = 0;
+		file->wbuf_used_bytes += snd_pcm_frames_to_bytes(pcm, n);
+		if (file->wbuf_used_bytes > file->buffer_bytes)
+			snd_pcm_file_write_bytes(pcm, file->wbuf_used_bytes - file->buffer_bytes);
+		assert(file->wbuf_used_bytes < file->wbuf_size_bytes);
+	}
+}
+
+static int snd_pcm_file_close(snd_pcm_t *pcm)
+{
+	snd_pcm_file_t *file = pcm->private_data;
+	if (file->fname) {
+		if (file->wav_header.fmt)
+			fixup_wav_header(pcm);
+		free((void *)file->fname);
+		close(file->fd);
+	}
+	if (file->ifname) {
+		free((void *)file->ifname);
+		close(file->ifd);
+	}
+	return snd_pcm_generic_close(pcm);
+}
+
+static int snd_pcm_file_reset(snd_pcm_t *pcm)
+{
+	snd_pcm_file_t *file = pcm->private_data;
+	int err = snd_pcm_reset(file->gen.slave);
+	if (err >= 0) {
+		/* FIXME: Questionable here */
+		snd_pcm_file_write_bytes(pcm, file->wbuf_used_bytes);
+		assert(file->wbuf_used_bytes == 0);
+	}
+	return err;
+}
+
+static int snd_pcm_file_drop(snd_pcm_t *pcm)
+{
+	snd_pcm_file_t *file = pcm->private_data;
+	int err = snd_pcm_drop(file->gen.slave);
+	if (err >= 0) {
+		/* FIXME: Questionable here */
+		snd_pcm_file_write_bytes(pcm, file->wbuf_used_bytes);
+		assert(file->wbuf_used_bytes == 0);
+	}
+	return err;
+}
+
+static int snd_pcm_file_drain(snd_pcm_t *pcm)
+{
+	snd_pcm_file_t *file = pcm->private_data;
+	int err = snd_pcm_drain(file->gen.slave);
+	if (err >= 0) {
+		snd_pcm_file_write_bytes(pcm, file->wbuf_used_bytes);
+		assert(file->wbuf_used_bytes == 0);
+	}
+	return err;
+}
+
+static snd_pcm_sframes_t snd_pcm_file_rewindable(snd_pcm_t *pcm)
+{
+	snd_pcm_file_t *file = pcm->private_data;
+	snd_pcm_sframes_t res = snd_pcm_rewindable(pcm);
+	snd_pcm_sframes_t n = snd_pcm_bytes_to_frames(pcm, file->wbuf_used_bytes);
+	if (res > n)
+		res = n;
+	return res;
+}
+
+static snd_pcm_sframes_t snd_pcm_file_rewind(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
+{
+	snd_pcm_file_t *file = pcm->private_data;
+	snd_pcm_sframes_t err;
+	snd_pcm_uframes_t n;
+	
+	n = snd_pcm_frames_to_bytes(pcm, frames);
+	if (n > file->wbuf_used_bytes)
+		frames = snd_pcm_bytes_to_frames(pcm, file->wbuf_used_bytes);
+	err = snd_pcm_rewind(file->gen.slave, frames);
+	if (err > 0) {
+		file->appl_ptr = (file->appl_ptr - err + file->wbuf_size) % file->wbuf_size;
+		n = snd_pcm_frames_to_bytes(pcm, err);
+		file->wbuf_used_bytes -= n;
+	}
+	return err;
+}
+
+static snd_pcm_sframes_t snd_pcm_file_forwardable(snd_pcm_t *pcm)
+{
+	snd_pcm_file_t *file = pcm->private_data;
+	snd_pcm_sframes_t res = snd_pcm_forwardable(pcm);
+	snd_pcm_sframes_t n = snd_pcm_bytes_to_frames(pcm, file->wbuf_size_bytes - file->wbuf_used_bytes);
+	if (res > n)
+		res = n;
+	return res;
+}
+
+static snd_pcm_sframes_t snd_pcm_file_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
+{
+	snd_pcm_file_t *file = pcm->private_data;
+	snd_pcm_sframes_t err;
+	snd_pcm_uframes_t n;
+	
+	n = snd_pcm_frames_to_bytes(pcm, frames);
+	if (file->wbuf_used_bytes + n > file->wbuf_size_bytes)
+		frames = snd_pcm_bytes_to_frames(pcm, file->wbuf_size_bytes - file->wbuf_used_bytes);
+	err = INTERNAL(snd_pcm_forward)(file->gen.slave, frames);
+	if (err > 0) {
+		file->appl_ptr = (file->appl_ptr + err) % file->wbuf_size;
+		n = snd_pcm_frames_to_bytes(pcm, err);
+		file->wbuf_used_bytes += n;
+	}
+	return err;
+}
+
+static snd_pcm_sframes_t snd_pcm_file_writei(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size)
+{
+	snd_pcm_file_t *file = pcm->private_data;
+	snd_pcm_channel_area_t areas[pcm->channels];
+	snd_pcm_sframes_t n = snd_pcm_writei(file->gen.slave, buffer, size);
+	if (n > 0) {
+		snd_pcm_areas_from_buf(pcm, areas, (void*) buffer);
+		snd_pcm_file_add_frames(pcm, areas, 0, n);
+	}
+	return n;
+}
+
+static snd_pcm_sframes_t snd_pcm_file_writen(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size)
+{
+	snd_pcm_file_t *file = pcm->private_data;
+	snd_pcm_channel_area_t areas[pcm->channels];
+	snd_pcm_sframes_t n = snd_pcm_writen(file->gen.slave, bufs, size);
+	if (n > 0) {
+		snd_pcm_areas_from_bufs(pcm, areas, bufs);
+		snd_pcm_file_add_frames(pcm, areas, 0, n);
+	}
+	return n;
+}
+
+static snd_pcm_sframes_t snd_pcm_file_readi(snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t size)
+{
+	snd_pcm_file_t *file = pcm->private_data;
+	snd_pcm_channel_area_t areas[pcm->channels];
+	snd_pcm_sframes_t n;
+
+	n = snd_pcm_readi(file->gen.slave, buffer, size);
+	if (n <= 0)
+		return n;
+	if (file->ifd >= 0) {
+		n = read(file->ifd, buffer, n * pcm->frame_bits / 8);
+		if (n < 0)
+			return n;
+		return n * 8 / pcm->frame_bits;
+	}
+	snd_pcm_areas_from_buf(pcm, areas, buffer);
+	snd_pcm_file_add_frames(pcm, areas, 0, n);
+	return n;
+}
+
+static snd_pcm_sframes_t snd_pcm_file_readn(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size)
+{
+	snd_pcm_file_t *file = pcm->private_data;
+	snd_pcm_channel_area_t areas[pcm->channels];
+	snd_pcm_sframes_t n;
+
+	if (file->ifd >= 0) {
+		SNDERR("DEBUG: Noninterleaved read not yet implemented.\n");
+		return 0;	/* TODO: Noninterleaved read */
+	}
+
+	n = snd_pcm_readn(file->gen.slave, bufs, size);
+	if (n > 0) {
+		snd_pcm_areas_from_bufs(pcm, areas, bufs);
+		snd_pcm_file_add_frames(pcm, areas, 0, n);
+	}
+	return n;
+}
+
+static snd_pcm_sframes_t snd_pcm_file_mmap_commit(snd_pcm_t *pcm,
+					          snd_pcm_uframes_t offset,
+						  snd_pcm_uframes_t size)
+{
+	snd_pcm_file_t *file = pcm->private_data;
+	snd_pcm_uframes_t ofs;
+	snd_pcm_uframes_t siz = size;
+	const snd_pcm_channel_area_t *areas;
+	snd_pcm_sframes_t result;
+
+	snd_pcm_mmap_begin(file->gen.slave, &areas, &ofs, &siz);
+	assert(ofs == offset && siz == size);
+	result = snd_pcm_mmap_commit(file->gen.slave, ofs, siz);
+	if (result > 0)
+		snd_pcm_file_add_frames(pcm, areas, ofs, result);
+	return result;
+}
+
+static int snd_pcm_file_hw_free(snd_pcm_t *pcm)
+{
+	snd_pcm_file_t *file = pcm->private_data;
+	free(file->wbuf);
+	free(file->wbuf_areas);
+	file->wbuf = NULL;
+	file->wbuf_areas = NULL;
+	return snd_pcm_hw_free(file->gen.slave);
+}
+
+static int snd_pcm_file_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t * params)
+{
+	snd_pcm_file_t *file = pcm->private_data;
+	unsigned int channel;
+	snd_pcm_t *slave = file->gen.slave;
+	int err = _snd_pcm_hw_params(slave, params);
+	if (err < 0)
+		return err;
+	file->buffer_bytes = snd_pcm_frames_to_bytes(slave, slave->buffer_size);
+	file->wbuf_size = slave->buffer_size * 2;
+	file->wbuf_size_bytes = snd_pcm_frames_to_bytes(slave, file->wbuf_size);
+	file->wbuf_used_bytes = 0;
+	assert(!file->wbuf);
+	file->wbuf = malloc(file->wbuf_size_bytes);
+	if (file->wbuf == NULL) {
+		snd_pcm_file_hw_free(pcm);
+		return -ENOMEM;
+	}
+	file->wbuf_areas = malloc(sizeof(*file->wbuf_areas) * slave->channels);
+	if (file->wbuf_areas == NULL) {
+		snd_pcm_file_hw_free(pcm);
+		return -ENOMEM;
+	}
+	file->appl_ptr = file->file_ptr_bytes = 0;
+	for (channel = 0; channel < slave->channels; ++channel) {
+		snd_pcm_channel_area_t *a = &file->wbuf_areas[channel];
+		a->addr = file->wbuf;
+		a->first = slave->sample_bits * channel;
+		a->step = slave->frame_bits;
+	}
+	if (file->fd < 0) {
+		err = snd_pcm_file_open_output_file(file);
+		if (err < 0) {
+			SYSERR("failed opening output file %s", file->fname);
+			return err;
+		}
+	}
+	return 0;
+}
+
+static void snd_pcm_file_dump(snd_pcm_t *pcm, snd_output_t *out)
+{
+	snd_pcm_file_t *file = pcm->private_data;
+	if (file->fname)
+		snd_output_printf(out, "File PCM (file=%s)\n", file->fname);
+	else
+		snd_output_printf(out, "File PCM (fd=%d)\n", file->fd);
+	if (file->final_fname)
+		snd_output_printf(out, "Final file PCM (file=%s)\n",
+				file->final_fname);
+
+	if (pcm->setup) {
+		snd_output_printf(out, "Its setup is:\n");
+		snd_pcm_dump_setup(pcm, out);
+	}
+	snd_output_printf(out, "Slave: ");
+	snd_pcm_dump(file->gen.slave, out);
+}
+
+static const snd_pcm_ops_t snd_pcm_file_ops = {
+	.close = snd_pcm_file_close,
+	.info = snd_pcm_generic_info,
+	.hw_refine = snd_pcm_generic_hw_refine,
+	.hw_params = snd_pcm_file_hw_params,
+	.hw_free = snd_pcm_file_hw_free,
+	.sw_params = snd_pcm_generic_sw_params,
+	.channel_info = snd_pcm_generic_channel_info,
+	.dump = snd_pcm_file_dump,
+	.nonblock = snd_pcm_generic_nonblock,
+	.async = snd_pcm_generic_async,
+	.mmap = snd_pcm_generic_mmap,
+	.munmap = snd_pcm_generic_munmap,
+};
+
+static const snd_pcm_fast_ops_t snd_pcm_file_fast_ops = {
+	.status = snd_pcm_generic_status,
+	.state = snd_pcm_generic_state,
+	.hwsync = snd_pcm_generic_hwsync,
+	.delay = snd_pcm_generic_delay,
+	.prepare = snd_pcm_generic_prepare,
+	.reset = snd_pcm_file_reset,
+	.start = snd_pcm_generic_start,
+	.drop = snd_pcm_file_drop,
+	.drain = snd_pcm_file_drain,
+	.pause = snd_pcm_generic_pause,
+	.rewindable = snd_pcm_file_rewindable,
+	.rewind = snd_pcm_file_rewind,
+	.forwardable = snd_pcm_file_forwardable,
+	.forward = snd_pcm_file_forward,
+	.resume = snd_pcm_generic_resume,
+	.link = snd_pcm_generic_link,
+	.link_slaves = snd_pcm_generic_link_slaves,
+	.unlink = snd_pcm_generic_unlink,
+	.writei = snd_pcm_file_writei,
+	.writen = snd_pcm_file_writen,
+	.readi = snd_pcm_file_readi,
+	.readn = snd_pcm_file_readn,
+	.avail_update = snd_pcm_generic_avail_update,
+	.mmap_commit = snd_pcm_file_mmap_commit,
+	.poll_descriptors_count = snd_pcm_generic_poll_descriptors_count,
+	.poll_descriptors = snd_pcm_generic_poll_descriptors,
+	.poll_revents = snd_pcm_generic_poll_revents,
+};
+
+/**
+ * \brief Creates a new File PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param fname Output filename (or NULL if file descriptor fd is available)
+ * \param fd Output file descriptor
+ * \param ifname Input filename (or NULL if file descriptor ifd is available)
+ * \param ifd Input file descriptor (if (ifd < 0) && (ifname == NULL), no input
+ *            redirection will be performed)
+ * \param trunc Truncate the file if it already exists
+ * \param fmt File format ("raw" or "wav" are available)
+ * \param perm File permission
+ * \param slave Slave PCM handle
+ * \param close_slave When set, the slave PCM handle is closed with copy PCM
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int snd_pcm_file_open(snd_pcm_t **pcmp, const char *name,
+		      const char *fname, int fd, const char *ifname, int ifd,
+		      int trunc,
+		      const char *fmt, int perm, snd_pcm_t *slave, int close_slave)
+{
+	snd_pcm_t *pcm;
+	snd_pcm_file_t *file;
+	snd_pcm_file_format_t format;
+	struct timespec timespec;
+	int err;
+
+	assert(pcmp);
+	if (fmt == NULL ||
+	    strcmp(fmt, "raw") == 0)
+		format = SND_PCM_FILE_FORMAT_RAW;
+	else if (!strcmp(fmt, "wav"))
+		format = SND_PCM_FILE_FORMAT_WAV;
+	else {
+		SNDERR("file format %s is unknown", fmt);
+		return -EINVAL;
+	}
+	file = calloc(1, sizeof(snd_pcm_file_t));
+	if (!file) {
+		return -ENOMEM;
+	}
+
+	/* opening output fname is delayed until writing,
+	 when PCM params are known */
+	if (fname)
+		file->fname = strdup(fname);
+	file->trunc = trunc;
+	file->perm = perm;
+
+	if (ifname) {
+		ifd = open(ifname, O_RDONLY);	/* TODO: mind blocking mode */
+		if (ifd < 0) {
+			SYSERR("open %s for reading failed", ifname);
+			free(file);
+			return -errno;
+		}
+		file->ifname = strdup(ifname);
+	}
+	file->fd = fd;
+	file->ifd = ifd;
+	file->format = format;
+	file->gen.slave = slave;
+	file->gen.close_slave = close_slave;
+
+	err = snd_pcm_new(&pcm, SND_PCM_TYPE_FILE, name, slave->stream, slave->mode);
+	if (err < 0) {
+		free(file->fname);
+		free(file);
+		return err;
+	}
+	pcm->ops = &snd_pcm_file_ops;
+	pcm->fast_ops = &snd_pcm_file_fast_ops;
+	pcm->private_data = file;
+	pcm->poll_fd = slave->poll_fd;
+	pcm->poll_events = slave->poll_events;
+	pcm->mmap_shadow = 1;
+#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
+	pcm->monotonic = clock_gettime(CLOCK_MONOTONIC, &timespec) == 0;
+#else
+	pcm->monotonic = 0;
+#endif
+	snd_pcm_link_hw_ptr(pcm, slave);
+	snd_pcm_link_appl_ptr(pcm, slave);
+	*pcmp = pcm;
+	return 0;
+}
+
+/*! \page pcm_plugins
+
+\section pcm_plugins_file Plugin: File
+
+This plugin stores contents of a PCM stream to file or pipes the stream
+to a command, and optionally uses an existing file as an input data source
+(i.e., "virtual mic")
+
+\code
+pcm.name {
+        type file               # File PCM
+        slave STR               # Slave name
+        # or
+        slave {                 # Slave definition
+                pcm STR         # Slave PCM name
+                # or
+                pcm { }         # Slave PCM definition
+        }
+	file STR		# Output filename (or shell command the stream
+				# will be piped to if STR starts with the pipe
+				# char).
+				# STR can contain format keys, replaced by
+				# real values corresponding to the stream:
+				# %r	rate (replaced with: 48000)
+				# %c	channels (replaced with: 2)
+				# %b	bits per sample (replaced with: 16)
+				# %f	sample format string
+				#			(replaced with: S16_LE)
+				# %%	replaced with %
+	or
+	file INT		# Output file descriptor number
+	infile STR		# Input filename - only raw format
+	or
+	infile INT		# Input file descriptor number
+	[format STR]		# File format ("raw" or "wav")
+	[perm INT]		# Output file permission (octal, def. 0600)
+}
+\endcode
+
+\subsection pcm_plugins_file_funcref Function reference
+
+<UL>
+  <LI>snd_pcm_file_open()
+  <LI>_snd_pcm_file_open()
+</UL>
+
+*/
+
+/**
+ * \brief Creates a new File PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param root Root configuration node
+ * \param conf Configuration node with File PCM description
+ * \param stream Stream type
+ * \param mode Stream mode
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int _snd_pcm_file_open(snd_pcm_t **pcmp, const char *name,
+		       snd_config_t *root, snd_config_t *conf, 
+		       snd_pcm_stream_t stream, int mode)
+{
+	snd_config_iterator_t i, next;
+	int err;
+	snd_pcm_t *spcm;
+	snd_config_t *slave = NULL, *sconf;
+	const char *fname = NULL, *ifname = NULL;
+	const char *format = NULL;
+	long fd = -1, ifd = -1, trunc = 1;
+	long perm = 0600;
+	snd_config_for_each(i, next, conf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (snd_pcm_conf_generic_id(id))
+			continue;
+		if (strcmp(id, "slave") == 0) {
+			slave = n;
+			continue;
+		}
+		if (strcmp(id, "format") == 0) {
+			err = snd_config_get_string(n, &format);
+			if (err < 0) {
+				SNDERR("Invalid type for %s", id);
+				return -EINVAL;
+			}
+			continue;
+		}
+		if (strcmp(id, "file") == 0) {
+			err = snd_config_get_string(n, &fname);
+			if (err < 0) {
+				err = snd_config_get_integer(n, &fd);
+				if (err < 0) {
+					SNDERR("Invalid type for %s", id);
+					return -EINVAL;
+				}
+			}
+			continue;
+		}
+		if (strcmp(id, "infile") == 0) {
+			err = snd_config_get_string(n, &ifname);
+			if (err < 0) {
+				err = snd_config_get_integer(n, &ifd);
+				if (err < 0) {
+					SNDERR("Invalid type for %s", id);
+					return -EINVAL;
+				}
+			}
+			continue;
+		}
+		if (strcmp(id, "perm") == 0) {
+			err = snd_config_get_integer(n, &perm);
+			if (err < 0) {
+				SNDERR("Invalid type for %s", id);
+				return err;
+			}
+			if ((perm & ~0777) != 0) {
+				SNDERR("The field perm must be a valid file permission");
+				return -EINVAL;
+			}
+			continue;
+		}
+		if (strcmp(id, "truncate") == 0) {
+			err = snd_config_get_bool(n);
+			if (err < 0)
+				return -EINVAL;
+			trunc = err;
+			continue;
+		}
+		SNDERR("Unknown field %s", id);
+		return -EINVAL;
+	}
+	if (!format) {
+		snd_config_t *n;
+		/* read defaults */
+		if (snd_config_search(root, "defaults.pcm.file_format", &n) >= 0) {
+			err = snd_config_get_string(n, &format);
+			if (err < 0) {
+				SNDERR("Invalid file format");
+				return -EINVAL;
+			}
+		}
+	}
+	if (!slave) {
+		SNDERR("slave is not defined");
+		return -EINVAL;
+	}
+	err = snd_pcm_slave_conf(root, slave, &sconf, 0);
+	if (err < 0)
+		return err;
+	if ((!fname || strlen(fname) == 0) && fd < 0 && !ifname) {
+		snd_config_delete(sconf);
+		SNDERR("file is not defined");
+		return -EINVAL;
+	}
+	err = snd_pcm_open_slave(&spcm, root, sconf, stream, mode, conf);
+	snd_config_delete(sconf);
+	if (err < 0)
+		return err;
+	err = snd_pcm_file_open(pcmp, name, fname, fd, ifname, ifd,
+				trunc, format, perm, spcm, 1);
+	if (err < 0)
+		snd_pcm_close(spcm);
+	return err;
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(_snd_pcm_file_open, SND_PCM_DLSYM_VERSION);
+#endif
diff --git a/src/pcm/pcm_generic.c b/src/pcm/pcm_generic.c
new file mode 100644
index 0000000..84ea85f
--- /dev/null
+++ b/src/pcm/pcm_generic.c
@@ -0,0 +1,326 @@
+/**
+ * \file pcm/pcm_generic.c
+ * \ingroup PCM
+ * \brief PCM Interface
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \date 2004
+ */
+/*
+ *  PCM - Common generic plugin code
+ *  Copyright (c) 2004 by Jaroslav Kysela <perex@perex.cz> 
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <sys/shm.h>
+#include <sys/ioctl.h>
+#include <limits.h>
+#include "pcm_local.h"
+#include "pcm_generic.h"
+
+#ifndef DOC_HIDDEN
+
+int snd_pcm_generic_close(snd_pcm_t *pcm)
+{
+	snd_pcm_generic_t *generic = pcm->private_data;
+	int err = 0;
+	if (generic->close_slave)
+		err = snd_pcm_close(generic->slave);
+	free(generic);
+	return 0;
+}
+
+int snd_pcm_generic_nonblock(snd_pcm_t *pcm, int nonblock)
+{
+	snd_pcm_generic_t *generic = pcm->private_data;
+	return snd_pcm_nonblock(generic->slave, nonblock);
+}
+
+int snd_pcm_generic_async(snd_pcm_t *pcm, int sig, pid_t pid)
+{
+	snd_pcm_generic_t *generic = pcm->private_data;
+	return snd_pcm_async(generic->slave, sig, pid);
+}
+
+int snd_pcm_generic_poll_descriptors_count(snd_pcm_t *pcm)
+{
+	snd_pcm_generic_t *generic = pcm->private_data;
+	return snd_pcm_poll_descriptors_count(generic->slave);
+}
+
+int snd_pcm_generic_poll_descriptors(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int space)
+{
+	snd_pcm_generic_t *generic = pcm->private_data;
+	return snd_pcm_poll_descriptors(generic->slave, pfds, space);
+}
+
+int snd_pcm_generic_poll_revents(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int nfds, unsigned short *revents)
+{
+	snd_pcm_generic_t *generic = pcm->private_data;
+	return snd_pcm_poll_descriptors_revents(generic->slave, pfds, nfds, revents);
+}
+
+int snd_pcm_generic_info(snd_pcm_t *pcm, snd_pcm_info_t * info)
+{
+	snd_pcm_generic_t *generic = pcm->private_data;
+	return snd_pcm_info(generic->slave, info);
+}
+
+int snd_pcm_generic_hw_free(snd_pcm_t *pcm)
+{
+	snd_pcm_generic_t *generic = pcm->private_data;
+	return snd_pcm_hw_free(generic->slave);
+}
+
+int snd_pcm_generic_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t *params)
+{
+	snd_pcm_generic_t *generic = pcm->private_data;
+	return snd_pcm_sw_params(generic->slave, params);
+}
+
+int snd_pcm_generic_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	snd_pcm_generic_t *generic = pcm->private_data;
+	return snd_pcm_hw_refine(generic->slave, params);
+}
+
+int snd_pcm_generic_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	snd_pcm_generic_t *generic = pcm->private_data;
+	return _snd_pcm_hw_params(generic->slave, params);
+}
+
+int snd_pcm_generic_prepare(snd_pcm_t *pcm)
+{
+	snd_pcm_generic_t *generic = pcm->private_data;
+	return snd_pcm_prepare(generic->slave);
+}
+
+int snd_pcm_generic_channel_info(snd_pcm_t *pcm, snd_pcm_channel_info_t *info)
+{
+	snd_pcm_generic_t *generic = pcm->private_data;
+	if (pcm->mmap_shadow) {
+		/* No own buffer is required - the plugin won't change
+		 * the data on the buffer, or do safely on-the-place
+		 * conversion
+		 */
+		return snd_pcm_channel_info(generic->slave, info);
+	} else {
+		/* Allocate own buffer */
+		return snd_pcm_channel_info_shm(pcm, info, -1);
+	}
+}
+
+int snd_pcm_generic_status(snd_pcm_t *pcm, snd_pcm_status_t * status)
+{ 
+	snd_pcm_generic_t *generic = pcm->private_data;
+	return snd_pcm_status(generic->slave, status);
+}
+
+snd_pcm_state_t snd_pcm_generic_state(snd_pcm_t *pcm)
+{
+	snd_pcm_generic_t *generic = pcm->private_data;
+	return snd_pcm_state(generic->slave);
+}
+
+int snd_pcm_generic_hwsync(snd_pcm_t *pcm)
+{
+	snd_pcm_generic_t *generic = pcm->private_data;
+	return snd_pcm_hwsync(generic->slave);
+}
+
+int snd_pcm_generic_reset(snd_pcm_t *pcm)
+{
+	snd_pcm_generic_t *generic = pcm->private_data;
+	return snd_pcm_reset(generic->slave);
+}
+
+int snd_pcm_generic_start(snd_pcm_t *pcm)
+{
+	snd_pcm_generic_t *generic = pcm->private_data;
+	return snd_pcm_start(generic->slave);
+}
+
+int snd_pcm_generic_drop(snd_pcm_t *pcm)
+{
+	snd_pcm_generic_t *generic = pcm->private_data;
+	return snd_pcm_drop(generic->slave);
+}
+
+int snd_pcm_generic_drain(snd_pcm_t *pcm)
+{
+	snd_pcm_generic_t *generic = pcm->private_data;
+	return snd_pcm_drain(generic->slave);
+}
+
+int snd_pcm_generic_pause(snd_pcm_t *pcm, int enable)
+{
+	snd_pcm_generic_t *generic = pcm->private_data;
+	return snd_pcm_pause(generic->slave, enable);
+}
+
+int snd_pcm_generic_resume(snd_pcm_t *pcm)
+{
+	snd_pcm_generic_t *generic = pcm->private_data;
+	return snd_pcm_resume(generic->slave);
+}
+
+int snd_pcm_generic_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp)
+{
+	snd_pcm_generic_t *generic = pcm->private_data;
+	return snd_pcm_delay(generic->slave, delayp);
+}
+
+snd_pcm_sframes_t snd_pcm_generic_forwardable(snd_pcm_t *pcm)
+{
+	snd_pcm_generic_t *generic = pcm->private_data;
+	return snd_pcm_forwardable(generic->slave);
+}
+
+snd_pcm_sframes_t snd_pcm_generic_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
+{
+	snd_pcm_generic_t *generic = pcm->private_data;
+	return INTERNAL(snd_pcm_forward)(generic->slave, frames);
+}
+
+snd_pcm_sframes_t snd_pcm_generic_rewindable(snd_pcm_t *pcm)
+{
+	snd_pcm_generic_t *generic = pcm->private_data;
+	return snd_pcm_rewindable(generic->slave);
+}
+
+snd_pcm_sframes_t snd_pcm_generic_rewind(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
+{
+	snd_pcm_generic_t *generic = pcm->private_data;
+	return snd_pcm_rewind(generic->slave, frames);
+}
+
+int snd_pcm_generic_link(snd_pcm_t *pcm1, snd_pcm_t *pcm2)
+{
+	snd_pcm_generic_t *generic = pcm1->private_data;
+	if (generic->slave->fast_ops->link)
+		return generic->slave->fast_ops->link(generic->slave->fast_op_arg, pcm2);
+	return -ENOSYS;
+}
+
+int snd_pcm_generic_link_slaves(snd_pcm_t *pcm, snd_pcm_t *master)
+{
+	snd_pcm_generic_t *generic = pcm->private_data;
+	if (generic->slave->fast_ops->link_slaves)
+		return generic->slave->fast_ops->link_slaves(generic->slave->fast_op_arg, master);
+	return -ENOSYS;
+}
+
+int snd_pcm_generic_unlink(snd_pcm_t *pcm)
+{
+	snd_pcm_generic_t *generic = pcm->private_data;
+	if (generic->slave->fast_ops->unlink)
+		return generic->slave->fast_ops->unlink(generic->slave->fast_op_arg);
+	return -ENOSYS;
+}
+
+snd_pcm_sframes_t snd_pcm_generic_writei(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size)
+{
+	snd_pcm_generic_t *generic = pcm->private_data;
+	return snd_pcm_writei(generic->slave, buffer, size);
+}
+
+snd_pcm_sframes_t snd_pcm_generic_writen(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size)
+{
+	snd_pcm_generic_t *generic = pcm->private_data;
+	return snd_pcm_writen(generic->slave, bufs, size);
+}
+
+snd_pcm_sframes_t snd_pcm_generic_readi(snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t size)
+{
+	snd_pcm_generic_t *generic = pcm->private_data;
+	return snd_pcm_readi(generic->slave, buffer, size);
+}
+
+snd_pcm_sframes_t snd_pcm_generic_readn(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size)
+{
+	snd_pcm_generic_t *generic = pcm->private_data;
+	return snd_pcm_readn(generic->slave, bufs, size);
+}
+
+snd_pcm_sframes_t snd_pcm_generic_mmap_commit(snd_pcm_t *pcm, 
+					      snd_pcm_uframes_t offset,
+					      snd_pcm_uframes_t size)
+{
+	snd_pcm_generic_t *generic = pcm->private_data;
+	return snd_pcm_mmap_commit(generic->slave, offset, size);
+}
+
+snd_pcm_sframes_t snd_pcm_generic_avail_update(snd_pcm_t *pcm)
+{
+	snd_pcm_generic_t *generic = pcm->private_data;
+	return snd_pcm_avail_update(generic->slave);
+}
+
+int snd_pcm_generic_htimestamp(snd_pcm_t *pcm, snd_pcm_uframes_t *avail,
+			       snd_htimestamp_t *tstamp)
+{
+	snd_pcm_generic_t *generic = pcm->private_data;
+	return snd_pcm_htimestamp(generic->slave, avail, tstamp);
+}
+
+/* stand-alone version - similar like snd_pcm_hw_htimestamp but
+ * taking the tstamp via gettimestamp().
+ */
+int snd_pcm_generic_real_htimestamp(snd_pcm_t *pcm, snd_pcm_uframes_t *avail,
+				    snd_htimestamp_t *tstamp)
+{
+	snd_pcm_sframes_t avail1;
+	int ok = 0;
+
+	while (1) {
+		avail1 = snd_pcm_avail_update(pcm);
+		if (avail1 < 0)
+			return avail1;
+		if (ok && (snd_pcm_uframes_t)avail1 == *avail)
+			break;
+		*avail = avail1;
+		gettimestamp(tstamp, pcm->monotonic);
+		ok = 1;
+	}
+	return 0;
+}
+
+int snd_pcm_generic_mmap(snd_pcm_t *pcm)
+{
+	if (pcm->mmap_shadow) {
+		/* Copy the slave mmapped buffer data */
+		snd_pcm_generic_t *generic = pcm->private_data;
+		pcm->mmap_channels = generic->slave->mmap_channels;
+		pcm->running_areas = generic->slave->running_areas;
+		pcm->stopped_areas = generic->slave->stopped_areas;
+	}
+	return 0;
+}
+
+int snd_pcm_generic_munmap(snd_pcm_t *pcm)
+{
+	if (pcm->mmap_shadow) {
+		/* Clean up */
+		pcm->mmap_channels = NULL;
+		pcm->running_areas = NULL;
+		pcm->stopped_areas = NULL;
+	}
+	return 0;
+}
+
+#endif /* DOC_HIDDEN */
diff --git a/src/pcm/pcm_generic.h b/src/pcm/pcm_generic.h
new file mode 100644
index 0000000..430b8cf
--- /dev/null
+++ b/src/pcm/pcm_generic.h
@@ -0,0 +1,151 @@
+/*
+ *  PCM - Common generic plugin code
+ *  Copyright (c) 2004 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+typedef struct {
+	snd_pcm_t *slave;
+	int close_slave;
+} snd_pcm_generic_t;	
+
+/* make local functions really local */
+#define snd_pcm_generic_close \
+	snd1_pcm_generic_close
+#define snd_pcm_generic_nonblock \
+	snd1_pcm_generic_nonblock
+#define snd_pcm_generic_async \
+	snd1_pcm_generic_async
+#define snd_pcm_generic_poll_descriptors_count \
+	snd1_pcm_generic_poll_descriptors_count
+#define snd_pcm_generic_poll_descriptors \
+	snd1_pcm_generic_poll_descriptors
+#define snd_pcm_generic_poll_revents \
+	snd1_pcm_generic_poll_revents
+#define snd_pcm_generic_info \
+	snd1_pcm_generic_info
+#define snd_pcm_generic_hw_free \
+	snd1_pcm_generic_hw_free
+#define snd_pcm_generic_sw_params \
+	snd1_pcm_generic_sw_params
+#define snd_pcm_generic_hw_refine \
+	snd1_pcm_generic_hw_refine
+#define snd_pcm_generic_hw_params \
+	snd1_pcm_generic_hw_params
+#define snd_pcm_generic_channel_info \
+	snd1_pcm_generic_channel_info
+#define snd_pcm_generic_channel_info_no_buffer \
+	snd1_pcm_generic_channel_info_no_buffer
+#define snd_pcm_generic_status \
+	snd1_pcm_generic_status
+#define snd_pcm_generic_state \
+	snd1_pcm_generic_state
+#define snd_pcm_generic_prepare \
+	snd1_pcm_generic_prepare
+#define snd_pcm_generic_hwsync \
+	snd1_pcm_generic_hwsync
+#define snd_pcm_generic_reset \
+	snd1_pcm_generic_reset
+#define snd_pcm_generic_start \
+	snd1_pcm_generic_start
+#define snd_pcm_generic_drop \
+	snd1_pcm_generic_drop
+#define snd_pcm_generic_drain \
+	snd1_pcm_generic_drain
+#define snd_pcm_generic_pause \
+	snd1_pcm_generic_pause
+#define snd_pcm_generic_resume \
+	snd1_pcm_generic_resume
+#define snd_pcm_generic_delay \
+	snd1_pcm_generic_delay
+#define snd_pcm_generic_forwardable \
+	snd1_pcm_generic_forwardable
+#define snd_pcm_generic_forward \
+	snd1_pcm_generic_forward
+#define snd_pcm_generic_rewindable \
+	snd1_pcm_generic_rewindable
+#define snd_pcm_generic_rewind \
+	snd1_pcm_generic_rewind
+#define snd_pcm_generic_link \
+	snd1_pcm_generic_link
+#define snd_pcm_generic_link_slaves \
+	snd1_pcm_generic_link_slaves
+#define snd_pcm_generic_unlink \
+	snd1_pcm_generic_unlink
+#define snd_pcm_generic_writei \
+	snd1_pcm_generic_writei
+#define snd_pcm_generic_writen \
+	snd1_pcm_generic_writen
+#define snd_pcm_generic_readi \
+	snd1_pcm_generic_readi
+#define snd_pcm_generic_readn \
+	snd1_pcm_generic_readn
+#define snd_pcm_generic_mmap_commit \
+	snd1_pcm_generic_mmap_commit
+#define snd_pcm_generic_avail_update	\
+	snd1_pcm_generic_avail_update
+#define snd_pcm_generic_mmap \
+	snd1_pcm_generic_mmap
+#define snd_pcm_generic_munmap \
+	snd1_pcm_generic_munmap
+
+int snd_pcm_generic_close(snd_pcm_t *pcm);
+int snd_pcm_generic_nonblock(snd_pcm_t *pcm, int nonblock);
+int snd_pcm_generic_async(snd_pcm_t *pcm, int sig, pid_t pid);
+int snd_pcm_generic_poll_descriptors_count(snd_pcm_t *pcm);
+int snd_pcm_generic_poll_descriptors(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int space);
+int snd_pcm_generic_poll_revents(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int nfds, unsigned short *revents);
+int snd_pcm_generic_info(snd_pcm_t *pcm, snd_pcm_info_t * info);
+int snd_pcm_generic_hw_free(snd_pcm_t *pcm);
+int snd_pcm_generic_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t *params);
+int snd_pcm_generic_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params);
+int snd_pcm_generic_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t *params);
+int snd_pcm_generic_channel_info(snd_pcm_t *pcm, snd_pcm_channel_info_t * info);
+int snd_pcm_generic_channel_info_no_buffer(snd_pcm_t *pcm, snd_pcm_channel_info_t * info);
+int snd_pcm_generic_status(snd_pcm_t *pcm, snd_pcm_status_t * status);
+snd_pcm_state_t snd_pcm_generic_state(snd_pcm_t *pcm);
+int snd_pcm_generic_prepare(snd_pcm_t *pcm);
+int snd_pcm_generic_hwsync(snd_pcm_t *pcm);
+int snd_pcm_generic_reset(snd_pcm_t *pcm);
+int snd_pcm_generic_start(snd_pcm_t *pcm);
+int snd_pcm_generic_drop(snd_pcm_t *pcm);
+int snd_pcm_generic_drain(snd_pcm_t *pcm);
+int snd_pcm_generic_pause(snd_pcm_t *pcm, int enable);
+int snd_pcm_generic_resume(snd_pcm_t *pcm);
+int snd_pcm_generic_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp);
+snd_pcm_sframes_t snd_pcm_generic_forwardable(snd_pcm_t *pcm);
+snd_pcm_sframes_t snd_pcm_generic_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames);
+snd_pcm_sframes_t snd_pcm_generic_rewindable(snd_pcm_t *pcm);
+snd_pcm_sframes_t snd_pcm_generic_rewind(snd_pcm_t *pcm, snd_pcm_uframes_t frames);
+int snd_pcm_generic_link(snd_pcm_t *pcm1, snd_pcm_t *pcm2);
+int snd_pcm_generic_link_slaves(snd_pcm_t *pcm, snd_pcm_t *master);
+int snd_pcm_generic_unlink(snd_pcm_t *pcm);
+snd_pcm_sframes_t snd_pcm_generic_writei(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size);
+snd_pcm_sframes_t snd_pcm_generic_writen(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size);
+snd_pcm_sframes_t snd_pcm_generic_readi(snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t size);
+snd_pcm_sframes_t snd_pcm_generic_readn(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size);
+snd_pcm_sframes_t snd_pcm_generic_mmap_commit(snd_pcm_t *pcm,
+					      snd_pcm_uframes_t offset,
+					      snd_pcm_uframes_t size);
+snd_pcm_sframes_t snd_pcm_generic_avail_update(snd_pcm_t *pcm);
+int snd_pcm_generic_htimestamp(snd_pcm_t *pcm, snd_pcm_uframes_t *avail,
+			       snd_htimestamp_t *timestamp);
+int snd_pcm_generic_real_htimestamp(snd_pcm_t *pcm, snd_pcm_uframes_t *avail,
+				    snd_htimestamp_t *tstamp);
+int snd_pcm_generic_mmap(snd_pcm_t *pcm);
+int snd_pcm_generic_munmap(snd_pcm_t *pcm);
diff --git a/src/pcm/pcm_hooks.c b/src/pcm/pcm_hooks.c
new file mode 100644
index 0000000..404d51e
--- /dev/null
+++ b/src/pcm/pcm_hooks.c
@@ -0,0 +1,723 @@
+/**
+ * \file pcm/pcm_hooks.c
+ * \ingroup PCM_Hook
+ * \brief PCM Hook Interface
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \date 2000-2001
+ */
+/*
+ *  PCM - Hook functions
+ *  Copyright (c) 2001 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+#include "pcm_local.h"
+#include "pcm_generic.h"
+
+#ifndef PIC
+/* entry for static linking */
+const char *_snd_module_pcm_hooks = "";
+#endif
+
+#ifndef DOC_HIDDEN
+struct _snd_pcm_hook {
+	snd_pcm_t *pcm;
+	snd_pcm_hook_func_t func;
+	void *private_data;
+	struct list_head list;
+};
+
+struct snd_pcm_hook_dllist {
+	void *dlobj;
+	struct list_head list;
+};
+
+typedef struct {
+	snd_pcm_generic_t gen;
+	struct list_head hooks[SND_PCM_HOOK_TYPE_LAST + 1];
+	struct list_head dllist;
+} snd_pcm_hooks_t;
+#endif
+
+static int hook_add_dlobj(snd_pcm_t *pcm, void *dlobj)
+{
+	snd_pcm_hooks_t *h = pcm->private_data;
+	struct snd_pcm_hook_dllist *dl;
+
+	dl = malloc(sizeof(*dl));
+	if (!dl)
+		return -ENOMEM;
+
+	dl->dlobj = dlobj;
+	list_add_tail(&dl->list, &h->dllist);
+	return 0;
+}
+
+static void hook_remove_dlobj(struct snd_pcm_hook_dllist *dl)
+{
+	list_del(&dl->list);
+	snd_dlclose(dl->dlobj);
+	free(dl);
+}
+
+static int snd_pcm_hooks_close(snd_pcm_t *pcm)
+{
+	snd_pcm_hooks_t *h = pcm->private_data;
+	struct list_head *pos, *next;
+	unsigned int k;
+	int res = 0, err;
+
+	list_for_each_safe(pos, next, &h->hooks[SND_PCM_HOOK_TYPE_CLOSE]) {
+		snd_pcm_hook_t *hook = list_entry(pos, snd_pcm_hook_t, list);
+		err = hook->func(hook);
+		if (err < 0)
+			res = err;
+	}
+	for (k = 0; k <= SND_PCM_HOOK_TYPE_LAST; ++k) {
+		struct list_head *hooks = &h->hooks[k];
+		while (!list_empty(hooks)) {
+			snd_pcm_hook_t *hook;
+			pos = hooks->next;
+			hook = list_entry(pos, snd_pcm_hook_t, list);
+			snd_pcm_hook_remove(hook);
+		}
+	}
+	while (!list_empty(&h->dllist)) {
+		pos = h->dllist.next;
+		hook_remove_dlobj(list_entry(pos, struct snd_pcm_hook_dllist, list));
+	}
+	err = snd_pcm_generic_close(pcm);
+	if (err < 0)
+		res = err;
+	return res;
+}
+
+static int snd_pcm_hooks_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	snd_pcm_hooks_t *h = pcm->private_data;
+	struct list_head *pos, *next;
+	int err = snd_pcm_generic_hw_params(pcm, params);
+	if (err < 0)
+		return err;
+	list_for_each_safe(pos, next, &h->hooks[SND_PCM_HOOK_TYPE_HW_PARAMS]) {
+		snd_pcm_hook_t *hook = list_entry(pos, snd_pcm_hook_t, list);
+		err = hook->func(hook);
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+static int snd_pcm_hooks_hw_free(snd_pcm_t *pcm)
+{
+	snd_pcm_hooks_t *h = pcm->private_data;
+	struct list_head *pos, *next;
+	int err = snd_pcm_generic_hw_free(pcm);
+	if (err < 0)
+		return err;
+	list_for_each_safe(pos, next, &h->hooks[SND_PCM_HOOK_TYPE_HW_FREE]) {
+		snd_pcm_hook_t *hook = list_entry(pos, snd_pcm_hook_t, list);
+		err = hook->func(hook);
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+static void snd_pcm_hooks_dump(snd_pcm_t *pcm, snd_output_t *out)
+{
+	snd_pcm_hooks_t *h = pcm->private_data;
+	snd_output_printf(out, "Hooks PCM\n");
+	if (pcm->setup) {
+		snd_output_printf(out, "Its setup is:\n");
+		snd_pcm_dump_setup(pcm, out);
+	}
+	snd_output_printf(out, "Slave: ");
+	snd_pcm_dump(h->gen.slave, out);
+}
+
+static const snd_pcm_ops_t snd_pcm_hooks_ops = {
+	.close = snd_pcm_hooks_close,
+	.info = snd_pcm_generic_info,
+	.hw_refine = snd_pcm_generic_hw_refine,
+	.hw_params = snd_pcm_hooks_hw_params,
+	.hw_free = snd_pcm_hooks_hw_free,
+	.sw_params = snd_pcm_generic_sw_params,
+	.channel_info = snd_pcm_generic_channel_info,
+	.dump = snd_pcm_hooks_dump,
+	.nonblock = snd_pcm_generic_nonblock,
+	.async = snd_pcm_generic_async,
+	.mmap = snd_pcm_generic_mmap,
+	.munmap = snd_pcm_generic_munmap,
+};
+
+static const snd_pcm_fast_ops_t snd_pcm_hooks_fast_ops = {
+	.status = snd_pcm_generic_status,
+	.state = snd_pcm_generic_state,
+	.hwsync = snd_pcm_generic_hwsync,
+	.delay = snd_pcm_generic_delay,
+	.prepare = snd_pcm_generic_prepare,
+	.reset = snd_pcm_generic_reset,
+	.start = snd_pcm_generic_start,
+	.drop = snd_pcm_generic_drop,
+	.drain = snd_pcm_generic_drain,
+	.pause = snd_pcm_generic_pause,
+	.rewindable = snd_pcm_generic_rewindable,
+	.rewind = snd_pcm_generic_rewind,
+	.forwardable = snd_pcm_generic_forwardable,
+	.forward = snd_pcm_generic_forward,
+	.resume = snd_pcm_generic_resume,
+	.link = snd_pcm_generic_link,
+	.link_slaves = snd_pcm_generic_link_slaves,
+	.unlink = snd_pcm_generic_unlink,
+	.writei = snd_pcm_generic_writei,
+	.writen = snd_pcm_generic_writen,
+	.readi = snd_pcm_generic_readi,
+	.readn = snd_pcm_generic_readn,
+	.avail_update = snd_pcm_generic_avail_update,
+	.mmap_commit = snd_pcm_generic_mmap_commit,
+	.htimestamp = snd_pcm_generic_htimestamp,
+	.poll_descriptors_count = snd_pcm_generic_poll_descriptors_count,
+	.poll_descriptors = snd_pcm_generic_poll_descriptors,
+	.poll_revents = snd_pcm_generic_poll_revents,
+};
+
+/**
+ * \brief Creates a new hooks PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param slave Slave PCM
+ * \param close_slave If set, slave PCM handle is closed when hooks PCM is closed
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *	    changed in future.
+ */
+int snd_pcm_hooks_open(snd_pcm_t **pcmp, const char *name, snd_pcm_t *slave, int close_slave)
+{
+	snd_pcm_t *pcm;
+	snd_pcm_hooks_t *h;
+	unsigned int k;
+	int err;
+	assert(pcmp && slave);
+	h = calloc(1, sizeof(snd_pcm_hooks_t));
+	if (!h)
+		return -ENOMEM;
+	h->gen.slave = slave;
+	h->gen.close_slave = close_slave;
+	for (k = 0; k <= SND_PCM_HOOK_TYPE_LAST; ++k) {
+		INIT_LIST_HEAD(&h->hooks[k]);
+	}
+	INIT_LIST_HEAD(&h->dllist);
+	err = snd_pcm_new(&pcm, SND_PCM_TYPE_HOOKS, name, slave->stream, slave->mode);
+	if (err < 0) {
+		free(h);
+		return err;
+	}
+	pcm->ops = &snd_pcm_hooks_ops;
+	pcm->fast_ops = &snd_pcm_hooks_fast_ops;
+	pcm->private_data = h;
+	pcm->poll_fd = slave->poll_fd;
+	pcm->poll_events = slave->poll_events;
+	pcm->mmap_shadow = 1;
+	pcm->monotonic = slave->monotonic;
+	snd_pcm_link_hw_ptr(pcm, slave);
+	snd_pcm_link_appl_ptr(pcm, slave);
+	*pcmp = pcm;
+
+	return 0;
+}
+
+/*! \page pcm_plugins
+
+\section pcm_plugins_hooks Plugin: hooks
+
+This plugin is used to call some 'hook' function when this plugin is opened,
+modified or closed.
+Typically, it is used to change control values for a certain state
+specially for the PCM (see the example below).
+
+\code
+# Hook arguments definition
+hook_args.NAME {
+	...			# Arbitrary arguments
+}
+
+# PCM hook type
+pcm_hook_type.NAME {
+	[lib STR]		# Library file (default libasound.so)
+	[install STR]		# Install function (default _snd_pcm_hook_NAME_install)
+}
+
+# PCM hook definition
+pcm_hook.NAME {
+	type STR		# PCM Hook type (see pcm_hook_type)
+	[args STR]		# Arguments for install function (see hook_args)
+	# or
+	[args { }]		# Arguments for install function
+}
+
+# PCM hook plugin
+pcm.NAME {
+	type hooks		# PCM with hooks
+	slave STR		# Slave name
+	# or
+	slave {			# Slave definition
+	  	pcm STR		# Slave PCM name
+		# or
+	  	pcm { }		# Slave PCM definition
+	}
+	hooks {
+		ID STR		# Hook name (see pcm_hook)
+		# or
+		ID { }		# Hook definition (see pcm_hook)
+	}
+}
+\endcode
+
+Example:
+
+\code
+	hooks.0 {
+		type ctl_elems
+		hook_args [
+			{
+				name "Wave Surround Playback Volume"
+				preserve true
+				lock true
+				optional true
+				value [ 0 0 ]
+			}
+			{
+				name "EMU10K1 PCM Send Volume"
+				index { @func private_pcm_subdevice }
+				lock true
+				value [ 0 0 0 0 0 0 255 0 0 0 0 255 ]
+			}
+		]
+	}
+\endcode
+Here, the controls "Wave Surround Playback Volume" and "EMU10K1 PCM Send Volume"
+are set to the given values when this pcm is accessed.  Since these controls
+take multi-dimensional values, the <code>value</code> field is written as
+an array.
+When <code>preserve</code> is true, the old values are saved and restored
+when the pcm is closed.  The <code>lock</code> means that the control is
+locked during this pcm is opened, and cannot be changed by others.
+When <code>optional</code> is set, no error is returned but ignored
+even if the specified control doesn't exist.
+
+\subsection pcm_plugins_hooks_funcref Function reference
+
+<UL>
+  <LI>The function ctl_elems - _snd_pcm_hook_ctl_elems_install() - installs
+      CTL settings described by given configuration.
+  <LI>snd_pcm_hooks_open()
+  <LI>_snd_pcm_hooks_open()
+</UL>
+
+*/
+
+static int snd_pcm_hook_add_conf(snd_pcm_t *pcm, snd_config_t *root, snd_config_t *conf)
+{
+	int err;
+	char buf[256];
+	const char *str, *id;
+	const char *lib = NULL, *install = NULL;
+	snd_config_t *type = NULL, *args = NULL;
+	snd_config_iterator_t i, next;
+	int (*install_func)(snd_pcm_t *pcm, snd_config_t *args) = NULL;
+	void *h = NULL;
+
+	if (snd_config_get_type(conf) != SND_CONFIG_TYPE_COMPOUND) {
+		SNDERR("Invalid hook definition");
+		return -EINVAL;
+	}
+	snd_config_for_each(i, next, conf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (strcmp(id, "comment") == 0)
+			continue;
+		if (strcmp(id, "type") == 0) {
+			type = n;
+			continue;
+		}
+		if (strcmp(id, "hook_args") == 0) {
+			args = n;
+			continue;
+		}
+		SNDERR("Unknown field %s", id);
+		return -EINVAL;
+	}
+	if (!type) {
+		SNDERR("type is not defined");
+		return -EINVAL;
+	}
+	err = snd_config_get_id(type, &id);
+	if (err < 0) {
+		SNDERR("unable to get id");
+		return err;
+	}
+	err = snd_config_get_string(type, &str);
+	if (err < 0) {
+		SNDERR("Invalid type for %s", id);
+		return err;
+	}
+	err = snd_config_search_definition(root, "pcm_hook_type", str, &type);
+	if (err >= 0) {
+		if (snd_config_get_type(type) != SND_CONFIG_TYPE_COMPOUND) {
+			SNDERR("Invalid type for PCM type %s definition", str);
+			err = -EINVAL;
+			goto _err;
+		}
+		snd_config_for_each(i, next, type) {
+			snd_config_t *n = snd_config_iterator_entry(i);
+			const char *id;
+			if (snd_config_get_id(n, &id) < 0)
+				continue;
+			if (strcmp(id, "comment") == 0)
+				continue;
+			if (strcmp(id, "lib") == 0) {
+				err = snd_config_get_string(n, &lib);
+				if (err < 0) {
+					SNDERR("Invalid type for %s", id);
+					goto _err;
+				}
+				continue;
+			}
+			if (strcmp(id, "install") == 0) {
+				err = snd_config_get_string(n, &install);
+				if (err < 0) {
+					SNDERR("Invalid type for %s", id);
+					goto _err;
+				}
+				continue;
+			}
+			SNDERR("Unknown field %s", id);
+			err = -EINVAL;
+			goto _err;
+		}
+	}
+	if (!install) {
+		install = buf;
+		snprintf(buf, sizeof(buf), "_snd_pcm_hook_%s_install", str);
+	}
+	h = snd_dlopen(lib, RTLD_NOW);
+	install_func = h ? snd_dlsym(h, install, SND_DLSYM_VERSION(SND_PCM_DLSYM_VERSION)) : NULL;
+	err = 0;
+	if (!h) {
+		SNDERR("Cannot open shared library %s",
+		       lib ? lib : "[builtin]");
+		err = -ENOENT;
+	} else if (!install_func) {
+		SNDERR("symbol %s is not defined inside %s", install,
+		       lib ? lib : "[builtin]");
+		snd_dlclose(h);
+		err = -ENXIO;
+	}
+       _err:
+	if (type)
+		snd_config_delete(type);
+	if (err < 0)
+		return err;
+
+	if (args && snd_config_get_string(args, &str) >= 0) {
+		err = snd_config_search_definition(root, "hook_args", str, &args);
+		if (err < 0)
+			SNDERR("unknown hook_args %s", str);
+		else
+			err = install_func(pcm, args);
+		snd_config_delete(args);
+	} else
+		err = install_func(pcm, args);
+
+	if (err >= 0)
+		err = hook_add_dlobj(pcm, h);
+
+	if (err < 0) {
+		snd_dlclose(h);
+		return err;
+	}
+	return 0;
+}
+
+/**
+ * \brief Creates a new hooks PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param root Root configuration node
+ * \param conf Configuration node with hooks PCM description
+ * \param stream PCM Stream
+ * \param mode PCM Mode
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *	    changed in future.
+ */
+int _snd_pcm_hooks_open(snd_pcm_t **pcmp, const char *name,
+			snd_config_t *root, snd_config_t *conf, 
+			snd_pcm_stream_t stream, int mode)
+{
+	snd_config_iterator_t i, next;
+	int err;
+	snd_pcm_t *rpcm = NULL, *spcm;
+	snd_config_t *slave = NULL, *sconf;
+	snd_config_t *hooks = NULL;
+	snd_config_for_each(i, next, conf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (snd_pcm_conf_generic_id(id))
+			continue;
+		if (strcmp(id, "slave") == 0) {
+			slave = n;
+			continue;
+		}
+		if (strcmp(id, "hooks") == 0) {
+			if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) {
+				SNDERR("Invalid type for %s", id);
+				return -EINVAL;
+			}
+			hooks = n;
+			continue;
+		}
+		SNDERR("Unknown field %s", id);
+		return -EINVAL;
+	}
+	if (!slave) {
+		SNDERR("slave is not defined");
+		return -EINVAL;
+	}
+	err = snd_pcm_slave_conf(root, slave, &sconf, 0);
+	if (err < 0)
+		return err;
+	err = snd_pcm_open_slave(&spcm, root, sconf, stream, mode, conf);
+	snd_config_delete(sconf);
+	if (err < 0)
+		return err;
+	err = snd_pcm_hooks_open(&rpcm, name, spcm, 1);
+	if (err < 0) {
+		snd_pcm_close(spcm);
+		return err;
+	}
+	if (!hooks)
+		goto _done;
+	snd_config_for_each(i, next, hooks) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *str;
+		if (snd_config_get_string(n, &str) >= 0) {
+			err = snd_config_search_definition(root, "pcm_hook", str, &n);
+			if (err < 0) {
+				SNDERR("unknown pcm_hook %s", str);
+			} else {
+				err = snd_pcm_hook_add_conf(rpcm, root, n);
+				snd_config_delete(n);
+			}
+		} else
+			err = snd_pcm_hook_add_conf(rpcm, root, n);
+		if (err < 0) {
+			snd_pcm_close(rpcm);
+			return err;
+		}
+	}
+ _done:
+	*pcmp = rpcm;
+	return 0;
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(_snd_pcm_hooks_open, SND_PCM_DLSYM_VERSION);
+#endif
+
+/**
+ * \brief Get PCM handle for a PCM hook
+ * \param hook PCM hook handle
+ * \return PCM handle
+ */
+snd_pcm_t *snd_pcm_hook_get_pcm(snd_pcm_hook_t *hook)
+{
+	assert(hook);
+	return hook->pcm;
+}
+
+/**
+ * \brief Get callback function private data for a PCM hook
+ * \param hook PCM hook handle
+ * \return callback function private data
+ */
+void *snd_pcm_hook_get_private(snd_pcm_hook_t *hook)
+{
+	assert(hook);
+	return hook->private_data;
+}
+
+/**
+ * \brief Set callback function private data for a PCM hook
+ * \param hook PCM hook handle
+ * \param private_data The private data value
+ */
+void snd_pcm_hook_set_private(snd_pcm_hook_t *hook, void *private_data)
+{
+	assert(hook);
+	hook->private_data = private_data;
+}
+
+/**
+ * \brief Add a PCM hook at end of hooks chain
+ * \param hookp Returned PCM hook handle
+ * \param pcm PCM handle
+ * \param type PCM hook type
+ * \param func PCM hook callback function
+ * \param private_data PCM hook private data
+ * \return 0 on success otherwise a negative error code
+ *
+ * Warning: an hook callback function cannot remove an hook of the same type
+ * different from itself
+ */
+int snd_pcm_hook_add(snd_pcm_hook_t **hookp, snd_pcm_t *pcm,
+		     snd_pcm_hook_type_t type,
+		     snd_pcm_hook_func_t func, void *private_data)
+{
+	snd_pcm_hook_t *h;
+	snd_pcm_hooks_t *hooks;
+	assert(hookp && func);
+	assert(snd_pcm_type(pcm) == SND_PCM_TYPE_HOOKS);
+	h = calloc(1, sizeof(*h));
+	if (!h)
+		return -ENOMEM;
+	h->pcm = pcm;
+	h->func = func;
+	h->private_data = private_data;
+	hooks = pcm->private_data;
+	list_add_tail(&h->list, &hooks->hooks[type]);
+	*hookp = h;
+	return 0;
+}
+
+/**
+ * \brief Remove a PCM hook
+ * \param hook PCM hook handle
+ * \return 0 on success otherwise a negative error code
+ *
+ * Warning: an hook callback cannot remove an hook of the same type
+ * different from itself
+ */
+int snd_pcm_hook_remove(snd_pcm_hook_t *hook)
+{
+	assert(hook);
+	list_del(&hook->list);
+	free(hook);
+	return 0;
+}
+
+/*
+ *
+ */
+
+static int snd_pcm_hook_ctl_elems_hw_params(snd_pcm_hook_t *hook)
+{
+	snd_sctl_t *h = snd_pcm_hook_get_private(hook);
+	return snd_sctl_install(h);
+}
+
+static int snd_pcm_hook_ctl_elems_hw_free(snd_pcm_hook_t *hook)
+{
+	snd_sctl_t *h = snd_pcm_hook_get_private(hook);
+	return snd_sctl_remove(h);
+}
+
+static int snd_pcm_hook_ctl_elems_close(snd_pcm_hook_t *hook)
+{
+	snd_sctl_t *h = snd_pcm_hook_get_private(hook);
+	int err = snd_sctl_free(h);
+	snd_pcm_hook_set_private(hook, NULL);
+	return err;
+}
+
+/**
+ * \brief Install CTL settings using hardware associated with PCM handle
+ * \param pcm PCM handle
+ * \param conf Configuration node with CTL settings
+ * \return zero on success otherwise a negative error code
+ */
+int _snd_pcm_hook_ctl_elems_install(snd_pcm_t *pcm, snd_config_t *conf)
+{
+	int err;
+	int card;
+	snd_pcm_info_t *info;
+	char ctl_name[16];
+	snd_ctl_t *ctl;
+	snd_sctl_t *sctl = NULL;
+	snd_config_t *pcm_conf = NULL;
+	snd_pcm_hook_t *h_hw_params = NULL, *h_hw_free = NULL, *h_close = NULL;
+	assert(conf);
+	assert(snd_config_get_type(conf) == SND_CONFIG_TYPE_COMPOUND);
+	snd_pcm_info_alloca(&info);
+	err = snd_pcm_info(pcm, info);
+	if (err < 0)
+		return err;
+	card = snd_pcm_info_get_card(info);
+	if (card < 0) {
+		SNDERR("No card for this PCM");
+		return -EINVAL;
+	}
+	sprintf(ctl_name, "hw:%d", card);
+	err = snd_ctl_open(&ctl, ctl_name, 0);
+	if (err < 0) {
+		SNDERR("Cannot open CTL %s", ctl_name);
+		return err;
+	}
+	err = snd_config_imake_pointer(&pcm_conf, "pcm_handle", pcm);
+	if (err < 0)
+		goto _err;
+	err = snd_sctl_build(&sctl, ctl, conf, pcm_conf, 0);
+	if (err < 0)
+		goto _err;
+	err = snd_pcm_hook_add(&h_hw_params, pcm, SND_PCM_HOOK_TYPE_HW_PARAMS,
+			       snd_pcm_hook_ctl_elems_hw_params, sctl);
+	if (err < 0)
+		goto _err;
+	err = snd_pcm_hook_add(&h_hw_free, pcm, SND_PCM_HOOK_TYPE_HW_FREE,
+			       snd_pcm_hook_ctl_elems_hw_free, sctl);
+	if (err < 0)
+		goto _err;
+	err = snd_pcm_hook_add(&h_close, pcm, SND_PCM_HOOK_TYPE_CLOSE,
+			       snd_pcm_hook_ctl_elems_close, sctl);
+	if (err < 0)
+		goto _err;
+	snd_config_delete(pcm_conf);
+	return 0;
+ _err:
+	if (h_hw_params)
+		snd_pcm_hook_remove(h_hw_params);
+	if (h_hw_free)
+		snd_pcm_hook_remove(h_hw_free);
+	if (h_close)
+		snd_pcm_hook_remove(h_close);
+	if (sctl)
+		snd_sctl_free(sctl);
+	if (pcm_conf)
+		snd_config_delete(pcm_conf);
+	return err;
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(_snd_pcm_hook_ctl_elems_install, SND_PCM_DLSYM_VERSION);
+#endif
diff --git a/src/pcm/pcm_hw.c b/src/pcm/pcm_hw.c
new file mode 100644
index 0000000..9d243d5
--- /dev/null
+++ b/src/pcm/pcm_hw.c
@@ -0,0 +1,1570 @@
+/**
+ * \file pcm/pcm_hw.c
+ * \ingroup PCM_Plugins
+ * \brief PCM HW Plugin Interface
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \date 2000-2001
+ */
+/*
+ *  PCM - Hardware
+ *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/shm.h>
+#include "pcm_local.h"
+#include "../control/control_local.h"
+#include "../timer/timer_local.h"
+
+//#define DEBUG_RW		/* use to debug readi/writei/readn/writen */
+//#define DEBUG_MMAP		/* debug mmap_commit */
+
+#ifndef PIC
+/* entry for static linking */
+const char *_snd_module_pcm_hw = "";
+#endif
+
+#ifndef DOC_HIDDEN
+
+#ifndef F_SETSIG
+#define F_SETSIG 10
+#endif
+
+/*
+ *  Compatibility
+ */
+
+struct sndrv_pcm_hw_params_old {
+	unsigned int flags;
+	unsigned int masks[SNDRV_PCM_HW_PARAM_SUBFORMAT -
+			   SNDRV_PCM_HW_PARAM_ACCESS + 1];
+	struct sndrv_interval intervals[SNDRV_PCM_HW_PARAM_TICK_TIME -
+					SNDRV_PCM_HW_PARAM_SAMPLE_BITS + 1];
+	unsigned int rmask;
+	unsigned int cmask;
+	unsigned int info;
+	unsigned int msbits;
+	unsigned int rate_num;
+	unsigned int rate_den;
+	sndrv_pcm_uframes_t fifo_size;
+	unsigned char reserved[64];
+};
+
+#define SND_PCM_IOCTL_HW_REFINE_OLD _IOWR('A', 0x10, struct sndrv_pcm_hw_params_old)
+#define SND_PCM_IOCTL_HW_PARAMS_OLD _IOWR('A', 0x11, struct sndrv_pcm_hw_params_old)
+
+static int use_old_hw_params_ioctl(int fd, unsigned int cmd, snd_pcm_hw_params_t *params);
+static snd_pcm_sframes_t snd_pcm_hw_avail_update(snd_pcm_t *pcm);
+static const snd_pcm_fast_ops_t snd_pcm_hw_fast_ops;
+static const snd_pcm_fast_ops_t snd_pcm_hw_fast_ops_timer;
+
+/*
+ *
+ */
+
+typedef struct {
+	int version;
+	int fd;
+	int card, device, subdevice;
+	int sync_ptr_ioctl;
+	volatile struct sndrv_pcm_mmap_status * mmap_status;
+	struct sndrv_pcm_mmap_control *mmap_control;
+	struct sndrv_pcm_sync_ptr *sync_ptr;
+	snd_pcm_uframes_t hw_ptr;
+	snd_pcm_uframes_t appl_ptr;
+	int period_event;
+	snd_timer_t *period_timer;
+	struct pollfd period_timer_pfd;
+	int period_timer_need_poll;
+	/* restricted parameters */
+	snd_pcm_format_t format;
+	int rate;
+	int channels;
+} snd_pcm_hw_t;
+
+#define SNDRV_FILE_PCM_STREAM_PLAYBACK		ALSA_DEVICE_DIRECTORY "pcmC%iD%ip"
+#define SNDRV_FILE_PCM_STREAM_CAPTURE		ALSA_DEVICE_DIRECTORY "pcmC%iD%ic"
+#define SNDRV_PCM_VERSION_MAX			SNDRV_PROTOCOL_VERSION(2, 0, 9)
+
+/* update appl_ptr with driver */
+#define FAST_PCM_STATE(hw) \
+	((enum sndrv_pcm_state) (hw)->mmap_status->state)
+#define FAST_PCM_TSTAMP(hw) \
+	((hw)->mmap_status->tstamp)
+
+struct timespec snd_pcm_hw_fast_tstamp(snd_pcm_t *pcm)
+{
+	struct timespec res;
+	snd_pcm_hw_t *hw = pcm->private_data;
+	res = FAST_PCM_TSTAMP(hw);
+	if (SNDRV_PROTOCOL_VERSION(2, 0, 5) > hw->version)
+		res.tv_nsec *= 1000L;
+	return res;
+}
+#endif /* DOC_HIDDEN */
+
+static int sync_ptr1(snd_pcm_hw_t *hw, unsigned int flags)
+{
+	int err;
+	hw->sync_ptr->flags = flags;
+	err = ioctl((hw)->fd, SNDRV_PCM_IOCTL_SYNC_PTR, (hw)->sync_ptr);
+	if (err < 0) {
+		err = -errno;
+		SYSMSG("SNDRV_PCM_IOCTL_SYNC_PTR failed (%i)", err);
+		return err;
+	}
+	return 0;
+}
+
+static inline int sync_ptr(snd_pcm_hw_t *hw, unsigned int flags)
+{
+	return hw->sync_ptr ? sync_ptr1(hw, flags) : 0;
+}
+
+static int snd_pcm_hw_clear_timer_queue(snd_pcm_hw_t *hw)
+{
+	if (hw->period_timer_need_poll) {
+		while (poll(&hw->period_timer_pfd, 1, 0) > 0) {
+			snd_timer_tread_t rbuf[4];
+			snd_timer_read(hw->period_timer, rbuf, sizeof(rbuf));
+		}
+	} else {
+		snd_timer_tread_t rbuf[4];
+		snd_timer_read(hw->period_timer, rbuf, sizeof(rbuf));
+	}
+	return 0;
+}
+
+static int snd_pcm_hw_poll_descriptors_count(snd_pcm_t *pcm ATTRIBUTE_UNUSED)
+{
+	return 2;
+}
+
+static int snd_pcm_hw_poll_descriptors(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int space)
+{
+	snd_pcm_hw_t *hw = pcm->private_data;
+
+	if (space < 2)
+		return -ENOMEM;
+	pfds[0].fd = hw->fd;
+	pfds[0].events = pcm->poll_events | POLLERR | POLLNVAL;
+	pfds[1].fd = hw->period_timer_pfd.fd;
+	pfds[1].events = POLLIN | POLLERR | POLLNVAL;
+	return 2;
+}
+
+static int snd_pcm_hw_poll_revents(snd_pcm_t *pcm, struct pollfd *pfds, unsigned nfds, unsigned short *revents)
+{
+	snd_pcm_hw_t *hw = pcm->private_data;
+	unsigned int events;
+
+	if (nfds != 2 || pfds[0].fd != hw->fd || pfds[1].fd != hw->period_timer_pfd.fd)
+		return -EINVAL;
+	events = pfds[0].revents;
+	if (pfds[1].revents & POLLIN) {
+		snd_pcm_hw_clear_timer_queue(hw);
+		events |= pcm->poll_events & ~(POLLERR|POLLNVAL);
+	}
+	*revents = events;
+	return 0;
+}
+
+static int snd_pcm_hw_nonblock(snd_pcm_t *pcm, int nonblock)
+{
+	long flags;
+	snd_pcm_hw_t *hw = pcm->private_data;
+	int fd = hw->fd, err;
+
+	if ((flags = fcntl(fd, F_GETFL)) < 0) {
+		err = -errno;
+		SYSMSG("F_GETFL failed (%i)", err);
+		return err;
+	}
+	if (nonblock)
+		flags |= O_NONBLOCK;
+	else
+		flags &= ~O_NONBLOCK;
+	if (fcntl(fd, F_SETFL, flags) < 0) {
+		err = -errno;
+		SYSMSG("F_SETFL for O_NONBLOCK failed (%i)", err);
+		return err;
+	}
+	return 0;
+}
+
+static int snd_pcm_hw_async(snd_pcm_t *pcm, int sig, pid_t pid)
+{
+	long flags;
+	snd_pcm_hw_t *hw = pcm->private_data;
+	int fd = hw->fd, err;
+
+	if ((flags = fcntl(fd, F_GETFL)) < 0) {
+		err = -errno;
+		SYSMSG("F_GETFL failed (%i)", err);
+		return err;
+	}
+	if (sig >= 0)
+		flags |= O_ASYNC;
+	else
+		flags &= ~O_ASYNC;
+	if (fcntl(fd, F_SETFL, flags) < 0) {
+		err = -errno;
+		SYSMSG("F_SETFL for O_ASYNC failed (%i)", err);
+		return err;
+	}
+	if (sig < 0)
+		return 0;
+	if (fcntl(fd, F_SETSIG, (long)sig) < 0) {
+		err = -errno;
+		SYSMSG("F_SETSIG failed (%i)", err);
+		return err;
+	}
+	if (fcntl(fd, F_SETOWN, (long)pid) < 0) {
+		err = -errno;
+		SYSMSG("F_SETOWN failed (%i)", err);
+		return err;
+	}
+	return 0;
+}
+
+static int snd_pcm_hw_info(snd_pcm_t *pcm, snd_pcm_info_t * info)
+{
+	snd_pcm_hw_t *hw = pcm->private_data;
+	int fd = hw->fd, err;
+	if (ioctl(fd, SNDRV_PCM_IOCTL_INFO, info) < 0) {
+		err = -errno;
+		SYSMSG("SNDRV_PCM_IOCTL_INFO failed (%i)", err);
+		return err;
+	}
+	return 0;
+}
+
+static inline int hw_refine_call(snd_pcm_hw_t *pcm_hw, snd_pcm_hw_params_t *params)
+{
+	/* check for new hw_params structure; it's available from 2.0.2 version of PCM API */
+	if (SNDRV_PROTOCOL_VERSION(2, 0, 2) <= pcm_hw->version)
+		return ioctl(pcm_hw->fd, SNDRV_PCM_IOCTL_HW_REFINE, params);
+	return use_old_hw_params_ioctl(pcm_hw->fd, SND_PCM_IOCTL_HW_REFINE_OLD, params);
+}
+
+static int snd_pcm_hw_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	snd_pcm_hw_t *hw = pcm->private_data;
+	int err;
+
+	if (hw->format != SND_PCM_FORMAT_UNKNOWN) {
+		err = _snd_pcm_hw_params_set_format(params, hw->format);
+		if (err < 0)
+			return err;
+	}
+	if (hw->channels > 0) {
+		err = _snd_pcm_hw_param_set(params, SND_PCM_HW_PARAM_CHANNELS,
+					    hw->channels, 0);
+		if (err < 0)
+			return err;
+	}
+	if (hw->rate > 0) {
+		err = _snd_pcm_hw_param_set_minmax(params, SND_PCM_HW_PARAM_RATE,
+						   hw->rate, 0, hw->rate + 1, -1);
+		if (err < 0)
+			return err;
+	}
+
+	if (hw_refine_call(hw, params) < 0) {
+		err = -errno;
+		// SYSMSG("SNDRV_PCM_IOCTL_HW_REFINE failed");
+		return err;
+	}
+
+	if (params->info != ~0U) {
+		params->info &= ~0xf0000000;
+		params->info |= (pcm->monotonic ? SND_PCM_INFO_MONOTONIC : 0);
+	}
+	
+	return 0;
+}
+
+static inline int hw_params_call(snd_pcm_hw_t *pcm_hw, snd_pcm_hw_params_t *params)
+{
+	/* check for new hw_params structure; it's available from 2.0.2 version of PCM API */
+	if (SNDRV_PROTOCOL_VERSION(2, 0, 2) <= pcm_hw->version)
+		return ioctl(pcm_hw->fd, SNDRV_PCM_IOCTL_HW_PARAMS, params);
+	return use_old_hw_params_ioctl(pcm_hw->fd, SND_PCM_IOCTL_HW_PARAMS_OLD, params);
+}
+
+static int snd_pcm_hw_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t * params)
+{
+	snd_pcm_hw_t *hw = pcm->private_data;
+	int err;
+	if (hw_params_call(hw, params) < 0) {
+		err = -errno;
+		SYSMSG("SNDRV_PCM_IOCTL_HW_PARAMS failed (%i)", err);
+		return err;
+	}
+	params->info &= ~0xf0000000;
+	params->info |= (pcm->monotonic ? SND_PCM_INFO_MONOTONIC : 0);
+	err = sync_ptr(hw, 0);
+	if (err < 0)
+		return err;
+	if (pcm->stream == SND_PCM_STREAM_CAPTURE) {
+		snd_pcm_set_appl_ptr(pcm, &hw->mmap_control->appl_ptr, hw->fd,
+				     SNDRV_PCM_MMAP_OFFSET_CONTROL);
+	}
+	return 0;
+}
+
+static void snd_pcm_hw_close_timer(snd_pcm_hw_t *hw)
+{
+	if (hw->period_timer) {
+		snd_timer_close(hw->period_timer);
+		hw->period_timer = NULL;
+	}
+}
+
+static int snd_pcm_hw_change_timer(snd_pcm_t *pcm, int enable)
+{
+	snd_pcm_hw_t *hw = pcm->private_data;
+	snd_timer_params_t *params;
+	unsigned int suspend, resume;
+	int err;
+	
+	if (enable) {
+		snd_timer_params_alloca(&params);
+		err = snd_timer_hw_open(&hw->period_timer, "hw-pcm-period-event", SND_TIMER_CLASS_PCM, SND_TIMER_SCLASS_NONE, hw->card, hw->device, (hw->subdevice << 1) | (pcm->stream & 1), SND_TIMER_OPEN_NONBLOCK | SND_TIMER_OPEN_TREAD);
+		if (err < 0) {
+			err = snd_timer_hw_open(&hw->period_timer, "hw-pcm-period-event", SND_TIMER_CLASS_PCM, SND_TIMER_SCLASS_NONE, hw->card, hw->device, (hw->subdevice << 1) | (pcm->stream & 1), SND_TIMER_OPEN_NONBLOCK);
+			return err;
+		}
+		if (snd_timer_poll_descriptors_count(hw->period_timer) != 1) {
+			snd_pcm_hw_close_timer(hw);
+			return -EINVAL;
+		}
+		hw->period_timer_pfd.events = POLLIN;
+ 		hw->period_timer_pfd.revents = 0;
+		snd_timer_poll_descriptors(hw->period_timer, &hw->period_timer_pfd, 1);
+		hw->period_timer_need_poll = 0;
+		suspend = 1<<SND_TIMER_EVENT_MSUSPEND;
+		resume = 1<<SND_TIMER_EVENT_MRESUME;
+		/*
+		 * hacks for older kernel drivers
+		 */
+		{
+			int ver = 0;
+			ioctl(hw->period_timer_pfd.fd, SNDRV_TIMER_IOCTL_PVERSION, &ver);
+			/* In older versions, check via poll before read() is needed
+                         * because of the confliction between TIMER_START and
+                         * FIONBIO ioctls.
+                         */
+			if (ver < SNDRV_PROTOCOL_VERSION(2, 0, 4))
+				hw->period_timer_need_poll = 1;
+			/*
+			 * In older versions, timer uses pause events instead
+			 * suspend/resume events.
+			 */
+			if (ver < SNDRV_PROTOCOL_VERSION(2, 0, 5)) {
+				suspend = 1<<SND_TIMER_EVENT_MPAUSE;
+				resume = 1<<SND_TIMER_EVENT_MCONTINUE;
+			}
+		}
+		snd_timer_params_set_auto_start(params, 1);
+		snd_timer_params_set_ticks(params, 1);
+		snd_timer_params_set_filter(params, (1<<SND_TIMER_EVENT_TICK) |
+					    suspend | resume);
+		err = snd_timer_params(hw->period_timer, params);
+		if (err < 0) {
+			snd_pcm_hw_close_timer(hw);
+			return err;
+		}
+		err = snd_timer_start(hw->period_timer);
+		if (err < 0) {
+			snd_pcm_hw_close_timer(hw);
+			return err;
+		}
+		pcm->fast_ops = &snd_pcm_hw_fast_ops_timer;
+	} else {
+		snd_pcm_hw_close_timer(hw);
+		pcm->fast_ops = &snd_pcm_hw_fast_ops;
+		hw->period_event = 0;
+	}
+	return 0;
+}
+
+static int snd_pcm_hw_hw_free(snd_pcm_t *pcm)
+{
+	snd_pcm_hw_t *hw = pcm->private_data;
+	int fd = hw->fd, err;
+	snd_pcm_hw_change_timer(pcm, 0);
+	if (ioctl(fd, SNDRV_PCM_IOCTL_HW_FREE) < 0) {
+		err = -errno;
+		SYSMSG("SNDRV_PCM_IOCTL_HW_FREE failed (%i)", err);
+		return err;
+	}
+	return 0;
+}
+
+static int snd_pcm_hw_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t * params)
+{
+	snd_pcm_hw_t *hw = pcm->private_data;
+	int fd = hw->fd, err;
+	int old_period_event = params->period_event;
+	params->period_event = 0;
+	if ((snd_pcm_tstamp_t) params->tstamp_mode == pcm->tstamp_mode &&
+	    params->period_step == pcm->period_step &&
+	    params->start_threshold == pcm->start_threshold &&
+	    params->stop_threshold == pcm->stop_threshold &&
+	    params->silence_threshold == pcm->silence_threshold &&
+	    params->silence_size == pcm->silence_size &&
+	    old_period_event == hw->period_event) {
+		hw->mmap_control->avail_min = params->avail_min;
+		return sync_ptr(hw, 0);
+	}
+	if (ioctl(fd, SNDRV_PCM_IOCTL_SW_PARAMS, params) < 0) {
+		err = -errno;
+		SYSMSG("SNDRV_PCM_IOCTL_SW_PARAMS failed (%i)", err);
+		return err;
+	}
+	params->period_event = old_period_event;
+	hw->mmap_control->avail_min = params->avail_min;
+	if (hw->period_event != old_period_event) {
+		err = snd_pcm_hw_change_timer(pcm, old_period_event);
+		if (err < 0)
+			return err;
+		hw->period_event = old_period_event;
+	}
+	return 0;
+}
+
+static int snd_pcm_hw_channel_info(snd_pcm_t *pcm, snd_pcm_channel_info_t * info)
+{
+	snd_pcm_hw_t *hw = pcm->private_data;
+	struct sndrv_pcm_channel_info i;
+	int fd = hw->fd, err;
+	i.channel = info->channel;
+	if (ioctl(fd, SNDRV_PCM_IOCTL_CHANNEL_INFO, &i) < 0) {
+		err = -errno;
+		SYSMSG("SNDRV_PCM_IOCTL_CHANNEL_INFO failed (%i)", err);
+		return err;
+	}
+	info->channel = i.channel;
+	info->addr = 0;
+	info->first = i.first;
+	info->step = i.step;
+	info->type = SND_PCM_AREA_MMAP;
+	info->u.mmap.fd = fd;
+	info->u.mmap.offset = i.offset;
+	return 0;
+}
+
+static int snd_pcm_hw_status(snd_pcm_t *pcm, snd_pcm_status_t * status)
+{
+	snd_pcm_hw_t *hw = pcm->private_data;
+	int fd = hw->fd, err;
+	if (ioctl(fd, SNDRV_PCM_IOCTL_STATUS, status) < 0) {
+		err = -errno;
+		SYSMSG("SNDRV_PCM_IOCTL_STATUS failed (%i)", err);
+		return err;
+	}
+	if (SNDRV_PROTOCOL_VERSION(2, 0, 5) > hw->version) {
+		status->tstamp.tv_nsec *= 1000L;
+		status->trigger_tstamp.tv_nsec *= 1000L;
+	}
+	return 0;
+}
+
+static snd_pcm_state_t snd_pcm_hw_state(snd_pcm_t *pcm)
+{
+	snd_pcm_hw_t *hw = pcm->private_data;
+	int err = sync_ptr(hw, 0);
+	if (err < 0)
+		return err;
+	return (snd_pcm_state_t) hw->mmap_status->state;
+}
+
+static int snd_pcm_hw_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp)
+{
+	snd_pcm_hw_t *hw = pcm->private_data;
+	int fd = hw->fd, err;
+	if (ioctl(fd, SNDRV_PCM_IOCTL_DELAY, delayp) < 0) {
+		err = -errno;
+		SYSMSG("SNDRV_PCM_IOCTL_DELAY failed (%i)", err);
+		return err;
+	}
+	return 0;
+}
+
+static int snd_pcm_hw_hwsync(snd_pcm_t *pcm)
+{
+	snd_pcm_hw_t *hw = pcm->private_data;
+	int fd = hw->fd, err;
+	if (SNDRV_PROTOCOL_VERSION(2, 0, 3) <= hw->version) {
+		if (hw->sync_ptr) {
+			err = sync_ptr1(hw, SNDRV_PCM_SYNC_PTR_HWSYNC);
+			if (err < 0)
+				return err;
+		} else {
+			if (ioctl(fd, SNDRV_PCM_IOCTL_HWSYNC) < 0) {
+				err = -errno;
+				SYSMSG("SNDRV_PCM_IOCTL_HWSYNC failed (%i)", err);
+				return err;
+			}
+		}
+	} else {
+		snd_pcm_sframes_t delay;
+		int err = snd_pcm_hw_delay(pcm, &delay);
+		if (err < 0) {
+			switch (FAST_PCM_STATE(hw)) {
+			case SND_PCM_STATE_PREPARED:
+			case SND_PCM_STATE_SUSPENDED:
+				return 0;
+			default:
+				return err;
+			}
+		}
+	}
+	return 0;
+}
+
+static int snd_pcm_hw_prepare(snd_pcm_t *pcm)
+{
+	snd_pcm_hw_t *hw = pcm->private_data;
+	int fd = hw->fd, err;
+	if (ioctl(fd, SNDRV_PCM_IOCTL_PREPARE) < 0) {
+		err = -errno;
+		SYSMSG("SNDRV_PCM_IOCTL_PREPARE failed (%i)", err);
+		return err;
+	}
+	return sync_ptr(hw, SNDRV_PCM_SYNC_PTR_APPL);
+}
+
+static int snd_pcm_hw_reset(snd_pcm_t *pcm)
+{
+	snd_pcm_hw_t *hw = pcm->private_data;
+	int fd = hw->fd, err;
+	if (ioctl(fd, SNDRV_PCM_IOCTL_RESET) < 0) {
+		err = -errno;
+		SYSMSG("SNDRV_PCM_IOCTL_RESET failed (%i)", err);
+		return err;
+	}
+	return sync_ptr(hw, SNDRV_PCM_SYNC_PTR_APPL);
+}
+
+static int snd_pcm_hw_start(snd_pcm_t *pcm)
+{
+	snd_pcm_hw_t *hw = pcm->private_data;
+	int err;
+#if 0
+	assert(pcm->stream != SND_PCM_STREAM_PLAYBACK ||
+	       snd_pcm_mmap_playback_hw_avail(pcm) > 0);
+#endif
+	sync_ptr(hw, 0);
+	if (ioctl(hw->fd, SNDRV_PCM_IOCTL_START) < 0) {
+		err = -errno;
+		SYSMSG("SNDRV_PCM_IOCTL_START failed (%i)", err);
+#if 0
+		if (err == -EBADFD)
+			SNDERR("PCM state = %s", snd_pcm_state_name(snd_pcm_hw_state(pcm)));
+#endif
+		return err;
+	}
+	return 0;
+}
+
+static int snd_pcm_hw_drop(snd_pcm_t *pcm)
+{
+	snd_pcm_hw_t *hw = pcm->private_data;
+	int err;
+	if (ioctl(hw->fd, SNDRV_PCM_IOCTL_DROP) < 0) {
+		err = -errno;
+		SYSMSG("SNDRV_PCM_IOCTL_DROP failed (%i)", err);
+		return err;
+	} else {
+	}
+	return 0;
+}
+
+static int snd_pcm_hw_drain(snd_pcm_t *pcm)
+{
+	snd_pcm_hw_t *hw = pcm->private_data;
+	int err;
+	if (ioctl(hw->fd, SNDRV_PCM_IOCTL_DRAIN) < 0) {
+		err = -errno;
+		SYSMSG("SNDRV_PCM_IOCTL_DRAIN failed (%i)", err);
+		return err;
+	}
+	return 0;
+}
+
+static int snd_pcm_hw_pause(snd_pcm_t *pcm, int enable)
+{
+	snd_pcm_hw_t *hw = pcm->private_data;
+	int err;
+	if (ioctl(hw->fd, SNDRV_PCM_IOCTL_PAUSE, enable) < 0) {
+		err = -errno;
+		SYSMSG("SNDRV_PCM_IOCTL_PAUSE failed (%i)", err);
+		return err;
+	}
+	return 0;
+}
+
+static snd_pcm_sframes_t snd_pcm_hw_rewindable(snd_pcm_t *pcm)
+{
+	return snd_pcm_mmap_hw_avail(pcm);
+}
+
+static snd_pcm_sframes_t snd_pcm_hw_rewind(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
+{
+	snd_pcm_hw_t *hw = pcm->private_data;
+	int err;
+	if (ioctl(hw->fd, SNDRV_PCM_IOCTL_REWIND, &frames) < 0) {
+		err = -errno;
+		SYSMSG("SNDRV_PCM_IOCTL_REWIND failed (%i)", err);
+		return err;
+	}
+	err = sync_ptr(hw, SNDRV_PCM_SYNC_PTR_APPL);
+	if (err < 0)
+		return err;
+	return frames;
+}
+
+static snd_pcm_sframes_t snd_pcm_hw_forwardable(snd_pcm_t *pcm)
+{
+	return snd_pcm_mmap_avail(pcm);
+}
+
+static snd_pcm_sframes_t snd_pcm_hw_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
+{
+	snd_pcm_hw_t *hw = pcm->private_data;
+	int err;
+	if (SNDRV_PROTOCOL_VERSION(2, 0, 4) <= hw->version) {
+		if (ioctl(hw->fd, SNDRV_PCM_IOCTL_FORWARD, &frames) < 0) {
+			err = -errno;
+			SYSMSG("SNDRV_PCM_IOCTL_FORWARD failed (%i)", err);
+			return err;
+		}
+		err = sync_ptr(hw, SNDRV_PCM_SYNC_PTR_APPL);
+		if (err < 0)
+			return err;
+		return frames;
+	} else {
+		snd_pcm_sframes_t avail;
+
+		err = sync_ptr(hw, SNDRV_PCM_SYNC_PTR_HWSYNC);
+		if (err < 0)
+			return err;
+		switch (FAST_PCM_STATE(hw)) {
+		case SNDRV_PCM_STATE_RUNNING:
+		case SNDRV_PCM_STATE_DRAINING:
+		case SNDRV_PCM_STATE_PAUSED:
+		case SNDRV_PCM_STATE_PREPARED:
+			break;
+		case SNDRV_PCM_STATE_XRUN:
+			return -EPIPE;
+		default:
+			return -EBADFD;
+		}
+		avail = snd_pcm_mmap_avail(pcm);
+		if (avail < 0)
+			return 0;
+		if (frames > (snd_pcm_uframes_t)avail)
+			frames = avail;
+		snd_pcm_mmap_appl_forward(pcm, frames);
+		err = sync_ptr(hw, 0);
+		if (err < 0)
+			return err;
+		return frames;
+	}
+}
+
+static int snd_pcm_hw_resume(snd_pcm_t *pcm)
+{
+	snd_pcm_hw_t *hw = pcm->private_data;
+	int fd = hw->fd, err;
+	if (ioctl(fd, SNDRV_PCM_IOCTL_RESUME) < 0) {
+		err = -errno;
+		SYSMSG("SNDRV_PCM_IOCTL_RESUME failed (%i)", err);
+		return err;
+	}
+	return 0;
+}
+
+static int hw_link(snd_pcm_t *pcm1, snd_pcm_t *pcm2)
+{
+	snd_pcm_hw_t *hw1 = pcm1->private_data;
+	snd_pcm_hw_t *hw2 = pcm2->private_data;
+	if (ioctl(hw1->fd, SNDRV_PCM_IOCTL_LINK, hw2->fd) < 0) {
+		SYSMSG("SNDRV_PCM_IOCTL_LINK failed (%i)", -errno);
+		return -errno;
+	}
+	return 0;
+}
+
+static int snd_pcm_hw_link_slaves(snd_pcm_t *pcm, snd_pcm_t *master)
+{
+	if (master->type != SND_PCM_TYPE_HW) {
+		SYSMSG("Invalid type for SNDRV_PCM_IOCTL_LINK (%i)", master->type);
+		return -EINVAL;
+	}
+	return hw_link(master, pcm);
+}
+
+static int snd_pcm_hw_link(snd_pcm_t *pcm1, snd_pcm_t *pcm2)
+{
+	if (pcm2->type != SND_PCM_TYPE_HW) {
+		if (pcm2->fast_ops->link_slaves)
+			return pcm2->fast_ops->link_slaves(pcm2, pcm1);
+		return -ENOSYS;
+	}
+	return hw_link(pcm1, pcm2);
+ }
+
+static int snd_pcm_hw_unlink(snd_pcm_t *pcm)
+{
+	snd_pcm_hw_t *hw = pcm->private_data;
+
+	if (ioctl(hw->fd, SNDRV_PCM_IOCTL_UNLINK) < 0) {
+		SYSMSG("SNDRV_PCM_IOCTL_UNLINK failed (%i)", -errno);
+		return -errno;
+	}
+	return 0;
+}
+
+static snd_pcm_sframes_t snd_pcm_hw_writei(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size)
+{
+	int err;
+	snd_pcm_hw_t *hw = pcm->private_data;
+	int fd = hw->fd;
+	struct sndrv_xferi xferi;
+	xferi.buf = (char*) buffer;
+	xferi.frames = size;
+	xferi.result = 0; /* make valgrind happy */
+	err = ioctl(fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &xferi);
+	err = err >= 0 ? sync_ptr(hw, SNDRV_PCM_SYNC_PTR_APPL) : -errno;
+#ifdef DEBUG_RW
+	fprintf(stderr, "hw_writei: frames = %li, xferi.result = %li, err = %i\n", size, xferi.result, err);
+#endif
+	if (err < 0)
+		return snd_pcm_check_error(pcm, err);
+	return xferi.result;
+}
+
+static snd_pcm_sframes_t snd_pcm_hw_writen(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size)
+{
+	int err;
+	snd_pcm_hw_t *hw = pcm->private_data;
+	int fd = hw->fd;
+	struct sndrv_xfern xfern;
+	memset(&xfern, 0, sizeof(xfern)); /* make valgrind happy */
+	xfern.bufs = bufs;
+	xfern.frames = size;
+	err = ioctl(fd, SNDRV_PCM_IOCTL_WRITEN_FRAMES, &xfern);
+	err = err >= 0 ? sync_ptr(hw, SNDRV_PCM_SYNC_PTR_APPL) : -errno;
+#ifdef DEBUG_RW
+	fprintf(stderr, "hw_writen: frames = %li, result = %li, err = %i\n", size, xfern.result, err);
+#endif
+	if (err < 0)
+		return snd_pcm_check_error(pcm, err);
+	return xfern.result;
+}
+
+static snd_pcm_sframes_t snd_pcm_hw_readi(snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t size)
+{
+	int err;
+	snd_pcm_hw_t *hw = pcm->private_data;
+	int fd = hw->fd;
+	struct sndrv_xferi xferi;
+	xferi.buf = buffer;
+	xferi.frames = size;
+	xferi.result = 0; /* make valgrind happy */
+	err = ioctl(fd, SNDRV_PCM_IOCTL_READI_FRAMES, &xferi);
+	err = err >= 0 ? sync_ptr(hw, SNDRV_PCM_SYNC_PTR_APPL) : -errno;
+#ifdef DEBUG_RW
+	fprintf(stderr, "hw_readi: frames = %li, result = %li, err = %i\n", size, xferi.result, err);
+#endif
+	if (err < 0)
+		return snd_pcm_check_error(pcm, err);
+	return xferi.result;
+}
+
+static snd_pcm_sframes_t snd_pcm_hw_readn(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size)
+{
+	int err;
+	snd_pcm_hw_t *hw = pcm->private_data;
+	int fd = hw->fd;
+	struct sndrv_xfern xfern;
+	memset(&xfern, 0, sizeof(xfern)); /* make valgrind happy */
+	xfern.bufs = bufs;
+	xfern.frames = size;
+	err = ioctl(fd, SNDRV_PCM_IOCTL_READN_FRAMES, &xfern);
+	err = err >= 0 ? sync_ptr(hw, SNDRV_PCM_SYNC_PTR_APPL) : -errno;
+#ifdef DEBUG_RW
+	fprintf(stderr, "hw_readn: frames = %li, result = %li, err = %i\n", size, xfern.result, err);
+#endif
+	if (err < 0)
+		return snd_pcm_check_error(pcm, err);
+	return xfern.result;
+}
+
+static int snd_pcm_hw_mmap_status(snd_pcm_t *pcm)
+{
+	snd_pcm_hw_t *hw = pcm->private_data;
+	struct sndrv_pcm_sync_ptr sync_ptr;
+	void *ptr;
+	int err;
+	ptr = MAP_FAILED;
+	if (hw->sync_ptr_ioctl == 0)
+		ptr = mmap(NULL, page_align(sizeof(struct sndrv_pcm_mmap_status)),
+			   PROT_READ, MAP_FILE|MAP_SHARED, 
+			   hw->fd, SNDRV_PCM_MMAP_OFFSET_STATUS);
+	if (ptr == MAP_FAILED || ptr == NULL) {
+		memset(&sync_ptr, 0, sizeof(sync_ptr));
+		sync_ptr.c.control.appl_ptr = 0;
+		sync_ptr.c.control.avail_min = 1;
+		err = ioctl(hw->fd, SNDRV_PCM_IOCTL_SYNC_PTR, &sync_ptr);
+		if (err < 0) {
+			err = -errno;
+			SYSMSG("SNDRV_PCM_IOCTL_SYNC_PTR failed (%i)", err);
+			return err;
+		}
+		hw->sync_ptr = calloc(1, sizeof(struct sndrv_pcm_sync_ptr));
+		if (hw->sync_ptr == NULL)
+			return -ENOMEM;
+		hw->mmap_status = &hw->sync_ptr->s.status;
+		hw->mmap_control = &hw->sync_ptr->c.control;
+		hw->sync_ptr_ioctl = 1;
+	} else {
+		hw->mmap_status = ptr;
+	}
+	snd_pcm_set_hw_ptr(pcm, &hw->mmap_status->hw_ptr, hw->fd, SNDRV_PCM_MMAP_OFFSET_STATUS + offsetof(struct sndrv_pcm_mmap_status, hw_ptr));
+	return 0;
+}
+
+static int snd_pcm_hw_mmap_control(snd_pcm_t *pcm)
+{
+	snd_pcm_hw_t *hw = pcm->private_data;
+	void *ptr;
+	int err;
+	if (hw->sync_ptr == NULL) {
+		ptr = mmap(NULL, page_align(sizeof(struct sndrv_pcm_mmap_control)),
+			   PROT_READ|PROT_WRITE, MAP_FILE|MAP_SHARED, 
+			   hw->fd, SNDRV_PCM_MMAP_OFFSET_CONTROL);
+		if (ptr == MAP_FAILED || ptr == NULL) {
+			err = -errno;
+			SYSMSG("control mmap failed (%i)", err);
+			return err;
+		}
+		hw->mmap_control = ptr;
+	} else {
+		hw->mmap_control->avail_min = 1;
+	}
+	snd_pcm_set_appl_ptr(pcm, &hw->mmap_control->appl_ptr, hw->fd, SNDRV_PCM_MMAP_OFFSET_CONTROL);
+	return 0;
+}
+
+static int snd_pcm_hw_munmap_status(snd_pcm_t *pcm)
+{
+	snd_pcm_hw_t *hw = pcm->private_data;
+	int err;
+	if (hw->sync_ptr_ioctl) {
+		free(hw->sync_ptr);
+		hw->sync_ptr = NULL;
+	} else {
+		if (munmap((void*)hw->mmap_status, page_align(sizeof(*hw->mmap_status))) < 0) {
+			err = -errno;
+			SYSMSG("status munmap failed (%i)", err);
+			return err;
+		}
+	}
+	return 0;
+}
+
+static int snd_pcm_hw_munmap_control(snd_pcm_t *pcm)
+{
+	snd_pcm_hw_t *hw = pcm->private_data;
+	int err;
+	if (hw->sync_ptr_ioctl) {
+		free(hw->sync_ptr);
+		hw->sync_ptr = NULL;
+	} else {
+		if (munmap(hw->mmap_control, page_align(sizeof(*hw->mmap_control))) < 0) {
+			err = -errno;
+			SYSMSG("control munmap failed (%i)", err);
+			return err;
+		}
+	}
+	return 0;
+}
+
+static int snd_pcm_hw_mmap(snd_pcm_t *pcm ATTRIBUTE_UNUSED)
+{
+	return 0;
+}
+
+static int snd_pcm_hw_munmap(snd_pcm_t *pcm ATTRIBUTE_UNUSED)
+{
+	return 0;
+}
+
+static int snd_pcm_hw_close(snd_pcm_t *pcm)
+{
+	snd_pcm_hw_t *hw = pcm->private_data;
+	int err = 0;
+	if (close(hw->fd)) {
+		err = -errno;
+		SYSMSG("close failed (%i)\n", err);
+	}
+	snd_pcm_hw_munmap_status(pcm);
+	snd_pcm_hw_munmap_control(pcm);
+	free(hw);
+	return err;
+}
+
+static snd_pcm_sframes_t snd_pcm_hw_mmap_commit(snd_pcm_t *pcm,
+						snd_pcm_uframes_t offset ATTRIBUTE_UNUSED,
+						snd_pcm_uframes_t size)
+{
+	snd_pcm_hw_t *hw = pcm->private_data;
+
+	snd_pcm_mmap_appl_forward(pcm, size);
+	sync_ptr(hw, 0);
+#ifdef DEBUG_MMAP
+	fprintf(stderr, "appl_forward: hw_ptr = %li, appl_ptr = %li, size = %li\n", *pcm->hw.ptr, *pcm->appl.ptr, size);
+#endif
+	return size;
+}
+
+static snd_pcm_sframes_t snd_pcm_hw_avail_update(snd_pcm_t *pcm)
+{
+	snd_pcm_hw_t *hw = pcm->private_data;
+	snd_pcm_uframes_t avail;
+
+	sync_ptr(hw, 0);
+	avail = snd_pcm_mmap_avail(pcm);
+	switch (FAST_PCM_STATE(hw)) {
+	case SNDRV_PCM_STATE_RUNNING:
+		if (avail >= pcm->stop_threshold) {
+			/* SNDRV_PCM_IOCTL_XRUN ioctl has been implemented since PCM kernel API 2.0.1 */
+			if (SNDRV_PROTOCOL_VERSION(2, 0, 1) <= hw->version) {
+				if (ioctl(hw->fd, SNDRV_PCM_IOCTL_XRUN) < 0)
+					return -errno;
+			}
+			/* everything is ok, state == SND_PCM_STATE_XRUN at the moment */
+			return -EPIPE;
+		}
+		break;
+	case SNDRV_PCM_STATE_XRUN:
+		return -EPIPE;
+	default:
+		break;
+	}
+	return avail;
+}
+
+static int snd_pcm_hw_htimestamp(snd_pcm_t *pcm, snd_pcm_uframes_t *avail,
+				 snd_htimestamp_t *tstamp)
+{
+	snd_pcm_sframes_t avail1;
+	int ok = 0;
+
+	/* unfortunately, loop is necessary to ensure valid timestamp */
+	while (1) {
+		avail1 = snd_pcm_hw_avail_update(pcm);
+		if (avail1 < 0)
+			return avail1;
+		if (ok && (snd_pcm_uframes_t)avail1 == *avail)
+			break;
+		*avail = avail1;
+		*tstamp = snd_pcm_hw_fast_tstamp(pcm);
+		ok = 1;
+	}
+	return 0;
+}
+
+static void snd_pcm_hw_dump(snd_pcm_t *pcm, snd_output_t *out)
+{
+	snd_pcm_hw_t *hw = pcm->private_data;
+	char *name;
+	int err = snd_card_get_name(hw->card, &name);
+	if (err < 0) {
+		SNDERR("cannot get card name");
+		return;
+	}
+	snd_output_printf(out, "Hardware PCM card %d '%s' device %d subdevice %d\n",
+			  hw->card, name, hw->device, hw->subdevice);
+	free(name);
+	if (pcm->setup) {
+		snd_output_printf(out, "Its setup is:\n");
+		snd_pcm_dump_setup(pcm, out);
+		snd_output_printf(out, "  appl_ptr     : %li\n", hw->mmap_control->appl_ptr);
+		snd_output_printf(out, "  hw_ptr       : %li\n", hw->mmap_status->hw_ptr);
+	}
+}
+
+static const snd_pcm_ops_t snd_pcm_hw_ops = {
+	.close = snd_pcm_hw_close,
+	.info = snd_pcm_hw_info,
+	.hw_refine = snd_pcm_hw_hw_refine,
+	.hw_params = snd_pcm_hw_hw_params,
+	.hw_free = snd_pcm_hw_hw_free,
+	.sw_params = snd_pcm_hw_sw_params,
+	.channel_info = snd_pcm_hw_channel_info,
+	.dump = snd_pcm_hw_dump,
+	.nonblock = snd_pcm_hw_nonblock,
+	.async = snd_pcm_hw_async,
+	.mmap = snd_pcm_hw_mmap,
+	.munmap = snd_pcm_hw_munmap,
+};
+
+static const snd_pcm_fast_ops_t snd_pcm_hw_fast_ops = {
+	.status = snd_pcm_hw_status,
+	.state = snd_pcm_hw_state,
+	.hwsync = snd_pcm_hw_hwsync,
+	.delay = snd_pcm_hw_delay,
+	.prepare = snd_pcm_hw_prepare,
+	.reset = snd_pcm_hw_reset,
+	.start = snd_pcm_hw_start,
+	.drop = snd_pcm_hw_drop,
+	.drain = snd_pcm_hw_drain,
+	.pause = snd_pcm_hw_pause,
+	.rewindable = snd_pcm_hw_rewindable,
+	.rewind = snd_pcm_hw_rewind,
+	.forwardable = snd_pcm_hw_forwardable,
+	.forward = snd_pcm_hw_forward,
+	.resume = snd_pcm_hw_resume,
+	.link = snd_pcm_hw_link,
+	.link_slaves = snd_pcm_hw_link_slaves,
+	.unlink = snd_pcm_hw_unlink,
+	.writei = snd_pcm_hw_writei,
+	.writen = snd_pcm_hw_writen,
+	.readi = snd_pcm_hw_readi,
+	.readn = snd_pcm_hw_readn,
+	.avail_update = snd_pcm_hw_avail_update,
+	.mmap_commit = snd_pcm_hw_mmap_commit,
+	.htimestamp = snd_pcm_hw_htimestamp,
+	.poll_descriptors = NULL,
+	.poll_descriptors_count = NULL,
+	.poll_revents = NULL,
+};
+
+static const snd_pcm_fast_ops_t snd_pcm_hw_fast_ops_timer = {
+	.status = snd_pcm_hw_status,
+	.state = snd_pcm_hw_state,
+	.hwsync = snd_pcm_hw_hwsync,
+	.delay = snd_pcm_hw_delay,
+	.prepare = snd_pcm_hw_prepare,
+	.reset = snd_pcm_hw_reset,
+	.start = snd_pcm_hw_start,
+	.drop = snd_pcm_hw_drop,
+	.drain = snd_pcm_hw_drain,
+	.pause = snd_pcm_hw_pause,
+	.rewindable = snd_pcm_hw_rewindable,
+	.rewind = snd_pcm_hw_rewind,
+	.forwardable = snd_pcm_hw_forwardable,
+	.forward = snd_pcm_hw_forward,
+	.resume = snd_pcm_hw_resume,
+	.link = snd_pcm_hw_link,
+	.link_slaves = snd_pcm_hw_link_slaves,
+	.unlink = snd_pcm_hw_unlink,
+	.writei = snd_pcm_hw_writei,
+	.writen = snd_pcm_hw_writen,
+	.readi = snd_pcm_hw_readi,
+	.readn = snd_pcm_hw_readn,
+	.avail_update = snd_pcm_hw_avail_update,
+	.mmap_commit = snd_pcm_hw_mmap_commit,
+	.htimestamp = snd_pcm_hw_htimestamp,
+	.poll_descriptors = snd_pcm_hw_poll_descriptors,
+	.poll_descriptors_count = snd_pcm_hw_poll_descriptors_count,
+	.poll_revents = snd_pcm_hw_poll_revents,
+};
+
+/**
+ * \brief Creates a new hw PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param fd File descriptor
+ * \param mmap_emulation Obsoleted parameter
+ * \param sync_ptr_ioctl Boolean flag for sync_ptr ioctl
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int snd_pcm_hw_open_fd(snd_pcm_t **pcmp, const char *name,
+		       int fd, int mmap_emulation ATTRIBUTE_UNUSED,
+		       int sync_ptr_ioctl)
+{
+	int ver, mode, monotonic = 0;
+	long fmode;
+	snd_pcm_t *pcm = NULL;
+	snd_pcm_hw_t *hw = NULL;
+	snd_pcm_info_t info;
+	int ret;
+
+	assert(pcmp);
+
+	memset(&info, 0, sizeof(info));
+	if (ioctl(fd, SNDRV_PCM_IOCTL_INFO, &info) < 0) {
+		ret = -errno;
+		SYSMSG("SNDRV_PCM_IOCTL_INFO failed (%i)", ret);
+		close(fd);
+		return ret;
+
+	}
+
+	if ((fmode = fcntl(fd, F_GETFL)) < 0) {
+		ret = -errno;
+		close(fd);
+		return ret;
+	}
+	mode = 0;
+	if (fmode & O_NONBLOCK)
+		mode |= SND_PCM_NONBLOCK;
+	if (fmode & O_ASYNC)
+		mode |= SND_PCM_ASYNC;
+
+	if (ioctl(fd, SNDRV_PCM_IOCTL_PVERSION, &ver) < 0) {
+		ret = -errno;
+		SYSMSG("SNDRV_PCM_IOCTL_PVERSION failed (%i)", ret);
+		close(fd);
+		return ret;
+	}
+	if (SNDRV_PROTOCOL_INCOMPATIBLE(ver, SNDRV_PCM_VERSION_MAX))
+		return -SND_ERROR_INCOMPATIBLE_VERSION;
+
+#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
+	if (SNDRV_PROTOCOL_VERSION(2, 0, 9) <= ver) {
+		struct timespec timespec;
+		if (clock_gettime(CLOCK_MONOTONIC, &timespec) == 0) {
+			int on = SNDRV_PCM_TSTAMP_TYPE_MONOTONIC;
+			if (ioctl(fd, SNDRV_PCM_IOCTL_TTSTAMP, &on) < 0) {
+				ret = -errno;
+				SNDMSG("TTSTAMP failed\n");
+				return ret;
+			}
+			monotonic = 1;
+		}
+	} else
+#endif
+	  if (SNDRV_PROTOCOL_VERSION(2, 0, 5) <= ver) {
+		int on = 1;
+		if (ioctl(fd, SNDRV_PCM_IOCTL_TSTAMP, &on) < 0) {
+			ret = -errno;
+			SNDMSG("TSTAMP failed\n");
+			return ret;
+		}
+	}
+	
+	hw = calloc(1, sizeof(snd_pcm_hw_t));
+	if (!hw) {
+		close(fd);
+		return -ENOMEM;
+	}
+
+	hw->version = ver;
+	hw->card = info.card;
+	hw->device = info.device;
+	hw->subdevice = info.subdevice;
+	hw->fd = fd;
+	hw->sync_ptr_ioctl = sync_ptr_ioctl;
+	/* no restriction */
+	hw->format = SND_PCM_FORMAT_UNKNOWN;
+	hw->rate = 0;
+	hw->channels = 0;
+
+	ret = snd_pcm_new(&pcm, SND_PCM_TYPE_HW, name, info.stream, mode);
+	if (ret < 0) {
+		free(hw);
+		close(fd);
+		return ret;
+	}
+
+	pcm->ops = &snd_pcm_hw_ops;
+	pcm->fast_ops = &snd_pcm_hw_fast_ops;
+	pcm->private_data = hw;
+	pcm->poll_fd = fd;
+	pcm->poll_events = info.stream == SND_PCM_STREAM_PLAYBACK ? POLLOUT : POLLIN;
+	pcm->monotonic = monotonic;
+
+	ret = snd_pcm_hw_mmap_status(pcm);
+	if (ret < 0) {
+		snd_pcm_close(pcm);
+		return ret;
+	}
+	ret = snd_pcm_hw_mmap_control(pcm);
+	if (ret < 0) {
+		snd_pcm_close(pcm);
+		return ret;
+	}
+
+	*pcmp = pcm;
+	return 0;
+}
+
+/**
+ * \brief Creates a new hw PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param card Number of card
+ * \param device Number of device
+ * \param subdevice Number of subdevice
+ * \param stream PCM Stream
+ * \param mode PCM Mode
+ * \param mmap_emulation Obsoleted parameter
+ * \param sync_ptr_ioctl Use SYNC_PTR ioctl rather than mmap for control structures
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int snd_pcm_hw_open(snd_pcm_t **pcmp, const char *name,
+		    int card, int device, int subdevice,
+		    snd_pcm_stream_t stream, int mode,
+		    int mmap_emulation ATTRIBUTE_UNUSED,
+		    int sync_ptr_ioctl)
+{
+	char filename[sizeof(SNDRV_FILE_PCM_STREAM_PLAYBACK) + 20];
+	const char *filefmt;
+	int ret = 0, fd = -1;
+	int attempt = 0;
+	snd_pcm_info_t info;
+	int fmode;
+	snd_ctl_t *ctl;
+
+	assert(pcmp);
+
+	if ((ret = snd_ctl_hw_open(&ctl, NULL, card, 0)) < 0)
+		return ret;
+
+	switch (stream) {
+	case SND_PCM_STREAM_PLAYBACK:
+		filefmt = SNDRV_FILE_PCM_STREAM_PLAYBACK;
+		break;
+	case SND_PCM_STREAM_CAPTURE:
+		filefmt = SNDRV_FILE_PCM_STREAM_CAPTURE;
+		break;
+	default:
+		SNDERR("invalid stream %d", stream);
+		return -EINVAL;
+	}
+	sprintf(filename, filefmt, card, device);
+
+      __again:
+      	if (attempt++ > 3) {
+		ret = -EBUSY;
+		goto _err;
+	}
+	ret = snd_ctl_pcm_prefer_subdevice(ctl, subdevice);
+	if (ret < 0)
+		goto _err;
+	fmode = O_RDWR;
+	if (mode & SND_PCM_NONBLOCK)
+		fmode |= O_NONBLOCK;
+	if (mode & SND_PCM_ASYNC)
+		fmode |= O_ASYNC;
+	if (mode & SND_PCM_APPEND)
+		fmode |= O_APPEND;
+	fd = snd_open_device(filename, fmode);
+	if (fd < 0) {
+		ret = -errno;
+		SYSMSG("open '%s' failed (%i)", filename, ret);
+		goto _err;
+	}
+	if (subdevice >= 0) {
+		memset(&info, 0, sizeof(info));
+		if (ioctl(fd, SNDRV_PCM_IOCTL_INFO, &info) < 0) {
+			ret = -errno;
+			SYSMSG("SNDRV_PCM_IOCTL_INFO failed (%i)", ret);
+			goto _err;
+		}
+		if (info.subdevice != (unsigned int) subdevice) {
+			close(fd);
+			goto __again;
+		}
+	}
+	snd_ctl_close(ctl);
+	return snd_pcm_hw_open_fd(pcmp, name, fd, 0, sync_ptr_ioctl);
+       _err:
+	snd_ctl_close(ctl);
+	return ret;
+}
+
+/*! \page pcm_plugins
+
+\section pcm_plugins_hw Plugin: hw
+
+This plugin communicates directly with the ALSA kernel driver. It is a raw
+communication without any conversions. The emulation of mmap access can be
+optionally enabled, but expect worse latency in the case.
+
+The nonblock option specifies whether the device is opened in a non-blocking
+manner.  Note that the blocking behavior for read/write access won't be
+changed by this option.  This influences only on the blocking behavior at
+opening the device.  If you would like to keep the compatibility with the
+older ALSA stuff, turn this option off.
+
+\code
+pcm.name {
+	type hw			# Kernel PCM
+	card INT/STR		# Card name (string) or number (integer)
+	[device INT]		# Device number (default 0)
+	[subdevice INT]		# Subdevice number (default -1: first available)
+	[sync_ptr_ioctl BOOL]	# Use SYNC_PTR ioctl rather than the direct mmap access for control structures
+	[nonblock BOOL]		# Force non-blocking open mode
+	[format STR]		# Restrict only to the given format
+	[channels INT]		# Restrict only to the given channels
+	[rate INT]		# Restrict only to the given rate
+}
+\endcode
+
+\subsection pcm_plugins_hw_funcref Function reference
+
+<UL>
+  <LI>snd_pcm_hw_open()
+  <LI>_snd_pcm_hw_open()
+</UL>
+
+*/
+
+/**
+ * \brief Creates a new hw PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param root Root configuration node
+ * \param conf Configuration node with hw PCM description
+ * \param stream PCM Stream
+ * \param mode PCM Mode
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int _snd_pcm_hw_open(snd_pcm_t **pcmp, const char *name,
+		     snd_config_t *root ATTRIBUTE_UNUSED, snd_config_t *conf,
+		     snd_pcm_stream_t stream, int mode)
+{
+	snd_config_iterator_t i, next;
+	long card = -1, device = 0, subdevice = -1;
+	const char *str;
+	int err, sync_ptr_ioctl = 0;
+	int rate = 0, channels = 0;
+	snd_pcm_format_t format = SND_PCM_FORMAT_UNKNOWN;
+	snd_config_t *n;
+	int nonblock = 1; /* non-block per default */
+	snd_pcm_hw_t *hw;
+
+	/* look for defaults.pcm.nonblock definition */
+	if (snd_config_search(root, "defaults.pcm.nonblock", &n) >= 0) {
+		err = snd_config_get_bool(n);
+		if (err >= 0)
+			nonblock = err;
+	}
+	snd_config_for_each(i, next, conf) {
+		const char *id;
+		n = snd_config_iterator_entry(i);
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (snd_pcm_conf_generic_id(id))
+			continue;
+		if (strcmp(id, "card") == 0) {
+			err = snd_config_get_integer(n, &card);
+			if (err < 0) {
+				err = snd_config_get_string(n, &str);
+				if (err < 0) {
+					SNDERR("Invalid type for %s", id);
+					return -EINVAL;
+				}
+				card = snd_card_get_index(str);
+				if (card < 0) {
+					SNDERR("Invalid value for %s", id);
+					return card;
+				}
+			}
+			continue;
+		}
+		if (strcmp(id, "device") == 0) {
+			err = snd_config_get_integer(n, &device);
+			if (err < 0) {
+				SNDERR("Invalid type for %s", id);
+				return err;
+			}
+			continue;
+		}
+		if (strcmp(id, "subdevice") == 0) {
+			err = snd_config_get_integer(n, &subdevice);
+			if (err < 0) {
+				SNDERR("Invalid type for %s", id);
+				return err;
+			}
+			continue;
+		}
+		if (strcmp(id, "sync_ptr_ioctl") == 0) {
+			err = snd_config_get_bool(n);
+			if (err < 0)
+				continue;
+			sync_ptr_ioctl = err;
+			continue;
+		}
+		if (strcmp(id, "nonblock") == 0) {
+			err = snd_config_get_bool(n);
+			if (err < 0)
+				continue;
+			nonblock = err;
+			continue;
+		}
+		if (strcmp(id, "rate") == 0) {
+			long val;
+			err = snd_config_get_integer(n, &val);
+			if (err < 0) {
+				SNDERR("Invalid type for %s", id);
+				return err;
+			}
+			rate = val;
+			continue;
+		}
+		if (strcmp(id, "format") == 0) {
+			err = snd_config_get_string(n, &str);
+			if (err < 0) {
+				SNDERR("invalid type for %s", id);
+				return err;
+			}
+			format = snd_pcm_format_value(str);
+			continue;
+		}
+		if (strcmp(id, "channels") == 0) {
+			long val;
+			err = snd_config_get_integer(n, &val);
+			if (err < 0) {
+				SNDERR("Invalid type for %s", id);
+				return err;
+			}
+			channels = val;
+			continue;
+		}
+		SNDERR("Unknown field %s", id);
+		return -EINVAL;
+	}
+	if (card < 0) {
+		SNDERR("card is not defined");
+		return -EINVAL;
+	}
+	err = snd_pcm_hw_open(pcmp, name, card, device, subdevice, stream,
+			      mode | (nonblock ? SND_PCM_NONBLOCK : 0),
+			      0, sync_ptr_ioctl);
+	if (err < 0)
+		return err;
+	if (nonblock && ! (mode & SND_PCM_NONBLOCK)) {
+		/* revert to blocking mode for read/write access */
+		snd_pcm_hw_nonblock(*pcmp, 0);
+		(*pcmp)->mode = mode;
+	} else
+		/* make sure the SND_PCM_NO_xxx flags don't get lost on the
+		 * way */
+		(*pcmp)->mode |= mode & (SND_PCM_NO_AUTO_RESAMPLE|
+					 SND_PCM_NO_AUTO_CHANNELS|
+					 SND_PCM_NO_AUTO_FORMAT|
+					 SND_PCM_NO_SOFTVOL);
+
+	hw = (*pcmp)->private_data;
+	if (format != SND_PCM_FORMAT_UNKNOWN)
+		hw->format = format;
+	if (channels > 0)
+		hw->channels = channels;
+	if (rate > 0)
+		hw->rate = rate;
+
+	return 0;
+}
+
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(_snd_pcm_hw_open, SND_PCM_DLSYM_VERSION);
+#endif
+
+/*
+ *  To be removed helpers, but keep binary compatibility at the time
+ */
+
+#ifndef DOC_HIDDEN
+#define __OLD_TO_NEW_MASK(x) ((x&7)|((x&0x07fffff8)<<5))
+#define __NEW_TO_OLD_MASK(x) ((x&7)|((x&0xffffff00)>>5))
+#endif
+
+static void snd_pcm_hw_convert_from_old_params(snd_pcm_hw_params_t *params,
+					       struct sndrv_pcm_hw_params_old *oparams)
+{
+	unsigned int i;
+
+	memset(params, 0, sizeof(*params));
+	params->flags = oparams->flags;
+	for (i = 0; i < sizeof(oparams->masks) / sizeof(unsigned int); i++)
+		params->masks[i].bits[0] = oparams->masks[i];
+	memcpy(params->intervals, oparams->intervals, sizeof(oparams->intervals));
+	params->rmask = __OLD_TO_NEW_MASK(oparams->rmask);
+	params->cmask = __OLD_TO_NEW_MASK(oparams->cmask);
+	params->info = oparams->info;
+	params->msbits = oparams->msbits;
+	params->rate_num = oparams->rate_num;
+	params->rate_den = oparams->rate_den;
+	params->fifo_size = oparams->fifo_size;
+}
+
+static void snd_pcm_hw_convert_to_old_params(struct sndrv_pcm_hw_params_old *oparams,
+					     snd_pcm_hw_params_t *params,
+					     unsigned int *cmask)
+{
+	unsigned int i, j;
+
+	memset(oparams, 0, sizeof(*oparams));
+	oparams->flags = params->flags;
+	for (i = 0; i < sizeof(oparams->masks) / sizeof(unsigned int); i++) {
+		oparams->masks[i] = params->masks[i].bits[0];
+		for (j = 1; j < sizeof(params->masks[i].bits) / sizeof(unsigned int); j++)
+			if (params->masks[i].bits[j]) {
+				*cmask |= 1 << i;
+				break;
+			}
+	}
+	memcpy(oparams->intervals, params->intervals, sizeof(oparams->intervals));
+	oparams->rmask = __NEW_TO_OLD_MASK(params->rmask);
+	oparams->cmask = __NEW_TO_OLD_MASK(params->cmask);
+	oparams->info = params->info;
+	oparams->msbits = params->msbits;
+	oparams->rate_num = params->rate_num;
+	oparams->rate_den = params->rate_den;
+	oparams->fifo_size = params->fifo_size;
+}
+
+static int use_old_hw_params_ioctl(int fd, unsigned int cmd, snd_pcm_hw_params_t *params)
+{
+	struct sndrv_pcm_hw_params_old oparams;
+	unsigned int cmask = 0;
+	int res;
+	
+	snd_pcm_hw_convert_to_old_params(&oparams, params, &cmask);
+	res = ioctl(fd, cmd, &oparams);
+	snd_pcm_hw_convert_from_old_params(params, &oparams);
+	params->cmask |= cmask;
+	return res;
+}
diff --git a/src/pcm/pcm_iec958.c b/src/pcm/pcm_iec958.c
new file mode 100644
index 0000000..3d70ed0
--- /dev/null
+++ b/src/pcm/pcm_iec958.c
@@ -0,0 +1,679 @@
+/**
+ * \file pcm/pcm_iec958.c
+ * \ingroup PCM_Plugins
+ * \brief PCM IEC958 Subframe Conversion Plugin Interface
+ * \author Takashi Iwai <tiwai@suse.de>
+ * \date 2004
+ */
+/*
+ *  PCM - IEC958 Subframe Conversion Plugin
+ *  Copyright (c) 2004 by Takashi Iwai <tiwai@suse.de>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+#include <byteswap.h>
+#include "pcm_local.h"
+#include "pcm_plugin.h"
+
+#include "plugin_ops.h"
+
+#ifndef PIC
+/* entry for static linking */
+const char *_snd_module_pcm_iec958 = "";
+#endif
+
+/*
+ */
+
+#ifndef DOC_HIDDEN
+
+typedef struct snd_pcm_iec958 snd_pcm_iec958_t;
+
+typedef void (*iec958_f)(snd_pcm_iec958_t *iec,
+			 const snd_pcm_channel_area_t *dst_areas,
+			 snd_pcm_uframes_t dst_offset,
+			 const snd_pcm_channel_area_t *src_areas,
+			 snd_pcm_uframes_t src_offset,
+			 unsigned int channels, snd_pcm_uframes_t frames);
+
+struct snd_pcm_iec958 {
+	/* This field need to be the first */
+	snd_pcm_plugin_t plug;
+	unsigned int getput_idx;
+	iec958_f func;
+	snd_pcm_format_t sformat;
+	snd_pcm_format_t format;
+	unsigned int counter;
+	unsigned char status[24];
+	unsigned int byteswap;
+	unsigned char preamble[3];	/* B/M/W or Z/X/Y */
+};
+
+enum { PREAMBLE_Z, PREAMBLE_X, PREAMBLE_Y };
+
+#endif /* DOC_HIDDEN */
+
+/*
+ * Determine parity for time slots 4 upto 30
+ * to be sure that bit 4 upt 31 will carry
+ * an even number of ones and zeros.
+ */
+static unsigned int iec958_parity(unsigned int data)
+{
+	unsigned int parity;
+	int bit;
+
+	data >>= 4;     /* start from bit 4 */
+	parity = 0;
+	for (bit = 4; bit <= 30; bit++) {
+		if (data & 1)
+			parity++;
+		data >>= 1;
+	}
+	return (parity & 1);
+}
+
+/*
+ * Compose 32bit IEC958 subframe, two sub frames
+ * build one frame with two channels.
+ *
+ * bit 0-3  = preamble
+ *     4-7  = AUX (=0)
+ *     8-27 = data (12-27 for 16bit, 8-27 for 20bit, and 24bit without AUX)
+ *     28   = validity (0 for valid data, else 'in error')
+ *     29   = user data (0)
+ *     30   = channel status (24 bytes for 192 frames)
+ *     31   = parity
+ */
+
+static inline u_int32_t iec958_subframe(snd_pcm_iec958_t *iec, u_int32_t data, int channel)
+{
+	unsigned int byte = iec->counter >> 3;
+	unsigned int mask = 1 << (iec->counter - (byte << 3));
+
+	/* bit 4-27 */
+	data >>= 4;
+	data &= ~0xf;
+
+	/* set IEC status bits (up to 192 bits) */
+	if (iec->status[byte] & mask)
+		data |= 0x40000000;
+
+	if (iec958_parity(data))	/* parity bit 4-30 */
+		data |= 0x80000000;
+
+	/* Preamble */
+	if (channel)
+		data |= iec->preamble[PREAMBLE_Y];	/* odd sub frame, 'Y' */
+	else if (! iec->counter)
+		data |= iec->preamble[PREAMBLE_Z];	/* Block start, 'Z' */
+	else
+		data |= iec->preamble[PREAMBLE_X];	/* even sub frame, 'X' */
+
+	if (iec->byteswap)
+		data = bswap_32(data);
+
+	return data;
+}
+
+static inline int32_t iec958_to_s32(snd_pcm_iec958_t *iec, u_int32_t data)
+{
+	if (iec->byteswap)
+		data = bswap_32(data);
+	data &= ~0xf;
+	data <<= 4;
+	return (int32_t)data;
+}
+
+#ifndef DOC_HIDDEN
+static void snd_pcm_iec958_decode(snd_pcm_iec958_t *iec,
+				  const snd_pcm_channel_area_t *dst_areas,
+				  snd_pcm_uframes_t dst_offset,
+				  const snd_pcm_channel_area_t *src_areas,
+				  snd_pcm_uframes_t src_offset,
+				  unsigned int channels, snd_pcm_uframes_t frames)
+{
+#define PUT32_LABELS
+#include "plugin_ops.h"
+#undef PUT32_LABELS
+	void *put = put32_labels[iec->getput_idx];
+	unsigned int channel;
+	for (channel = 0; channel < channels; ++channel) {
+		const u_int32_t *src;
+		char *dst;
+		int src_step, dst_step;
+		snd_pcm_uframes_t frames1;
+		const snd_pcm_channel_area_t *src_area = &src_areas[channel];
+		const snd_pcm_channel_area_t *dst_area = &dst_areas[channel];
+		src = snd_pcm_channel_area_addr(src_area, src_offset);
+		dst = snd_pcm_channel_area_addr(dst_area, dst_offset);
+		src_step = snd_pcm_channel_area_step(src_area) / sizeof(u_int32_t);
+		dst_step = snd_pcm_channel_area_step(dst_area);
+		frames1 = frames;
+		while (frames1-- > 0) {
+			int32_t sample = iec958_to_s32(iec, *src);
+			goto *put;
+#define PUT32_END after
+#include "plugin_ops.h"
+#undef PUT32_END
+		after:
+			src += src_step;
+			dst += dst_step;
+		}
+	}
+}
+
+static void snd_pcm_iec958_encode(snd_pcm_iec958_t *iec,
+				  const snd_pcm_channel_area_t *dst_areas,
+				  snd_pcm_uframes_t dst_offset,
+				  const snd_pcm_channel_area_t *src_areas,
+				  snd_pcm_uframes_t src_offset,
+				  unsigned int channels, snd_pcm_uframes_t frames)
+{
+#define GET32_LABELS
+#include "plugin_ops.h"
+#undef GET32_LABELS
+	void *get = get32_labels[iec->getput_idx];
+	unsigned int channel;
+	int32_t sample = 0;
+	int counter = iec->counter;
+	for (channel = 0; channel < channels; ++channel) {
+		const char *src;
+		u_int32_t *dst;
+		int src_step, dst_step;
+		snd_pcm_uframes_t frames1;
+		const snd_pcm_channel_area_t *src_area = &src_areas[channel];
+		const snd_pcm_channel_area_t *dst_area = &dst_areas[channel];
+		src = snd_pcm_channel_area_addr(src_area, src_offset);
+		dst = snd_pcm_channel_area_addr(dst_area, dst_offset);
+		src_step = snd_pcm_channel_area_step(src_area);
+		dst_step = snd_pcm_channel_area_step(dst_area) / sizeof(u_int32_t);
+		frames1 = frames;
+		iec->counter = counter;
+		while (frames1-- > 0) {
+			goto *get;
+#define GET32_END after
+#include "plugin_ops.h"
+#undef GET32_END
+		after:
+			sample = iec958_subframe(iec, sample, channel);
+			// fprintf(stderr, "%d:%08x\n", frames1, sample);
+			*dst = sample;
+			src += src_step;
+			dst += dst_step;
+			iec->counter++;
+			iec->counter %= 192;
+		}
+	}
+}
+#endif /* DOC_HIDDEN */
+
+static int snd_pcm_iec958_hw_refine_cprepare(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	snd_pcm_iec958_t *iec = pcm->private_data;
+	int err;
+	snd_pcm_access_mask_t access_mask = { SND_PCM_ACCBIT_SHM };
+	err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_ACCESS,
+					 &access_mask);
+	if (err < 0)
+		return err;
+	if (iec->sformat == SND_PCM_FORMAT_IEC958_SUBFRAME_LE ||
+	    iec->sformat == SND_PCM_FORMAT_IEC958_SUBFRAME_BE) {
+		snd_pcm_format_mask_t format_mask = { SND_PCM_FMTBIT_LINEAR };
+		err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_FORMAT,
+						 &format_mask);
+	} else {
+		snd_pcm_format_mask_t format_mask = {
+			{ (1U << SND_PCM_FORMAT_IEC958_SUBFRAME_LE) |
+			  (1U << SND_PCM_FORMAT_IEC958_SUBFRAME_BE) }
+		};
+		err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_FORMAT,
+						 &format_mask);
+	}
+	if (err < 0)
+		return err;
+	err = _snd_pcm_hw_params_set_subformat(params, SND_PCM_SUBFORMAT_STD);
+	if (err < 0)
+		return err;
+	params->info &= ~(SND_PCM_INFO_MMAP | SND_PCM_INFO_MMAP_VALID);
+	return 0;
+}
+
+static int snd_pcm_iec958_hw_refine_sprepare(snd_pcm_t *pcm, snd_pcm_hw_params_t *sparams)
+{
+	snd_pcm_iec958_t *iec = pcm->private_data;
+	snd_pcm_access_mask_t saccess_mask = { SND_PCM_ACCBIT_MMAP };
+	_snd_pcm_hw_params_any(sparams);
+	_snd_pcm_hw_param_set_mask(sparams, SND_PCM_HW_PARAM_ACCESS,
+				   &saccess_mask);
+	_snd_pcm_hw_params_set_format(sparams, iec->sformat);
+	_snd_pcm_hw_params_set_subformat(sparams, SND_PCM_SUBFORMAT_STD);
+	return 0;
+}
+
+static int snd_pcm_iec958_hw_refine_schange(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params,
+					    snd_pcm_hw_params_t *sparams)
+{
+	int err;
+	unsigned int links = (SND_PCM_HW_PARBIT_CHANNELS |
+			      SND_PCM_HW_PARBIT_RATE |
+			      SND_PCM_HW_PARBIT_PERIOD_SIZE |
+			      SND_PCM_HW_PARBIT_BUFFER_SIZE |
+			      SND_PCM_HW_PARBIT_PERIODS |
+			      SND_PCM_HW_PARBIT_PERIOD_TIME |
+			      SND_PCM_HW_PARBIT_BUFFER_TIME |
+			      SND_PCM_HW_PARBIT_TICK_TIME);
+	err = _snd_pcm_hw_params_refine(sparams, links, params);
+	if (err < 0)
+		return err;
+	return 0;
+}
+	
+static int snd_pcm_iec958_hw_refine_cchange(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params,
+					    snd_pcm_hw_params_t *sparams)
+{
+	int err;
+	unsigned int links = (SND_PCM_HW_PARBIT_CHANNELS |
+			      SND_PCM_HW_PARBIT_RATE |
+			      SND_PCM_HW_PARBIT_PERIOD_SIZE |
+			      SND_PCM_HW_PARBIT_BUFFER_SIZE |
+			      SND_PCM_HW_PARBIT_PERIODS |
+			      SND_PCM_HW_PARBIT_PERIOD_TIME |
+			      SND_PCM_HW_PARBIT_BUFFER_TIME |
+			      SND_PCM_HW_PARBIT_TICK_TIME);
+	err = _snd_pcm_hw_params_refine(params, links, sparams);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static int snd_pcm_iec958_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	return snd_pcm_hw_refine_slave(pcm, params,
+				       snd_pcm_iec958_hw_refine_cprepare,
+				       snd_pcm_iec958_hw_refine_cchange,
+				       snd_pcm_iec958_hw_refine_sprepare,
+				       snd_pcm_iec958_hw_refine_schange,
+				       snd_pcm_generic_hw_refine);
+}
+
+static int snd_pcm_iec958_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t * params)
+{
+	snd_pcm_iec958_t *iec = pcm->private_data;
+	snd_pcm_format_t format;
+	int err = snd_pcm_hw_params_slave(pcm, params,
+					  snd_pcm_iec958_hw_refine_cchange,
+					  snd_pcm_iec958_hw_refine_sprepare,
+					  snd_pcm_iec958_hw_refine_schange,
+					  snd_pcm_generic_hw_params);
+	if (err < 0)
+		return err;
+
+	err = INTERNAL(snd_pcm_hw_params_get_format)(params, &format);
+	if (err < 0)
+		return err;
+
+	iec->format = format;
+	if (pcm->stream == SND_PCM_STREAM_PLAYBACK) {
+		if (iec->sformat == SND_PCM_FORMAT_IEC958_SUBFRAME_LE ||
+		    iec->sformat == SND_PCM_FORMAT_IEC958_SUBFRAME_BE) {
+			iec->getput_idx = snd_pcm_linear_get_index(format, SND_PCM_FORMAT_S32);
+			iec->func = snd_pcm_iec958_encode;
+			iec->byteswap = iec->sformat != SND_PCM_FORMAT_IEC958_SUBFRAME;
+		} else {
+			iec->getput_idx = snd_pcm_linear_put_index(SND_PCM_FORMAT_S32, iec->sformat);
+			iec->func = snd_pcm_iec958_decode;
+			iec->byteswap = format != SND_PCM_FORMAT_IEC958_SUBFRAME;
+		}
+	} else {
+		if (iec->sformat == SND_PCM_FORMAT_IEC958_SUBFRAME_LE ||
+		    iec->sformat == SND_PCM_FORMAT_IEC958_SUBFRAME_BE) {
+			iec->getput_idx = snd_pcm_linear_put_index(SND_PCM_FORMAT_S32, format);
+			iec->func = snd_pcm_iec958_decode;
+			iec->byteswap = iec->sformat != SND_PCM_FORMAT_IEC958_SUBFRAME;
+		} else {
+			iec->getput_idx = snd_pcm_linear_get_index(iec->sformat, SND_PCM_FORMAT_S32);
+			iec->func = snd_pcm_iec958_encode;
+			iec->byteswap = format != SND_PCM_FORMAT_IEC958_SUBFRAME;
+		}
+	}
+	/* FIXME: needs to adjust status_bits according to the format
+	 *        and sample rate
+	 */
+	return 0;
+}
+
+static snd_pcm_uframes_t
+snd_pcm_iec958_write_areas(snd_pcm_t *pcm,
+			   const snd_pcm_channel_area_t *areas,
+			   snd_pcm_uframes_t offset,
+			   snd_pcm_uframes_t size,
+			   const snd_pcm_channel_area_t *slave_areas,
+			   snd_pcm_uframes_t slave_offset,
+			   snd_pcm_uframes_t *slave_sizep)
+{
+	snd_pcm_iec958_t *iec = pcm->private_data;
+	if (size > *slave_sizep)
+		size = *slave_sizep;
+	iec->func(iec, slave_areas, slave_offset,
+		  areas, offset, 
+		  pcm->channels, size);
+	*slave_sizep = size;
+	return size;
+}
+
+static snd_pcm_uframes_t
+snd_pcm_iec958_read_areas(snd_pcm_t *pcm,
+			  const snd_pcm_channel_area_t *areas,
+			  snd_pcm_uframes_t offset,
+			  snd_pcm_uframes_t size,
+			  const snd_pcm_channel_area_t *slave_areas,
+			  snd_pcm_uframes_t slave_offset,
+			  snd_pcm_uframes_t *slave_sizep)
+{
+	snd_pcm_iec958_t *iec = pcm->private_data;
+	if (size > *slave_sizep)
+		size = *slave_sizep;
+	iec->func(iec, areas, offset, 
+		  slave_areas, slave_offset,
+		  pcm->channels, size);
+	*slave_sizep = size;
+	return size;
+}
+
+static int snd_pcm_iec958_init(snd_pcm_t *pcm)
+{
+	snd_pcm_iec958_t *iec = pcm->private_data;
+	iec->counter = 0;
+	return 0;
+}
+
+static void snd_pcm_iec958_dump(snd_pcm_t *pcm, snd_output_t *out)
+{
+	snd_pcm_iec958_t *iec = pcm->private_data;
+	snd_output_printf(out, "IEC958 subframe conversion PCM (%s)\n", 
+			  snd_pcm_format_name(iec->sformat));
+	if (pcm->setup) {
+		snd_output_printf(out, "Its setup is:\n");
+		snd_pcm_dump_setup(pcm, out);
+	}
+	snd_output_printf(out, "Slave: ");
+	snd_pcm_dump(iec->plug.gen.slave, out);
+}
+
+static const snd_pcm_ops_t snd_pcm_iec958_ops = {
+	.close = snd_pcm_generic_close,
+	.info = snd_pcm_generic_info,
+	.hw_refine = snd_pcm_iec958_hw_refine,
+	.hw_params = snd_pcm_iec958_hw_params,
+	.hw_free = snd_pcm_generic_hw_free,
+	.sw_params = snd_pcm_generic_sw_params,
+	.channel_info = snd_pcm_generic_channel_info,
+	.dump = snd_pcm_iec958_dump,
+	.nonblock = snd_pcm_generic_nonblock,
+	.async = snd_pcm_generic_async,
+	.mmap = snd_pcm_generic_mmap,
+	.munmap = snd_pcm_generic_munmap,
+};
+
+/**
+ * \brief Creates a new IEC958 subframe conversion PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param sformat Slave (destination) format
+ * \param slave Slave PCM handle
+ * \param close_slave When set, the slave PCM handle is closed with copy PCM
+ * \param status_bits The IEC958 status bits
+ * \param preamble_vals The preamble byte values
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */           
+int snd_pcm_iec958_open(snd_pcm_t **pcmp, const char *name, snd_pcm_format_t sformat,
+			snd_pcm_t *slave, int close_slave,
+			const unsigned char *status_bits,
+			const unsigned char *preamble_vals)
+{
+	snd_pcm_t *pcm;
+	snd_pcm_iec958_t *iec;
+	int err;
+	static const unsigned char default_status_bits[] = {
+		IEC958_AES0_CON_EMPHASIS_NONE,
+		IEC958_AES1_CON_ORIGINAL | IEC958_AES1_CON_PCM_CODER,
+		0,
+		IEC958_AES3_CON_FS_48000
+	};
+
+	assert(pcmp && slave);
+	if (snd_pcm_format_linear(sformat) != 1 &&
+	    sformat != SND_PCM_FORMAT_IEC958_SUBFRAME_LE &&
+	    sformat != SND_PCM_FORMAT_IEC958_SUBFRAME_BE)
+		return -EINVAL;
+	iec = calloc(1, sizeof(snd_pcm_iec958_t));
+	if (!iec) {
+		return -ENOMEM;
+	}
+	snd_pcm_plugin_init(&iec->plug);
+	iec->sformat = sformat;
+	iec->plug.read = snd_pcm_iec958_read_areas;
+	iec->plug.write = snd_pcm_iec958_write_areas;
+	iec->plug.init = snd_pcm_iec958_init;
+	iec->plug.undo_read = snd_pcm_plugin_undo_read_generic;
+	iec->plug.undo_write = snd_pcm_plugin_undo_write_generic;
+	iec->plug.gen.slave = slave;
+	iec->plug.gen.close_slave = close_slave;
+
+	if (status_bits)
+		memcpy(iec->status, status_bits, sizeof(iec->status));
+	else
+		memcpy(iec->status, default_status_bits, sizeof(default_status_bits));
+
+	memcpy(iec->preamble, preamble_vals, 3);
+
+	err = snd_pcm_new(&pcm, SND_PCM_TYPE_IEC958, name, slave->stream, slave->mode);
+	if (err < 0) {
+		free(iec);
+		return err;
+	}
+	pcm->ops = &snd_pcm_iec958_ops;
+	pcm->fast_ops = &snd_pcm_plugin_fast_ops;
+	pcm->private_data = iec;
+	pcm->poll_fd = slave->poll_fd;
+	pcm->poll_events = slave->poll_events;
+	pcm->monotonic = slave->monotonic;
+	snd_pcm_set_hw_ptr(pcm, &iec->plug.hw_ptr, -1, 0);
+	snd_pcm_set_appl_ptr(pcm, &iec->plug.appl_ptr, -1, 0);
+	*pcmp = pcm;
+
+	return 0;
+}
+
+/*! \page pcm_plugins
+
+\section pcm_plugins_iec958 Plugin: IEC958
+
+This plugin converts 32bit IEC958 subframe samples to linear, or linear to
+32bit IEC958 subframe samples.
+
+\code
+pcm.name {
+        type iec958             # IEC958 subframe conversion PCM
+        slave STR               # Slave name
+        # or
+        slave {                 # Slave definition
+                pcm STR         # Slave PCM name
+                # or
+                pcm { }         # Slave PCM definition
+        }
+	[status status-bytes]	# IEC958 status bits (given in byte array)
+	# IEC958 preamble bits definitions
+	# B/M/W or Z/X/Y, B = block start, M = even subframe, W = odd subframe
+	# As default, Z = 0x08, Y = 0x04, X = 0x02
+	[preamble.z or preamble.b val]
+	[preamble.x or preamble.m val]
+	[preamble.y or preamble.w val]
+}
+\endcode
+
+\subsection pcm_plugins_iec958_funcref Function reference
+
+<UL>
+  <LI>snd_pcm_iec958_open()
+  <LI>_snd_pcm_iec958_open()
+</UL>
+
+*/
+
+/**
+ * \brief Creates a new IEC958 subframe conversion PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param root Root configuration node
+ * \param conf Configuration node with copy PCM description
+ * \param stream Stream type
+ * \param mode Stream mode
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int _snd_pcm_iec958_open(snd_pcm_t **pcmp, const char *name,
+			 snd_config_t *root, snd_config_t *conf, 
+			 snd_pcm_stream_t stream, int mode)
+{
+	snd_config_iterator_t i, next;
+	int err;
+	snd_pcm_t *spcm;
+	snd_config_t *slave = NULL, *sconf;
+	snd_config_t *status = NULL, *preamble = NULL;
+	snd_pcm_format_t sformat;
+	unsigned char status_bits[24];
+	unsigned char preamble_vals[3] = {
+		0x08, 0x02, 0x04 /* Z, X, Y */
+	};
+
+	snd_config_for_each(i, next, conf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (snd_pcm_conf_generic_id(id))
+			continue;
+		if (strcmp(id, "slave") == 0) {
+			slave = n;
+			continue;
+		}
+		if (strcmp(id, "status") == 0) {
+			if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) {
+				SNDERR("Invalid type for %s", id);
+				return -EINVAL;
+			}
+			status = n;
+			continue;
+		}
+		if (strcmp(id, "preamble") == 0) {
+			if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) {
+				SNDERR("Invalid type for %s", id);
+				return -EINVAL;
+			}
+			preamble = n;
+			continue;
+		}
+		SNDERR("Unknown field %s", id);
+		return -EINVAL;
+	}
+	memset(status_bits, 0, sizeof(status_bits));
+	if (status) {
+		snd_config_iterator_t i, inext;
+		int bytes = 0;
+		snd_config_for_each(i, inext, status) {
+			long val;
+			snd_config_t *n = snd_config_iterator_entry(i);
+			if (snd_config_get_type(n) != SND_CONFIG_TYPE_INTEGER) {
+				SNDERR("invalid IEC958 status bits");
+				return -EINVAL;
+			}
+			err = snd_config_get_integer(n, &val);
+			if (err < 0) {
+				SNDERR("invalid IEC958 status bits");
+				return err;
+			}
+			status_bits[bytes] = val;
+			bytes++;
+			if (bytes >= (int)sizeof(status_bits))
+				break;
+		}
+		// fprintf(stderr, "STATUS bits: %02x %02x %02x %02x\n", status_bits[0], status_bits[1], status_bits[2], status_bits[3]);
+	}
+	if (preamble) {
+		snd_config_iterator_t i, inext;
+		snd_config_for_each(i, inext, preamble) {
+			long val;
+			snd_config_t *n = snd_config_iterator_entry(i);
+			const char *id;
+			int idx;
+			if (snd_config_get_id(n, &id) < 0)
+				continue;
+			if (strcmp(id, "b") == 0 || strcmp(id, "z") == 0)
+				idx = PREAMBLE_Z;
+			else if (strcmp(id, "m") == 0 || strcmp(id, "x") == 0)
+				idx = PREAMBLE_X;
+			else if (strcmp(id, "w") == 0 || strcmp(id, "y") == 0)
+				idx = PREAMBLE_Y;
+			else {
+				SNDERR("invalid IEC958 preamble type %s", id);
+				return -EINVAL;
+			}
+			err = snd_config_get_integer(n, &val);
+			if (err < 0) {
+				SNDERR("invalid IEC958 preamble value");
+				return err;
+			}
+			preamble_vals[idx] = val;
+		}
+	}
+	if (!slave) {
+		SNDERR("slave is not defined");
+		return -EINVAL;
+	}
+	err = snd_pcm_slave_conf(root, slave, &sconf, 1,
+				 SND_PCM_HW_PARAM_FORMAT, SCONF_MANDATORY, &sformat);
+	if (err < 0)
+		return err;
+	if (snd_pcm_format_linear(sformat) != 1 &&
+	    sformat != SND_PCM_FORMAT_IEC958_SUBFRAME_LE &&
+	    sformat != SND_PCM_FORMAT_IEC958_SUBFRAME_BE) {
+	    	snd_config_delete(sconf);
+		SNDERR("invalid slave format");
+		return -EINVAL;
+	}
+	err = snd_pcm_open_slave(&spcm, root, sconf, stream, mode, conf);
+	snd_config_delete(sconf);
+	if (err < 0)
+		return err;
+	err = snd_pcm_iec958_open(pcmp, name, sformat, spcm, 1,
+				  status ? status_bits : NULL,
+				  preamble_vals);
+	if (err < 0)
+		snd_pcm_close(spcm);
+	return err;
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(_snd_pcm_iec958_open, SND_PCM_DLSYM_VERSION);
+#endif
diff --git a/src/pcm/pcm_ioplug.c b/src/pcm/pcm_ioplug.c
new file mode 100644
index 0000000..2aa7572
--- /dev/null
+++ b/src/pcm/pcm_ioplug.c
@@ -0,0 +1,1072 @@
+/**
+ * \file pcm/pcm_ioplug.c
+ * \ingroup Plugin_SDK
+ * \brief I/O Plugin SDK
+ * \author Takashi Iwai <tiwai@suse.de>
+ * \date 2005
+ */
+/*
+ *  PCM - External I/O Plugin SDK
+ *  Copyright (c) 2005 by Takashi Iwai <tiwai@suse.de>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+#include "pcm_local.h"
+#include "pcm_ioplug.h"
+#include "pcm_ext_parm.h"
+#include "pcm_generic.h"
+
+#ifndef PIC
+/* entry for static linking */
+const char *_snd_module_pcm_ioplug = "";
+#endif
+
+#ifndef DOC_HIDDEN
+
+/* hw_params */
+typedef struct snd_pcm_ioplug_priv {
+	snd_pcm_ioplug_t *data;
+	struct snd_ext_parm params[SND_PCM_IOPLUG_HW_PARAMS];
+	unsigned int last_hw;
+	snd_pcm_uframes_t avail_max;
+	snd_htimestamp_t trigger_tstamp;
+} ioplug_priv_t;
+
+/* update the hw pointer */
+static void snd_pcm_ioplug_hw_ptr_update(snd_pcm_t *pcm)
+{
+	ioplug_priv_t *io = pcm->private_data;
+	snd_pcm_sframes_t hw;
+
+	hw = io->data->callback->pointer(io->data);
+	if (hw >= 0) {
+		unsigned int delta;
+		if ((unsigned int)hw >= io->last_hw)
+			delta = hw - io->last_hw;
+		else
+			delta = pcm->buffer_size + hw - io->last_hw;
+		io->data->hw_ptr += delta;
+		io->last_hw = hw;
+	} else
+		io->data->state = SNDRV_PCM_STATE_XRUN;
+}
+
+static int snd_pcm_ioplug_info(snd_pcm_t *pcm, snd_pcm_info_t *info)
+{
+	memset(info, 0, sizeof(*info));
+	info->stream = pcm->stream;
+	info->card = -1;
+	if (pcm->name) {
+		strncpy((char *)info->id, pcm->name, sizeof(info->id));
+		strncpy((char *)info->name, pcm->name, sizeof(info->name));
+		strncpy((char *)info->subname, pcm->name, sizeof(info->subname));
+	}
+	info->subdevices_count = 1;
+	return 0;
+}
+
+static int snd_pcm_ioplug_channel_info(snd_pcm_t *pcm, snd_pcm_channel_info_t *info)
+{
+	return snd_pcm_channel_info_shm(pcm, info, -1);
+}
+
+static int snd_pcm_ioplug_status(snd_pcm_t *pcm, snd_pcm_status_t * status)
+{
+	ioplug_priv_t *io = pcm->private_data;
+
+	memset(status, 0, sizeof(*status));
+	snd_pcm_ioplug_hw_ptr_update(pcm);
+	status->state = io->data->state;
+	status->trigger_tstamp = io->trigger_tstamp;
+	status->avail = snd_pcm_mmap_avail(pcm);
+	status->avail_max = io->avail_max;
+	return 0;
+}
+
+static snd_pcm_state_t snd_pcm_ioplug_state(snd_pcm_t *pcm)
+{
+	ioplug_priv_t *io = pcm->private_data;
+	return io->data->state;
+}
+
+static int snd_pcm_ioplug_hwsync(snd_pcm_t *pcm)
+{
+	snd_pcm_ioplug_hw_ptr_update(pcm);
+	return 0;
+}
+
+static int snd_pcm_ioplug_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp)
+{
+	ioplug_priv_t *io = pcm->private_data;
+
+	if (io->data->version >= 0x010001 &&
+	    io->data->callback->delay)
+		return io->data->callback->delay(io->data, delayp);
+	else {
+		snd_pcm_ioplug_hw_ptr_update(pcm);
+		*delayp = snd_pcm_mmap_hw_avail(pcm);
+	}
+	return 0;
+}
+
+static int snd_pcm_ioplug_reset(snd_pcm_t *pcm)
+{
+	ioplug_priv_t *io = pcm->private_data;
+
+	io->data->appl_ptr = 0;
+	io->data->hw_ptr = 0;
+	io->last_hw = 0;
+	io->avail_max = 0;
+	return 0;
+}
+
+static int snd_pcm_ioplug_prepare(snd_pcm_t *pcm)
+{
+	ioplug_priv_t *io = pcm->private_data;
+
+	io->data->state = SND_PCM_STATE_PREPARED;
+	snd_pcm_ioplug_reset(pcm);
+	if (io->data->callback->prepare)
+		return io->data->callback->prepare(io->data);
+	return 0;
+}
+
+static const int hw_params_type[SND_PCM_IOPLUG_HW_PARAMS] = {
+	[SND_PCM_IOPLUG_HW_ACCESS] = SND_PCM_HW_PARAM_ACCESS,
+	[SND_PCM_IOPLUG_HW_FORMAT] = SND_PCM_HW_PARAM_FORMAT,
+	[SND_PCM_IOPLUG_HW_CHANNELS] = SND_PCM_HW_PARAM_CHANNELS,
+	[SND_PCM_IOPLUG_HW_RATE] = SND_PCM_HW_PARAM_RATE,
+	[SND_PCM_IOPLUG_HW_PERIOD_BYTES] = SND_PCM_HW_PARAM_PERIOD_BYTES,
+	[SND_PCM_IOPLUG_HW_BUFFER_BYTES] = SND_PCM_HW_PARAM_BUFFER_BYTES,
+	[SND_PCM_IOPLUG_HW_PERIODS] = SND_PCM_HW_PARAM_PERIODS,
+};
+
+/* x = a * b */
+static int rule_mul(snd_pcm_hw_params_t *params, int x, int a, int b)
+{
+	snd_interval_t t;
+
+	snd_interval_mul(hw_param_interval(params, a),
+			 hw_param_interval(params, b), &t);
+	return snd_interval_refine(hw_param_interval(params, x), &t);
+}
+
+/* x = a / b */
+static int rule_div(snd_pcm_hw_params_t *params, int x, int a, int b)
+{
+	snd_interval_t t;
+
+	snd_interval_div(hw_param_interval(params, a),
+			 hw_param_interval(params, b), &t);
+	return snd_interval_refine(hw_param_interval(params, x), &t);
+}
+
+/* x = a * b / k */
+static int rule_muldivk(snd_pcm_hw_params_t *params, int x, int a, int b, int k)
+{
+	snd_interval_t t;
+
+	snd_interval_muldivk(hw_param_interval(params, a),
+			     hw_param_interval(params, b), k, &t);
+	return snd_interval_refine(hw_param_interval(params, x), &t);
+}
+
+/* x = a * k / b */
+static int rule_mulkdiv(snd_pcm_hw_params_t *params, int x, int a, int k, int b)
+{
+	snd_interval_t t;
+
+	snd_interval_mulkdiv(hw_param_interval(params, a), k,
+			     hw_param_interval(params, b), &t);
+	return snd_interval_refine(hw_param_interval(params, x), &t);
+}
+
+#if 0
+static void dump_parm(snd_pcm_hw_params_t *params)
+{
+	snd_output_t *log;
+	snd_output_stdio_attach(&log, stderr, 0);
+	snd_pcm_hw_params_dump(params, log);
+	snd_output_close(log);
+}
+#endif
+
+/* refine *_TIME and *_SIZE, then update *_BYTES */
+static int refine_time_and_size(snd_pcm_hw_params_t *params,
+				int time, int size, int bytes)
+{
+	int err, change1 = 0;
+
+	/* size = time * rate / 1000000 */
+	err = rule_muldivk(params, size, time,
+			   SND_PCM_HW_PARAM_RATE, 1000000);
+	if (err < 0)
+		return err;
+	change1 |= err;
+
+	/* bytes = size * framebits / 8 */
+	err = rule_muldivk(params, bytes, size,
+			   SND_PCM_HW_PARAM_FRAME_BITS, 8);
+	if (err < 0)
+		return err;
+	change1 |= err;
+	return change1;
+}
+
+/* refine *_TIME and *_SIZE from *_BYTES */
+static int refine_back_time_and_size(snd_pcm_hw_params_t *params,
+				     int time, int size, int bytes)
+{
+	int err;
+
+	/* size = bytes * 8 / framebits */
+	err = rule_mulkdiv(params, size, bytes, 8, SND_PCM_HW_PARAM_FRAME_BITS);
+	if (err < 0)
+		return err;
+	/* time = size * 1000000 / rate */
+	err = rule_mulkdiv(params, time, size, 1000000, SND_PCM_HW_PARAM_RATE);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+
+static int snd_pcm_ioplug_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	int change = 0, change1, change2, err;
+	ioplug_priv_t *io = pcm->private_data;
+	struct snd_ext_parm *p;
+	unsigned int i;
+
+	/* access, format */
+	for (i = SND_PCM_IOPLUG_HW_ACCESS; i <= SND_PCM_IOPLUG_HW_FORMAT; i++) {
+		err = snd_ext_parm_mask_refine(hw_param_mask(params, hw_params_type[i]),
+					       io->params, i);
+		if (err < 0)
+			return err;
+		change |= err;
+	}
+	/* channels, rate */
+	for (; i <= SND_PCM_IOPLUG_HW_RATE; i++) {
+		err = snd_ext_parm_interval_refine(hw_param_interval(params, hw_params_type[i]),
+						   io->params, i);
+		if (err < 0)
+			return err;
+		change |= err;
+	}
+
+	if (params->rmask & ((1 << SND_PCM_HW_PARAM_ACCESS) |
+			     (1 << SND_PCM_HW_PARAM_FORMAT) |
+			     (1 << SND_PCM_HW_PARAM_SUBFORMAT) |
+			     (1 << SND_PCM_HW_PARAM_CHANNELS) |
+			     (1 << SND_PCM_HW_PARAM_RATE))) {
+		err = snd_pcm_hw_refine_soft(pcm, params);
+		if (err < 0)
+			return err;
+		change |= err;
+	}
+
+	change1 = refine_time_and_size(params, SND_PCM_HW_PARAM_PERIOD_TIME,
+				       SND_PCM_HW_PARAM_PERIOD_SIZE,
+				       SND_PCM_HW_PARAM_PERIOD_BYTES);
+	if (change1 < 0)
+		return change1;
+	err = snd_ext_parm_interval_refine(hw_param_interval(params, SND_PCM_HW_PARAM_PERIOD_BYTES),
+					   io->params, SND_PCM_IOPLUG_HW_PERIOD_BYTES);
+	if (err < 0)
+		return err;
+	change1 |= err;
+	if (change1) {
+		change |= change1;
+		err = refine_back_time_and_size(params, SND_PCM_HW_PARAM_PERIOD_TIME,
+						SND_PCM_HW_PARAM_PERIOD_SIZE,
+						SND_PCM_HW_PARAM_PERIOD_BYTES);
+		if (err < 0)
+			return err;
+	}
+
+	change1 = refine_time_and_size(params, SND_PCM_HW_PARAM_BUFFER_TIME,
+				       SND_PCM_HW_PARAM_BUFFER_SIZE,
+				       SND_PCM_HW_PARAM_BUFFER_BYTES);
+	if (change1 < 0)
+		return change1;
+	change |= change1;
+
+	do {
+		change2 = 0;
+		err = snd_ext_parm_interval_refine(hw_param_interval(params, SND_PCM_HW_PARAM_BUFFER_BYTES),
+						   io->params, SND_PCM_IOPLUG_HW_BUFFER_BYTES);
+		if (err < 0)
+			return err;
+		change2 |= err;
+		/* periods = buffer_bytes / period_bytes */
+		err = rule_div(params, SND_PCM_HW_PARAM_PERIODS,
+			       SND_PCM_HW_PARAM_BUFFER_BYTES,
+			       SND_PCM_HW_PARAM_PERIOD_BYTES);
+		if (err < 0)
+			return err;
+		change2 |= err;
+		err = snd_ext_parm_interval_refine(hw_param_interval(params, SND_PCM_HW_PARAM_PERIODS),
+						   io->params, SND_PCM_IOPLUG_HW_PERIODS);
+		if (err < 0)
+			return err;
+		change2 |= err;
+		/* buffer_bytes = periods * period_bytes */
+		err = rule_mul(params, SND_PCM_HW_PARAM_BUFFER_BYTES,
+			       SND_PCM_HW_PARAM_PERIOD_BYTES,
+			       SND_PCM_HW_PARAM_PERIODS);
+		if (err < 0)
+			return err;
+		change2 |= err;
+		change1 |= change2;
+	} while (change2);
+	change |= change1;
+
+	if (change1) {
+		err = refine_back_time_and_size(params, SND_PCM_HW_PARAM_BUFFER_TIME,
+						SND_PCM_HW_PARAM_BUFFER_SIZE,
+						SND_PCM_HW_PARAM_BUFFER_BYTES);
+		if (err < 0)
+			return err;
+	}
+
+	/* period_bytes = buffer_bytes / periods */
+	err = rule_div(params, SND_PCM_HW_PARAM_PERIOD_BYTES,
+		       SND_PCM_HW_PARAM_BUFFER_BYTES,
+		       SND_PCM_HW_PARAM_PERIODS);
+	if (err < 0)
+		return err;
+	if (err) {
+		/* update period_size and period_time */
+		change |= err;
+		err = snd_ext_parm_interval_refine(hw_param_interval(params, SND_PCM_HW_PARAM_PERIOD_BYTES),
+						   io->params, SND_PCM_IOPLUG_HW_PERIOD_BYTES);
+		if (err < 0)
+			return err;
+		err = refine_back_time_and_size(params, SND_PCM_HW_PARAM_PERIOD_TIME,
+						SND_PCM_HW_PARAM_PERIOD_SIZE,
+						SND_PCM_HW_PARAM_PERIOD_BYTES);
+		if (err < 0)
+			return err;
+	}
+
+	params->info = SND_PCM_INFO_BLOCK_TRANSFER;
+	p = &io->params[SND_PCM_IOPLUG_HW_ACCESS];
+	if (p->active) {
+		for (i = 0; i < p->num_list; i++)
+			switch (p->list[i]) {
+			case SND_PCM_ACCESS_MMAP_INTERLEAVED:
+			case SND_PCM_ACCESS_RW_INTERLEAVED:
+				params->info |= SND_PCM_INFO_INTERLEAVED;
+				break;
+			case SND_PCM_ACCESS_MMAP_NONINTERLEAVED:
+			case SND_PCM_ACCESS_RW_NONINTERLEAVED:
+				params->info |= SND_PCM_INFO_NONINTERLEAVED;
+				break;
+			}
+	}
+	if (io->data->callback->pause)
+		params->info |= SND_PCM_INFO_PAUSE;
+	if (io->data->callback->resume)
+		params->info |= SND_PCM_INFO_RESUME;
+
+#if 0
+	fprintf(stderr, "XXX\n");
+	dump_parm(params);
+#endif
+	return change;
+}
+
+static int snd_pcm_ioplug_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	ioplug_priv_t *io = pcm->private_data;
+	int err;
+
+	INTERNAL(snd_pcm_hw_params_get_access)(params, &io->data->access);
+	INTERNAL(snd_pcm_hw_params_get_format)(params, &io->data->format);
+	INTERNAL(snd_pcm_hw_params_get_channels)(params, &io->data->channels);
+	INTERNAL(snd_pcm_hw_params_get_rate)(params, &io->data->rate, 0);
+	INTERNAL(snd_pcm_hw_params_get_period_size)(params, &io->data->period_size, 0);
+	INTERNAL(snd_pcm_hw_params_get_buffer_size)(params, &io->data->buffer_size);
+	if (io->data->callback->hw_params) {
+		err = io->data->callback->hw_params(io->data, params);
+		if (err < 0)
+			return err;
+		INTERNAL(snd_pcm_hw_params_get_access)(params, &io->data->access);
+		INTERNAL(snd_pcm_hw_params_get_format)(params, &io->data->format);
+		INTERNAL(snd_pcm_hw_params_get_channels)(params, &io->data->channels);
+		INTERNAL(snd_pcm_hw_params_get_rate)(params, &io->data->rate, 0);
+		INTERNAL(snd_pcm_hw_params_get_period_size)(params, &io->data->period_size, 0);
+		INTERNAL(snd_pcm_hw_params_get_buffer_size)(params, &io->data->buffer_size);
+	}
+	return 0;
+}
+
+static int snd_pcm_ioplug_hw_free(snd_pcm_t *pcm)
+{
+	ioplug_priv_t *io = pcm->private_data;
+
+	if (io->data->callback->hw_free)
+		return io->data->callback->hw_free(io->data);
+	return 0;
+}
+
+static int snd_pcm_ioplug_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t *params)
+{
+	ioplug_priv_t *io = pcm->private_data;
+
+	if (io->data->callback->sw_params)
+		return io->data->callback->sw_params(io->data, params);
+	return 0;
+}
+
+
+static int snd_pcm_ioplug_start(snd_pcm_t *pcm)
+{
+	ioplug_priv_t *io = pcm->private_data;
+	int err;
+	
+	if (io->data->state != SND_PCM_STATE_PREPARED)
+		return -EBADFD;
+
+	err = io->data->callback->start(io->data);
+	if (err < 0)
+		return err;
+
+	gettimestamp(&io->trigger_tstamp, pcm->monotonic);
+	io->data->state = SND_PCM_STATE_RUNNING;
+
+	return 0;
+}
+
+static int snd_pcm_ioplug_drop(snd_pcm_t *pcm)
+{
+	ioplug_priv_t *io = pcm->private_data;
+
+	if (io->data->state == SND_PCM_STATE_OPEN)
+		return -EBADFD;
+
+	io->data->callback->stop(io->data);
+
+	gettimestamp(&io->trigger_tstamp, pcm->monotonic);
+	io->data->state = SND_PCM_STATE_SETUP;
+
+	return 0;
+}
+
+static int snd_pcm_ioplug_drain(snd_pcm_t *pcm)
+{
+	ioplug_priv_t *io = pcm->private_data;
+
+	if (io->data->state == SND_PCM_STATE_OPEN)
+		return -EBADFD;
+	if (io->data->callback->drain)
+		io->data->callback->drain(io->data);
+	return snd_pcm_ioplug_drop(pcm);
+}
+
+static int snd_pcm_ioplug_pause(snd_pcm_t *pcm, int enable)
+{
+	ioplug_priv_t *io = pcm->private_data;
+	static const snd_pcm_state_t states[2] = {
+		SND_PCM_STATE_RUNNING, SND_PCM_STATE_PAUSED
+	};
+	int prev, err;
+
+	prev = !enable;
+	enable = !prev;
+	if (io->data->state != states[prev])
+		return -EBADFD;
+	if (io->data->callback->pause) {
+		err = io->data->callback->pause(io->data, enable);
+		if (err < 0)
+			return err;
+	}
+	io->data->state = states[enable];
+	return 0;
+}
+
+static snd_pcm_sframes_t snd_pcm_ioplug_rewindable(snd_pcm_t *pcm)
+{
+	return snd_pcm_mmap_hw_avail(pcm);
+}
+
+static snd_pcm_sframes_t snd_pcm_ioplug_rewind(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
+{
+	snd_pcm_mmap_appl_backward(pcm, frames);
+	return frames;
+}
+
+static snd_pcm_sframes_t snd_pcm_ioplug_forwardable(snd_pcm_t *pcm)
+{
+	return snd_pcm_mmap_avail(pcm);
+}
+
+static snd_pcm_sframes_t snd_pcm_ioplug_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
+{
+	snd_pcm_mmap_appl_forward(pcm, frames);
+	return frames;
+}
+
+static int snd_pcm_ioplug_resume(snd_pcm_t *pcm)
+{
+	ioplug_priv_t *io = pcm->private_data;
+
+	if (io->data->callback->resume)
+		io->data->callback->resume(io->data);
+	return 0;
+}
+
+static snd_pcm_sframes_t ioplug_priv_transfer_areas(snd_pcm_t *pcm,
+						       const snd_pcm_channel_area_t *areas,
+						       snd_pcm_uframes_t offset,
+						       snd_pcm_uframes_t size)
+{
+	ioplug_priv_t *io = pcm->private_data;
+	snd_pcm_sframes_t result;
+		
+	if (! size)
+		return 0;
+	if (io->data->callback->transfer)
+		result = io->data->callback->transfer(io->data, areas, offset, size);
+	else
+		result = size;
+	if (result > 0)
+		snd_pcm_mmap_appl_forward(pcm, result);
+	return result;
+}
+
+static snd_pcm_sframes_t snd_pcm_ioplug_writei(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size)
+{
+	if (pcm->mmap_rw)
+		return snd_pcm_mmap_writei(pcm, buffer, size);
+	else {
+		snd_pcm_channel_area_t areas[pcm->channels];
+		snd_pcm_areas_from_buf(pcm, areas, (void*)buffer);
+		return snd_pcm_write_areas(pcm, areas, 0, size, 
+					   ioplug_priv_transfer_areas);
+	}
+}
+
+static snd_pcm_sframes_t snd_pcm_ioplug_writen(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size)
+{
+	if (pcm->mmap_rw)
+		return snd_pcm_mmap_writen(pcm, bufs, size);
+	else {
+		snd_pcm_channel_area_t areas[pcm->channels];
+		snd_pcm_areas_from_bufs(pcm, areas, bufs);
+		return snd_pcm_write_areas(pcm, areas, 0, size,
+					   ioplug_priv_transfer_areas);
+	}
+}
+
+static snd_pcm_sframes_t snd_pcm_ioplug_readi(snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t size)
+{
+	if (pcm->mmap_rw)
+		return snd_pcm_mmap_readi(pcm, buffer, size);
+	else {
+		snd_pcm_channel_area_t areas[pcm->channels];
+		snd_pcm_areas_from_buf(pcm, areas, buffer);
+		return snd_pcm_read_areas(pcm, areas, 0, size,
+					  ioplug_priv_transfer_areas);
+	}
+}
+
+static snd_pcm_sframes_t snd_pcm_ioplug_readn(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size)
+{
+	if (pcm->mmap_rw)
+		return snd_pcm_mmap_readn(pcm, bufs, size);
+	else {
+		snd_pcm_channel_area_t areas[pcm->channels];
+		snd_pcm_areas_from_bufs(pcm, areas, bufs);
+		return snd_pcm_read_areas(pcm, areas, 0, size,
+					  ioplug_priv_transfer_areas);
+	}
+}
+
+static snd_pcm_sframes_t snd_pcm_ioplug_mmap_commit(snd_pcm_t *pcm,
+						    snd_pcm_uframes_t offset,
+						    snd_pcm_uframes_t size)
+{
+	if (pcm->stream == SND_PCM_STREAM_PLAYBACK &&
+	    pcm->access != SND_PCM_ACCESS_RW_INTERLEAVED &&
+	    pcm->access != SND_PCM_ACCESS_RW_NONINTERLEAVED) {
+		const snd_pcm_channel_area_t *areas;
+		snd_pcm_uframes_t ofs, frames = size;
+
+		snd_pcm_mmap_begin(pcm, &areas, &ofs, &frames);
+		if (ofs != offset)
+			return -EIO;
+		return ioplug_priv_transfer_areas(pcm, areas, offset, frames);
+	}
+
+	snd_pcm_mmap_appl_forward(pcm, size);
+	return size;
+}
+
+static snd_pcm_sframes_t snd_pcm_ioplug_avail_update(snd_pcm_t *pcm)
+{
+	ioplug_priv_t *io = pcm->private_data;
+	snd_pcm_uframes_t avail;
+
+	snd_pcm_ioplug_hw_ptr_update(pcm);
+	if (io->data->state == SNDRV_PCM_STATE_XRUN)
+		return -EPIPE;
+	if (pcm->stream == SND_PCM_STREAM_CAPTURE &&
+	    pcm->access != SND_PCM_ACCESS_RW_INTERLEAVED &&
+	    pcm->access != SND_PCM_ACCESS_RW_NONINTERLEAVED) {
+		if (io->data->callback->transfer) {
+			const snd_pcm_channel_area_t *areas;
+			snd_pcm_uframes_t offset, size = UINT_MAX;
+			snd_pcm_sframes_t result;
+
+			snd_pcm_mmap_begin(pcm, &areas, &offset, &size);
+			result = io->data->callback->transfer(io->data, areas, offset, size);
+			if (result < 0)
+				return result;
+		}
+	}
+	avail = snd_pcm_mmap_avail(pcm);
+	if (avail > io->avail_max)
+		io->avail_max = avail;
+	return (snd_pcm_sframes_t)avail;
+}
+
+static int snd_pcm_ioplug_nonblock(snd_pcm_t *pcm, int nonblock)
+{
+	ioplug_priv_t *io = pcm->private_data;
+
+	io->data->nonblock = nonblock;
+	return 0;
+}
+
+static int snd_pcm_ioplug_poll_descriptors_count(snd_pcm_t *pcm)
+{
+	ioplug_priv_t *io = pcm->private_data;
+
+	if (io->data->callback->poll_descriptors_count)
+		return io->data->callback->poll_descriptors_count(io->data);
+	else
+		return 1;
+}
+
+static int snd_pcm_ioplug_poll_descriptors(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int space)
+{
+	ioplug_priv_t *io = pcm->private_data;
+
+	if (io->data->callback->poll_descriptors)
+		return io->data->callback->poll_descriptors(io->data, pfds, space);
+	if (pcm->poll_fd < 0)
+		return -EIO;
+	if (space >= 1 && pfds) {
+		pfds->fd = pcm->poll_fd;
+		pfds->events = pcm->poll_events | POLLERR | POLLNVAL;
+	} else {
+		return 0;
+	}
+	return 1;
+}
+
+static int snd_pcm_ioplug_poll_revents(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int nfds, unsigned short *revents)
+{
+	ioplug_priv_t *io = pcm->private_data;
+
+	if (io->data->callback->poll_revents)
+		return io->data->callback->poll_revents(io->data, pfds, nfds, revents);
+	else
+		*revents = pfds->revents;
+	return 0;
+}
+
+static int snd_pcm_ioplug_mmap(snd_pcm_t *pcm ATTRIBUTE_UNUSED)
+{
+	return 0;
+}
+
+static int snd_pcm_ioplug_async(snd_pcm_t *pcm ATTRIBUTE_UNUSED,
+				int sig ATTRIBUTE_UNUSED,
+				pid_t pid ATTRIBUTE_UNUSED)
+{
+	return -ENOSYS;
+}
+
+static int snd_pcm_ioplug_munmap(snd_pcm_t *pcm ATTRIBUTE_UNUSED)
+{
+	return 0;
+}
+
+static void snd_pcm_ioplug_dump(snd_pcm_t *pcm, snd_output_t *out)
+{
+	ioplug_priv_t *io = pcm->private_data;
+
+	if (io->data->callback->dump)
+		io->data->callback->dump(io->data, out);
+	else {
+		if (io->data->name)
+			snd_output_printf(out, "%s\n", io->data->name);
+		else
+			snd_output_printf(out, "IO-PCM Plugin\n");
+		if (pcm->setup) {
+			snd_output_printf(out, "Its setup is:\n");
+			snd_pcm_dump_setup(pcm, out);
+		}
+	}
+}
+
+static void clear_io_params(ioplug_priv_t *io)
+{
+	int i;
+	for (i = 0; i < SND_PCM_IOPLUG_HW_PARAMS; i++)
+		snd_ext_parm_clear(&io->params[i]);
+}
+
+static int snd_pcm_ioplug_close(snd_pcm_t *pcm)
+{
+	ioplug_priv_t *io = pcm->private_data;
+
+	clear_io_params(io);
+	if (io->data->callback->close)
+		io->data->callback->close(io->data);
+	free(io);
+
+	return 0;
+}
+
+static const snd_pcm_ops_t snd_pcm_ioplug_ops = {
+	.close = snd_pcm_ioplug_close,
+	.nonblock = snd_pcm_ioplug_nonblock,
+	.async = snd_pcm_ioplug_async,
+	.info = snd_pcm_ioplug_info,
+	.hw_refine = snd_pcm_ioplug_hw_refine,
+	.hw_params = snd_pcm_ioplug_hw_params,
+	.hw_free = snd_pcm_ioplug_hw_free,
+	.sw_params = snd_pcm_ioplug_sw_params,
+	.channel_info = snd_pcm_ioplug_channel_info,
+	.dump = snd_pcm_ioplug_dump,
+	.mmap = snd_pcm_ioplug_mmap,
+	.munmap = snd_pcm_ioplug_munmap,
+};
+
+static const snd_pcm_fast_ops_t snd_pcm_ioplug_fast_ops = {
+	.status = snd_pcm_ioplug_status,
+	.prepare = snd_pcm_ioplug_prepare,
+	.reset = snd_pcm_ioplug_reset,
+	.start = snd_pcm_ioplug_start,
+	.drop = snd_pcm_ioplug_drop,
+	.drain = snd_pcm_ioplug_drain,
+	.pause = snd_pcm_ioplug_pause,
+	.state = snd_pcm_ioplug_state,
+	.hwsync = snd_pcm_ioplug_hwsync,
+	.delay = snd_pcm_ioplug_delay,
+	.resume = snd_pcm_ioplug_resume,
+	.link = NULL,
+	.link_slaves = NULL,
+	.unlink = NULL,
+	.rewindable = snd_pcm_ioplug_rewindable,
+	.rewind = snd_pcm_ioplug_rewind,
+	.forwardable = snd_pcm_ioplug_forwardable,
+	.forward = snd_pcm_ioplug_forward,
+	.writei = snd_pcm_ioplug_writei,
+	.writen = snd_pcm_ioplug_writen,
+	.readi = snd_pcm_ioplug_readi,
+	.readn = snd_pcm_ioplug_readn,
+	.avail_update = snd_pcm_ioplug_avail_update,
+	.mmap_commit = snd_pcm_ioplug_mmap_commit,
+	.htimestamp = snd_pcm_generic_real_htimestamp,
+	.poll_descriptors_count = snd_pcm_ioplug_poll_descriptors_count,
+	.poll_descriptors = snd_pcm_ioplug_poll_descriptors,
+	.poll_revents = snd_pcm_ioplug_poll_revents,
+};
+
+#endif /* !DOC_HIDDEN */
+
+/*
+ * Exported functions
+ */
+
+/*! \page pcm_external_plugins PCM External Plugin SDK
+
+\section pcm_ioplug External Plugin: I/O Plugin
+
+The I/O-type plugin is a PCM plugin to work as the input or output terminal point,
+i.e. as a user-space PCM driver.
+
+The new plugin is created via #snd_pcm_ioplug_create() function.
+The first argument is a pointer of the pluging information.  Some of
+this struct must be initialized in prior to call
+#snd_pcm_ioplug_create().  Then the function fills other fields in
+return.  The rest arguments, name, stream and mode, are usually
+identical with the values passed from the ALSA plugin constructor.
+
+The following fields are mandatory: version, name, callback.
+Otherfields are optional and should be initialized with zero.
+
+The constant #SND_PCM_IOPLUG_VERSION must be passed to the version
+field for the version check in alsa-lib.  A non-NULL ASCII string
+has to be passed to the name field.  The callback field contains the 
+table of callback functions for this plugin (defined as
+#snd_pcm_ioplug_callback_t).
+
+flags field specifies the optional bit-flags.  poll_fd and poll_events
+specify the poll file descriptor and the corresponding poll events
+(POLLIN, POLLOUT) for the plugin.  If the plugin requires multiple
+poll descriptors or poll descriptor(s) dynamically varying, set
+poll_descriptors and poll_descriptors_count callbacks to the callback
+table.  Then the poll_fd and poll_events field are ignored.
+
+mmap_rw specifies whether the plugin behaves in the pseudo mmap mode.
+When this value is set to 1, the plugin creates always a local buffer
+and performs read/write calls using this buffer as if it's mmapped.
+The address of local buffer can be obtained via
+#snd_pcm_ioplug_mmap_areas() function.
+When poll_fd, poll_events and mmap_rw fields are changed after
+#snd_pcm_ioplug_create(), call #snd_pcm_ioplug_reinit_status() to
+reflect the changes.
+
+The driver can set an arbitrary value (pointer) to private_data
+field to refer its own data in the callbacks.
+
+The rest fields are filled by #snd_pcm_ioplug_create().  The pcm field
+is the resultant PCM handle.  The others are the current status of the
+PCM.
+
+The callback functions in #snd_pcm_ioplug_callback_t define the real
+behavior of the driver.
+At least, start, stop and pointer callbacks must be given.  Other
+callbacks are optional.  The start and stop callbacks are called when
+the PCM stream is started and stopped, repsectively.  The pointer
+callback returns the current DMA position, which may be called at any
+time.
+
+The transfer callback is called when any data transfer happens.  It
+receives the area array, offset and the size to transfer.  The area
+array contains the array of snd_pcm_channel_area_t with the elements
+of number of channels.
+
+When the PCM is closed, close callback is called.  If the driver
+allocates any internal buffers, they should be released in this
+callback.  The hw_params and hw_free callbacks are called when
+hw_params are set and reset, respectively.  Note that they may be
+called multiple times according to the application.  Similarly,
+sw_params callback is called when sw_params is set or changed.
+
+The prepare, drain, pause and resume callbacks are called when
+#snd_pcm_prepare(), #snd_pcm_drain(), #snd_pcm_pause(), and
+#snd_pcm_resume() are called.  The poll_descriptors_count and
+poll_descriptors callbacks are used to return the multiple or dynamic
+poll descriptors as mentioned above.  The poll_revents callback is
+used to modify poll events.  If the driver needs to mangle the native
+poll events to proper poll events for PCM, you can do it in this
+callback.
+
+Finally, the dump callback is used to print the status of the plugin.
+
+The hw_params constraints can be defined via either
+#snd_pcm_ioplug_set_param_minmax() and #snd_pcm_ioplug_set_param_list()
+functions after calling #snd_pcm_ioplug_create().
+The former defines the minimal and maximal acceptable values for the
+given hw_params parameter (SND_PCM_IOPLUG_HW_XXX).
+This function can't be used for the format parameter.  The latter
+function specifies the available parameter values as the list.
+
+To clear the parameter constraints, call #snd_pcm_ioplug_params_reset() function.
+
+*/
+
+/**
+ * \brief Create an ioplug instance
+ * \param ioplug the ioplug handle
+ * \param name name of PCM
+ * \param stream stream direction
+ * \param mode PCM open mode
+ * \return 0 if successful, or a negative error code
+ *
+ * Creates the ioplug instance.
+ *
+ * The callback is the mandatory field of ioplug handle.  At least, start, stop and
+ * pointer callbacks must be set before calling this function.
+ *
+ */
+int snd_pcm_ioplug_create(snd_pcm_ioplug_t *ioplug, const char *name,
+			  snd_pcm_stream_t stream, int mode)
+{
+	ioplug_priv_t *io;
+	int err;
+	snd_pcm_t *pcm;
+
+	assert(ioplug && ioplug->callback);
+	assert(ioplug->callback->start &&
+	       ioplug->callback->stop &&
+	       ioplug->callback->pointer);
+
+	/* We support 1.0.0 to current */
+	if (ioplug->version < 0x010000 ||
+	    ioplug->version > SND_PCM_IOPLUG_VERSION) {
+		SNDERR("ioplug: Plugin version mismatch\n");
+		return -ENXIO;
+	}
+
+	io = calloc(1, sizeof(*io));
+	if (! io)
+		return -ENOMEM;
+
+	io->data = ioplug;
+	ioplug->state = SND_PCM_STATE_OPEN;
+	ioplug->stream = stream;
+
+	err = snd_pcm_new(&pcm, SND_PCM_TYPE_IOPLUG, name, stream, mode);
+	if (err < 0) {
+		free(io);
+		return err;
+	}
+
+	ioplug->pcm = pcm;
+	pcm->ops = &snd_pcm_ioplug_ops;
+	pcm->fast_ops = &snd_pcm_ioplug_fast_ops;
+	pcm->private_data = io;
+
+	snd_pcm_set_hw_ptr(pcm, &ioplug->hw_ptr, -1, 0);
+	snd_pcm_set_appl_ptr(pcm, &ioplug->appl_ptr, -1, 0);
+
+	snd_pcm_ioplug_reinit_status(ioplug);
+
+	return 0;
+}
+
+/**
+ * \brief Delete the ioplug instance
+ * \param ioplug the ioplug handle
+ * \return 0 if successful, or a negative error code
+ */
+int snd_pcm_ioplug_delete(snd_pcm_ioplug_t *ioplug)
+{
+	return snd_pcm_close(ioplug->pcm);
+}
+
+
+/**
+ * \brief Reset ioplug parameters
+ * \param ioplug the ioplug handle
+ *
+ * Resets the all parameters for the given ioplug handle.
+ */
+void snd_pcm_ioplug_params_reset(snd_pcm_ioplug_t *ioplug)
+{
+	ioplug_priv_t *io = ioplug->pcm->private_data;
+	clear_io_params(io);
+}
+
+/**
+ * \brief Set parameter as the list
+ * \param ioplug the ioplug handle
+ * \param type parameter type
+ * \param num_list number of available values
+ * \param list the list of available values
+ * \return 0 if successful, or a negative error code
+ *
+ * Sets the parameter as the list.
+ * The available values of the given parameter type is restricted to the ones of the given list.
+ */
+int snd_pcm_ioplug_set_param_list(snd_pcm_ioplug_t *ioplug, int type, unsigned int num_list, const unsigned int *list)
+{
+	ioplug_priv_t *io = ioplug->pcm->private_data;
+	if (type < 0 && type >= SND_PCM_IOPLUG_HW_PARAMS) {
+		SNDERR("IOPLUG: invalid parameter type %d", type);
+		return -EINVAL;
+	}
+	if (type == SND_PCM_IOPLUG_HW_PERIODS)
+		io->params[type].integer = 1;
+	return snd_ext_parm_set_list(&io->params[type], num_list, list);
+}
+
+/**
+ * \brief Set parameter as the min/max values
+ * \param ioplug the ioplug handle
+ * \param type parameter type
+ * \param min the minimum value
+ * \param max the maximum value
+ * \return 0 if successful, or a negative error code
+ *
+ * Sets the parameter as the min/max values.
+ * The available values of the given parameter type is restricted between the given
+ * minimum and maximum values.
+ */
+int snd_pcm_ioplug_set_param_minmax(snd_pcm_ioplug_t *ioplug, int type, unsigned int min, unsigned int max)
+{
+	ioplug_priv_t *io = ioplug->pcm->private_data;
+	if (type < 0 && type >= SND_PCM_IOPLUG_HW_PARAMS) {
+		SNDERR("IOPLUG: invalid parameter type %d", type);
+		return -EINVAL;
+	}
+	if (type == SND_PCM_IOPLUG_HW_ACCESS || type == SND_PCM_IOPLUG_HW_FORMAT) {
+		SNDERR("IOPLUG: invalid parameter type %d", type);
+		return -EINVAL;
+	}
+	if (type == SND_PCM_IOPLUG_HW_PERIODS)
+		io->params[type].integer = 1;
+	return snd_ext_parm_set_minmax(&io->params[type], min, max);
+}
+
+/**
+ * \brief Reinitialize the poll and mmap status
+ * \param ioplug the ioplug handle
+ * \return 0 if successful, or a negative error code
+ *
+ * Reinitializes the poll and the mmap status of the PCM.
+ * Call this function to propagate the status change in the ioplug instance to
+ * its PCM internals.
+ */
+int snd_pcm_ioplug_reinit_status(snd_pcm_ioplug_t *ioplug)
+{
+	ioplug->pcm->poll_fd = ioplug->poll_fd;
+	ioplug->pcm->poll_events = ioplug->poll_events;
+	ioplug->pcm->monotonic = (ioplug->flags & SND_PCM_IOPLUG_FLAG_MONOTONIC) != 0;
+	ioplug->pcm->mmap_rw = ioplug->mmap_rw;
+	return 0;
+}
+
+/**
+ * \brief Get mmap area of ioplug
+ * \param ioplug the ioplug handle
+ * \return the mmap channel areas if available, or NULL
+ *
+ * Returns the mmap channel areas if available.  When mmap_rw field is not set,
+ * this function always returns NULL.
+ */
+const snd_pcm_channel_area_t *snd_pcm_ioplug_mmap_areas(snd_pcm_ioplug_t *ioplug)
+{
+	if (ioplug->mmap_rw)
+		return snd_pcm_mmap_areas(ioplug->pcm);
+	return NULL;
+}
+
+/**
+ * \brief Change the ioplug PCM status
+ * \param ioplug the ioplug handle
+ * \param state the PCM status
+ * \return zero if successful or a negative error code
+ *
+ * Changes the PCM status of the ioplug to the given value.
+ * This function can be used for external plugins to notify the status
+ * change, e.g. XRUN.
+ */
+int snd_pcm_ioplug_set_state(snd_pcm_ioplug_t *ioplug, snd_pcm_state_t state)
+{
+	ioplug->state = state;
+	return 0;
+}
diff --git a/src/pcm/pcm_ladspa.c b/src/pcm/pcm_ladspa.c
new file mode 100644
index 0000000..84ebaa5
--- /dev/null
+++ b/src/pcm/pcm_ladspa.c
@@ -0,0 +1,1811 @@
+/**
+ * \file pcm/pcm_ladspa.c
+ * \ingroup PCM_Plugins
+ * \brief ALSA Plugin <-> LADSPA Plugin Interface
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \date 2001,2006
+ */
+/*
+ *  PCM - LADSPA integration plugin
+ *  Copyright (c) 2001-2006 by Jaroslav Kysela <perex@perex.cz>
+ *  Copyright (c) 2005 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ *
+ * perex@perex.cz 2005/12/13
+ *   The LADSPA plugin rewrite was sponsored by MediaNet AG
+ *   http://www.medianet.ag
+ */
+  
+#include <dirent.h>
+#include <locale.h>
+#include <math.h>
+#include "pcm_local.h"
+#include "pcm_plugin.h"
+
+#include "ladspa.h"
+
+#ifndef PIC
+/* entry for static linking */
+const char *_snd_module_pcm_ladspa = "";
+#endif
+
+#ifndef DOC_HIDDEN
+
+#define NO_ASSIGN	0xffffffff
+
+typedef enum _snd_pcm_ladspa_policy {
+	SND_PCM_LADSPA_POLICY_NONE,		/* use bindings only */
+	SND_PCM_LADSPA_POLICY_DUPLICATE		/* duplicate bindings for all channels */
+} snd_pcm_ladspa_policy_t;
+
+typedef struct {
+	/* This field need to be the first */
+	snd_pcm_plugin_t plug;
+	/* Plugin custom fields */
+	struct list_head pplugins;
+	struct list_head cplugins;
+	unsigned int channels;			/* forced input channels, 0 = auto */
+	unsigned int allocated;			/* count of allocated samples */
+	LADSPA_Data *zero[2];			/* zero input or dummy output */
+} snd_pcm_ladspa_t;
+ 
+typedef struct {
+        unsigned int size;
+        unsigned int *array;
+} snd_pcm_ladspa_array_t;
+
+typedef struct {
+        snd_pcm_ladspa_array_t channels;
+        snd_pcm_ladspa_array_t ports;
+	LADSPA_Data **m_data;
+        LADSPA_Data **data;
+} snd_pcm_ladspa_eps_t;
+
+typedef struct snd_pcm_ladspa_instance {
+	struct list_head list;
+	const LADSPA_Descriptor *desc;
+	LADSPA_Handle *handle;
+	unsigned int depth;
+	snd_pcm_ladspa_eps_t input;
+	snd_pcm_ladspa_eps_t output;
+	struct snd_pcm_ladspa_instance *prev;
+	struct snd_pcm_ladspa_instance *next;
+} snd_pcm_ladspa_instance_t;
+
+typedef struct {
+	LADSPA_PortDescriptor pdesc;		/* port description */
+	unsigned int port_bindings_size;	/* size of array */
+	unsigned int *port_bindings;		/* index = channel number, value = LADSPA port */
+	unsigned int controls_size;		/* size of array */
+	unsigned char *controls_initialized;	/* initialized by ALSA user */
+	LADSPA_Data *controls;			/* index = LADSPA control port index */
+} snd_pcm_ladspa_plugin_io_t;
+
+typedef struct {
+	struct list_head list;
+	snd_pcm_ladspa_policy_t policy;
+	char *filename;
+	void *dl_handle;
+	const LADSPA_Descriptor *desc;
+	snd_pcm_ladspa_plugin_io_t input;
+	snd_pcm_ladspa_plugin_io_t output;
+	struct list_head instances;		/* one LADSPA plugin might be used multiple times */
+} snd_pcm_ladspa_plugin_t;
+
+#endif /* DOC_HIDDEN */
+
+static unsigned int snd_pcm_ladspa_count_ports(snd_pcm_ladspa_plugin_t *lplug,
+                                               LADSPA_PortDescriptor pdesc)
+{
+        unsigned int res = 0, idx;
+        for (idx = 0; idx < lplug->desc->PortCount; idx++) {
+                if ((lplug->desc->PortDescriptors[idx] & pdesc) == pdesc)
+                        res++;
+        }
+        return res;
+}
+
+static int snd_pcm_ladspa_find_port(unsigned int *res,
+				    snd_pcm_ladspa_plugin_t *lplug,
+				    LADSPA_PortDescriptor pdesc,
+				    unsigned int port_idx)
+{
+	unsigned long idx;
+
+	for (idx = 0; idx < lplug->desc->PortCount; idx++)
+		if ((lplug->desc->PortDescriptors[idx] & pdesc) == pdesc) {
+			if (port_idx == 0) {
+				*res = idx;
+				return 0;
+			}
+			port_idx--;
+		}
+	return -EINVAL;
+}
+
+static int snd_pcm_ladspa_find_sport(unsigned int *res,
+				     snd_pcm_ladspa_plugin_t *lplug,
+				     LADSPA_PortDescriptor pdesc,
+				     const char *port_name)
+{
+	unsigned long idx;
+
+	for (idx = 0; idx < lplug->desc->PortCount; idx++)
+		if ((lplug->desc->PortDescriptors[idx] & pdesc) == pdesc &&
+		    !strcmp(lplug->desc->PortNames[idx], port_name)) {
+			*res = idx;
+			return 0;
+		}
+	return -EINVAL;
+}
+
+static int snd_pcm_ladspa_find_port_idx(unsigned int *res,
+					snd_pcm_ladspa_plugin_t *lplug,
+					LADSPA_PortDescriptor pdesc,
+					unsigned int port)
+{
+	unsigned long idx;
+	unsigned int r = 0;
+
+	if (port >= lplug->desc->PortCount)
+		return -EINVAL;
+	for (idx = 0; idx < port; idx++)
+		if ((lplug->desc->PortDescriptors[idx] & pdesc) == pdesc)
+			r++;
+	*res = r;
+	return 0;
+}
+
+static void snd_pcm_ladspa_free_io(snd_pcm_ladspa_plugin_io_t *io)
+{
+	free(io->controls);
+	free(io->controls_initialized);
+}
+
+static void snd_pcm_ladspa_free_plugins(struct list_head *plugins)
+{
+	while (!list_empty(plugins)) {
+		snd_pcm_ladspa_plugin_t *plugin = list_entry(plugins->next, snd_pcm_ladspa_plugin_t, list);
+                snd_pcm_ladspa_free_io(&plugin->input);
+                snd_pcm_ladspa_free_io(&plugin->output);
+		if (plugin->dl_handle)
+			dlclose(plugin->dl_handle);
+		free(plugin->filename);
+		list_del(&plugin->list);
+		free(plugin);
+	}
+}
+
+static void snd_pcm_ladspa_free(snd_pcm_ladspa_t *ladspa)
+{
+        unsigned int idx;
+
+	snd_pcm_ladspa_free_plugins(&ladspa->pplugins);
+	snd_pcm_ladspa_free_plugins(&ladspa->cplugins);
+	for (idx = 0; idx < 2; idx++) {
+		free(ladspa->zero[idx]);
+                ladspa->zero[idx] = NULL;
+        }
+        ladspa->allocated = 0;
+}
+
+static int snd_pcm_ladspa_close(snd_pcm_t *pcm)
+{
+	snd_pcm_ladspa_t *ladspa = pcm->private_data;
+
+	snd_pcm_ladspa_free(ladspa);
+	return snd_pcm_generic_close(pcm);
+}
+
+static int snd_pcm_ladspa_hw_refine_cprepare(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params)
+{
+	snd_pcm_ladspa_t *ladspa = pcm->private_data;
+	int err;
+	snd_pcm_access_mask_t access_mask = { SND_PCM_ACCBIT_SHMN };
+	err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_ACCESS,
+					 &access_mask);
+	if (err < 0)
+		return err;
+	err = _snd_pcm_hw_params_set_format(params, SND_PCM_FORMAT_FLOAT);
+	if (err < 0)
+		return err;
+	err = _snd_pcm_hw_params_set_subformat(params, SND_PCM_SUBFORMAT_STD);
+	if (err < 0)
+		return err;
+        if (ladspa->channels > 0 && pcm->stream == SND_PCM_STREAM_PLAYBACK) {
+        	err = _snd_pcm_hw_param_set(params, SND_PCM_HW_PARAM_CHANNELS, ladspa->channels, 0);
+        	if (err < 0)
+        		return err;
+        }
+	params->info &= ~(SND_PCM_INFO_MMAP | SND_PCM_INFO_MMAP_VALID);
+	return 0;
+}
+
+static int snd_pcm_ladspa_hw_refine_sprepare(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *sparams)
+{
+	snd_pcm_ladspa_t *ladspa = pcm->private_data;
+	snd_pcm_access_mask_t saccess_mask = { SND_PCM_ACCBIT_MMAPN };
+	_snd_pcm_hw_params_any(sparams);
+	_snd_pcm_hw_param_set_mask(sparams, SND_PCM_HW_PARAM_ACCESS,
+				   &saccess_mask);
+	_snd_pcm_hw_params_set_format(sparams, SND_PCM_FORMAT_FLOAT);
+	_snd_pcm_hw_params_set_subformat(sparams, SND_PCM_SUBFORMAT_STD);
+        if (ladspa->channels > 0 && pcm->stream == SND_PCM_STREAM_CAPTURE)
+                _snd_pcm_hw_param_set(sparams, SND_PCM_HW_PARAM_CHANNELS, ladspa->channels, 0);
+	return 0;
+}
+
+static int snd_pcm_ladspa_hw_refine_schange(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params,
+					    snd_pcm_hw_params_t *sparams)
+{
+	int err;
+	unsigned int links = (SND_PCM_HW_PARBIT_CHANNELS |
+			      SND_PCM_HW_PARBIT_RATE |
+			      SND_PCM_HW_PARBIT_PERIOD_SIZE |
+			      SND_PCM_HW_PARBIT_BUFFER_SIZE |
+			      SND_PCM_HW_PARBIT_PERIODS |
+			      SND_PCM_HW_PARBIT_PERIOD_TIME |
+			      SND_PCM_HW_PARBIT_BUFFER_TIME |
+			      SND_PCM_HW_PARBIT_TICK_TIME);
+	err = _snd_pcm_hw_params_refine(sparams, links, params);
+	if (err < 0)
+		return err;
+	return 0;
+}
+	
+static int snd_pcm_ladspa_hw_refine_cchange(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params,
+					    snd_pcm_hw_params_t *sparams)
+{
+	int err;
+	unsigned int links = (SND_PCM_HW_PARBIT_CHANNELS |
+			      SND_PCM_HW_PARBIT_RATE |
+			      SND_PCM_HW_PARBIT_PERIOD_SIZE |
+			      SND_PCM_HW_PARBIT_BUFFER_SIZE |
+			      SND_PCM_HW_PARBIT_PERIODS |
+			      SND_PCM_HW_PARBIT_PERIOD_TIME |
+			      SND_PCM_HW_PARBIT_BUFFER_TIME |
+			      SND_PCM_HW_PARBIT_TICK_TIME);
+	err = _snd_pcm_hw_params_refine(params, links, sparams);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static int snd_pcm_ladspa_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	return snd_pcm_hw_refine_slave(pcm, params,
+				       snd_pcm_ladspa_hw_refine_cprepare,
+				       snd_pcm_ladspa_hw_refine_cchange,
+				       snd_pcm_ladspa_hw_refine_sprepare,
+				       snd_pcm_ladspa_hw_refine_schange,
+				       snd_pcm_generic_hw_refine);
+}
+
+static int snd_pcm_ladspa_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t * params)
+{
+	// snd_pcm_ladspa_t *ladspa = pcm->private_data;
+	int err = snd_pcm_hw_params_slave(pcm, params,
+					  snd_pcm_ladspa_hw_refine_cchange,
+					  snd_pcm_ladspa_hw_refine_sprepare,
+					  snd_pcm_ladspa_hw_refine_schange,
+					  snd_pcm_generic_hw_params);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static void snd_pcm_ladspa_free_eps(snd_pcm_ladspa_eps_t *eps)
+{
+	free(eps->channels.array);
+	free(eps->ports.array);
+}
+
+static void snd_pcm_ladspa_free_instances(snd_pcm_t *pcm, snd_pcm_ladspa_t *ladspa, int cleanup)
+{
+	struct list_head *list, *pos, *pos1, *next1;
+	unsigned int idx;
+	
+	list = pcm->stream == SND_PCM_STREAM_PLAYBACK ? &ladspa->pplugins : &ladspa->cplugins;
+	list_for_each(pos, list) {
+		snd_pcm_ladspa_plugin_t *plugin = list_entry(pos, snd_pcm_ladspa_plugin_t, list);
+		list_for_each_safe(pos1, next1, &plugin->instances) {
+			snd_pcm_ladspa_instance_t *instance = list_entry(pos1, snd_pcm_ladspa_instance_t, list);
+			if (plugin->desc->deactivate)
+				plugin->desc->deactivate(instance->handle);
+			if (cleanup) {
+				if (plugin->desc->cleanup)
+					plugin->desc->cleanup(instance->handle);
+				if (instance->input.m_data) {
+				        for (idx = 0; idx < instance->input.channels.size; idx++)
+						free(instance->input.m_data[idx]);
+					free(instance->input.m_data);
+                                }
+				if (instance->output.m_data) {
+				        for (idx = 0; idx < instance->output.channels.size; idx++)
+						free(instance->output.m_data[idx]);
+					free(instance->output.m_data);
+                                }
+                                free(instance->input.data);
+                                free(instance->output.data);
+				list_del(&(instance->list));
+				snd_pcm_ladspa_free_eps(&instance->input);
+				snd_pcm_ladspa_free_eps(&instance->output);
+				free(instance);
+			} else {
+				if (plugin->desc->activate)
+					plugin->desc->activate(instance->handle);
+			}
+		}
+		if (cleanup) {
+			assert(list_empty(&plugin->instances));
+		}
+	}
+}
+
+static int snd_pcm_ladspa_add_to_carray(snd_pcm_ladspa_array_t *array,
+                                        unsigned int idx,
+                                        unsigned int val)
+{
+        unsigned int *narray;
+        unsigned int idx1;
+
+        if (idx >= array->size) {
+                narray = realloc(array->array, sizeof(unsigned int) * (idx + 1));
+                if (narray == NULL)
+                        return -ENOMEM;
+                for (idx1 = array->size; idx1 < idx; idx1++)
+                        narray[idx1] = NO_ASSIGN;
+                array->array = narray;
+                array->size = idx + 1;
+                array->array[idx] = val;
+                return 0;
+        }
+        if (array->array[idx] == NO_ASSIGN)
+                array->array[idx] = val;
+        else
+                return -EINVAL;
+        return 0;
+}
+
+static int snd_pcm_ladspa_add_to_array(snd_pcm_ladspa_array_t *array,
+                                       unsigned int idx,
+                                       unsigned int val)
+{
+        unsigned int *narray;
+        unsigned int idx1;
+
+        if (idx >= array->size) {
+                narray = realloc(array->array, sizeof(unsigned int) * (idx + 1));
+                if (narray == NULL)
+                        return -ENOMEM;
+                for (idx1 = array->size; idx1 < idx; idx1++)
+                        narray[idx1] = NO_ASSIGN;
+                array->array = narray;
+                array->size = idx + 1;
+        }
+        array->array[idx] = val;
+        return 0;
+}
+
+static int snd_pcm_ladspa_connect_plugin1(snd_pcm_ladspa_plugin_t *plugin,
+					  snd_pcm_ladspa_plugin_io_t *io,
+					  snd_pcm_ladspa_eps_t *eps)
+{
+	unsigned int port, channels, idx, idx1;
+	int err;
+
+	assert(plugin->policy == SND_PCM_LADSPA_POLICY_NONE);
+	channels = io->port_bindings_size > 0 ?
+	                io->port_bindings_size :
+	                snd_pcm_ladspa_count_ports(plugin, io->pdesc | LADSPA_PORT_AUDIO);
+	for (idx = idx1 = 0; idx < channels; idx++) {
+		if (io->port_bindings_size > 0)
+        		port = io->port_bindings[idx];
+                else {
+        		err = snd_pcm_ladspa_find_port(&port, plugin, io->pdesc | LADSPA_PORT_AUDIO, idx);
+        		if (err < 0) {
+        		        SNDERR("unable to find audio %s port %u plugin '%s'", io->pdesc & LADSPA_PORT_INPUT ? "input" : "output", idx, plugin->desc->Name);
+        			return err;
+                        }
+                }
+                if (port == NO_ASSIGN)
+                	continue;
+        	err = snd_pcm_ladspa_add_to_carray(&eps->channels, idx1, idx);
+        	if (err < 0) {
+        		SNDERR("unable to add channel %u for audio %s plugin '%s'", idx, io->pdesc & LADSPA_PORT_INPUT ? "input" : "output", plugin->desc->Name);
+        	        return err;
+                }
+        	err = snd_pcm_ladspa_add_to_array(&eps->ports, idx1, port);
+        	if (err < 0) {
+        		SNDERR("unable to add port %u for audio %s plugin '%s'", port, io->pdesc & LADSPA_PORT_INPUT ? "input" : "output", plugin->desc->Name);
+        	        return err;
+                }
+                idx1++;
+	}
+	return 0;
+}
+
+static int snd_pcm_ladspa_connect_plugin(snd_pcm_ladspa_plugin_t *plugin,
+					 snd_pcm_ladspa_instance_t *instance)
+{
+        int err;
+        
+        err = snd_pcm_ladspa_connect_plugin1(plugin, &plugin->input, &instance->input);
+        if (err < 0)
+                return err;
+        err = snd_pcm_ladspa_connect_plugin1(plugin, &plugin->output, &instance->output);
+        if (err < 0)
+                return err;
+        return 0;
+}
+
+static int snd_pcm_ladspa_connect_plugin_duplicate1(snd_pcm_ladspa_plugin_t *plugin,
+                                                    snd_pcm_ladspa_plugin_io_t *io,
+                                                    snd_pcm_ladspa_eps_t *eps,
+                                                    unsigned int idx)
+{
+	unsigned int port;
+	int err;
+
+	assert(plugin->policy == SND_PCM_LADSPA_POLICY_DUPLICATE);
+	if (io->port_bindings_size > 0) {
+		port = io->port_bindings[0];
+	} else {
+		err = snd_pcm_ladspa_find_port(&port, plugin, io->pdesc | LADSPA_PORT_AUDIO, 0);
+		if (err < 0) {
+		        SNDERR("unable to find audio %s port %u plugin '%s'", io->pdesc & LADSPA_PORT_INPUT ? "input" : "output", (unsigned int)0, plugin->desc->Name);
+			return err;
+                }
+	}
+	err = snd_pcm_ladspa_add_to_carray(&eps->channels, 0, idx);
+	if (err < 0) {
+        	SNDERR("unable to add channel %u for audio %s plugin '%s'", idx, io->pdesc & LADSPA_PORT_INPUT ? "input" : "output", plugin->desc->Name);
+	        return err;
+        }
+        err = snd_pcm_ladspa_add_to_array(&eps->ports, 0, port);
+        if (err < 0) {
+        	SNDERR("unable to add port %u for audio %s plugin '%s'", port, io->pdesc & LADSPA_PORT_INPUT ? "input" : "output", plugin->desc->Name);
+        	return err;
+        }
+        return 0;
+}
+
+static int snd_pcm_ladspa_connect_plugin_duplicate(snd_pcm_ladspa_plugin_t *plugin,
+						   snd_pcm_ladspa_plugin_io_t *in_io,
+						   snd_pcm_ladspa_plugin_io_t *out_io,
+						   snd_pcm_ladspa_instance_t *instance,
+						   unsigned int idx)
+{
+	int err;
+
+	err = snd_pcm_ladspa_connect_plugin_duplicate1(plugin, in_io, &instance->input, idx);
+	if (err < 0)
+	        return err;
+	err = snd_pcm_ladspa_connect_plugin_duplicate1(plugin, out_io, &instance->output, idx);
+	if (err < 0)
+	        return err;
+        return 0;
+}
+
+static void snd_pcm_ladspa_get_default_cvalue(const LADSPA_Descriptor * desc, unsigned int port, LADSPA_Data *val) 
+{
+        LADSPA_PortRangeHintDescriptor hdesc;
+
+        hdesc = desc->PortRangeHints[port].HintDescriptor;
+        switch (hdesc & LADSPA_HINT_DEFAULT_MASK) {
+        case LADSPA_HINT_DEFAULT_MINIMUM:
+                *val = desc->PortRangeHints[port].LowerBound;
+                break;
+        case LADSPA_HINT_DEFAULT_LOW:
+                if (LADSPA_IS_HINT_LOGARITHMIC(hdesc)) {
+                        *val = exp(log(desc->PortRangeHints[port].LowerBound)
+                                        * 0.75
+                                        + log(desc->PortRangeHints[port].UpperBound)
+                                        * 0.25);
+                } else {
+                        *val = (desc->PortRangeHints[port].LowerBound * 0.75) +
+                               (desc->PortRangeHints[port].UpperBound * 0.25);
+                }
+                break;
+        case LADSPA_HINT_DEFAULT_MIDDLE:
+                if (LADSPA_IS_HINT_LOGARITHMIC(hdesc)) {
+                        *val = sqrt(desc->PortRangeHints[port].LowerBound *
+                                    desc->PortRangeHints[port].UpperBound);
+                } else {
+                        *val = 0.5 *
+                               (desc->PortRangeHints[port].LowerBound +
+                                desc->PortRangeHints[port].UpperBound);
+                }
+                break;
+        case LADSPA_HINT_DEFAULT_HIGH:
+                if (LADSPA_IS_HINT_LOGARITHMIC(hdesc)) {
+                        *val = exp(log(desc->PortRangeHints[port].LowerBound)
+                                        * 0.25
+                                        + log(desc->PortRangeHints[port].UpperBound)
+                                        * 0.75);
+                } else {
+                        *val = (desc->PortRangeHints[port].LowerBound * 0.25) +
+                               (desc->PortRangeHints[port].UpperBound * 0.75);
+                }
+                break;
+        case LADSPA_HINT_DEFAULT_MAXIMUM:
+                *val = desc->PortRangeHints[port].UpperBound;
+                break;
+        case LADSPA_HINT_DEFAULT_0:
+                *val = 0;
+                break;
+        case LADSPA_HINT_DEFAULT_1:
+                *val = 1;
+                break;
+        case LADSPA_HINT_DEFAULT_100:
+                *val = 100;
+                break;
+        case LADSPA_HINT_DEFAULT_440:
+                *val = 440;
+                break;
+        default:
+                *val = 0;	/* reasonable default, if everything fails */
+                break;
+        }
+}
+
+static int snd_pcm_ladspa_connect_controls(snd_pcm_ladspa_plugin_t *plugin,
+					   snd_pcm_ladspa_plugin_io_t *io,
+					   snd_pcm_ladspa_instance_t *instance)
+{
+	unsigned long idx, midx;
+
+	for (idx = midx = 0; idx < plugin->desc->PortCount; idx++)
+		if ((plugin->desc->PortDescriptors[idx] & (io->pdesc | LADSPA_PORT_CONTROL)) == (io->pdesc | LADSPA_PORT_CONTROL)) {
+			if (io->controls_size > midx) {
+			        if (!io->controls_initialized[midx])
+			                snd_pcm_ladspa_get_default_cvalue(plugin->desc, idx, &io->controls[midx]);
+				plugin->desc->connect_port(instance->handle, idx, &io->controls[midx]);
+			} else {
+				return -EINVAL;
+			}
+			midx++;
+		}
+	return 0;
+}
+
+static int snd_pcm_ladspa_check_connect(snd_pcm_ladspa_plugin_t *plugin,
+                                        snd_pcm_ladspa_plugin_io_t *io,
+                                        snd_pcm_ladspa_eps_t *eps,
+                                        unsigned int depth)
+{
+        unsigned int idx, midx;
+        int err = 0;
+
+	for (idx = midx = 0; idx < plugin->desc->PortCount; idx++)
+		if ((plugin->desc->PortDescriptors[idx] & (io->pdesc | LADSPA_PORT_AUDIO)) == (io->pdesc | LADSPA_PORT_AUDIO)) {
+                        if (eps->channels.array[midx] == NO_ASSIGN) {
+                                SNDERR("%s port for plugin %s depth %u is not connected", io->pdesc & LADSPA_PORT_INPUT ? "input" : "output", plugin->desc->Name, depth);
+                                err++;
+                        }
+			midx++;
+		}
+        if (err > 0) {
+                SNDERR("%i connection errors total", err);
+                return -EINVAL;
+        }
+        return 0;
+}
+
+static int snd_pcm_ladspa_allocate_instances(snd_pcm_t *pcm, snd_pcm_ladspa_t *ladspa)
+{
+	struct list_head *list, *pos;
+	unsigned int depth, idx, count;
+        unsigned int in_channel, out_channel;
+        unsigned int in_channels, out_channels;
+	unsigned int in_ports, out_ports;
+	snd_pcm_ladspa_instance_t *instance = NULL;
+	int err;
+	
+	list = pcm->stream == SND_PCM_STREAM_PLAYBACK ? &ladspa->pplugins : &ladspa->cplugins;
+	in_channels = ladspa->channels > 0 ? ladspa->channels :
+	              (pcm->stream == SND_PCM_STREAM_PLAYBACK ? pcm->channels : ladspa->plug.gen.slave->channels);
+	depth = 0;
+	out_channels = 0;
+	list_for_each(pos, list) {
+		snd_pcm_ladspa_plugin_t *plugin = list_entry(pos, snd_pcm_ladspa_plugin_t, list);
+		if (pos->next == list)	/* last entry */
+		        out_channels = pcm->stream == SND_PCM_STREAM_PLAYBACK ? ladspa->plug.gen.slave->channels : pcm->channels;
+                in_ports = snd_pcm_ladspa_count_ports(plugin, LADSPA_PORT_INPUT | LADSPA_PORT_AUDIO);
+                out_ports = snd_pcm_ladspa_count_ports(plugin, LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO);
+		count = 1;
+		if (plugin->policy == SND_PCM_LADSPA_POLICY_DUPLICATE) {
+                        if (in_ports == 1 && out_ports == 1)
+                                count = in_channels;
+                        else
+                                plugin->policy = SND_PCM_LADSPA_POLICY_NONE;
+                }
+                in_channel = 0;
+                out_channel = 0;
+        	for (idx = 0; idx < count; idx++) {
+			instance = (snd_pcm_ladspa_instance_t *)calloc(1, sizeof(snd_pcm_ladspa_instance_t));
+			if (instance == NULL)
+				return -ENOMEM;
+			instance->desc = plugin->desc;
+			instance->handle = plugin->desc->instantiate(plugin->desc, pcm->rate);
+			instance->depth = depth;
+			if (instance->handle == NULL) {
+				SNDERR("Unable to create instance of LADSPA plugin '%s'", plugin->desc->Name);
+				free(instance);
+				return -EINVAL;
+			}
+			list_add_tail(&instance->list, &plugin->instances);
+			if (plugin->desc->activate)
+				plugin->desc->activate(instance->handle);
+			if (plugin->policy == SND_PCM_LADSPA_POLICY_DUPLICATE) {
+				err = snd_pcm_ladspa_connect_plugin_duplicate(plugin, &plugin->input, &plugin->output, instance, idx);
+				if (err < 0) {
+					SNDERR("Unable to connect duplicate port of plugin '%s' channel %u depth %u", plugin->desc->Name, idx, instance->depth);
+					return err;
+				}
+			} else {
+                		err = snd_pcm_ladspa_connect_plugin(plugin, instance);
+                		if (err < 0) {
+	                		SNDERR("Unable to connect plugin '%s' depth %u", plugin->desc->Name, depth);
+		                	return err;
+                		}
+			}
+			err = snd_pcm_ladspa_connect_controls(plugin, &plugin->input, instance);
+			assert(err >= 0);
+			err = snd_pcm_ladspa_connect_controls(plugin, &plugin->output, instance);
+			assert(err >= 0);
+		}
+		err = snd_pcm_ladspa_check_connect(plugin, &plugin->input, &instance->input, depth);
+		if (err < 0)
+		        return err;
+		err = snd_pcm_ladspa_check_connect(plugin, &plugin->output, &instance->output, depth);
+		if (err < 0)
+		        return err;
+		depth++;
+	}
+	return 0;
+}
+
+static LADSPA_Data *snd_pcm_ladspa_allocate_zero(snd_pcm_ladspa_t *ladspa, unsigned int idx)
+{
+        if (ladspa->zero[idx] == NULL)
+                ladspa->zero[idx] = calloc(ladspa->allocated, sizeof(LADSPA_Data));
+        return ladspa->zero[idx];
+}
+
+static int snd_pcm_ladspa_allocate_memory(snd_pcm_t *pcm, snd_pcm_ladspa_t *ladspa)
+{
+	struct list_head *list, *pos, *pos1;
+	snd_pcm_ladspa_instance_t *instance;
+	unsigned int channels = 16, nchannels;
+	unsigned int ichannels, ochannels;
+	void **pchannels, **npchannels;
+	unsigned int idx, chn;
+	
+        ladspa->allocated = 2048;
+        if (pcm->buffer_size > ladspa->allocated)
+                ladspa->allocated = pcm->buffer_size;
+        if (pcm->stream == SND_PCM_STREAM_PLAYBACK) {
+                ichannels = pcm->channels;
+                ochannels = ladspa->plug.gen.slave->channels;
+        } else {
+                ichannels = ladspa->plug.gen.slave->channels;
+                ochannels = pcm->channels;
+        }
+	pchannels = calloc(1, sizeof(void *) * channels);
+	if (pchannels == NULL)
+	        return -ENOMEM;
+	list = pcm->stream == SND_PCM_STREAM_PLAYBACK ? &ladspa->pplugins : &ladspa->cplugins;
+	list_for_each(pos, list) {
+		snd_pcm_ladspa_plugin_t *plugin = list_entry(pos, snd_pcm_ladspa_plugin_t, list);
+		list_for_each(pos1, &plugin->instances) {
+			instance = list_entry(pos1, snd_pcm_ladspa_instance_t, list);
+			nchannels = channels;
+			for (idx = 0; idx < instance->input.channels.size; idx++) {
+			        chn = instance->input.channels.array[idx];
+			        assert(instance->input.ports.array[idx] != NO_ASSIGN);
+        			if (chn >= nchannels)
+        			        nchannels = chn + 1;
+                        }
+			for (idx = 0; idx < instance->output.channels.size; idx++) {
+			        chn = instance->output.channels.array[idx];
+			        assert(instance->output.ports.array[idx] != NO_ASSIGN);
+        			if (chn >= nchannels)
+        			        nchannels = chn + 1;
+                        }
+                        if (nchannels != channels) {
+                                npchannels = realloc(pchannels, nchannels * sizeof(void *));
+                                if (npchannels == NULL) {
+                                        free(pchannels);
+                                        return -ENOMEM;
+                                }
+                                for (idx = channels; idx < nchannels; idx++)
+                                        npchannels[idx] = NULL;
+                                pchannels = npchannels;
+                        }
+                        assert(instance->input.data == NULL);
+                        assert(instance->input.m_data == NULL);
+                        assert(instance->output.data == NULL);
+                        assert(instance->output.m_data == NULL);
+                        instance->input.data = calloc(instance->input.channels.size, sizeof(void *));
+                        instance->input.m_data = calloc(instance->input.channels.size, sizeof(void *));
+                        instance->output.data = calloc(instance->output.channels.size, sizeof(void *));
+                        instance->output.m_data = calloc(instance->output.channels.size, sizeof(void *));
+                        if (instance->input.data == NULL ||
+                            instance->input.m_data == NULL ||
+                            instance->output.data == NULL ||
+                            instance->output.m_data == NULL) {
+                                free(pchannels);
+                                return -ENOMEM;
+                        }
+			for (idx = 0; idx < instance->input.channels.size; idx++) {
+			        chn = instance->output.channels.array[idx];
+			        if (pchannels[chn] == NULL && chn < ichannels) {
+			                instance->input.data[idx] = NULL;
+			                continue;
+                                }
+			        instance->input.data[idx] = pchannels[chn];
+			        if (instance->input.data[idx] == NULL) {
+                                        instance->input.data[idx] = snd_pcm_ladspa_allocate_zero(ladspa, 0);
+                                        if (instance->input.data[idx] == NULL) {
+                                                free(pchannels);
+                                                return -ENOMEM;
+                                        }
+                                }
+                        }
+                        for (idx = 0; idx < instance->output.channels.size; idx++) {
+			        chn = instance->output.channels.array[idx];
+                                /* FIXME/OPTIMIZE: check if we can remove double alloc */
+                                /* if LADSPA plugin has no broken inplace */
+                                instance->output.data[idx] = malloc(sizeof(LADSPA_Data) * ladspa->allocated);
+                                if (instance->output.data[idx] == NULL) {
+                                        free(pchannels);
+                                        return -ENOMEM;
+                                }
+                                pchannels[chn] = instance->output.m_data[idx] = instance->output.data[idx];
+                        }
+		}
+	}
+	/* OPTIMIZE: we have already allocated areas for ALSA output channels */
+	/* next loop deallocates the last output LADSPA areas and connects */
+	/* them to ALSA areas (NULL) or dummy area ladpsa->free[1] ; */
+	/* this algorithm might be optimized to not allocate the last LADSPA outputs */
+	list_for_each(pos, list) {
+		snd_pcm_ladspa_plugin_t *plugin = list_entry(pos, snd_pcm_ladspa_plugin_t, list);
+		list_for_each(pos1, &plugin->instances) {
+			instance = list_entry(pos1, snd_pcm_ladspa_instance_t, list);
+                        for (idx = 0; idx < instance->output.channels.size; idx++) {
+        			chn = instance->output.channels.array[idx];
+                                if (instance->output.data[idx] == pchannels[chn]) {
+					free(instance->output.m_data[idx]);
+					instance->output.m_data[idx] = NULL;
+                                        if (chn < ochannels) {
+                                                instance->output.data[idx] = NULL;
+                                        } else {
+                                                instance->output.data[idx] = snd_pcm_ladspa_allocate_zero(ladspa, 1);
+                                                if (instance->output.data[idx] == NULL) {
+                                                        free(pchannels);
+                                                        return -ENOMEM;
+                                                }
+                                        }
+                                }
+                        }
+                }
+        }
+#if 0
+        printf("zero[0] = %p\n", ladspa->zero[0]);
+        printf("zero[1] = %p\n", ladspa->zero[1]);
+	list_for_each(pos, list) {
+		snd_pcm_ladspa_plugin_t *plugin = list_entry(pos, snd_pcm_ladspa_plugin_t, list);
+		list_for_each(pos1, &plugin->instances) {
+			instance = list_entry(pos1, snd_pcm_ladspa_instance_t, list);
+                        for (idx = 0; idx < instance->input.channels.size; idx++)
+                                printf("%i:alloc-input%i:  data = %p, m_data = %p\n", instance->depth, idx, instance->input.data[idx], instance->input.m_data[idx]);
+                        for (idx = 0; idx < instance->output.channels.size; idx++)
+                                printf("%i:alloc-output%i:  data = %p, m_data = %p\n", instance->depth, idx, instance->output.data[idx], instance->output.m_data[idx]);
+		}
+	}
+#endif
+	free(pchannels);
+	return 0;
+}
+
+static int snd_pcm_ladspa_init(snd_pcm_t *pcm)
+{
+	snd_pcm_ladspa_t *ladspa = pcm->private_data;
+	int err;
+	
+	snd_pcm_ladspa_free_instances(pcm, ladspa, 1);
+	err = snd_pcm_ladspa_allocate_instances(pcm, ladspa);
+	if (err < 0) {
+		snd_pcm_ladspa_free_instances(pcm, ladspa, 1);
+		return err;
+	}
+	err = snd_pcm_ladspa_allocate_memory(pcm, ladspa);
+	if (err < 0) {
+		snd_pcm_ladspa_free_instances(pcm, ladspa, 1);
+		return err;
+	}
+	return 0;
+}
+
+static int snd_pcm_ladspa_hw_free(snd_pcm_t *pcm)
+{
+	snd_pcm_ladspa_t *ladspa = pcm->private_data;
+
+	snd_pcm_ladspa_free_instances(pcm, ladspa, 1);
+	return snd_pcm_generic_hw_free(pcm);
+}
+
+static snd_pcm_uframes_t
+snd_pcm_ladspa_write_areas(snd_pcm_t *pcm,
+			   const snd_pcm_channel_area_t *areas,
+			   snd_pcm_uframes_t offset,
+			   snd_pcm_uframes_t size,
+			   const snd_pcm_channel_area_t *slave_areas,
+			   snd_pcm_uframes_t slave_offset,
+			   snd_pcm_uframes_t *slave_sizep)
+{
+	snd_pcm_ladspa_t *ladspa = pcm->private_data;
+	snd_pcm_ladspa_instance_t *instance;
+	struct list_head *pos, *pos1;
+	LADSPA_Data *data;
+	unsigned int idx, chn, size1, size2;
+	
+	if (size > *slave_sizep)
+		size = *slave_sizep;
+        size2 = size;
+#if 0	/* no processing - for testing purposes only */
+	snd_pcm_areas_copy(slave_areas, slave_offset,
+			   areas, offset,
+			   pcm->channels, size, pcm->format);
+#else
+        while (size > 0) {
+                size1 = size;
+                if (size1 > ladspa->allocated)
+                        size1 = ladspa->allocated;
+        	list_for_each(pos, &ladspa->pplugins) {
+        		snd_pcm_ladspa_plugin_t *plugin = list_entry(pos, snd_pcm_ladspa_plugin_t, list);
+        		list_for_each(pos1, &plugin->instances) {
+        			instance = list_entry(pos1, snd_pcm_ladspa_instance_t, list);
+        			for (idx = 0; idx < instance->input.channels.size; idx++) {
+                                        chn = instance->input.channels.array[idx];
+                                        data = instance->input.data[idx];
+                                        if (data == NULL) {
+                                		data = (LADSPA_Data *)((char *)areas[chn].addr + (areas[chn].first / 8));
+                                       		data += offset;
+                                        }
+                                        instance->desc->connect_port(instance->handle, instance->input.ports.array[idx], data);
+        			}
+        			for (idx = 0; idx < instance->output.channels.size; idx++) {
+                                        chn = instance->output.channels.array[idx];
+                                        data = instance->output.data[idx];
+                                        if (data == NULL) {
+                                		data = (LADSPA_Data *)((char *)slave_areas[chn].addr + (areas[chn].first / 8));
+                                		data += slave_offset;
+                                        }
+					instance->desc->connect_port(instance->handle, instance->output.ports.array[idx], data);
+        			}
+        			instance->desc->run(instance->handle, size1);
+        		}
+        	}
+        	offset += size1;
+        	slave_offset += size1;
+        	size -= size1;
+	}
+#endif
+	*slave_sizep = size2;
+	return size2;
+}
+
+static snd_pcm_uframes_t
+snd_pcm_ladspa_read_areas(snd_pcm_t *pcm,
+			  const snd_pcm_channel_area_t *areas,
+			  snd_pcm_uframes_t offset,
+			  snd_pcm_uframes_t size,
+			  const snd_pcm_channel_area_t *slave_areas,
+			  snd_pcm_uframes_t slave_offset,
+			  snd_pcm_uframes_t *slave_sizep)
+{
+	snd_pcm_ladspa_t *ladspa = pcm->private_data;
+	snd_pcm_ladspa_instance_t *instance;
+	struct list_head *pos, *pos1;
+	LADSPA_Data *data;
+	unsigned int idx, chn, size1, size2;;
+
+	if (size > *slave_sizep)
+		size = *slave_sizep;
+        size2 = size;
+#if 0	/* no processing - for testing purposes only */
+	snd_pcm_areas_copy(areas, offset,
+			   slave_areas, slave_offset,
+			   pcm->channels, size, pcm->format);
+#else
+        while (size > 0) {
+                size1 = size;
+                if (size1 > ladspa->allocated)
+                        size1 = ladspa->allocated;
+        	list_for_each(pos, &ladspa->cplugins) {
+        		snd_pcm_ladspa_plugin_t *plugin = list_entry(pos, snd_pcm_ladspa_plugin_t, list);
+        		list_for_each(pos1, &plugin->instances) {
+        			instance = list_entry(pos1, snd_pcm_ladspa_instance_t, list);
+        			for (idx = 0; idx < instance->input.channels.size; idx++) {
+                                        chn = instance->input.channels.array[idx];
+                                        data = instance->input.data[idx];
+                                        if (data == NULL) {
+                                		data = (LADSPA_Data *)((char *)slave_areas[chn].addr + (areas[chn].first / 8));
+                                		data += slave_offset;
+                                        }	
+                			instance->desc->connect_port(instance->handle, instance->input.ports.array[idx], data);
+        			}
+        			for (idx = 0; idx < instance->output.channels.size; idx++) {
+                                        chn = instance->output.channels.array[idx];
+                                        data = instance->output.data[idx];
+                                        if (data == NULL) {
+                                		data = (LADSPA_Data *)((char *)areas[chn].addr + (areas[chn].first / 8));
+                                       		data += offset;
+                                        }
+        		        	instance->desc->connect_port(instance->handle, instance->output.ports.array[idx], data);
+        			}
+        			instance->desc->run(instance->handle, size1);
+        		}
+        	}
+        	offset += size1;
+        	slave_offset += size1;
+        	size -= size1;
+	}
+#endif
+	*slave_sizep = size2;
+	return size2;
+}
+
+static void snd_pcm_ladspa_dump_direction(snd_pcm_ladspa_plugin_t *plugin,
+                                          snd_pcm_ladspa_plugin_io_t *io,
+                                          snd_output_t *out)
+{
+	unsigned int idx, midx;
+
+	if (io->port_bindings_size == 0)
+		goto __control;
+	snd_output_printf(out, "    Audio %s port bindings:\n", io->pdesc == LADSPA_PORT_INPUT ? "input" : "output");
+	for (idx = 0; idx < io->port_bindings_size; idx++) {
+		if (io->port_bindings[idx] == NO_ASSIGN) 
+			snd_output_printf(out, "      %i -> NONE\n", idx);
+                else
+        		snd_output_printf(out, "      %i -> %i\n", idx, io->port_bindings[idx]);
+	}
+      __control:
+      	if (io->controls_size == 0)
+      		return;
+	snd_output_printf(out, "    Control %s port initial values:\n", io->pdesc == LADSPA_PORT_INPUT ? "input" : "output");
+	for (idx = midx = 0; idx < plugin->desc->PortCount; idx++) {
+		if ((plugin->desc->PortDescriptors[idx] & (io->pdesc | LADSPA_PORT_CONTROL)) == (io->pdesc | LADSPA_PORT_CONTROL)) {
+        		snd_output_printf(out, "      %i \"%s\" = %.8f\n", idx, plugin->desc->PortNames[idx], io->controls[midx]);
+        		midx++;
+                }
+        }
+}
+
+static void snd_pcm_ladspa_dump_array(snd_output_t *out,
+                                      snd_pcm_ladspa_array_t *array,
+                                      snd_pcm_ladspa_plugin_t *plugin)
+{
+        unsigned int size = array->size;
+        unsigned int val, idx = 0;
+
+        while (size-- > 0) {
+                if (idx > 0) {
+                        snd_output_putc(out, ',');
+                        snd_output_putc(out, ' ');
+                }
+                val = array->array[idx++];
+                if (val == NO_ASSIGN)
+                        snd_output_putc(out, '-');
+                else
+                        snd_output_printf(out, "%u", val);
+                if (plugin && val != NO_ASSIGN)
+                        snd_output_printf(out, " \"%s\"", plugin->desc->PortNames[val]);
+        }
+}
+
+static void snd_pcm_ladspa_plugins_dump(struct list_head *list, snd_output_t *out)
+{
+	struct list_head *pos, *pos2;
+	
+	list_for_each(pos, list) {
+		snd_pcm_ladspa_plugin_t *plugin = list_entry(pos, snd_pcm_ladspa_plugin_t, list);
+		snd_output_printf(out, "    Policy: %s\n", plugin->policy == SND_PCM_LADSPA_POLICY_NONE ? "none" : "duplicate");
+		snd_output_printf(out, "    Filename: %s\n", plugin->filename);
+		snd_output_printf(out, "    Plugin Name: %s\n", plugin->desc->Name);
+		snd_output_printf(out, "    Plugin Label: %s\n", plugin->desc->Label);
+		snd_output_printf(out, "    Plugin Unique ID: %lu\n", plugin->desc->UniqueID);
+                snd_output_printf(out, "    Instances:\n");
+		list_for_each(pos2, &plugin->instances) {
+		        snd_pcm_ladspa_instance_t *in = (snd_pcm_ladspa_instance_t *) pos2;
+		        snd_output_printf(out, "      Depth: %i\n", in->depth);
+		        snd_output_printf(out, "         InChannels: ");
+                        snd_pcm_ladspa_dump_array(out, &in->input.channels, NULL);
+                        snd_output_printf(out, "\n         InPorts: ");
+                        snd_pcm_ladspa_dump_array(out, &in->input.ports, plugin);
+                        snd_output_printf(out, "\n         OutChannels: ");
+                        snd_pcm_ladspa_dump_array(out, &in->output.channels, NULL);
+                        snd_output_printf(out, "\n         OutPorts: ");
+                        snd_pcm_ladspa_dump_array(out, &in->output.ports, plugin);
+                        snd_output_printf(out, "\n");
+		}
+		snd_pcm_ladspa_dump_direction(plugin, &plugin->input, out);
+		snd_pcm_ladspa_dump_direction(plugin, &plugin->output, out);
+	}
+}
+
+static void snd_pcm_ladspa_dump(snd_pcm_t *pcm, snd_output_t *out)
+{
+	snd_pcm_ladspa_t *ladspa = pcm->private_data;
+
+	snd_output_printf(out, "LADSPA PCM\n");
+	snd_output_printf(out, "  Playback:\n");
+	snd_pcm_ladspa_plugins_dump(&ladspa->pplugins, out);
+	snd_output_printf(out, "  Capture:\n");
+	snd_pcm_ladspa_plugins_dump(&ladspa->cplugins, out);
+	if (pcm->setup) {
+		snd_output_printf(out, "Its setup is:\n");
+		snd_pcm_dump_setup(pcm, out);
+	}
+	snd_output_printf(out, "Slave: ");
+	snd_pcm_dump(ladspa->plug.gen.slave, out);
+}
+
+static const snd_pcm_ops_t snd_pcm_ladspa_ops = {
+	.close = snd_pcm_ladspa_close,
+	.info = snd_pcm_generic_info,
+	.hw_refine = snd_pcm_ladspa_hw_refine,
+	.hw_params = snd_pcm_ladspa_hw_params,
+	.hw_free = snd_pcm_ladspa_hw_free,
+	.sw_params = snd_pcm_generic_sw_params,
+	.channel_info = snd_pcm_generic_channel_info,
+	.dump = snd_pcm_ladspa_dump,
+	.nonblock = snd_pcm_generic_nonblock,
+	.async = snd_pcm_generic_async,
+	.mmap = snd_pcm_generic_mmap,
+	.munmap = snd_pcm_generic_munmap,
+};
+
+static int snd_pcm_ladspa_check_file(snd_pcm_ladspa_plugin_t * const plugin,
+				     const char *filename,
+				     const char *label,
+				     const unsigned long ladspa_id)
+{
+	void *handle;
+
+	assert(filename);
+	handle = dlopen(filename, RTLD_LAZY);
+	if (handle) {
+		LADSPA_Descriptor_Function fcn = (LADSPA_Descriptor_Function)dlsym(handle, "ladspa_descriptor");
+		if (fcn) {
+			long idx;
+			const LADSPA_Descriptor *d;
+			for (idx = 0; (d = fcn(idx)) != NULL; idx++) {
+/*
+ * avoid locale problems - see ALSA bug#1553
+ */
+#if 0
+				if (strcmp(label, d->Label))
+					continue;
+#else
+                                char *labellocale;
+                                struct lconv *lc;
+                                if (label != NULL) {
+                                        lc = localeconv ();
+                                        labellocale = malloc (strlen (label) + 1);
+                                        if (labellocale == NULL) {
+                                        	dlclose(handle);
+                                                return -ENOMEM;
+					}
+                                        strcpy (labellocale, label);
+                                        if (strrchr(labellocale, '.'))
+                                                *strrchr (labellocale, '.') = *lc->decimal_point;
+                                        if (strcmp(label, d->Label) && strcmp(labellocale, d->Label)) {
+                                                free(labellocale);
+                                                continue;
+                                        }
+                                        free (labellocale);
+                                }
+#endif
+				if (ladspa_id > 0 && d->UniqueID != ladspa_id)
+					continue;
+				plugin->filename = strdup(filename);
+				if (plugin->filename == NULL) {
+					dlclose(handle);
+					return -ENOMEM;
+				}
+				plugin->dl_handle = handle;
+				plugin->desc = d;
+				return 1;
+			}
+		}
+		dlclose(handle);
+	}
+	return -ENOENT;
+}
+
+static int snd_pcm_ladspa_check_dir(snd_pcm_ladspa_plugin_t * const plugin,
+				    const char *path,
+				    const char *label,
+				    const unsigned long ladspa_id)
+{
+	DIR *dir;
+	struct dirent * dirent;
+	int len = strlen(path), err;
+	int need_slash;
+	char *filename;
+	
+	if (len < 1)
+		return 0;
+	need_slash = path[len - 1] != '/';
+	
+	dir = opendir(path);
+	if (!dir)
+		return -ENOENT;
+		
+	while (1) {
+		dirent = readdir(dir);
+		if (!dirent) {
+			closedir(dir);
+			return 0;
+		}
+		
+		filename = malloc(len + strlen(dirent->d_name) + 1 + need_slash);
+		if (filename == NULL) {
+			closedir(dir);
+			return -ENOMEM;
+		}
+		strcpy(filename, path);
+		if (need_slash)
+			strcat(filename, "/");
+		strcat(filename, dirent->d_name);
+		err = snd_pcm_ladspa_check_file(plugin, filename, label, ladspa_id);
+		free(filename);
+		if (err < 0 && err != -ENOENT) {
+			closedir(dir);
+			return err;
+		}
+		if (err > 0) {
+			closedir(dir);
+			return 1;
+		}
+	}
+	/* never reached */
+	return 0;
+}
+
+static int snd_pcm_ladspa_look_for_plugin(snd_pcm_ladspa_plugin_t * const plugin,
+					  const char *path,
+					  const char *label,
+					  const long ladspa_id)
+{
+	const char *c;
+	size_t l;
+	int err;
+	
+	for (c = path; (l = strcspn(c, ": ")) > 0; ) {
+		char name[l + 1];
+		char *fullpath;
+		memcpy(name, c, l);
+		name[l] = 0;
+		err = snd_user_file(name, &fullpath);
+		if (err < 0)
+			return err;
+		err = snd_pcm_ladspa_check_dir(plugin, fullpath, label, ladspa_id);
+		free(fullpath);
+		if (err < 0)
+			return err;
+		if (err > 0)
+			return 0;
+		c += l;
+		if (!*c)
+			break;
+		c++;
+	}
+	return -ENOENT;
+}					  
+
+static int snd_pcm_ladspa_add_default_controls(snd_pcm_ladspa_plugin_t *lplug,
+					       snd_pcm_ladspa_plugin_io_t *io) 
+{
+	unsigned int count = 0;
+	LADSPA_Data *array;
+	unsigned char *initialized;
+	unsigned long idx;
+
+	for (idx = 0; idx < lplug->desc->PortCount; idx++)
+		if ((lplug->desc->PortDescriptors[idx] & (io->pdesc | LADSPA_PORT_CONTROL)) == (io->pdesc | LADSPA_PORT_CONTROL))
+			count++;
+	array = (LADSPA_Data *)calloc(count, sizeof(LADSPA_Data));
+	if (!array)
+		return -ENOMEM;
+	initialized = (unsigned char *)calloc(count, sizeof(unsigned char));
+	if (!initialized) {
+		free(array);
+		return -ENOMEM;
+	}
+	io->controls_size = count;
+	io->controls_initialized = initialized;
+	io->controls = array;
+
+	return 0;
+}	
+
+static int snd_pcm_ladspa_parse_controls(snd_pcm_ladspa_plugin_t *lplug,
+					 snd_pcm_ladspa_plugin_io_t *io,
+					 snd_config_t *controls) 
+{
+	snd_config_iterator_t i, next;
+	int err;
+
+	if (snd_config_get_type(controls) != SND_CONFIG_TYPE_COMPOUND) {
+		SNDERR("controls definition must be a compound");
+		return -EINVAL;
+	}
+
+	snd_config_for_each(i, next, controls) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		long lval;
+		unsigned int port, uval;
+		double dval;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		err = safe_strtol(id, &lval);
+		if (err >= 0) {
+			err = snd_pcm_ladspa_find_port(&port, lplug, io->pdesc | LADSPA_PORT_CONTROL, lval);
+		} else {
+			err = snd_pcm_ladspa_find_sport(&port, lplug, io->pdesc | LADSPA_PORT_CONTROL, id);
+		}
+		if (err < 0) {
+			SNDERR("Unable to find an control port (%s)", id);
+			return err;
+		}
+		if (snd_config_get_ireal(n, &dval) < 0) {
+			SNDERR("Control port %s has not an float or integer value", id);
+			return err;
+		}
+		err = snd_pcm_ladspa_find_port_idx(&uval, lplug, io->pdesc | LADSPA_PORT_CONTROL, port);
+		if (err < 0) {
+			SNDERR("internal error");
+			return err;
+		}
+		io->controls_initialized[uval] = 1;
+		io->controls[uval] = (LADSPA_Data)dval;
+	}
+
+	return 0;
+}
+
+static int snd_pcm_ladspa_parse_bindings(snd_pcm_ladspa_plugin_t *lplug,
+					 snd_pcm_ladspa_plugin_io_t *io,
+					 snd_config_t *bindings) 
+{
+	unsigned int count = 0;
+	unsigned int *array;
+	snd_config_iterator_t i, next;
+	int err;
+
+	if (snd_config_get_type(bindings) != SND_CONFIG_TYPE_COMPOUND) {
+		SNDERR("bindings definition must be a compound");
+		return -EINVAL;
+	}
+	snd_config_for_each(i, next, bindings) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		long channel;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		err = safe_strtol(id, &channel);
+		if (err < 0 || channel < 0) {
+			SNDERR("Invalid channel number: %s", id);
+			return -EINVAL;
+		}
+		if (lplug->policy == SND_PCM_LADSPA_POLICY_DUPLICATE && channel > 0) {
+			SNDERR("Wrong channel specification for duplicate policy");
+			return -EINVAL;
+		}
+		if (count < (unsigned int)(channel + 1))
+			count = (unsigned int)(channel + 1);
+	}
+	if (count > 0) {
+		array = (unsigned int *)calloc(count, sizeof(unsigned int));
+		if (! array)
+			return -ENOMEM;
+		memset(array, 0xff, count * sizeof(unsigned int));
+		io->port_bindings_size = count;
+		io->port_bindings = array;
+		snd_config_for_each(i, next, bindings) {
+			snd_config_t *n = snd_config_iterator_entry(i);
+			const char *id, *sport;
+			long channel, port;
+			if (snd_config_get_id(n, &id) < 0)
+				continue;
+			err = safe_strtol(id, &channel);
+			if (err < 0 || channel < 0) {
+				assert(0);	/* should never happen */
+				return -EINVAL;
+			}
+			err = snd_config_get_integer(n, &port);
+			if (err >= 0) {
+				err = snd_pcm_ladspa_find_port(&array[channel], lplug, io->pdesc | LADSPA_PORT_AUDIO, port);
+				if (err < 0) {
+					SNDERR("Unable to find an audio port (%li) for channel %s", port, id);
+					return err;
+				}
+				continue;
+			}
+			err = snd_config_get_string(n, &sport);
+			if (err < 0) {
+				SNDERR("Invalid LADSPA port field type for %s", id);
+				return -EINVAL;
+			}
+			err = snd_pcm_ladspa_find_sport(&array[channel], lplug, io->pdesc | LADSPA_PORT_AUDIO, sport);
+			if (err < 0) {
+				SNDERR("Unable to find an audio port (%s) for channel %s", sport, id);
+				return err;
+			}
+		}
+	}
+
+	return 0;
+}
+
+static int snd_pcm_ladspa_parse_ioconfig(snd_pcm_ladspa_plugin_t *lplug,
+					 snd_pcm_ladspa_plugin_io_t *io,
+					 snd_config_t *conf)
+{
+	snd_config_iterator_t i, next;
+	snd_config_t *bindings = NULL, *controls = NULL;
+	int err;
+
+	/* always add default controls for both input and output */
+	err = snd_pcm_ladspa_add_default_controls(lplug, io);
+	if (err < 0) {
+		SNDERR("error adding default controls");
+		return err;
+	}
+		
+	if (conf == NULL) {
+		return 0;
+	}
+
+	if (snd_config_get_type(conf) != SND_CONFIG_TYPE_COMPOUND) {
+		SNDERR("input or output definition must be a compound");
+		return -EINVAL;
+	}
+	snd_config_for_each(i, next, conf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (strcmp(id, "bindings") == 0) {
+			bindings = n;
+			continue;
+		}
+		if (strcmp(id, "controls") == 0) {
+			controls = n;
+			continue;
+		}
+	}
+
+	/* ignore values of parameters for output controls */
+	if (controls && !(io->pdesc & LADSPA_PORT_OUTPUT)) {
+ 		err = snd_pcm_ladspa_parse_controls(lplug, io, controls);
+		if (err < 0) 
+			return err;
+	}
+
+	if (bindings) {
+ 		err = snd_pcm_ladspa_parse_bindings(lplug, io, bindings);
+		if (err < 0) 
+			return err;
+	}
+
+
+	return 0;
+}
+
+static int snd_pcm_ladspa_add_plugin(struct list_head *list,
+				     const char *path,
+				     snd_config_t *plugin,
+				     int reverse)
+{
+	snd_config_iterator_t i, next;
+	const char *label = NULL, *filename = NULL;
+	long ladspa_id = 0;
+	int err;
+	snd_pcm_ladspa_plugin_t *lplug;
+	snd_pcm_ladspa_policy_t policy = SND_PCM_LADSPA_POLICY_DUPLICATE;
+	snd_config_t *input = NULL, *output = NULL;
+
+	snd_config_for_each(i, next, plugin) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (strcmp(id, "label") == 0) {
+			err = snd_config_get_string(n, &label);
+			if (err < 0)
+				return err;
+			continue;
+		}
+		if (strcmp(id, "id") == 0) {
+			err = snd_config_get_integer(n, &ladspa_id);
+			if (err < 0)
+				return err;
+			continue;
+		}
+		if (strcmp(id, "filename") == 0) {
+			err = snd_config_get_string(n, &filename);
+			if (err < 0)
+				return err;
+			continue;
+		}
+		if (strcmp(id, "input") == 0) {
+			input = n;
+			continue;
+		}
+		if (strcmp(id, "output") == 0) {
+			output = n;
+			continue;
+		}
+		if (strcmp(id, "policy") == 0) {
+			const char *str;
+			err = snd_config_get_string(n, &str);
+			if (err < 0) {
+				SNDERR("policy field must be a string");
+				return err;
+			}
+			if (strcmp(str, "none") == 0)
+				policy = SND_PCM_LADSPA_POLICY_NONE;
+			else if (strcmp(str, "duplicate") == 0)
+				policy = SND_PCM_LADSPA_POLICY_DUPLICATE;
+			else {
+				SNDERR("unknown policy definition");
+				return -EINVAL;
+			}
+			continue;
+		}
+	}
+	if (label == NULL && ladspa_id <= 0) {
+		SNDERR("no plugin label or id");
+		return -EINVAL;
+	}
+	lplug = (snd_pcm_ladspa_plugin_t *)calloc(1, sizeof(snd_pcm_ladspa_plugin_t));
+	if (lplug == NULL)
+		return -ENOMEM;
+	lplug->policy = policy;
+	lplug->input.pdesc = LADSPA_PORT_INPUT;
+	lplug->output.pdesc = LADSPA_PORT_OUTPUT;
+	INIT_LIST_HEAD(&lplug->instances);
+	if (filename) {
+		err = snd_pcm_ladspa_check_file(lplug, filename, label, ladspa_id);
+		if (err < 0) {
+			SNDERR("Unable to load plugin '%s' ID %li, filename '%s'", label, ladspa_id, filename);
+			free(lplug);
+			return err;
+		}
+	} else {
+		err = snd_pcm_ladspa_look_for_plugin(lplug, path, label, ladspa_id);
+		if (err < 0) {
+			SNDERR("Unable to find or load plugin '%s' ID %li, path '%s'", label, ladspa_id, path);
+			free(lplug);
+			return err;
+		}
+	}
+	if (!reverse) {
+		list_add_tail(&lplug->list, list);
+	} else {
+		list_add(&lplug->list, list);
+	}
+	err = snd_pcm_ladspa_parse_ioconfig(lplug, &lplug->input, input);
+	if (err < 0)
+		return err;
+	err = snd_pcm_ladspa_parse_ioconfig(lplug, &lplug->output, output);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static int snd_pcm_ladspa_build_plugins(struct list_head *list,
+					const char *path,
+					snd_config_t *plugins,
+					int reverse)
+{
+	snd_config_iterator_t i, next;
+	int idx = 0, hit, err;
+
+	if (plugins == NULL)	/* nothing TODO */
+		return 0;
+	if (snd_config_get_type(plugins) != SND_CONFIG_TYPE_COMPOUND) {
+		SNDERR("plugins must be defined inside a compound");
+		return -EINVAL;
+	}
+	do {
+		hit = 0;
+		snd_config_for_each(i, next, plugins) {
+			snd_config_t *n = snd_config_iterator_entry(i);
+			const char *id;
+			long i;
+			if (snd_config_get_id(n, &id) < 0)
+				continue;
+			err = safe_strtol(id, &i);
+			if (err < 0) {
+				SNDERR("id of field %s is not an integer", id);
+				return err;
+			}
+			if (i == idx) {
+				idx++;
+				err = snd_pcm_ladspa_add_plugin(list, path, n, reverse);
+				if (err < 0)
+					return err;
+				hit = 1;
+			}
+		}
+	} while (hit);
+	if (list_empty(list)) {
+		SNDERR("empty plugin list is not accepted");
+		return -EINVAL;
+	}
+	return 0;
+}
+
+/**
+ * \brief Creates a new LADSPA<->ALSA Plugin
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param ladspa_path The path for LADSPA plugins
+ * \param channels Force input channel count to LADSPA plugin chain, 0 = no force (auto)
+ * \param ladspa_pplugins The playback configuration
+ * \param ladspa_cplugins The capture configuration
+ * \param slave Slave PCM handle
+ * \param close_slave When set, the slave PCM handle is closed with copy PCM
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int snd_pcm_ladspa_open(snd_pcm_t **pcmp, const char *name,
+			const char *ladspa_path,
+			unsigned int channels,
+			snd_config_t *ladspa_pplugins,
+			snd_config_t *ladspa_cplugins,
+			snd_pcm_t *slave, int close_slave)
+{
+	snd_pcm_t *pcm;
+	snd_pcm_ladspa_t *ladspa;
+	int err, reverse = 0;
+
+	assert(pcmp && (ladspa_pplugins || ladspa_cplugins) && slave);
+
+	if (!ladspa_path && !(ladspa_path = getenv("LADSPA_PATH")))
+		return -ENOENT;
+	ladspa = calloc(1, sizeof(snd_pcm_ladspa_t));
+	if (!ladspa)
+		return -ENOMEM;
+	snd_pcm_plugin_init(&ladspa->plug);
+	ladspa->plug.init = snd_pcm_ladspa_init;
+	ladspa->plug.read = snd_pcm_ladspa_read_areas;
+	ladspa->plug.write = snd_pcm_ladspa_write_areas;
+	ladspa->plug.undo_read = snd_pcm_plugin_undo_read_generic;
+	ladspa->plug.undo_write = snd_pcm_plugin_undo_write_generic;
+	ladspa->plug.gen.slave = slave;
+	ladspa->plug.gen.close_slave = close_slave;
+
+	INIT_LIST_HEAD(&ladspa->pplugins);
+	INIT_LIST_HEAD(&ladspa->cplugins);
+	ladspa->channels = channels;
+
+	if (slave->stream == SND_PCM_STREAM_PLAYBACK) {
+		err = snd_pcm_ladspa_build_plugins(&ladspa->pplugins, ladspa_path, ladspa_pplugins, reverse);
+		if (err < 0) {
+			snd_pcm_ladspa_free(ladspa);
+			return err;
+		}
+	}
+	if (slave->stream == SND_PCM_STREAM_CAPTURE) {
+		if (ladspa_cplugins == ladspa_pplugins)
+			reverse = 1;
+		err = snd_pcm_ladspa_build_plugins(&ladspa->cplugins, ladspa_path, ladspa_cplugins, reverse);
+		if (err < 0) {
+			snd_pcm_ladspa_free(ladspa);
+			return err;
+		}
+	}
+
+	err = snd_pcm_new(&pcm, SND_PCM_TYPE_LADSPA, name, slave->stream, slave->mode);
+	if (err < 0) {
+		snd_pcm_ladspa_free(ladspa);
+		return err;
+	}
+	pcm->ops = &snd_pcm_ladspa_ops;
+	pcm->fast_ops = &snd_pcm_plugin_fast_ops;
+	pcm->private_data = ladspa;
+	pcm->poll_fd = slave->poll_fd;
+	pcm->poll_events = slave->poll_events;
+	pcm->monotonic = slave->monotonic;
+	snd_pcm_set_hw_ptr(pcm, &ladspa->plug.hw_ptr, -1, 0);
+	snd_pcm_set_appl_ptr(pcm, &ladspa->plug.appl_ptr, -1, 0);
+	*pcmp = pcm;
+
+	return 0;
+}
+
+/*! \page pcm_plugins
+
+\section pcm_plugins_ladpsa Plugin: LADSPA <-> ALSA
+
+This plugin allows to apply a set of LADPSA plugins.
+The input and output format is always #SND_PCM_FORMAT_FLOAT (note: this type
+can be either little or big-endian depending on architecture).
+
+The policy duplicate means that there must be only one binding definition for
+channel zero. This definition is automatically duplicated for all channels.
+If the LADSPA plugin has multiple audio inputs or outputs the policy duplicate
+is automatically switched to policy none.
+
+The plugin serialization works as expected. You can eventually use more
+channels (inputs / outputs) inside the LADPSA plugin chain than processed
+in the ALSA plugin chain. If ALSA channel does not exist for given LADSPA
+input audio port, zero samples are given to this LADSPA port. On the output
+side (ALSA next plugin input), the valid channels are checked, too.
+If specific ALSA channel does not exist, the LADSPA output port is
+connected to a dummy sample area.
+
+Instances of LADSPA plugins are created dynamically.
+
+\code
+pcm.name {
+        type ladspa             # ALSA<->LADSPA PCM
+        slave STR               # Slave name
+        # or
+        slave {                 # Slave definition
+                pcm STR         # Slave PCM name
+                # or
+                pcm { }         # Slave PCM definition
+        }
+        [channels INT]		# count input channels (input to LADSPA plugin chain)
+	[path STR]		# Path (directory) with LADSPA plugins
+	plugins |		# Definition for both directions
+        playback_plugins |	# Definition for playback direction
+	capture_plugins {	# Definition for capture direction
+		N {		# Configuration for LADPSA plugin N
+			[id INT]	# LADSPA plugin ID (for example 1043)
+			[label STR]	# LADSPA plugin label (for example 'delay_5s')
+			[filename STR]	# Full filename of .so library with LADSPA plugin code
+			[policy STR]	# Policy can be 'none' or 'duplicate'
+			input | output {
+				bindings {
+					C INT or STR	# C - channel, INT - audio port index, STR - audio port name
+				}
+				controls {
+				        # valid only in the input block
+					I INT or REAL	# I - control port index, INT or REAL - control value
+					# or
+					STR INT or REAL	# STR - control port name, INT or REAL - control value
+				}
+			}
+		}
+	}
+}
+\endcode
+
+\subsection pcm_plugins_ladspa_funcref Function reference
+
+<UL>
+  <LI>snd_pcm_ladspa_open()
+  <LI>_snd_pcm_ladspa_open()
+</UL>
+
+*/
+
+/**
+ * \brief Creates a new LADSPA<->ALSA PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param root Root configuration node
+ * \param conf Configuration node with LADSPA<->ALSA PCM description
+ * \param stream Stream type
+ * \param mode Stream mode
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int _snd_pcm_ladspa_open(snd_pcm_t **pcmp, const char *name,
+			 snd_config_t *root, snd_config_t *conf, 
+			 snd_pcm_stream_t stream, int mode)
+{
+	snd_config_iterator_t i, next;
+	int err;
+	snd_pcm_t *spcm;
+	snd_config_t *slave = NULL, *sconf;
+	const char *path = NULL;
+	long channels = 0;
+	snd_config_t *plugins = NULL, *pplugins = NULL, *cplugins = NULL;
+	snd_config_for_each(i, next, conf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (snd_pcm_conf_generic_id(id))
+			continue;
+		if (strcmp(id, "slave") == 0) {
+			slave = n;
+			continue;
+		}
+		if (strcmp(id, "path") == 0) {
+			snd_config_get_string(n, &path);
+			continue;
+		}
+		if (strcmp(id, "channels") == 0) {
+			snd_config_get_integer(n, &channels);
+			if (channels > 1024)
+			        channels = 1024;
+                        if (channels < 0)
+                                channels = 0;
+			continue;
+		}
+		if (strcmp(id, "plugins") == 0) {
+			plugins = n;
+			continue;
+		}
+		if (strcmp(id, "playback_plugins") == 0) {
+			pplugins = n;
+			continue;
+		}
+		if (strcmp(id, "capture_plugins") == 0) {
+			cplugins = n;
+			continue;
+		}
+		SNDERR("Unknown field %s", id);
+		return -EINVAL;
+	}
+	if (!slave) {
+		SNDERR("slave is not defined");
+		return -EINVAL;
+	}
+	if (plugins) {
+		if (pplugins || cplugins) {
+			SNDERR("'plugins' definition cannot be combined with 'playback_plugins' or 'capture_plugins'");
+			return -EINVAL;
+		}
+		pplugins = plugins;
+		cplugins = plugins;
+	}
+	err = snd_pcm_slave_conf(root, slave, &sconf, 0);
+	if (err < 0)
+		return err;
+	err = snd_pcm_open_slave(&spcm, root, sconf, stream, mode, conf);
+	snd_config_delete(sconf);
+	if (err < 0)
+		return err;
+	err = snd_pcm_ladspa_open(pcmp, name, path, channels, pplugins, cplugins, spcm, 1);
+	if (err < 0)
+		snd_pcm_close(spcm);
+	return err;
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(_snd_pcm_ladspa_open, SND_PCM_DLSYM_VERSION);
+#endif
diff --git a/src/pcm/pcm_lfloat.c b/src/pcm/pcm_lfloat.c
new file mode 100644
index 0000000..62eb398
--- /dev/null
+++ b/src/pcm/pcm_lfloat.c
@@ -0,0 +1,537 @@
+/**
+ * \file pcm/pcm_lfloat.c
+ * \ingroup PCM_Plugins
+ * \brief PCM Linear<->Float Conversion Plugin Interface
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \date 2001
+ */
+/*
+ *  PCM - Linear Integer <-> Linear Float conversion
+ *  Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+#include <byteswap.h>
+#include "pcm_local.h"
+#include "pcm_plugin.h"
+
+#include "plugin_ops.h"
+
+#ifndef DOC_HIDDEN
+
+typedef float float_t;
+typedef double double_t;
+
+#if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ <= 91)
+#define BUGGY_GCC
+#endif
+
+#ifndef PIC
+/* entry for static linking */
+const char *_snd_module_pcm_lfloat = "";
+#endif
+
+typedef struct {
+	/* This field need to be the first */
+	snd_pcm_plugin_t plug;
+	unsigned int int32_idx;
+	unsigned int float32_idx;
+	snd_pcm_format_t sformat;
+	void (*func)(const snd_pcm_channel_area_t *dst_areas, snd_pcm_uframes_t dst_offset,
+		     const snd_pcm_channel_area_t *src_areas, snd_pcm_uframes_t src_offset,
+		     unsigned int channels, snd_pcm_uframes_t frames,
+		     unsigned int get32idx, unsigned int put32floatidx);
+} snd_pcm_lfloat_t;
+
+int snd_pcm_lfloat_get_s32_index(snd_pcm_format_t format)
+{
+	int width, endian;
+
+	switch (format) {
+	case SND_PCM_FORMAT_FLOAT_LE:
+	case SND_PCM_FORMAT_FLOAT_BE:
+		width = 32;
+		break;
+	case SND_PCM_FORMAT_FLOAT64_LE:
+	case SND_PCM_FORMAT_FLOAT64_BE:
+		width = 64;
+		break;
+	default:
+		return -EINVAL;
+	}
+#ifdef SND_LITTLE_ENDIAN
+	endian = snd_pcm_format_big_endian(format);
+#else
+	endian = snd_pcm_format_little_endian(format);
+#endif
+	return ((width / 32)-1) * 2 + endian;
+}
+
+int snd_pcm_lfloat_put_s32_index(snd_pcm_format_t format)
+{
+	return snd_pcm_lfloat_get_s32_index(format);
+}
+
+#endif /* DOC_HIDDEN */
+
+#ifndef BUGGY_GCC
+
+#ifndef DOC_HIDDEN
+
+void snd_pcm_lfloat_convert_integer_float(const snd_pcm_channel_area_t *dst_areas, snd_pcm_uframes_t dst_offset,
+					  const snd_pcm_channel_area_t *src_areas, snd_pcm_uframes_t src_offset,
+					  unsigned int channels, snd_pcm_uframes_t frames,
+					  unsigned int get32idx, unsigned int put32floatidx)
+{
+#define GET32_LABELS
+#define PUT32F_LABELS
+#include "plugin_ops.h"
+#undef PUT32F_LABELS
+#undef GET32_LABELS
+	void *get32 = get32_labels[get32idx];
+	void *put32float = put32float_labels[put32floatidx];
+	unsigned int channel;
+	for (channel = 0; channel < channels; ++channel) {
+		const char *src;
+		char *dst;
+		int src_step, dst_step;
+		snd_pcm_uframes_t frames1;
+		int32_t sample = 0;
+		snd_tmp_float_t tmp_float;
+		snd_tmp_double_t tmp_double;
+		const snd_pcm_channel_area_t *src_area = &src_areas[channel];
+		const snd_pcm_channel_area_t *dst_area = &dst_areas[channel];
+		src = snd_pcm_channel_area_addr(src_area, src_offset);
+		dst = snd_pcm_channel_area_addr(dst_area, dst_offset);
+		src_step = snd_pcm_channel_area_step(src_area);
+		dst_step = snd_pcm_channel_area_step(dst_area);
+		frames1 = frames;
+		while (frames1-- > 0) {
+			goto *get32;
+#define GET32_END sample_loaded
+#include "plugin_ops.h"
+#undef GET32_END
+		sample_loaded:
+			goto *put32float;
+#define PUT32F_END sample_put
+#include "plugin_ops.h"
+#undef PUT32F_END
+		sample_put:
+			src += src_step;
+			dst += dst_step;
+		}
+	}
+}
+
+void snd_pcm_lfloat_convert_float_integer(const snd_pcm_channel_area_t *dst_areas, snd_pcm_uframes_t dst_offset,
+					  const snd_pcm_channel_area_t *src_areas, snd_pcm_uframes_t src_offset,
+					  unsigned int channels, snd_pcm_uframes_t frames,
+					  unsigned int put32idx, unsigned int get32floatidx)
+{
+#define PUT32_LABELS
+#define GET32F_LABELS
+#include "plugin_ops.h"
+#undef GET32F_LABELS
+#undef PUT32_LABELS
+	void *put32 = put32_labels[put32idx];
+	void *get32float = get32float_labels[get32floatidx];
+	unsigned int channel;
+	for (channel = 0; channel < channels; ++channel) {
+		const char *src;
+		char *dst;
+		int src_step, dst_step;
+		snd_pcm_uframes_t frames1;
+		int32_t sample = 0;
+		snd_tmp_float_t tmp_float;
+		snd_tmp_double_t tmp_double;
+		const snd_pcm_channel_area_t *src_area = &src_areas[channel];
+		const snd_pcm_channel_area_t *dst_area = &dst_areas[channel];
+		src = snd_pcm_channel_area_addr(src_area, src_offset);
+		dst = snd_pcm_channel_area_addr(dst_area, dst_offset);
+		src_step = snd_pcm_channel_area_step(src_area);
+		dst_step = snd_pcm_channel_area_step(dst_area);
+		frames1 = frames;
+		while (frames1-- > 0) {
+			goto *get32float;
+#define GET32F_END sample_loaded
+#include "plugin_ops.h"
+#undef GET32F_END
+		sample_loaded:
+			goto *put32;
+#define PUT32_END sample_put
+#include "plugin_ops.h"
+#undef PUT32_END
+		sample_put:
+			src += src_step;
+			dst += dst_step;
+		}
+	}
+}
+
+#endif /* DOC_HIDDEN */
+
+static int snd_pcm_lfloat_hw_refine_cprepare(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	snd_pcm_lfloat_t *lfloat = pcm->private_data;
+	int err;
+	snd_pcm_access_mask_t access_mask = { SND_PCM_ACCBIT_SHM };
+	snd_pcm_format_mask_t lformat_mask = { SND_PCM_FMTBIT_LINEAR };
+	snd_pcm_format_mask_t fformat_mask = { SND_PCM_FMTBIT_FLOAT };
+	err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_ACCESS,
+					 &access_mask);
+	if (err < 0)
+		return err;
+	err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_FORMAT,
+					 snd_pcm_format_linear(lfloat->sformat) ?
+					 &fformat_mask : &lformat_mask);
+	if (err < 0)
+		return err;
+	err = _snd_pcm_hw_params_set_subformat(params, SND_PCM_SUBFORMAT_STD);
+	if (err < 0)
+		return err;
+	params->info &= ~(SND_PCM_INFO_MMAP | SND_PCM_INFO_MMAP_VALID);
+	return 0;
+}
+
+static int snd_pcm_lfloat_hw_refine_sprepare(snd_pcm_t *pcm, snd_pcm_hw_params_t *sparams)
+{
+	snd_pcm_lfloat_t *lfloat = pcm->private_data;
+	snd_pcm_access_mask_t saccess_mask = { SND_PCM_ACCBIT_MMAP };
+	_snd_pcm_hw_params_any(sparams);
+	_snd_pcm_hw_param_set_mask(sparams, SND_PCM_HW_PARAM_ACCESS,
+				   &saccess_mask);
+	_snd_pcm_hw_params_set_format(sparams, lfloat->sformat);
+	_snd_pcm_hw_params_set_subformat(sparams, SND_PCM_SUBFORMAT_STD);
+	return 0;
+}
+
+static int snd_pcm_lfloat_hw_refine_schange(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params,
+					    snd_pcm_hw_params_t *sparams)
+{
+	int err;
+	unsigned int links = (SND_PCM_HW_PARBIT_CHANNELS |
+			      SND_PCM_HW_PARBIT_RATE |
+			      SND_PCM_HW_PARBIT_PERIOD_SIZE |
+			      SND_PCM_HW_PARBIT_BUFFER_SIZE |
+			      SND_PCM_HW_PARBIT_PERIODS |
+			      SND_PCM_HW_PARBIT_PERIOD_TIME |
+			      SND_PCM_HW_PARBIT_BUFFER_TIME |
+			      SND_PCM_HW_PARBIT_TICK_TIME);
+	err = _snd_pcm_hw_params_refine(sparams, links, params);
+	if (err < 0)
+		return err;
+	return 0;
+}
+	
+static int snd_pcm_lfloat_hw_refine_cchange(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params,
+					    snd_pcm_hw_params_t *sparams)
+{
+	int err;
+	unsigned int links = (SND_PCM_HW_PARBIT_CHANNELS |
+			      SND_PCM_HW_PARBIT_RATE |
+			      SND_PCM_HW_PARBIT_PERIOD_SIZE |
+			      SND_PCM_HW_PARBIT_BUFFER_SIZE |
+			      SND_PCM_HW_PARBIT_PERIODS |
+			      SND_PCM_HW_PARBIT_PERIOD_TIME |
+			      SND_PCM_HW_PARBIT_BUFFER_TIME |
+			      SND_PCM_HW_PARBIT_TICK_TIME);
+	err = _snd_pcm_hw_params_refine(params, links, sparams);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static int snd_pcm_lfloat_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	return snd_pcm_hw_refine_slave(pcm, params,
+				       snd_pcm_lfloat_hw_refine_cprepare,
+				       snd_pcm_lfloat_hw_refine_cchange,
+				       snd_pcm_lfloat_hw_refine_sprepare,
+				       snd_pcm_lfloat_hw_refine_schange,
+				       snd_pcm_generic_hw_refine);
+}
+
+static int snd_pcm_lfloat_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	snd_pcm_lfloat_t *lfloat = pcm->private_data;
+	snd_pcm_t *slave = lfloat->plug.gen.slave;
+	snd_pcm_format_t src_format, dst_format;
+	int err = snd_pcm_hw_params_slave(pcm, params,
+					  snd_pcm_lfloat_hw_refine_cchange,
+					  snd_pcm_lfloat_hw_refine_sprepare,
+					  snd_pcm_lfloat_hw_refine_schange,
+					  snd_pcm_generic_hw_params);
+	if (err < 0)
+		return err;
+	if (pcm->stream == SND_PCM_STREAM_PLAYBACK) {
+		err = INTERNAL(snd_pcm_hw_params_get_format)(params, &src_format);
+		dst_format = slave->format;
+	} else {
+		src_format = slave->format;
+		err = INTERNAL(snd_pcm_hw_params_get_format)(params, &dst_format);
+	}
+	if (snd_pcm_format_linear(src_format)) {
+		lfloat->int32_idx = snd_pcm_linear_get32_index(src_format, SND_PCM_FORMAT_S32);
+		lfloat->float32_idx = snd_pcm_lfloat_put_s32_index(dst_format);
+		lfloat->func = snd_pcm_lfloat_convert_integer_float;
+	} else {
+		lfloat->int32_idx = snd_pcm_linear_put32_index(SND_PCM_FORMAT_S32, dst_format);
+		lfloat->float32_idx = snd_pcm_lfloat_get_s32_index(src_format);
+		lfloat->func = snd_pcm_lfloat_convert_float_integer;
+	}
+	return 0;
+}
+
+static snd_pcm_uframes_t
+snd_pcm_lfloat_write_areas(snd_pcm_t *pcm,
+			   const snd_pcm_channel_area_t *areas,
+			   snd_pcm_uframes_t offset,
+			   snd_pcm_uframes_t size,
+			   const snd_pcm_channel_area_t *slave_areas,
+			   snd_pcm_uframes_t slave_offset,
+			   snd_pcm_uframes_t *slave_sizep)
+{
+	snd_pcm_lfloat_t *lfloat = pcm->private_data;
+	if (size > *slave_sizep)
+		size = *slave_sizep;
+	lfloat->func(slave_areas, slave_offset,
+		     areas, offset, 
+		     pcm->channels, size,
+		     lfloat->int32_idx, lfloat->float32_idx);
+	*slave_sizep = size;
+	return size;
+}
+
+static snd_pcm_uframes_t
+snd_pcm_lfloat_read_areas(snd_pcm_t *pcm,
+			  const snd_pcm_channel_area_t *areas,
+			  snd_pcm_uframes_t offset,
+			  snd_pcm_uframes_t size,
+			  const snd_pcm_channel_area_t *slave_areas,
+			  snd_pcm_uframes_t slave_offset,
+			  snd_pcm_uframes_t *slave_sizep)
+{
+	snd_pcm_lfloat_t *lfloat = pcm->private_data;
+	if (size > *slave_sizep)
+		size = *slave_sizep;
+	lfloat->func(areas, offset, 
+		     slave_areas, slave_offset,
+		     pcm->channels, size,
+		     lfloat->int32_idx, lfloat->float32_idx);
+	*slave_sizep = size;
+	return size;
+}
+
+static void snd_pcm_lfloat_dump(snd_pcm_t *pcm, snd_output_t *out)
+{
+	snd_pcm_lfloat_t *lfloat = pcm->private_data;
+	snd_output_printf(out, "Linear Integer <-> Linear Float conversion PCM (%s)\n", 
+		snd_pcm_format_name(lfloat->sformat));
+	if (pcm->setup) {
+		snd_output_printf(out, "Its setup is:\n");
+		snd_pcm_dump_setup(pcm, out);
+	}
+	snd_output_printf(out, "Slave: ");
+	snd_pcm_dump(lfloat->plug.gen.slave, out);
+}
+
+static const snd_pcm_ops_t snd_pcm_lfloat_ops = {
+	.close = snd_pcm_generic_close,
+	.info = snd_pcm_generic_info,
+	.hw_refine = snd_pcm_lfloat_hw_refine,
+	.hw_params = snd_pcm_lfloat_hw_params,
+	.hw_free = snd_pcm_generic_hw_free,
+	.sw_params = snd_pcm_generic_sw_params,
+	.channel_info = snd_pcm_generic_channel_info,
+	.dump = snd_pcm_lfloat_dump,
+	.nonblock = snd_pcm_generic_nonblock,
+	.async = snd_pcm_generic_async,
+	.mmap = snd_pcm_generic_mmap,
+	.munmap = snd_pcm_generic_munmap,
+};
+
+/**
+ * \brief Creates a new linear conversion PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param sformat Slave (destination) format
+ * \param slave Slave PCM handle
+ * \param close_slave When set, the slave PCM handle is closed with copy PCM
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int snd_pcm_lfloat_open(snd_pcm_t **pcmp, const char *name, snd_pcm_format_t sformat, snd_pcm_t *slave, int close_slave)
+{
+	snd_pcm_t *pcm;
+	snd_pcm_lfloat_t *lfloat;
+	int err;
+	assert(pcmp && slave);
+	if (snd_pcm_format_linear(sformat) != 1 &&
+	    snd_pcm_format_float(sformat) != 1)
+		return -EINVAL;
+	lfloat = calloc(1, sizeof(snd_pcm_lfloat_t));
+	if (!lfloat) {
+		return -ENOMEM;
+	}
+	snd_pcm_plugin_init(&lfloat->plug);
+	lfloat->sformat = sformat;
+	lfloat->plug.read = snd_pcm_lfloat_read_areas;
+	lfloat->plug.write = snd_pcm_lfloat_write_areas;
+	lfloat->plug.undo_read = snd_pcm_plugin_undo_read_generic;
+	lfloat->plug.undo_write = snd_pcm_plugin_undo_write_generic;
+	lfloat->plug.gen.slave = slave;
+	lfloat->plug.gen.close_slave = close_slave;
+
+	err = snd_pcm_new(&pcm, SND_PCM_TYPE_LINEAR_FLOAT, name, slave->stream, slave->mode);
+	if (err < 0) {
+		free(lfloat);
+		return err;
+	}
+	pcm->ops = &snd_pcm_lfloat_ops;
+	pcm->fast_ops = &snd_pcm_plugin_fast_ops;
+	pcm->private_data = lfloat;
+	pcm->poll_fd = slave->poll_fd;
+	pcm->poll_events = slave->poll_events;
+	pcm->monotonic = slave->monotonic;
+	snd_pcm_set_hw_ptr(pcm, &lfloat->plug.hw_ptr, -1, 0);
+	snd_pcm_set_appl_ptr(pcm, &lfloat->plug.appl_ptr, -1, 0);
+	*pcmp = pcm;
+	
+	return 0;
+}
+
+/*! \page pcm_plugins
+
+\section pcm_plugins_lfloat Plugin: linear<->float
+
+This plugin converts linear to float samples and float to linear samples from master
+linear<->float conversion PCM to given slave PCM. The channel count, format and rate must
+match for both of them.
+
+\code
+pcm.name {
+        type lfloat             # Linear<->Float conversion PCM
+        slave STR               # Slave name
+        # or
+        slave {                 # Slave definition
+                pcm STR         # Slave PCM name
+                # or
+                pcm { }         # Slave PCM definition
+                format STR      # Slave format
+        }
+}
+\endcode
+
+\subsection pcm_plugins_lfloat_funcref Function reference
+
+<UL>
+  <LI>snd_pcm_lfloat_open()
+  <LI>_snd_pcm_lfloat_open()
+</UL>
+
+*/
+
+/**
+ * \brief Creates a new linear<->float conversion PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param root Root configuration node
+ * \param conf Configuration node with copy PCM description
+ * \param stream Stream type
+ * \param mode Stream mode
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int _snd_pcm_lfloat_open(snd_pcm_t **pcmp, const char *name,
+			 snd_config_t *root, snd_config_t *conf, 
+			 snd_pcm_stream_t stream, int mode)
+{
+	snd_config_iterator_t i, next;
+	int err;
+	snd_pcm_t *spcm;
+	snd_config_t *slave = NULL, *sconf;
+	snd_pcm_format_t sformat;
+	snd_config_for_each(i, next, conf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (snd_pcm_conf_generic_id(id))
+			continue;
+		if (strcmp(id, "slave") == 0) {
+			slave = n;
+			continue;
+		}
+		SNDERR("Unknown field %s", id);
+		return -EINVAL;
+	}
+	if (!slave) {
+		SNDERR("slave is not defined");
+		return -EINVAL;
+	}
+	err = snd_pcm_slave_conf(root, slave, &sconf, 1,
+				 SND_PCM_HW_PARAM_FORMAT, SCONF_MANDATORY, &sformat);
+	if (err < 0)
+		return err;
+	if (snd_pcm_format_linear(sformat) != 1 &&
+	    snd_pcm_format_float(sformat) != 1) {
+		snd_config_delete(sconf);
+		SNDERR("slave format is not linear integer or linear float");
+		return -EINVAL;
+	}
+	err = snd_pcm_open_slave(&spcm, root, sconf, stream, mode, conf);
+	snd_config_delete(sconf);
+	if (err < 0)
+		return err;
+	err = snd_pcm_lfloat_open(pcmp, name, sformat, spcm, 1);
+	if (err < 0)
+		snd_pcm_close(spcm);
+	return err;
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(_snd_pcm_lfloat_open, SND_PCM_DLSYM_VERSION);
+#endif
+
+#else /* BUGGY_GCC */
+
+int snd_pcm_lfloat_open(snd_pcm_t **pcmp ATTRIBUTE_UNUSED,
+			const char *name ATTRIBUTE_UNUSED,
+			snd_pcm_format_t sformat ATTRIBUTE_UNUSED,
+			snd_pcm_t *slave ATTRIBUTE_UNUSED,
+			int close_slave ATTRIBUTE_UNUSED)
+{
+	SNDERR("please, upgrade your GCC to use lfloat plugin");
+	return -EINVAL;
+}
+
+int _snd_pcm_lfloat_open(snd_pcm_t **pcmp ATTRIBUTE_UNUSED,
+			 const char *name ATTRIBUTE_UNUSED,
+			 snd_config_t *root ATTRIBUTE_UNUSED,
+			 snd_config_t *conf ATTRIBUTE_UNUSED, 
+			 snd_pcm_stream_t stream ATTRIBUTE_UNUSED,
+			 int mode ATTRIBUTE_UNUSED)
+{
+	SNDERR("please, upgrade your GCC to use lfloat plugin");
+	return -EINVAL;
+}
+
+#endif /* BUGGY_GCC */
diff --git a/src/pcm/pcm_linear.c b/src/pcm/pcm_linear.c
new file mode 100644
index 0000000..e85dfaa
--- /dev/null
+++ b/src/pcm/pcm_linear.c
@@ -0,0 +1,582 @@
+/**
+ * \file pcm/pcm_linear.c
+ * \ingroup PCM_Plugins
+ * \brief PCM Linear Conversion Plugin Interface
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \date 2000-2001
+ */
+/*
+ *  PCM - Linear conversion
+ *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+#include <byteswap.h>
+#include "pcm_local.h"
+#include "pcm_plugin.h"
+
+#include "plugin_ops.h"
+
+#ifndef PIC
+/* entry for static linking */
+const char *_snd_module_pcm_linear = "";
+#endif
+
+#ifndef DOC_HIDDEN
+typedef struct {
+	/* This field need to be the first */
+	snd_pcm_plugin_t plug;
+	unsigned int use_getput;
+	unsigned int conv_idx;
+	unsigned int get_idx, put_idx;
+	snd_pcm_format_t sformat;
+} snd_pcm_linear_t;
+#endif
+
+#ifndef DOC_HIDDEN
+
+int snd_pcm_linear_convert_index(snd_pcm_format_t src_format,
+				 snd_pcm_format_t dst_format)
+{
+	int src_endian, dst_endian, sign, src_width, dst_width;
+
+	sign = (snd_pcm_format_signed(src_format) !=
+		snd_pcm_format_signed(dst_format));
+#ifdef SND_LITTLE_ENDIAN
+	src_endian = snd_pcm_format_big_endian(src_format);
+	dst_endian = snd_pcm_format_big_endian(dst_format);
+#else
+	src_endian = snd_pcm_format_little_endian(src_format);
+	dst_endian = snd_pcm_format_little_endian(dst_format);
+#endif
+
+	if (src_endian < 0)
+		src_endian = 0;
+	if (dst_endian < 0)
+		dst_endian = 0;
+
+	src_width = snd_pcm_format_width(src_format) / 8 - 1;
+	dst_width = snd_pcm_format_width(dst_format) / 8 - 1;
+
+	return src_width * 32 + src_endian * 16 + sign * 8 + dst_width * 2 + dst_endian;
+}
+
+int snd_pcm_linear_get_index(snd_pcm_format_t src_format, snd_pcm_format_t dst_format)
+{
+	int sign, width, pwidth, endian;
+	sign = (snd_pcm_format_signed(src_format) != 
+		snd_pcm_format_signed(dst_format));
+#ifdef SND_LITTLE_ENDIAN
+	endian = snd_pcm_format_big_endian(src_format);
+#else
+	endian = snd_pcm_format_little_endian(src_format);
+#endif
+	if (endian < 0)
+		endian = 0;
+	pwidth = snd_pcm_format_physical_width(src_format);
+	width = snd_pcm_format_width(src_format);
+	if (pwidth == 24) {
+		switch (width) {
+		case 24:
+			width = 0; break;
+		case 20:
+			width = 1; break;
+		case 18:
+		default:
+			width = 2; break;
+		}
+		return width * 4 + endian * 2 + sign + 16;
+	} else {
+		width = width / 8 - 1;
+		return width * 4 + endian * 2 + sign;
+	}
+}
+
+int snd_pcm_linear_get32_index(snd_pcm_format_t src_format, snd_pcm_format_t dst_format)
+{
+	return snd_pcm_linear_get_index(src_format, dst_format);
+}
+
+int snd_pcm_linear_put_index(snd_pcm_format_t src_format, snd_pcm_format_t dst_format)
+{
+	int sign, width, pwidth, endian;
+	sign = (snd_pcm_format_signed(src_format) != 
+		snd_pcm_format_signed(dst_format));
+#ifdef SND_LITTLE_ENDIAN
+	endian = snd_pcm_format_big_endian(dst_format);
+#else
+	endian = snd_pcm_format_little_endian(dst_format);
+#endif
+	if (endian < 0)
+		endian = 0;
+	pwidth = snd_pcm_format_physical_width(dst_format);
+	width = snd_pcm_format_width(dst_format);
+	if (pwidth == 24) {
+		switch (width) {
+		case 24:
+			width = 0; break;
+		case 20:
+			width = 1; break;
+		case 18:
+		default:
+			width = 2; break;
+		}
+		return width * 4 + endian * 2 + sign + 16;
+	} else {
+		width = width / 8 - 1;
+		return width * 4 + endian * 2 + sign;
+	}
+}
+
+int snd_pcm_linear_put32_index(snd_pcm_format_t src_format, snd_pcm_format_t dst_format)
+{
+	int sign, width, pwidth, endian;
+	sign = (snd_pcm_format_signed(src_format) != 
+		snd_pcm_format_signed(dst_format));
+#ifdef SND_LITTLE_ENDIAN
+	endian = snd_pcm_format_big_endian(dst_format);
+#else
+	endian = snd_pcm_format_little_endian(dst_format);
+#endif
+	if (endian < 0)
+		endian = 0;
+	pwidth = snd_pcm_format_physical_width(dst_format);
+	width = snd_pcm_format_width(dst_format);
+	if (pwidth == 24) {
+		switch (width) {
+		case 24:
+			width = 0; break;
+		case 20:
+			width = 1; break;
+		case 18:
+		default:
+			width = 2; break;
+		}
+		return width * 4 + endian * 2 + sign + 16;
+	} else {
+		width = width / 8 - 1;
+		return width * 4 + endian * 2 + sign;
+	}
+}
+
+void snd_pcm_linear_convert(const snd_pcm_channel_area_t *dst_areas, snd_pcm_uframes_t dst_offset,
+			    const snd_pcm_channel_area_t *src_areas, snd_pcm_uframes_t src_offset,
+			    unsigned int channels, snd_pcm_uframes_t frames,
+			    unsigned int convidx)
+{
+#define CONV_LABELS
+#include "plugin_ops.h"
+#undef CONV_LABELS
+	void *conv = conv_labels[convidx];
+	unsigned int channel;
+	for (channel = 0; channel < channels; ++channel) {
+		const char *src;
+		char *dst;
+		int src_step, dst_step;
+		snd_pcm_uframes_t frames1;
+		const snd_pcm_channel_area_t *src_area = &src_areas[channel];
+		const snd_pcm_channel_area_t *dst_area = &dst_areas[channel];
+		src = snd_pcm_channel_area_addr(src_area, src_offset);
+		dst = snd_pcm_channel_area_addr(dst_area, dst_offset);
+		src_step = snd_pcm_channel_area_step(src_area);
+		dst_step = snd_pcm_channel_area_step(dst_area);
+		frames1 = frames;
+		while (frames1-- > 0) {
+			goto *conv;
+#define CONV_END after
+#include "plugin_ops.h"
+#undef CONV_END
+		after:
+			src += src_step;
+			dst += dst_step;
+		}
+	}
+}
+
+void snd_pcm_linear_getput(const snd_pcm_channel_area_t *dst_areas, snd_pcm_uframes_t dst_offset,
+			   const snd_pcm_channel_area_t *src_areas, snd_pcm_uframes_t src_offset,
+			   unsigned int channels, snd_pcm_uframes_t frames,
+			   unsigned int get_idx, unsigned int put_idx)
+{
+#define CONV24_LABELS
+#include "plugin_ops.h"
+#undef CONV24_LABELS
+	void *get = get32_labels[get_idx];
+	void *put = put32_labels[put_idx];
+	unsigned int channel;
+	u_int32_t sample = 0;
+	for (channel = 0; channel < channels; ++channel) {
+		const char *src;
+		char *dst;
+		int src_step, dst_step;
+		snd_pcm_uframes_t frames1;
+		const snd_pcm_channel_area_t *src_area = &src_areas[channel];
+		const snd_pcm_channel_area_t *dst_area = &dst_areas[channel];
+		src = snd_pcm_channel_area_addr(src_area, src_offset);
+		dst = snd_pcm_channel_area_addr(dst_area, dst_offset);
+		src_step = snd_pcm_channel_area_step(src_area);
+		dst_step = snd_pcm_channel_area_step(dst_area);
+		frames1 = frames;
+		while (frames1-- > 0) {
+			goto *get;
+#define CONV24_END after
+#include "plugin_ops.h"
+#undef CONV24_END
+		after:
+			src += src_step;
+			dst += dst_step;
+		}
+	}
+}
+
+#endif /* DOC_HIDDEN */
+
+static int snd_pcm_linear_hw_refine_cprepare(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params)
+{
+	int err;
+	snd_pcm_access_mask_t access_mask = { SND_PCM_ACCBIT_SHM };
+	snd_pcm_format_mask_t format_mask = { SND_PCM_FMTBIT_LINEAR };
+	err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_ACCESS,
+					 &access_mask);
+	if (err < 0)
+		return err;
+	err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_FORMAT,
+					 &format_mask);
+	if (err < 0)
+		return err;
+	err = _snd_pcm_hw_params_set_subformat(params, SND_PCM_SUBFORMAT_STD);
+	if (err < 0)
+		return err;
+	params->info &= ~(SND_PCM_INFO_MMAP | SND_PCM_INFO_MMAP_VALID);
+	return 0;
+}
+
+static int snd_pcm_linear_hw_refine_sprepare(snd_pcm_t *pcm, snd_pcm_hw_params_t *sparams)
+{
+	snd_pcm_linear_t *linear = pcm->private_data;
+	snd_pcm_access_mask_t saccess_mask = { SND_PCM_ACCBIT_MMAP };
+	_snd_pcm_hw_params_any(sparams);
+	_snd_pcm_hw_param_set_mask(sparams, SND_PCM_HW_PARAM_ACCESS,
+				   &saccess_mask);
+	_snd_pcm_hw_params_set_format(sparams, linear->sformat);
+	_snd_pcm_hw_params_set_subformat(sparams, SND_PCM_SUBFORMAT_STD);
+	return 0;
+}
+
+static int snd_pcm_linear_hw_refine_schange(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params,
+					    snd_pcm_hw_params_t *sparams)
+{
+	int err;
+	unsigned int links = (SND_PCM_HW_PARBIT_CHANNELS |
+			      SND_PCM_HW_PARBIT_RATE |
+			      SND_PCM_HW_PARBIT_PERIOD_SIZE |
+			      SND_PCM_HW_PARBIT_BUFFER_SIZE |
+			      SND_PCM_HW_PARBIT_PERIODS |
+			      SND_PCM_HW_PARBIT_PERIOD_TIME |
+			      SND_PCM_HW_PARBIT_BUFFER_TIME |
+			      SND_PCM_HW_PARBIT_TICK_TIME);
+	err = _snd_pcm_hw_params_refine(sparams, links, params);
+	if (err < 0)
+		return err;
+	return 0;
+}
+	
+static int snd_pcm_linear_hw_refine_cchange(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params,
+					    snd_pcm_hw_params_t *sparams)
+{
+	int err;
+	unsigned int links = (SND_PCM_HW_PARBIT_CHANNELS |
+			      SND_PCM_HW_PARBIT_RATE |
+			      SND_PCM_HW_PARBIT_PERIOD_SIZE |
+			      SND_PCM_HW_PARBIT_BUFFER_SIZE |
+			      SND_PCM_HW_PARBIT_PERIODS |
+			      SND_PCM_HW_PARBIT_PERIOD_TIME |
+			      SND_PCM_HW_PARBIT_BUFFER_TIME |
+			      SND_PCM_HW_PARBIT_TICK_TIME);
+	err = _snd_pcm_hw_params_refine(params, links, sparams);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static int snd_pcm_linear_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	return snd_pcm_hw_refine_slave(pcm, params,
+				       snd_pcm_linear_hw_refine_cprepare,
+				       snd_pcm_linear_hw_refine_cchange,
+				       snd_pcm_linear_hw_refine_sprepare,
+				       snd_pcm_linear_hw_refine_schange,
+				       snd_pcm_generic_hw_refine);
+}
+
+static int snd_pcm_linear_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	snd_pcm_linear_t *linear = pcm->private_data;
+	snd_pcm_format_t format;
+	int err = snd_pcm_hw_params_slave(pcm, params,
+					  snd_pcm_linear_hw_refine_cchange,
+					  snd_pcm_linear_hw_refine_sprepare,
+					  snd_pcm_linear_hw_refine_schange,
+					  snd_pcm_generic_hw_params);
+	if (err < 0)
+		return err;
+	err = INTERNAL(snd_pcm_hw_params_get_format)(params, &format);
+	if (err < 0)
+		return err;
+	linear->use_getput = (snd_pcm_format_physical_width(format) == 24 ||
+			      snd_pcm_format_physical_width(linear->sformat) == 24);
+	if (linear->use_getput) {
+		if (pcm->stream == SND_PCM_STREAM_PLAYBACK) {
+			linear->get_idx = snd_pcm_linear_get32_index(format, SND_PCM_FORMAT_S32);
+			linear->put_idx = snd_pcm_linear_put32_index(SND_PCM_FORMAT_S32, linear->sformat);
+		} else {
+			linear->get_idx = snd_pcm_linear_get32_index(linear->sformat, SND_PCM_FORMAT_S32);
+			linear->put_idx = snd_pcm_linear_put32_index(SND_PCM_FORMAT_S32, format);
+		}
+	} else {
+		if (pcm->stream == SND_PCM_STREAM_PLAYBACK)
+			linear->conv_idx = snd_pcm_linear_convert_index(format,
+									linear->sformat);
+		else
+			linear->conv_idx = snd_pcm_linear_convert_index(linear->sformat,
+									format);
+	}
+	return 0;
+}
+
+static snd_pcm_uframes_t
+snd_pcm_linear_write_areas(snd_pcm_t *pcm,
+			   const snd_pcm_channel_area_t *areas,
+			   snd_pcm_uframes_t offset,
+			   snd_pcm_uframes_t size,
+			   const snd_pcm_channel_area_t *slave_areas,
+			   snd_pcm_uframes_t slave_offset,
+			   snd_pcm_uframes_t *slave_sizep)
+{
+	snd_pcm_linear_t *linear = pcm->private_data;
+	if (size > *slave_sizep)
+		size = *slave_sizep;
+	if (linear->use_getput)
+		snd_pcm_linear_getput(slave_areas, slave_offset,
+				      areas, offset, 
+				      pcm->channels, size,
+				      linear->get_idx, linear->put_idx);
+	else
+		snd_pcm_linear_convert(slave_areas, slave_offset,
+				       areas, offset, 
+				       pcm->channels, size, linear->conv_idx);
+	*slave_sizep = size;
+	return size;
+}
+
+static snd_pcm_uframes_t
+snd_pcm_linear_read_areas(snd_pcm_t *pcm,
+			  const snd_pcm_channel_area_t *areas,
+			  snd_pcm_uframes_t offset,
+			  snd_pcm_uframes_t size,
+			  const snd_pcm_channel_area_t *slave_areas,
+			  snd_pcm_uframes_t slave_offset,
+			  snd_pcm_uframes_t *slave_sizep)
+{
+	snd_pcm_linear_t *linear = pcm->private_data;
+	if (size > *slave_sizep)
+		size = *slave_sizep;
+	if (linear->use_getput)
+		snd_pcm_linear_getput(areas, offset, 
+				      slave_areas, slave_offset,
+				      pcm->channels, size,
+				      linear->get_idx, linear->put_idx);
+	else
+		snd_pcm_linear_convert(areas, offset, 
+				       slave_areas, slave_offset,
+				       pcm->channels, size, linear->conv_idx);
+	*slave_sizep = size;
+	return size;
+}
+
+static void snd_pcm_linear_dump(snd_pcm_t *pcm, snd_output_t *out)
+{
+	snd_pcm_linear_t *linear = pcm->private_data;
+	snd_output_printf(out, "Linear conversion PCM (%s)\n", 
+		snd_pcm_format_name(linear->sformat));
+	if (pcm->setup) {
+		snd_output_printf(out, "Its setup is:\n");
+		snd_pcm_dump_setup(pcm, out);
+	}
+	snd_output_printf(out, "Slave: ");
+	snd_pcm_dump(linear->plug.gen.slave, out);
+}
+
+static const snd_pcm_ops_t snd_pcm_linear_ops = {
+	.close = snd_pcm_generic_close,
+	.info = snd_pcm_generic_info,
+	.hw_refine = snd_pcm_linear_hw_refine,
+	.hw_params = snd_pcm_linear_hw_params,
+	.hw_free = snd_pcm_generic_hw_free,
+	.sw_params = snd_pcm_generic_sw_params,
+	.channel_info = snd_pcm_generic_channel_info,
+	.dump = snd_pcm_linear_dump,
+	.nonblock = snd_pcm_generic_nonblock,
+	.async = snd_pcm_generic_async,
+	.mmap = snd_pcm_generic_mmap,
+	.munmap = snd_pcm_generic_munmap,
+};
+
+
+/**
+ * \brief Creates a new linear conversion PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param sformat Slave (destination) format
+ * \param slave Slave PCM handle
+ * \param close_slave When set, the slave PCM handle is closed with copy PCM
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int snd_pcm_linear_open(snd_pcm_t **pcmp, const char *name, snd_pcm_format_t sformat, snd_pcm_t *slave, int close_slave)
+{
+	snd_pcm_t *pcm;
+	snd_pcm_linear_t *linear;
+	int err;
+	assert(pcmp && slave);
+	if (snd_pcm_format_linear(sformat) != 1)
+		return -EINVAL;
+	linear = calloc(1, sizeof(snd_pcm_linear_t));
+	if (!linear) {
+		return -ENOMEM;
+	}
+	snd_pcm_plugin_init(&linear->plug);
+	linear->sformat = sformat;
+	linear->plug.read = snd_pcm_linear_read_areas;
+	linear->plug.write = snd_pcm_linear_write_areas;
+	linear->plug.undo_read = snd_pcm_plugin_undo_read_generic;
+	linear->plug.undo_write = snd_pcm_plugin_undo_write_generic;
+	linear->plug.gen.slave = slave;
+	linear->plug.gen.close_slave = close_slave;
+
+	err = snd_pcm_new(&pcm, SND_PCM_TYPE_LINEAR, name, slave->stream, slave->mode);
+	if (err < 0) {
+		free(linear);
+		return err;
+	}
+	pcm->ops = &snd_pcm_linear_ops;
+	pcm->fast_ops = &snd_pcm_plugin_fast_ops;
+	pcm->private_data = linear;
+	pcm->poll_fd = slave->poll_fd;
+	pcm->poll_events = slave->poll_events;
+	pcm->monotonic = slave->monotonic;
+	snd_pcm_set_hw_ptr(pcm, &linear->plug.hw_ptr, -1, 0);
+	snd_pcm_set_appl_ptr(pcm, &linear->plug.appl_ptr, -1, 0);
+	*pcmp = pcm;
+
+	return 0;
+}
+
+/*! \page pcm_plugins
+
+\section pcm_plugins_linear Plugin: linear
+
+This plugin converts linear samples from master linear conversion PCM to given
+slave PCM. The channel count, format and rate must match for both of them.
+
+\code
+pcm.name {
+        type linear             # Linear conversion PCM
+        slave STR               # Slave name
+        # or
+        slave {                 # Slave definition
+                pcm STR         # Slave PCM name
+                # or
+                pcm { }         # Slave PCM definition
+                format STR      # Slave format
+        }
+}
+\endcode
+
+\subsection pcm_plugins_linear_funcref Function reference
+
+<UL>
+  <LI>snd_pcm_linear_open()
+  <LI>_snd_pcm_linear_open()
+</UL>
+
+*/
+
+/**
+ * \brief Creates a new linear conversion PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param root Root configuration node
+ * \param conf Configuration node with copy PCM description
+ * \param stream Stream type
+ * \param mode Stream mode
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int _snd_pcm_linear_open(snd_pcm_t **pcmp, const char *name,
+			 snd_config_t *root, snd_config_t *conf, 
+			 snd_pcm_stream_t stream, int mode)
+{
+	snd_config_iterator_t i, next;
+	int err;
+	snd_pcm_t *spcm;
+	snd_config_t *slave = NULL, *sconf;
+	snd_pcm_format_t sformat;
+	snd_config_for_each(i, next, conf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (snd_pcm_conf_generic_id(id))
+			continue;
+		if (strcmp(id, "slave") == 0) {
+			slave = n;
+			continue;
+		}
+		SNDERR("Unknown field %s", id);
+		return -EINVAL;
+	}
+	if (!slave) {
+		SNDERR("slave is not defined");
+		return -EINVAL;
+	}
+	err = snd_pcm_slave_conf(root, slave, &sconf, 1,
+				 SND_PCM_HW_PARAM_FORMAT, SCONF_MANDATORY, &sformat);
+	if (err < 0)
+		return err;
+	if (snd_pcm_format_linear(sformat) != 1) {
+		snd_config_delete(sconf);
+		SNDERR("slave format is not linear");
+		return -EINVAL;
+	}
+	err = snd_pcm_open_slave(&spcm, root, sconf, stream, mode, conf);
+	snd_config_delete(sconf);
+	if (err < 0)
+		return err;
+	err = snd_pcm_linear_open(pcmp, name, sformat, spcm, 1);
+	if (err < 0)
+		snd_pcm_close(spcm);
+	return err;
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(_snd_pcm_linear_open, SND_PCM_DLSYM_VERSION);
+#endif
diff --git a/src/pcm/pcm_local.h b/src/pcm/pcm_local.h
new file mode 100644
index 0000000..2e901d5
--- /dev/null
+++ b/src/pcm/pcm_local.h
@@ -0,0 +1,972 @@
+/*
+ *  PCM Interface - local header file
+ *  Copyright (c) 2000 by Jaroslav Kysela <perex@perex.cz>
+ *                        Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <sys/uio.h>
+
+#define _snd_mask sndrv_mask
+#define _snd_pcm_access_mask _snd_mask
+#define _snd_pcm_format_mask _snd_mask
+#define _snd_pcm_subformat_mask _snd_mask
+
+#include "local.h"
+
+#define SND_INTERVAL_INLINE
+#include "interval.h"
+
+#define SND_MASK_INLINE
+#include "mask.h"
+
+typedef enum sndrv_pcm_hw_param snd_pcm_hw_param_t;
+#define SND_PCM_HW_PARAM_ACCESS SNDRV_PCM_HW_PARAM_ACCESS
+#define SND_PCM_HW_PARAM_FIRST_MASK SNDRV_PCM_HW_PARAM_FIRST_MASK
+#define SND_PCM_HW_PARAM_FORMAT SNDRV_PCM_HW_PARAM_FORMAT
+#define SND_PCM_HW_PARAM_SUBFORMAT SNDRV_PCM_HW_PARAM_SUBFORMAT
+#define SND_PCM_HW_PARAM_LAST_MASK SNDRV_PCM_HW_PARAM_LAST_MASK
+#define SND_PCM_HW_PARAM_SAMPLE_BITS SNDRV_PCM_HW_PARAM_SAMPLE_BITS
+#define SND_PCM_HW_PARAM_FIRST_INTERVAL SNDRV_PCM_HW_PARAM_FIRST_INTERVAL
+#define SND_PCM_HW_PARAM_FRAME_BITS SNDRV_PCM_HW_PARAM_FRAME_BITS
+#define SND_PCM_HW_PARAM_CHANNELS SNDRV_PCM_HW_PARAM_CHANNELS
+#define SND_PCM_HW_PARAM_RATE SNDRV_PCM_HW_PARAM_RATE
+#define SND_PCM_HW_PARAM_PERIOD_TIME SNDRV_PCM_HW_PARAM_PERIOD_TIME
+#define SND_PCM_HW_PARAM_PERIOD_SIZE SNDRV_PCM_HW_PARAM_PERIOD_SIZE
+#define SND_PCM_HW_PARAM_PERIOD_BYTES SNDRV_PCM_HW_PARAM_PERIOD_BYTES
+#define SND_PCM_HW_PARAM_PERIODS SNDRV_PCM_HW_PARAM_PERIODS
+#define SND_PCM_HW_PARAM_BUFFER_TIME SNDRV_PCM_HW_PARAM_BUFFER_TIME
+#define SND_PCM_HW_PARAM_BUFFER_SIZE SNDRV_PCM_HW_PARAM_BUFFER_SIZE
+#define SND_PCM_HW_PARAM_BUFFER_BYTES SNDRV_PCM_HW_PARAM_BUFFER_BYTES
+#define SND_PCM_HW_PARAM_TICK_TIME SNDRV_PCM_HW_PARAM_TICK_TIME
+#define SND_PCM_HW_PARAM_LAST_INTERVAL SNDRV_PCM_HW_PARAM_LAST_INTERVAL
+#define SND_PCM_HW_PARAM_LAST_MASK SNDRV_PCM_HW_PARAM_LAST_MASK
+#define SND_PCM_HW_PARAM_FIRST_MASK SNDRV_PCM_HW_PARAM_FIRST_MASK
+#define SND_PCM_HW_PARAM_LAST_INTERVAL SNDRV_PCM_HW_PARAM_LAST_INTERVAL
+#define SND_PCM_HW_PARAM_FIRST_INTERVAL SNDRV_PCM_HW_PARAM_FIRST_INTERVAL
+
+/** device accepts mmaped access */
+#define SND_PCM_INFO_MMAP SNDRV_PCM_INFO_MMAP
+/** device accepts  mmaped access with sample resolution */
+#define SND_PCM_INFO_MMAP_VALID SNDRV_PCM_INFO_MMAP_VALID
+/** device is doing double buffering */
+#define SND_PCM_INFO_DOUBLE SNDRV_PCM_INFO_DOUBLE
+/** device transfers samples in batch */
+#define SND_PCM_INFO_BATCH SNDRV_PCM_INFO_BATCH
+/** device accepts interleaved samples */
+#define SND_PCM_INFO_INTERLEAVED SNDRV_PCM_INFO_INTERLEAVED
+/** device accepts non-interleaved samples */
+#define SND_PCM_INFO_NONINTERLEAVED SNDRV_PCM_INFO_NONINTERLEAVED
+/** device accepts complex sample organization */
+#define SND_PCM_INFO_COMPLEX SNDRV_PCM_INFO_COMPLEX
+/** device is capable block transfers */
+#define SND_PCM_INFO_BLOCK_TRANSFER SNDRV_PCM_INFO_BLOCK_TRANSFER
+/** device can detect DAC/ADC overrange */
+#define SND_PCM_INFO_OVERRANGE SNDRV_PCM_INFO_OVERRANGE
+/** device supports resume */
+#define SND_PCM_INFO_RESUME SNDRV_PCM_INFO_RESUME
+/** device is capable to pause */
+#define SND_PCM_INFO_PAUSE SNDRV_PCM_INFO_PAUSE
+/** device can do only half duplex */
+#define SND_PCM_INFO_HALF_DUPLEX SNDRV_PCM_INFO_HALF_DUPLEX
+/** device can do only joint duplex (same parameters) */
+#define SND_PCM_INFO_JOINT_DUPLEX SNDRV_PCM_INFO_JOINT_DUPLEX
+/** device can do a kind of synchronized start */
+#define SND_PCM_INFO_SYNC_START SNDRV_PCM_INFO_SYNC_START
+/** device can disable period wakeups */
+#define SND_PCM_INFO_NO_PERIOD_WAKEUP SNDRV_PCM_INFO_NO_PERIOD_WAKEUP
+
+#define SND_PCM_HW_PARAMS_NORESAMPLE SNDRV_PCM_HW_PARAMS_NORESAMPLE
+#define SND_PCM_HW_PARAMS_EXPORT_BUFFER SNDRV_PCM_HW_PARAMS_EXPORT_BUFFER
+#define SND_PCM_HW_PARAMS_NO_PERIOD_WAKEUP SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP
+
+#define SND_PCM_INFO_MONOTONIC	0x80000000
+
+typedef struct _snd_pcm_rbptr {
+	snd_pcm_t *master;
+	volatile snd_pcm_uframes_t *ptr;
+	int fd;
+	off_t offset;
+	int link_dst_count;
+	snd_pcm_t **link_dst;
+	void *private_data;
+	void (*changed)(snd_pcm_t *pcm, snd_pcm_t *src);
+} snd_pcm_rbptr_t;
+
+typedef struct _snd_pcm_channel_info {
+	unsigned int channel;
+	void *addr;			/* base address of channel samples */
+	unsigned int first;		/* offset to first sample in bits */
+	unsigned int step;		/* samples distance in bits */
+	enum { SND_PCM_AREA_SHM, SND_PCM_AREA_MMAP, SND_PCM_AREA_LOCAL } type;
+	union {
+		struct {
+			struct snd_shm_area *area;
+			int shmid;
+		} shm;
+		struct {
+			int fd;
+			off_t offset;
+		} mmap;
+	} u;
+	char reserved[64];
+} snd_pcm_channel_info_t;
+
+typedef struct {
+	int (*close)(snd_pcm_t *pcm);
+	int (*nonblock)(snd_pcm_t *pcm, int nonblock);
+	int (*async)(snd_pcm_t *pcm, int sig, pid_t pid);
+	int (*info)(snd_pcm_t *pcm, snd_pcm_info_t *info);
+	int (*hw_refine)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params);
+	int (*hw_params)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params);
+	int (*hw_free)(snd_pcm_t *pcm);
+	int (*sw_params)(snd_pcm_t *pcm, snd_pcm_sw_params_t *params);
+	int (*channel_info)(snd_pcm_t *pcm, snd_pcm_channel_info_t *info);
+	void (*dump)(snd_pcm_t *pcm, snd_output_t *out);
+	int (*mmap)(snd_pcm_t *pcm);
+	int (*munmap)(snd_pcm_t *pcm);
+} snd_pcm_ops_t;
+
+typedef struct {
+	int (*status)(snd_pcm_t *pcm, snd_pcm_status_t *status);
+	int (*prepare)(snd_pcm_t *pcm);
+	int (*reset)(snd_pcm_t *pcm);
+	int (*start)(snd_pcm_t *pcm);
+	int (*drop)(snd_pcm_t *pcm);
+	int (*drain)(snd_pcm_t *pcm);
+	int (*pause)(snd_pcm_t *pcm, int enable);
+	snd_pcm_state_t (*state)(snd_pcm_t *pcm);
+	int (*hwsync)(snd_pcm_t *pcm);
+	int (*delay)(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp);
+	int (*resume)(snd_pcm_t *pcm);
+	int (*link)(snd_pcm_t *pcm1, snd_pcm_t *pcm2);
+	int (*link_slaves)(snd_pcm_t *pcm, snd_pcm_t *master);
+	int (*unlink)(snd_pcm_t *pcm);
+	snd_pcm_sframes_t (*rewindable)(snd_pcm_t *pcm);
+	snd_pcm_sframes_t (*rewind)(snd_pcm_t *pcm, snd_pcm_uframes_t frames);
+	snd_pcm_sframes_t (*forwardable)(snd_pcm_t *pcm);
+	snd_pcm_sframes_t (*forward)(snd_pcm_t *pcm, snd_pcm_uframes_t frames);
+	snd_pcm_sframes_t (*writei)(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size);
+	snd_pcm_sframes_t (*writen)(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size);
+	snd_pcm_sframes_t (*readi)(snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t size);
+	snd_pcm_sframes_t (*readn)(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size);
+	snd_pcm_sframes_t (*avail_update)(snd_pcm_t *pcm);
+	snd_pcm_sframes_t (*mmap_commit)(snd_pcm_t *pcm, snd_pcm_uframes_t offset, snd_pcm_uframes_t size);
+	int (*htimestamp)(snd_pcm_t *pcm, snd_pcm_uframes_t *avail, snd_htimestamp_t *tstamp);
+	int (*poll_descriptors_count)(snd_pcm_t *pcm);
+	int (*poll_descriptors)(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int space);
+	int (*poll_revents)(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int nfds, unsigned short *revents);
+} snd_pcm_fast_ops_t;
+
+struct _snd_pcm {
+	void *open_func;
+	char *name;
+	snd_pcm_type_t type;
+	snd_pcm_stream_t stream;
+	int mode;
+	long minperiodtime;		/* in us */
+	int poll_fd_count;
+	int poll_fd;
+	unsigned short poll_events;
+	int setup: 1,
+	    monotonic: 1,
+	    compat: 1;
+	snd_pcm_access_t access;	/* access mode */
+	snd_pcm_format_t format;	/* SND_PCM_FORMAT_* */
+	snd_pcm_subformat_t subformat;	/* subformat */
+	unsigned int channels;		/* channels */
+	unsigned int rate;		/* rate in Hz */
+	snd_pcm_uframes_t period_size;
+	unsigned int period_time;	/* period duration */
+	snd_interval_t periods;
+	snd_pcm_tstamp_t tstamp_mode;	/* timestamp mode */
+	unsigned int period_step;
+	snd_pcm_uframes_t avail_min;	/* min avail frames for wakeup */
+	int period_event;
+	snd_pcm_uframes_t start_threshold;
+	snd_pcm_uframes_t stop_threshold;
+	snd_pcm_uframes_t silence_threshold;	/* Silence filling happens when
+					   noise is nearest than this */
+	snd_pcm_uframes_t silence_size;	/* Silence filling size */
+	snd_pcm_uframes_t boundary;	/* pointers wrap point */
+	unsigned int info;		/* Info for returned setup */
+	unsigned int msbits;		/* used most significant bits */
+	unsigned int rate_num;		/* rate numerator */
+	unsigned int rate_den;		/* rate denominator */
+	unsigned int hw_flags;		/* actual hardware flags */
+	snd_pcm_uframes_t fifo_size;	/* chip FIFO size in frames */
+	snd_pcm_uframes_t buffer_size;
+	snd_interval_t buffer_time;
+	unsigned int sample_bits;
+	unsigned int frame_bits;
+	snd_pcm_rbptr_t appl;
+	snd_pcm_rbptr_t hw;
+	snd_pcm_uframes_t min_align;
+	unsigned int mmap_rw: 1;	/* use always mmapped buffer */
+	unsigned int mmap_shadow: 1;	/* don't call actual mmap,
+					 * use the mmaped buffer of the slave
+					 */
+	unsigned int donot_close: 1;	/* don't close this PCM */
+	snd_pcm_channel_info_t *mmap_channels;
+	snd_pcm_channel_area_t *running_areas;
+	snd_pcm_channel_area_t *stopped_areas;
+	const snd_pcm_ops_t *ops;
+	const snd_pcm_fast_ops_t *fast_ops;
+	snd_pcm_t *op_arg;
+	snd_pcm_t *fast_op_arg;
+	void *private_data;
+	struct list_head async_handlers;
+};
+
+/* make local functions really local */
+/* Grrr, these cannot be local - a bad aserver uses them!
+#define snd_pcm_async \
+	snd1_pcm_async
+#define snd_pcm_mmap \
+	snd1_pcm_mmap
+#define snd_pcm_munmap \
+	snd1_pcm_munmap
+#define snd_pcm_hw_refine \
+	snd1_pcm_hw_refine
+*/
+#define snd_pcm_new \
+	snd1_pcm_new
+#define snd_pcm_free \
+	snd1_pcm_free
+#define snd_pcm_areas_from_buf \
+	snd1_pcm_areas_from_buf
+#define snd_pcm_areas_from_bufs \
+	snd1_pcm_areas_from_bufs
+#define snd_pcm_open_named_slave \
+	snd1_pcm_open_named_slave
+#define snd_pcm_conf_generic_id \
+	snd1_pcm_conf_generic_id
+#define snd_pcm_hw_open_fd \
+	snd1_pcm_hw_open_fd
+#define snd_pcm_wait_nocheck \
+	snd1_pcm_wait_nocheck
+#define snd_pcm_rate_get_default_converter \
+	snd1_pcm_rate_get_default_converter
+#define snd_pcm_set_hw_ptr \
+	snd1_pcm_set_hw_ptr
+#define snd_pcm_set_appl_ptr \
+	snd1_pcm_set_appl_ptr
+#define snd_pcm_link_hw_ptr \
+	snd1_pcm_link_hw_ptr
+#define snd_pcm_link_appl_ptr \
+	snd1_pcm_link_appl_ptr
+#define snd_pcm_unlink_hw_ptr \
+	snd1_pcm_unlink_hw_ptr
+#define snd_pcm_unlink_appl_ptr \
+	snd1_pcm_unlink_appl_ptr
+#define snd_pcm_mmap_appl_ptr \
+	snd1_pcm_mmap_appl_ptr
+#define snd_pcm_mmap_appl_backward \
+	snd1_pcm_mmap_appl_backward
+#define snd_pcm_mmap_appl_forward \
+	snd1_pcm_mmap_appl_forward
+#define snd_pcm_mmap_hw_backward \
+	snd1_pcm_mmap_hw_backward
+#define snd_pcm_mmap_hw_forward \
+	snd1_pcm_mmap_hw_forward
+#define snd_pcm_read_areas \
+	snd1_pcm_read_areas
+#define snd_pcm_write_areas \
+	snd1_pcm_write_areas
+#define snd_pcm_read_mmap \
+	snd1_pcm_read_mmap
+#define snd_pcm_write_mmap \
+	snd1_pcm_write_mmap
+#define snd_pcm_channel_info_shm \
+	snd1_pcm_channel_info_shm
+#define snd_pcm_hw_refine_soft \
+	snd1_pcm_hw_refine_soft
+#define snd_pcm_hw_refine_slave \
+	snd1_pcm_hw_refine_slave
+#define snd_pcm_hw_params_slave \
+	snd1_pcm_hw_params_slave
+#define snd_pcm_hw_param_refine_near \
+	snd1_pcm_hw_param_refine_near
+#define snd_pcm_hw_param_refine_multiple \
+	snd1_pcm_hw_param_refine_multiple
+#define snd_pcm_hw_param_empty \
+	snd1_pcm_hw_param_empty
+#define snd_pcm_hw_param_always_eq \
+	snd1_pcm_hw_param_always_eq
+#define snd_pcm_hw_param_never_eq \
+	snd1_pcm_hw_param_never_eq
+#define snd_pcm_hw_param_get_mask \
+	snd1_pcm_hw_param_get_mask
+#define snd_pcm_hw_param_get_interval \
+	snd1_pcm_hw_param_get_interval
+#define snd_pcm_hw_param_any \
+	snd1_pcm_hw_param_any
+#define snd_pcm_hw_param_set_integer \
+	snd1_pcm_hw_param_set_integer
+#define snd_pcm_hw_param_set_first \
+	snd1_pcm_hw_param_set_first
+#define snd_pcm_hw_param_set_last \
+	snd1_pcm_hw_param_set_last
+#define snd_pcm_hw_param_set_near \
+	snd1_pcm_hw_param_set_near
+#define snd_pcm_hw_param_set_min \
+	snd1_pcm_hw_param_set_min
+#define snd_pcm_hw_param_set_max \
+	snd1_pcm_hw_param_set_max
+#define snd_pcm_hw_param_set_minmax \
+	snd1_pcm_hw_param_set_minmax
+#define snd_pcm_hw_param_set \
+	snd1_pcm_hw_param_set
+#define snd_pcm_hw_param_set_mask \
+	snd1_pcm_hw_param_set_mask
+#define snd_pcm_hw_param_get \
+	snd1_pcm_hw_param_get
+#define snd_pcm_hw_param_get_min \
+	snd1_pcm_hw_param_get_min
+#define snd_pcm_hw_param_get_max \
+	snd1_pcm_hw_param_get_max
+#define snd_pcm_hw_param_name		\
+	snd1_pcm_hw_param_name
+
+int snd_pcm_new(snd_pcm_t **pcmp, snd_pcm_type_t type, const char *name,
+		snd_pcm_stream_t stream, int mode);
+int snd_pcm_free(snd_pcm_t *pcm);
+
+void snd_pcm_areas_from_buf(snd_pcm_t *pcm, snd_pcm_channel_area_t *areas, void *buf);
+void snd_pcm_areas_from_bufs(snd_pcm_t *pcm, snd_pcm_channel_area_t *areas, void **bufs);
+
+int snd_pcm_async(snd_pcm_t *pcm, int sig, pid_t pid);
+int snd_pcm_mmap(snd_pcm_t *pcm);
+int snd_pcm_munmap(snd_pcm_t *pcm);
+int snd_pcm_mmap_ready(snd_pcm_t *pcm);
+void snd_pcm_set_hw_ptr(snd_pcm_t *pcm, volatile snd_pcm_uframes_t *hw_ptr, int fd, off_t offset);
+void snd_pcm_set_appl_ptr(snd_pcm_t *pcm, volatile snd_pcm_uframes_t *appl_ptr, int fd, off_t offset);
+void snd_pcm_link_hw_ptr(snd_pcm_t *pcm, snd_pcm_t *slave);
+void snd_pcm_link_appl_ptr(snd_pcm_t *pcm, snd_pcm_t *slave);
+void snd_pcm_unlink_hw_ptr(snd_pcm_t *pcm, snd_pcm_t *slave);
+void snd_pcm_unlink_appl_ptr(snd_pcm_t *pcm, snd_pcm_t *slave);
+snd_pcm_sframes_t snd_pcm_mmap_appl_ptr(snd_pcm_t *pcm, off_t offset);
+void snd_pcm_mmap_appl_backward(snd_pcm_t *pcm, snd_pcm_uframes_t frames);
+void snd_pcm_mmap_appl_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames);
+void snd_pcm_mmap_hw_backward(snd_pcm_t *pcm, snd_pcm_uframes_t frames);
+void snd_pcm_mmap_hw_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames);
+
+snd_pcm_sframes_t snd_pcm_mmap_writei(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size);
+snd_pcm_sframes_t snd_pcm_mmap_readi(snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t size);
+snd_pcm_sframes_t snd_pcm_mmap_writen(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size);
+snd_pcm_sframes_t snd_pcm_mmap_readn(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size);
+
+typedef snd_pcm_sframes_t (*snd_pcm_xfer_areas_func_t)(snd_pcm_t *pcm, 
+						       const snd_pcm_channel_area_t *areas,
+						       snd_pcm_uframes_t offset, 
+						       snd_pcm_uframes_t size);
+
+snd_pcm_sframes_t snd_pcm_read_areas(snd_pcm_t *pcm, const snd_pcm_channel_area_t *areas,
+				     snd_pcm_uframes_t offset, snd_pcm_uframes_t size,
+				     snd_pcm_xfer_areas_func_t func);
+snd_pcm_sframes_t snd_pcm_write_areas(snd_pcm_t *pcm, const snd_pcm_channel_area_t *areas,
+				      snd_pcm_uframes_t offset, snd_pcm_uframes_t size,
+				      snd_pcm_xfer_areas_func_t func);
+snd_pcm_sframes_t snd_pcm_read_mmap(snd_pcm_t *pcm, snd_pcm_uframes_t offset,
+				    snd_pcm_uframes_t size);
+snd_pcm_sframes_t snd_pcm_write_mmap(snd_pcm_t *pcm, snd_pcm_uframes_t offset,
+				     snd_pcm_uframes_t size);
+static inline int snd_pcm_channel_info(snd_pcm_t *pcm, snd_pcm_channel_info_t *info)
+{
+	return pcm->ops->channel_info(pcm, info);
+}
+int snd_pcm_channel_info_shm(snd_pcm_t *pcm, snd_pcm_channel_info_t *info, int shmid);
+int _snd_pcm_poll_descriptor(snd_pcm_t *pcm);
+#define _snd_pcm_link_descriptor _snd_pcm_poll_descriptor /* FIXME */
+#define _snd_pcm_async_descriptor _snd_pcm_poll_descriptor /* FIXME */
+
+/* handle special error cases */
+static inline int snd_pcm_check_error(snd_pcm_t *pcm, int err)
+{
+	if (err == -EINTR) {
+		switch (snd_pcm_state(pcm)) {
+		case SND_PCM_STATE_XRUN:
+			return -EPIPE;
+		case SND_PCM_STATE_SUSPENDED:
+			return -ESTRPIPE;
+		case SND_PCM_STATE_DISCONNECTED:
+			return -ENODEV;
+		default:
+			break;
+		}
+	}
+	return err;
+}
+
+static inline snd_pcm_uframes_t snd_pcm_mmap_playback_avail(snd_pcm_t *pcm)
+{
+	snd_pcm_sframes_t avail;
+	avail = *pcm->hw.ptr + pcm->buffer_size - *pcm->appl.ptr;
+	if (avail < 0)
+		avail += pcm->boundary;
+	else if ((snd_pcm_uframes_t) avail >= pcm->boundary)
+		avail -= pcm->boundary;
+	return avail;
+}
+
+static inline snd_pcm_uframes_t snd_pcm_mmap_capture_avail(snd_pcm_t *pcm)
+{
+	snd_pcm_sframes_t avail;
+	avail = *pcm->hw.ptr - *pcm->appl.ptr;
+	if (avail < 0)
+		avail += pcm->boundary;
+	return avail;
+}
+
+static inline snd_pcm_uframes_t snd_pcm_mmap_avail(snd_pcm_t *pcm)
+{
+	if (pcm->stream == SND_PCM_STREAM_PLAYBACK)
+		return snd_pcm_mmap_playback_avail(pcm);
+	else
+		return snd_pcm_mmap_capture_avail(pcm);
+}
+
+static inline snd_pcm_sframes_t snd_pcm_mmap_playback_hw_avail(snd_pcm_t *pcm)
+{
+	return pcm->buffer_size - snd_pcm_mmap_playback_avail(pcm);
+}
+
+static inline snd_pcm_sframes_t snd_pcm_mmap_capture_hw_avail(snd_pcm_t *pcm)
+{
+	return pcm->buffer_size - snd_pcm_mmap_capture_avail(pcm);
+}
+
+static inline snd_pcm_sframes_t snd_pcm_mmap_hw_avail(snd_pcm_t *pcm)
+{
+	snd_pcm_sframes_t avail;
+	avail = *pcm->hw.ptr - *pcm->appl.ptr;
+	if (pcm->stream == SND_PCM_STREAM_PLAYBACK)
+		avail += pcm->buffer_size;
+	if (avail < 0)
+		avail += pcm->boundary;
+	return pcm->buffer_size - avail;
+}
+
+static inline const snd_pcm_channel_area_t *snd_pcm_mmap_areas(snd_pcm_t *pcm)
+{
+	if (pcm->stopped_areas &&
+	    snd_pcm_state(pcm) != SND_PCM_STATE_RUNNING) 
+		return pcm->stopped_areas;
+	return pcm->running_areas;
+}
+
+static inline snd_pcm_uframes_t snd_pcm_mmap_offset(snd_pcm_t *pcm)
+{
+        assert(pcm);
+	return *pcm->appl.ptr % pcm->buffer_size;
+}
+
+static inline snd_pcm_uframes_t snd_pcm_mmap_hw_offset(snd_pcm_t *pcm)
+{
+        assert(pcm);
+	return *pcm->hw.ptr % pcm->buffer_size;
+}
+
+static inline snd_pcm_uframes_t snd_pcm_mmap_playback_delay(snd_pcm_t *pcm)
+{
+	return snd_pcm_mmap_playback_hw_avail(pcm);
+}
+
+static inline snd_pcm_uframes_t snd_pcm_mmap_capture_delay(snd_pcm_t *pcm)
+{
+	return snd_pcm_mmap_capture_hw_avail(pcm);
+}
+
+static inline snd_pcm_sframes_t snd_pcm_mmap_delay(snd_pcm_t *pcm)
+{
+	if (pcm->stream == SND_PCM_STREAM_PLAYBACK)
+		return snd_pcm_mmap_playback_delay(pcm);
+	else
+		return snd_pcm_mmap_capture_delay(pcm);
+}
+
+static inline void *snd_pcm_channel_area_addr(const snd_pcm_channel_area_t *area, snd_pcm_uframes_t offset)
+{
+	unsigned int bitofs = area->first + area->step * offset;
+	assert(bitofs % 8 == 0);
+	return (char *) area->addr + bitofs / 8;
+}
+
+static inline unsigned int snd_pcm_channel_area_step(const snd_pcm_channel_area_t *area)
+{
+	assert(area->step % 8 == 0);
+	return area->step / 8;
+}
+
+static inline snd_pcm_sframes_t _snd_pcm_writei(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size)
+{
+	return pcm->fast_ops->writei(pcm->fast_op_arg, buffer, size);
+}
+
+static inline snd_pcm_sframes_t _snd_pcm_writen(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size)
+{
+	return pcm->fast_ops->writen(pcm->fast_op_arg, bufs, size);
+}
+
+static inline snd_pcm_sframes_t _snd_pcm_readi(snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t size)
+{
+	return pcm->fast_ops->readi(pcm->fast_op_arg, buffer, size);
+}
+
+static inline snd_pcm_sframes_t _snd_pcm_readn(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size)
+{
+	return pcm->fast_ops->readn(pcm->fast_op_arg, bufs, size);
+}
+
+static inline int muldiv(int a, int b, int c, int *r)
+{
+	int64_t n = (int64_t)a * b;
+	int64_t v = n / c;
+	if (v > INT_MAX) {
+		*r = 0;
+		return INT_MAX;
+	}
+	if (v < INT_MIN) {
+		*r = 0;
+		return INT_MIN;
+	}
+	*r = n % c;
+	return v;
+}
+
+static inline int muldiv_down(int a, int b, int c)
+{
+	int64_t v = (int64_t)a * b / c;
+	if (v > INT_MAX) {
+		return INT_MAX;
+	}
+	if (v < INT_MIN) {
+		return INT_MIN;
+	}
+	return v;
+}
+
+static inline int muldiv_near(int a, int b, int c)
+{
+	int r;
+	int n = muldiv(a, b, c, &r);
+	if (r >= (c + 1) / 2)
+		n++;
+	return n;
+}
+
+int snd_pcm_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params);
+int _snd_pcm_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t *params);
+int snd_pcm_hw_refine_soft(snd_pcm_t *pcm, snd_pcm_hw_params_t *params);
+int snd_pcm_hw_refine_slave(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
+			    int (*cprepare)(snd_pcm_t *pcm,
+					    snd_pcm_hw_params_t *params),
+			    int (*cchange)(snd_pcm_t *pcm,
+					   snd_pcm_hw_params_t *params,
+					   snd_pcm_hw_params_t *sparams),
+			    int (*sprepare)(snd_pcm_t *pcm,
+					    snd_pcm_hw_params_t *params),
+			    int (*schange)(snd_pcm_t *pcm,
+					   snd_pcm_hw_params_t *params,
+					   snd_pcm_hw_params_t *sparams),
+			    int (*srefine)(snd_pcm_t *pcm,
+					   snd_pcm_hw_params_t *sparams));
+int snd_pcm_hw_params_slave(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
+			    int (*cchange)(snd_pcm_t *pcm,
+					   snd_pcm_hw_params_t *params,
+					   snd_pcm_hw_params_t *sparams),
+			    int (*sprepare)(snd_pcm_t *pcm,
+					    snd_pcm_hw_params_t *params),
+			    int (*schange)(snd_pcm_t *pcm,
+					   snd_pcm_hw_params_t *params,
+					   snd_pcm_hw_params_t *sparams),
+			    int (*sparams)(snd_pcm_t *pcm,
+					   snd_pcm_hw_params_t *sparams));
+
+
+void _snd_pcm_hw_params_any(snd_pcm_hw_params_t *params);
+void _snd_pcm_hw_param_set_empty(snd_pcm_hw_params_t *params,
+				 snd_pcm_hw_param_t var);
+int _snd_pcm_hw_param_set_interval(snd_pcm_hw_params_t *params,
+				   snd_pcm_hw_param_t var,
+				   const snd_interval_t *val);
+int _snd_pcm_hw_param_set_mask(snd_pcm_hw_params_t *params,
+			   snd_pcm_hw_param_t var, const snd_mask_t *mask);
+int _snd_pcm_hw_param_first(snd_pcm_hw_params_t *params,
+			    snd_pcm_hw_param_t var);
+int _snd_pcm_hw_param_last(snd_pcm_hw_params_t *params,
+			   snd_pcm_hw_param_t var);
+int _snd_pcm_hw_param_set(snd_pcm_hw_params_t *params,
+			  snd_pcm_hw_param_t var, unsigned int val, int dir);
+static inline int _snd_pcm_hw_params_set_format(snd_pcm_hw_params_t *params,
+						snd_pcm_format_t val)
+{
+	return _snd_pcm_hw_param_set(params, SND_PCM_HW_PARAM_FORMAT,
+				     (unsigned long) val, 0);
+}
+
+static inline int _snd_pcm_hw_params_set_subformat(snd_pcm_hw_params_t *params,
+				     snd_pcm_subformat_t val)
+{
+	return _snd_pcm_hw_param_set(params, SND_PCM_HW_PARAM_SUBFORMAT,
+				     (unsigned long) val, 0);
+}
+
+int _snd_pcm_hw_param_set_min(snd_pcm_hw_params_t *params,
+			      snd_pcm_hw_param_t var, unsigned int val, int dir);
+int _snd_pcm_hw_param_set_max(snd_pcm_hw_params_t *params,
+			      snd_pcm_hw_param_t var, unsigned int val, int dir);
+int _snd_pcm_hw_param_set_minmax(snd_pcm_hw_params_t *params,
+				 snd_pcm_hw_param_t var,
+				 unsigned int min, int mindir,
+				 unsigned int max, int maxdir);
+int _snd_pcm_hw_param_refine(snd_pcm_hw_params_t *params,
+			     snd_pcm_hw_param_t var,
+			     const snd_pcm_hw_params_t *src);
+int _snd_pcm_hw_params_refine(snd_pcm_hw_params_t *params,
+			      unsigned int vars,
+			      const snd_pcm_hw_params_t *src);
+int snd_pcm_hw_param_refine_near(snd_pcm_t *pcm,
+				 snd_pcm_hw_params_t *params,
+				 snd_pcm_hw_param_t var,
+				 const snd_pcm_hw_params_t *src);
+int snd_pcm_hw_param_refine_multiple(snd_pcm_t *pcm,
+				     snd_pcm_hw_params_t *params,
+				     snd_pcm_hw_param_t var,
+				     const snd_pcm_hw_params_t *src);
+int snd_pcm_hw_param_empty(const snd_pcm_hw_params_t *params,
+			   snd_pcm_hw_param_t var);
+int snd_pcm_hw_param_always_eq(const snd_pcm_hw_params_t *params,
+			       snd_pcm_hw_param_t var,
+			       const snd_pcm_hw_params_t *params1);
+int snd_pcm_hw_param_never_eq(const snd_pcm_hw_params_t *params,
+			      snd_pcm_hw_param_t var,
+			      const snd_pcm_hw_params_t *params1);
+const snd_mask_t *snd_pcm_hw_param_get_mask(const snd_pcm_hw_params_t *params,
+					      snd_pcm_hw_param_t var);
+const snd_interval_t *snd_pcm_hw_param_get_interval(const snd_pcm_hw_params_t *params,
+						      snd_pcm_hw_param_t var);
+
+int snd_pcm_hw_param_any(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
+			 snd_pcm_hw_param_t var);
+int snd_pcm_hw_param_set_integer(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
+				 snd_set_mode_t mode,
+				 snd_pcm_hw_param_t var);
+int snd_pcm_hw_param_set_first(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
+			       snd_pcm_hw_param_t var, unsigned int *rval, int *dir);
+int snd_pcm_hw_param_set_last(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
+			      snd_pcm_hw_param_t var, unsigned int *rval, int *dir);
+int snd_pcm_hw_param_set_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
+			      snd_pcm_hw_param_t var, unsigned int *val, int *dir);
+int snd_pcm_hw_param_set_min(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
+			     snd_set_mode_t mode,
+			     snd_pcm_hw_param_t var,
+			     unsigned int *val, int *dir);
+int snd_pcm_hw_param_set_max(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
+			     snd_set_mode_t mode,
+			     snd_pcm_hw_param_t var, unsigned int *val, int *dir);
+int snd_pcm_hw_param_set_minmax(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
+				snd_set_mode_t mode,
+				snd_pcm_hw_param_t var,
+				unsigned int *min, int *mindir,
+				unsigned int *max, int *maxdir);
+int snd_pcm_hw_param_set(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
+			 snd_set_mode_t mode,
+			 snd_pcm_hw_param_t var, unsigned int val, int dir);
+int snd_pcm_hw_param_set_mask(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
+			      snd_set_mode_t mode,
+			      snd_pcm_hw_param_t var, const snd_mask_t *mask);
+int snd_pcm_hw_param_get(const snd_pcm_hw_params_t *params, snd_pcm_hw_param_t var,
+			 unsigned int *val, int *dir);
+int snd_pcm_hw_param_get_min(const snd_pcm_hw_params_t *params,
+			     snd_pcm_hw_param_t var,
+			     unsigned int *val, int *dir);
+int snd_pcm_hw_param_get_max(const snd_pcm_hw_params_t *params,
+			     snd_pcm_hw_param_t var,
+			     unsigned int *val, int *dir);
+
+#ifdef INTERNAL
+snd_pcm_sframes_t INTERNAL(snd_pcm_forward)(snd_pcm_t *pcm, snd_pcm_uframes_t frames);
+
+int INTERNAL(snd_pcm_hw_params_get_access)(const snd_pcm_hw_params_t *params, snd_pcm_access_t *access);
+int snd_pcm_hw_params_test_access(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_access_t access);
+int snd_pcm_hw_params_set_access(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_access_t access);
+int INTERNAL(snd_pcm_hw_params_set_access_first)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_access_t *access);
+int INTERNAL(snd_pcm_hw_params_set_access_last)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_access_t *access);
+int snd_pcm_hw_params_set_access_mask(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_access_mask_t *mask);
+int snd_pcm_hw_params_get_access_mask(snd_pcm_hw_params_t *params, snd_pcm_access_mask_t *mask);
+
+int INTERNAL(snd_pcm_hw_params_get_format)(const snd_pcm_hw_params_t *params, snd_pcm_format_t *val);
+int snd_pcm_hw_params_test_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_format_t val);
+int snd_pcm_hw_params_set_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_format_t val);
+int INTERNAL(snd_pcm_hw_params_set_format_first)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_format_t *format);
+int INTERNAL(snd_pcm_hw_params_set_format_last)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_format_t *format);
+int snd_pcm_hw_params_set_format_mask(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_format_mask_t *mask);
+void snd_pcm_hw_params_get_format_mask(snd_pcm_hw_params_t *params, snd_pcm_format_mask_t *mask);
+
+int INTERNAL(snd_pcm_hw_params_get_subformat)(const snd_pcm_hw_params_t *params, snd_pcm_subformat_t *subformat);
+int snd_pcm_hw_params_test_subformat(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_subformat_t subformat);
+int snd_pcm_hw_params_set_subformat(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_subformat_t subformat);
+int INTERNAL(snd_pcm_hw_params_set_subformat_first)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_subformat_t *subformat);
+int INTERNAL(snd_pcm_hw_params_set_subformat_last)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_subformat_t *subformat);
+int snd_pcm_hw_params_set_subformat_mask(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_subformat_mask_t *mask);
+void snd_pcm_hw_params_get_subformat_mask(snd_pcm_hw_params_t *params, snd_pcm_subformat_mask_t *mask);
+
+int INTERNAL(snd_pcm_hw_params_get_channels)(const snd_pcm_hw_params_t *params, unsigned int *val);
+int INTERNAL(snd_pcm_hw_params_get_channels_min)(const snd_pcm_hw_params_t *params, unsigned int *val);
+int INTERNAL(snd_pcm_hw_params_get_channels_max)(const snd_pcm_hw_params_t *params, unsigned int *val);
+int snd_pcm_hw_params_test_channels(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val);
+int snd_pcm_hw_params_set_channels(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val);
+int snd_pcm_hw_params_set_channels_min(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val);
+int snd_pcm_hw_params_set_channels_max(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val);
+int snd_pcm_hw_params_set_channels_minmax(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *min, unsigned int *max);
+int INTERNAL(snd_pcm_hw_params_set_channels_near)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val);
+int INTERNAL(snd_pcm_hw_params_set_channels_first)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val);
+int INTERNAL(snd_pcm_hw_params_set_channels_last)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val);
+
+int INTERNAL(snd_pcm_hw_params_get_rate)(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int INTERNAL(snd_pcm_hw_params_get_rate_min)(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int INTERNAL(snd_pcm_hw_params_get_rate_max)(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_test_rate(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int dir);
+int snd_pcm_hw_params_set_rate(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int dir);
+int snd_pcm_hw_params_set_rate_min(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_set_rate_max(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_set_rate_minmax(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *min, int *mindir, unsigned int *max, int *maxdir);
+int INTERNAL(snd_pcm_hw_params_set_rate_near)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int INTERNAL(snd_pcm_hw_params_set_rate_first)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int INTERNAL(snd_pcm_hw_params_set_rate_last)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+
+int INTERNAL(snd_pcm_hw_params_get_period_time)(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int INTERNAL(snd_pcm_hw_params_get_period_time_min)(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int INTERNAL(snd_pcm_hw_params_get_period_time_max)(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_test_period_time(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int dir);
+int snd_pcm_hw_params_set_period_time(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int dir);
+int snd_pcm_hw_params_set_period_time_min(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_set_period_time_max(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_set_period_time_minmax(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *min, int *mindir, unsigned int *max, int *maxdir);
+int INTERNAL(snd_pcm_hw_params_set_period_time_near)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int INTERNAL(snd_pcm_hw_params_set_period_time_first)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int INTERNAL(snd_pcm_hw_params_set_period_time_last)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+
+int INTERNAL(snd_pcm_hw_params_get_period_size)(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *frames, int *dir);
+int INTERNAL(snd_pcm_hw_params_get_period_size_min)(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *frames, int *dir);
+int INTERNAL(snd_pcm_hw_params_get_period_size_max)(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *frames, int *dir);
+int snd_pcm_hw_params_test_period_size(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t val, int dir);
+int snd_pcm_hw_params_set_period_size(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t val, int dir);
+int snd_pcm_hw_params_set_period_size_min(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val, int *dir);
+int snd_pcm_hw_params_set_period_size_max(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val, int *dir);
+int snd_pcm_hw_params_set_period_size_minmax(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *min, int *mindir, snd_pcm_uframes_t *max, int *maxdir);
+int INTERNAL(snd_pcm_hw_params_set_period_size_near)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val, int *dir);
+int INTERNAL(snd_pcm_hw_params_set_period_size_first)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val, int *dir);
+int INTERNAL(snd_pcm_hw_params_set_period_size_last)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val, int *dir);
+int snd_pcm_hw_params_set_period_size_integer(snd_pcm_t *pcm, snd_pcm_hw_params_t *params);
+
+int INTERNAL(snd_pcm_hw_params_get_periods)(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int INTERNAL(snd_pcm_hw_params_get_periods_min)(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int INTERNAL(snd_pcm_hw_params_get_periods_max)(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_test_periods(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int dir);
+int snd_pcm_hw_params_set_periods(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int dir);
+int snd_pcm_hw_params_set_periods_min(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_set_periods_max(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_set_periods_minmax(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *min, int *mindir, unsigned int *max, int *maxdir);
+int INTERNAL(snd_pcm_hw_params_set_periods_near)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int INTERNAL(snd_pcm_hw_params_set_periods_first)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int INTERNAL(snd_pcm_hw_params_set_periods_last)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_set_periods_integer(snd_pcm_t *pcm, snd_pcm_hw_params_t *params);
+
+int INTERNAL(snd_pcm_hw_params_get_buffer_time)(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int INTERNAL(snd_pcm_hw_params_get_buffer_time_min)(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int INTERNAL(snd_pcm_hw_params_get_buffer_time_max)(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_test_buffer_time(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int dir);
+int snd_pcm_hw_params_set_buffer_time(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val, int dir);
+int snd_pcm_hw_params_set_buffer_time_min(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_set_buffer_time_max(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int snd_pcm_hw_params_set_buffer_time_minmax(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *min, int *mindir, unsigned int *max, int *maxdir);
+int INTERNAL(snd_pcm_hw_params_set_buffer_time_near)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int INTERNAL(snd_pcm_hw_params_set_buffer_time_first)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+int INTERNAL(snd_pcm_hw_params_set_buffer_time_last)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
+
+int INTERNAL(snd_pcm_hw_params_get_buffer_size)(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val);
+int INTERNAL(snd_pcm_hw_params_get_buffer_size_min)(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val);
+int INTERNAL(snd_pcm_hw_params_get_buffer_size_max)(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val);
+int snd_pcm_hw_params_test_buffer_size(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t val);
+int snd_pcm_hw_params_set_buffer_size(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t val);
+int snd_pcm_hw_params_set_buffer_size_min(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val);
+int snd_pcm_hw_params_set_buffer_size_max(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val);
+int snd_pcm_hw_params_set_buffer_size_minmax(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *min, snd_pcm_uframes_t *max);
+int INTERNAL(snd_pcm_hw_params_set_buffer_size_near)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val);
+int INTERNAL(snd_pcm_hw_params_set_buffer_size_first)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val);
+int INTERNAL(snd_pcm_hw_params_set_buffer_size_last)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val);
+
+int snd_pcm_sw_params_set_tstamp_mode(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_tstamp_t val);
+int INTERNAL(snd_pcm_sw_params_get_tstamp_mode)(const snd_pcm_sw_params_t *params, snd_pcm_tstamp_t *val);
+int snd_pcm_sw_params_set_avail_min(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val);
+int INTERNAL(snd_pcm_sw_params_get_avail_min)(const snd_pcm_sw_params_t *params, snd_pcm_uframes_t *val);
+int snd_pcm_sw_params_set_start_threshold(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val);
+int INTERNAL(snd_pcm_sw_params_get_start_threshold)(const snd_pcm_sw_params_t *paramsm, snd_pcm_uframes_t *val);
+int snd_pcm_sw_params_set_stop_threshold(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val);
+int INTERNAL(snd_pcm_sw_params_get_stop_threshold)(const snd_pcm_sw_params_t *params, snd_pcm_uframes_t *val);
+int snd_pcm_sw_params_set_silence_threshold(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val);
+int INTERNAL(snd_pcm_sw_params_get_silence_threshold)(const snd_pcm_sw_params_t *params, snd_pcm_uframes_t *val);
+int snd_pcm_sw_params_set_silence_size(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val);
+int INTERNAL(snd_pcm_sw_params_get_silence_size)(const snd_pcm_sw_params_t *params, snd_pcm_uframes_t *val);
+#endif /* INTERNAL */
+
+const char *snd_pcm_hw_param_name(snd_pcm_hw_param_t param);
+void snd_pcm_hw_param_dump(const snd_pcm_hw_params_t *params,
+			   snd_pcm_hw_param_t var, snd_output_t *out);
+#if 0
+int snd_pcm_hw_strategy_simple_near(snd_pcm_hw_strategy_t *strategy, int order,
+				    snd_pcm_hw_param_t var,
+				    unsigned int best,
+				    unsigned int mul);
+int snd_pcm_hw_strategy_simple_choices(snd_pcm_hw_strategy_t *strategy, int order,
+				       snd_pcm_hw_param_t var,
+				       unsigned int count,
+				       snd_pcm_hw_strategy_simple_choices_list_t *choices);
+#endif
+
+#define SCONF_MANDATORY	1
+#define SCONF_UNCHANGED	2
+
+int snd_pcm_slave_conf(snd_config_t *root, snd_config_t *conf,
+		       snd_config_t **pcm_conf, unsigned int count, ...);
+
+#define SND_PCM_APPEND	(1<<8)
+
+int snd_pcm_open_named_slave(snd_pcm_t **pcmp, const char *name,
+			     snd_config_t *root,
+			     snd_config_t *conf, snd_pcm_stream_t stream,
+			     int mode, snd_config_t *parent_conf);
+static inline int
+snd_pcm_open_slave(snd_pcm_t **pcmp, snd_config_t *root,
+		   snd_config_t *conf, snd_pcm_stream_t stream,
+		   int mode, snd_config_t *parent_conf)
+{
+	return snd_pcm_open_named_slave(pcmp, NULL, root, conf, stream,
+					mode, parent_conf);
+}
+int snd_pcm_conf_generic_id(const char *id);
+
+int snd_pcm_hw_open_fd(snd_pcm_t **pcmp, const char *name, int fd, int mmap_emulation, int sync_ptr_ioctl);
+int __snd_pcm_mmap_emul_open(snd_pcm_t **pcmp, const char *name,
+			     snd_pcm_t *slave, int close_slave);
+
+int snd_pcm_wait_nocheck(snd_pcm_t *pcm, int timeout);
+
+const snd_config_t *snd_pcm_rate_get_default_converter(snd_config_t *root);
+
+#define SND_PCM_HW_PARBIT_ACCESS	(1U << SND_PCM_HW_PARAM_ACCESS)
+#define SND_PCM_HW_PARBIT_FORMAT	(1U << SND_PCM_HW_PARAM_FORMAT)
+#define SND_PCM_HW_PARBIT_SUBFORMAT	(1U << SND_PCM_HW_PARAM_SUBFORMAT)
+#define SND_PCM_HW_PARBIT_CHANNELS	(1U << SND_PCM_HW_PARAM_CHANNELS)
+#define SND_PCM_HW_PARBIT_RATE		(1U << SND_PCM_HW_PARAM_RATE)
+#define SND_PCM_HW_PARBIT_PERIOD_TIME	(1U << SND_PCM_HW_PARAM_PERIOD_TIME)
+#define SND_PCM_HW_PARBIT_PERIOD_SIZE	(1U << SND_PCM_HW_PARAM_PERIOD_SIZE)
+#define SND_PCM_HW_PARBIT_PERIODS	(1U << SND_PCM_HW_PARAM_PERIODS)
+#define SND_PCM_HW_PARBIT_BUFFER_TIME	(1U << SND_PCM_HW_PARAM_BUFFER_TIME)
+#define SND_PCM_HW_PARBIT_BUFFER_SIZE	(1U << SND_PCM_HW_PARAM_BUFFER_SIZE)
+#define SND_PCM_HW_PARBIT_SAMPLE_BITS	(1U << SND_PCM_HW_PARAM_SAMPLE_BITS)
+#define SND_PCM_HW_PARBIT_FRAME_BITS	(1U << SND_PCM_HW_PARAM_FRAME_BITS)
+#define SND_PCM_HW_PARBIT_PERIOD_BYTES	(1U << SND_PCM_HW_PARAM_PERIOD_BYTES)
+#define SND_PCM_HW_PARBIT_BUFFER_BYTES	(1U << SND_PCM_HW_PARAM_BUFFER_BYTES)
+#define SND_PCM_HW_PARBIT_TICK_TIME	(1U << SND_PCM_HW_PARAM_TICK_TIME)
+
+
+#define SND_PCM_ACCBIT_MMAP { ((1U << SND_PCM_ACCESS_MMAP_INTERLEAVED) | \
+			     (1U << SND_PCM_ACCESS_MMAP_NONINTERLEAVED) | \
+			     (1U << SND_PCM_ACCESS_MMAP_COMPLEX)) }
+#define SND_PCM_ACCBIT_MMAPI { (1U << SND_PCM_ACCESS_MMAP_INTERLEAVED) }
+#define SND_PCM_ACCBIT_MMAPN { (1U << SND_PCM_ACCESS_MMAP_NONINTERLEAVED) }
+#define SND_PCM_ACCBIT_MMAPC { (1U << SND_PCM_ACCESS_MMAP_COMPLEX) }
+
+#define SND_PCM_ACCBIT_SHM { ((1U << SND_PCM_ACCESS_MMAP_INTERLEAVED) | \
+			    (1U << SND_PCM_ACCESS_RW_INTERLEAVED) | \
+			    (1U << SND_PCM_ACCESS_MMAP_NONINTERLEAVED) | \
+			    (1U << SND_PCM_ACCESS_RW_NONINTERLEAVED)) }
+#define SND_PCM_ACCBIT_SHMI { ((1U << SND_PCM_ACCESS_MMAP_INTERLEAVED) | \
+			     (1U << SND_PCM_ACCESS_RW_INTERLEAVED)) }
+#define SND_PCM_ACCBIT_SHMN { ((1U << SND_PCM_ACCESS_MMAP_NONINTERLEAVED) | \
+			     (1U << SND_PCM_ACCESS_RW_NONINTERLEAVED)) }
+
+#define SND_PCM_FMTBIT_LINEAR \
+	{ ((1U << SND_PCM_FORMAT_S8) | \
+	 (1U << SND_PCM_FORMAT_U8) | \
+	 (1U << SND_PCM_FORMAT_S16_LE) | \
+	 (1U << SND_PCM_FORMAT_S16_BE) | \
+	 (1U << SND_PCM_FORMAT_U16_LE) | \
+	 (1U << SND_PCM_FORMAT_U16_BE) | \
+	 (1U << SND_PCM_FORMAT_S24_LE) | \
+	 (1U << SND_PCM_FORMAT_S24_BE) | \
+	 (1U << SND_PCM_FORMAT_U24_LE) | \
+	 (1U << SND_PCM_FORMAT_U24_BE) | \
+	 (1U << SND_PCM_FORMAT_S32_LE) | \
+	 (1U << SND_PCM_FORMAT_S32_BE) | \
+	 (1U << SND_PCM_FORMAT_U32_LE) | \
+	 (1U << SND_PCM_FORMAT_U32_BE)), \
+	((1U << (SND_PCM_FORMAT_S24_3LE - 32)) | \
+	 (1U << (SND_PCM_FORMAT_U24_3LE - 32)) | \
+	 (1U << (SND_PCM_FORMAT_S24_3BE - 32)) | \
+	 (1U << (SND_PCM_FORMAT_U24_3BE - 32)) | \
+	 (1U << (SND_PCM_FORMAT_S20_3LE - 32)) | \
+	 (1U << (SND_PCM_FORMAT_U20_3LE - 32)) | \
+	 (1U << (SND_PCM_FORMAT_S20_3BE - 32)) | \
+	 (1U << (SND_PCM_FORMAT_U20_3BE - 32)) | \
+	 (1U << (SND_PCM_FORMAT_S18_3LE - 32)) | \
+	 (1U << (SND_PCM_FORMAT_U18_3LE - 32)) | \
+	 (1U << (SND_PCM_FORMAT_S18_3BE - 32)) | \
+	 (1U << (SND_PCM_FORMAT_U18_3BE - 32))) }
+	
+
+#define SND_PCM_FMTBIT_FLOAT \
+	{ ((1U << SND_PCM_FORMAT_FLOAT_LE) | \
+	 (1U << SND_PCM_FORMAT_FLOAT_BE) | \
+	 (1U << SND_PCM_FORMAT_FLOAT64_LE) | \
+	 (1U << SND_PCM_FORMAT_FLOAT64_BE)) }
+
+
+typedef union snd_tmp_float {
+	float f;
+	int32_t i;
+} snd_tmp_float_t;
+
+typedef union snd_tmp_double {
+	double d;
+	int64_t l;
+} snd_tmp_double_t;
+
+/* get the current timestamp */
+static inline void gettimestamp(snd_htimestamp_t *tstamp, int monotonic)
+{
+#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
+	if (monotonic) {
+		clock_gettime(CLOCK_MONOTONIC, tstamp);
+	} else {
+#endif
+		struct timeval tv;
+
+		gettimeofday(&tv, 0);
+		tstamp->tv_sec = tv.tv_sec;
+		tstamp->tv_nsec = tv.tv_usec * 1000L;
+#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
+	}
+#endif
+}
diff --git a/src/pcm/pcm_meter.c b/src/pcm/pcm_meter.c
new file mode 100644
index 0000000..5acc7bc
--- /dev/null
+++ b/src/pcm/pcm_meter.c
@@ -0,0 +1,1228 @@
+/**
+ * \file pcm/pcm_meter.c
+ * \brief Helper functions for #SND_PCM_TYPE_METER PCM scopes
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \date 2001
+ *
+ * Helper functions for #SND_PCM_TYPE_METER PCM scopes
+ */
+/*
+ *  PCM - Meter plugin
+ *  Copyright (c) 2001 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+
+#include <byteswap.h>
+#include <time.h>
+#include <pthread.h>
+#include <dlfcn.h>
+#include "pcm_local.h"
+#include "pcm_plugin.h"
+#include "iatomic.h"
+
+#ifndef PIC
+/* entry for static linking */
+const char *_snd_module_pcm_meter = "";
+#endif
+
+#ifndef DOC_HIDDEN
+#define FREQUENCY 50
+
+struct _snd_pcm_scope {
+	int enabled;
+	char *name;
+	const snd_pcm_scope_ops_t *ops;
+	void *private_data;
+	struct list_head list;
+};
+
+typedef struct _snd_pcm_meter {
+	snd_pcm_generic_t gen;
+	snd_pcm_uframes_t rptr;
+	snd_pcm_uframes_t buf_size;
+	snd_pcm_channel_area_t *buf_areas;
+	snd_pcm_uframes_t now;
+	unsigned char *buf;
+	struct list_head scopes;
+	int closed;
+	int running;
+	atomic_t reset;
+	pthread_t thread;
+	pthread_mutex_t update_mutex;
+	pthread_mutex_t running_mutex;
+	pthread_cond_t running_cond;
+	struct timespec delay;
+	void *dl_handle;
+} snd_pcm_meter_t;
+
+static void snd_pcm_meter_add_frames(snd_pcm_t *pcm,
+				     const snd_pcm_channel_area_t *areas,
+				     snd_pcm_uframes_t ptr,
+				     snd_pcm_uframes_t frames)
+{
+	snd_pcm_meter_t *meter = pcm->private_data;
+	while (frames > 0) {
+		snd_pcm_uframes_t n = frames;
+		snd_pcm_uframes_t dst_offset = ptr % meter->buf_size;
+		snd_pcm_uframes_t src_offset = ptr % pcm->buffer_size;
+		snd_pcm_uframes_t dst_cont = meter->buf_size - dst_offset;
+		snd_pcm_uframes_t src_cont = pcm->buffer_size - src_offset;
+		if (n > dst_cont)
+			n = dst_cont;
+		if (n > src_cont)
+			n = src_cont;
+		snd_pcm_areas_copy(meter->buf_areas, dst_offset, 
+				   areas, src_offset,
+				   pcm->channels, n, pcm->format);
+		frames -= n;
+		ptr += n;
+		if (ptr == pcm->boundary)
+			ptr = 0;
+	}
+}
+
+static void snd_pcm_meter_update_main(snd_pcm_t *pcm)
+{
+	snd_pcm_meter_t *meter = pcm->private_data;
+	snd_pcm_sframes_t frames;
+	snd_pcm_uframes_t rptr, old_rptr;
+	const snd_pcm_channel_area_t *areas;
+	int locked;
+	locked = (pthread_mutex_trylock(&meter->update_mutex) >= 0);
+	areas = snd_pcm_mmap_areas(pcm);
+	rptr = *pcm->hw.ptr;
+	old_rptr = meter->rptr;
+	meter->rptr = rptr;
+	frames = rptr - old_rptr;
+	if (frames < 0)
+		frames += pcm->boundary;
+	if (frames > 0) {
+		assert((snd_pcm_uframes_t) frames <= pcm->buffer_size);
+		snd_pcm_meter_add_frames(pcm, areas, old_rptr,
+					 (snd_pcm_uframes_t) frames);
+	}
+	if (locked)
+		pthread_mutex_unlock(&meter->update_mutex);
+}
+
+static int snd_pcm_meter_update_scope(snd_pcm_t *pcm)
+{
+	snd_pcm_meter_t *meter = pcm->private_data;
+	snd_pcm_sframes_t frames;
+	snd_pcm_uframes_t rptr, old_rptr;
+	const snd_pcm_channel_area_t *areas;
+	int reset = 0;
+	/* Wait main thread */
+	pthread_mutex_lock(&meter->update_mutex);
+	areas = snd_pcm_mmap_areas(pcm);
+ _again:
+	rptr = *pcm->hw.ptr;
+	old_rptr = meter->rptr;
+	rmb();
+	if (atomic_read(&meter->reset)) {
+		reset = 1;
+		atomic_dec(&meter->reset);
+		goto _again;
+	}
+	meter->rptr = rptr;
+	frames = rptr - old_rptr;
+	if (frames < 0)
+		frames += pcm->boundary;
+	if (frames > 0) {
+		assert((snd_pcm_uframes_t) frames <= pcm->buffer_size);
+		snd_pcm_meter_add_frames(pcm, areas, old_rptr,
+					 (snd_pcm_uframes_t) frames);
+	}
+	pthread_mutex_unlock(&meter->update_mutex);
+	return reset;
+}
+
+static int snd_pcm_scope_remove(snd_pcm_scope_t *scope)
+{
+	free(scope->name);
+	scope->ops->close(scope);
+	list_del(&scope->list);
+	free(scope);
+	return 0;
+}
+
+static int snd_pcm_scope_enable(snd_pcm_scope_t *scope)
+{
+	int err;
+	assert(!scope->enabled);
+	err = scope->ops->enable(scope);
+	scope->enabled = (err >= 0);
+	return err;
+}
+
+static int snd_pcm_scope_disable(snd_pcm_scope_t *scope)
+{
+	assert(scope->enabled);
+	scope->ops->disable(scope);
+	scope->enabled = 0;
+	return 0;
+}
+
+static void *snd_pcm_meter_thread(void *data)
+{
+	snd_pcm_t *pcm = data;
+	snd_pcm_meter_t *meter = pcm->private_data;
+	snd_pcm_t *spcm = meter->gen.slave;
+	struct list_head *pos;
+	snd_pcm_scope_t *scope;
+	int reset;
+	list_for_each(pos, &meter->scopes) {
+		scope = list_entry(pos, snd_pcm_scope_t, list);
+		snd_pcm_scope_enable(scope);
+	}
+	while (!meter->closed) {
+		snd_pcm_sframes_t now;
+		snd_pcm_status_t status;
+		int err;
+		pthread_mutex_lock(&meter->running_mutex);
+		err = snd_pcm_status(spcm, &status);
+		assert(err >= 0);
+		if (status.state != SND_PCM_STATE_RUNNING &&
+		    (status.state != SND_PCM_STATE_DRAINING ||
+		     spcm->stream != SND_PCM_STREAM_PLAYBACK)) {
+			if (meter->running) {
+				list_for_each(pos, &meter->scopes) {
+					scope = list_entry(pos, snd_pcm_scope_t, list);
+					scope->ops->stop(scope);
+				}
+				meter->running = 0;
+			}
+			pthread_cond_wait(&meter->running_cond,
+					  &meter->running_mutex);
+			pthread_mutex_unlock(&meter->running_mutex);
+			continue;
+		}
+		pthread_mutex_unlock(&meter->running_mutex);
+		if (pcm->stream == SND_PCM_STREAM_PLAYBACK) {
+			now = status.appl_ptr - status.delay;
+			if (now < 0)
+				now += pcm->boundary;
+		} else {
+			now = status.appl_ptr + status.delay;
+			if ((snd_pcm_uframes_t) now >= pcm->boundary)
+				now -= pcm->boundary;
+		}
+		meter->now = now;
+		if (pcm->stream == SND_PCM_STREAM_CAPTURE)
+			reset = snd_pcm_meter_update_scope(pcm);
+		else {
+			reset = 0;
+			while (atomic_read(&meter->reset)) {
+				reset = 1;
+				atomic_dec(&meter->reset);
+			}
+		}
+		if (reset) {
+			list_for_each(pos, &meter->scopes) {
+				scope = list_entry(pos, snd_pcm_scope_t, list);
+				if (scope->enabled)
+					scope->ops->reset(scope);
+			}
+			continue;
+		}
+		if (!meter->running) {
+			list_for_each(pos, &meter->scopes) {
+				scope = list_entry(pos, snd_pcm_scope_t, list);
+				if (scope->enabled)
+					scope->ops->start(scope);
+			}
+			meter->running = 1;
+		}
+		list_for_each(pos, &meter->scopes) {
+			scope = list_entry(pos, snd_pcm_scope_t, list);
+			if (scope->enabled)
+				scope->ops->update(scope);
+		}
+	        nanosleep(&meter->delay, NULL);
+	}
+	list_for_each(pos, &meter->scopes) {
+		scope = list_entry(pos, snd_pcm_scope_t, list);
+		if (scope->enabled)
+			snd_pcm_scope_disable(scope);
+	}
+	return NULL;
+}
+
+static int snd_pcm_meter_close(snd_pcm_t *pcm)
+{
+	snd_pcm_meter_t *meter = pcm->private_data;
+	struct list_head *pos, *npos;
+	int err = 0;
+	pthread_mutex_destroy(&meter->update_mutex);
+	pthread_mutex_destroy(&meter->running_mutex);
+	pthread_cond_destroy(&meter->running_cond);
+	if (meter->gen.close_slave)
+		err = snd_pcm_close(meter->gen.slave);
+	list_for_each_safe(pos, npos, &meter->scopes) {
+		snd_pcm_scope_t *scope;
+		scope = list_entry(pos, snd_pcm_scope_t, list);
+		snd_pcm_scope_remove(scope);
+	}
+	if (meter->dl_handle)
+		snd_dlclose(meter->dl_handle);
+	free(meter);
+	return err;
+}
+
+static int snd_pcm_meter_prepare(snd_pcm_t *pcm)
+{
+	snd_pcm_meter_t *meter = pcm->private_data;
+	int err;
+	atomic_inc(&meter->reset);
+	err = snd_pcm_prepare(meter->gen.slave);
+	if (err >= 0) {
+		if (pcm->stream == SND_PCM_STREAM_PLAYBACK)
+			meter->rptr = *pcm->appl.ptr;
+		else
+			meter->rptr = *pcm->hw.ptr;
+	}
+	return err;
+}
+
+static int snd_pcm_meter_reset(snd_pcm_t *pcm)
+{
+	snd_pcm_meter_t *meter = pcm->private_data;
+	int err = snd_pcm_reset(meter->gen.slave);
+	if (err >= 0) {
+		if (pcm->stream == SND_PCM_STREAM_PLAYBACK)
+			meter->rptr = *pcm->appl.ptr;
+	}
+	return err;
+}
+
+static int snd_pcm_meter_start(snd_pcm_t *pcm)
+{
+	snd_pcm_meter_t *meter = pcm->private_data;
+	int err;
+	pthread_mutex_lock(&meter->running_mutex);
+	err = snd_pcm_start(meter->gen.slave);
+	if (err >= 0)
+		pthread_cond_signal(&meter->running_cond);
+	pthread_mutex_unlock(&meter->running_mutex);
+	return err;
+}
+
+static snd_pcm_sframes_t snd_pcm_meter_rewind(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
+{
+	snd_pcm_meter_t *meter = pcm->private_data;
+	snd_pcm_sframes_t err = snd_pcm_rewind(meter->gen.slave, frames);
+	if (err > 0 && pcm->stream == SND_PCM_STREAM_PLAYBACK)
+		meter->rptr = *pcm->appl.ptr;
+	return err;
+}
+
+static snd_pcm_sframes_t snd_pcm_meter_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
+{
+	snd_pcm_meter_t *meter = pcm->private_data;
+	snd_pcm_sframes_t err = INTERNAL(snd_pcm_forward)(meter->gen.slave, frames);
+	if (err > 0 && pcm->stream == SND_PCM_STREAM_PLAYBACK)
+		meter->rptr = *pcm->appl.ptr;
+	return err;
+}
+
+static snd_pcm_sframes_t snd_pcm_meter_mmap_commit(snd_pcm_t *pcm,
+						   snd_pcm_uframes_t offset,
+						   snd_pcm_uframes_t size)
+{
+	snd_pcm_meter_t *meter = pcm->private_data;
+	snd_pcm_uframes_t old_rptr = *pcm->appl.ptr;
+	snd_pcm_sframes_t result = snd_pcm_mmap_commit(meter->gen.slave, offset, size);
+	if (result <= 0)
+		return result;
+	if (pcm->stream == SND_PCM_STREAM_PLAYBACK) {
+		snd_pcm_meter_add_frames(pcm, snd_pcm_mmap_areas(pcm), old_rptr, result);
+		meter->rptr = *pcm->appl.ptr;
+	}
+	return result;
+}
+
+static snd_pcm_sframes_t snd_pcm_meter_avail_update(snd_pcm_t *pcm)
+{
+	snd_pcm_meter_t *meter = pcm->private_data;
+	snd_pcm_sframes_t result = snd_pcm_avail_update(meter->gen.slave);
+	if (result <= 0)
+		return result;
+	if (pcm->stream == SND_PCM_STREAM_CAPTURE)
+		snd_pcm_meter_update_main(pcm);
+	return result;
+}
+
+static int snd_pcm_meter_hw_refine_cprepare(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params)
+{
+	int err;
+	snd_pcm_access_mask_t access_mask = { SND_PCM_ACCBIT_SHM };
+	err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_ACCESS,
+					 &access_mask);
+	if (err < 0)
+		return err;
+	params->info &= ~(SND_PCM_INFO_MMAP | SND_PCM_INFO_MMAP_VALID);
+	return 0;
+}
+
+static int snd_pcm_meter_hw_refine_sprepare(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *sparams)
+{
+	snd_pcm_access_mask_t saccess_mask = { SND_PCM_ACCBIT_MMAP };
+	_snd_pcm_hw_params_any(sparams);
+	_snd_pcm_hw_param_set_mask(sparams, SND_PCM_HW_PARAM_ACCESS,
+				   &saccess_mask);
+	return 0;
+}
+
+static int snd_pcm_meter_hw_refine_schange(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params,
+					  snd_pcm_hw_params_t *sparams)
+{
+	int err;
+	unsigned int links = ~SND_PCM_HW_PARBIT_ACCESS;
+	err = _snd_pcm_hw_params_refine(sparams, links, params);
+	if (err < 0)
+		return err;
+	return 0;
+}
+	
+static int snd_pcm_meter_hw_refine_cchange(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params,
+					  snd_pcm_hw_params_t *sparams)
+{
+	int err;
+	unsigned int links = ~SND_PCM_HW_PARBIT_ACCESS;
+	err = _snd_pcm_hw_params_refine(params, links, sparams);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static int snd_pcm_meter_hw_refine_slave(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	snd_pcm_meter_t *meter = pcm->private_data;
+	return snd_pcm_hw_refine(meter->gen.slave, params);
+}
+
+static int snd_pcm_meter_hw_params_slave(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	snd_pcm_meter_t *meter = pcm->private_data;
+	return _snd_pcm_hw_params(meter->gen.slave, params);
+}
+
+static int snd_pcm_meter_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	return snd_pcm_hw_refine_slave(pcm, params,
+				       snd_pcm_meter_hw_refine_cprepare,
+				       snd_pcm_meter_hw_refine_cchange,
+				       snd_pcm_meter_hw_refine_sprepare,
+				       snd_pcm_meter_hw_refine_schange,
+				       snd_pcm_meter_hw_refine_slave);
+}
+
+static int snd_pcm_meter_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t * params)
+{
+	snd_pcm_meter_t *meter = pcm->private_data;
+	unsigned int channel;
+	snd_pcm_t *slave = meter->gen.slave;
+	size_t buf_size_bytes;
+	int err;
+	err = snd_pcm_hw_params_slave(pcm, params,
+				      snd_pcm_meter_hw_refine_cchange,
+				      snd_pcm_meter_hw_refine_sprepare,
+				      snd_pcm_meter_hw_refine_schange,
+				      snd_pcm_meter_hw_params_slave);
+	if (err < 0)
+		return err;
+	/* more than 1 second of buffer */
+	meter->buf_size = slave->buffer_size;
+	while (meter->buf_size < slave->rate)
+		meter->buf_size *= 2;
+	buf_size_bytes = snd_pcm_frames_to_bytes(slave, meter->buf_size);
+	assert(!meter->buf);
+	meter->buf = malloc(buf_size_bytes);
+	if (!meter->buf)
+		return -ENOMEM;
+	meter->buf_areas = malloc(sizeof(*meter->buf_areas) * slave->channels);
+	if (!meter->buf_areas) {
+		free(meter->buf);
+		return -ENOMEM;
+	}
+	for (channel = 0; channel < slave->channels; ++channel) {
+		snd_pcm_channel_area_t *a = &meter->buf_areas[channel];
+		a->addr = meter->buf + buf_size_bytes / slave->channels * channel;
+		a->first = 0;
+		a->step = slave->sample_bits;
+	}
+	meter->closed = 0;
+	err = pthread_create(&meter->thread, NULL, snd_pcm_meter_thread, pcm);
+	assert(err == 0);
+	return 0;
+}
+
+static int snd_pcm_meter_hw_free(snd_pcm_t *pcm)
+{
+	snd_pcm_meter_t *meter = pcm->private_data;
+	int err;
+	meter->closed = 1;
+	pthread_mutex_lock(&meter->running_mutex);
+	pthread_cond_signal(&meter->running_cond);
+	pthread_mutex_unlock(&meter->running_mutex);
+	err = pthread_join(meter->thread, 0);
+	assert(err == 0);
+	free(meter->buf);
+	free(meter->buf_areas);
+	meter->buf = NULL;
+	meter->buf_areas = NULL;
+	return snd_pcm_hw_free(meter->gen.slave);
+}
+
+static void snd_pcm_meter_dump(snd_pcm_t *pcm, snd_output_t *out)
+{
+	snd_pcm_meter_t *meter = pcm->private_data;
+	snd_output_printf(out, "Meter PCM\n");
+	if (pcm->setup) {
+		snd_output_printf(out, "Its setup is:\n");
+		snd_pcm_dump_setup(pcm, out);
+	}
+	snd_output_printf(out, "Slave: ");
+	snd_pcm_dump(meter->gen.slave, out);
+}
+
+static const snd_pcm_ops_t snd_pcm_meter_ops = {
+	.close = snd_pcm_meter_close,
+	.info = snd_pcm_generic_info,
+	.hw_refine = snd_pcm_meter_hw_refine,
+	.hw_params = snd_pcm_meter_hw_params,
+	.hw_free = snd_pcm_meter_hw_free,
+	.sw_params = snd_pcm_generic_sw_params,
+	.channel_info = snd_pcm_generic_channel_info,
+	.dump = snd_pcm_meter_dump,
+	.nonblock = snd_pcm_generic_nonblock,
+	.async = snd_pcm_generic_async,
+	.mmap = snd_pcm_generic_mmap,
+	.munmap = snd_pcm_generic_munmap,
+};
+
+static const snd_pcm_fast_ops_t snd_pcm_meter_fast_ops = {
+	.status = snd_pcm_generic_status,
+	.state = snd_pcm_generic_state,
+	.hwsync = snd_pcm_generic_hwsync,
+	.delay = snd_pcm_generic_delay,
+	.prepare = snd_pcm_meter_prepare,
+	.reset = snd_pcm_meter_reset,
+	.start = snd_pcm_meter_start,
+	.drop = snd_pcm_generic_drop,
+	.drain = snd_pcm_generic_drain,
+	.pause = snd_pcm_generic_pause,
+	.rewindable = snd_pcm_generic_rewindable,
+	.rewind = snd_pcm_meter_rewind,
+	.forwardable = snd_pcm_generic_forwardable,
+	.forward = snd_pcm_meter_forward,
+	.resume = snd_pcm_generic_resume,
+	.writei = snd_pcm_mmap_writei,
+	.writen = snd_pcm_mmap_writen,
+	.readi = snd_pcm_mmap_readi,
+	.readn = snd_pcm_mmap_readn,
+	.avail_update = snd_pcm_meter_avail_update,
+	.mmap_commit = snd_pcm_meter_mmap_commit,
+	.htimestamp = snd_pcm_generic_htimestamp,
+	.poll_descriptors_count = snd_pcm_generic_poll_descriptors_count,
+	.poll_descriptors = snd_pcm_generic_poll_descriptors,
+	.poll_revents = snd_pcm_generic_poll_revents,
+};
+
+/**
+ * \brief Creates a new Meter PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param frequency Update frequency
+ * \param slave Slave PCM handle
+ * \param close_slave When set, the slave PCM handle is closed with copy PCM
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int snd_pcm_meter_open(snd_pcm_t **pcmp, const char *name, unsigned int frequency,
+		       snd_pcm_t *slave, int close_slave)
+{
+	snd_pcm_t *pcm;
+	snd_pcm_meter_t *meter;
+	int err;
+	assert(pcmp);
+	meter = calloc(1, sizeof(snd_pcm_meter_t));
+	if (!meter)
+		return -ENOMEM;
+	meter->gen.slave = slave;
+	meter->gen.close_slave = close_slave;
+	meter->delay.tv_sec = 0;
+	meter->delay.tv_nsec = 1000000000 / frequency;
+	INIT_LIST_HEAD(&meter->scopes);
+
+	err = snd_pcm_new(&pcm, SND_PCM_TYPE_METER, name, slave->stream, slave->mode);
+	if (err < 0) {
+		free(meter);
+		return err;
+	}
+	pcm->mmap_rw = 1;
+	pcm->mmap_shadow = 1;
+	pcm->ops = &snd_pcm_meter_ops;
+	pcm->fast_ops = &snd_pcm_meter_fast_ops;
+	pcm->private_data = meter;
+	pcm->poll_fd = slave->poll_fd;
+	pcm->poll_events = slave->poll_events;
+	pcm->monotonic = slave->monotonic;
+	snd_pcm_link_hw_ptr(pcm, slave);
+	snd_pcm_link_appl_ptr(pcm, slave);
+	*pcmp = pcm;
+
+	pthread_mutex_init(&meter->update_mutex, NULL);
+	pthread_mutex_init(&meter->running_mutex, NULL);
+	pthread_cond_init(&meter->running_cond, NULL);
+	return 0;
+}
+
+
+static int snd_pcm_meter_add_scope_conf(snd_pcm_t *pcm, const char *name,
+					snd_config_t *root, snd_config_t *conf)
+{
+	char buf[256];
+	snd_config_iterator_t i, next;
+	const char *id;
+	const char *lib = NULL, *open_name = NULL, *str = NULL;
+	snd_config_t *c, *type_conf = NULL;
+	int (*open_func)(snd_pcm_t *, const char *,
+			 snd_config_t *, snd_config_t *) = NULL;
+	snd_pcm_meter_t *meter = pcm->private_data;
+	void *h = NULL;
+	int err;
+
+	if (snd_config_get_type(conf) != SND_CONFIG_TYPE_COMPOUND) {
+		SNDERR("Invalid type for scope %s", str);
+		err = -EINVAL;
+		goto _err;
+	}
+	err = snd_config_search(conf, "type", &c);
+	if (err < 0) {
+		SNDERR("type is not defined");
+		goto _err;
+	}
+	err = snd_config_get_id(c, &id);
+	if (err < 0) {
+		SNDERR("unable to get id");
+		goto _err;
+	}
+	err = snd_config_get_string(c, &str);
+	if (err < 0) {
+		SNDERR("Invalid type for %s", id);
+		goto _err;
+	}
+	err = snd_config_search_definition(root, "pcm_scope_type", str, &type_conf);
+	if (err >= 0) {
+		snd_config_for_each(i, next, type_conf) {
+			snd_config_t *n = snd_config_iterator_entry(i);
+			const char *id;
+			if (snd_config_get_id(n, &id) < 0)
+				continue;
+			if (strcmp(id, "comment") == 0)
+				continue;
+			if (strcmp(id, "lib") == 0) {
+				err = snd_config_get_string(n, &lib);
+				if (err < 0) {
+					SNDERR("Invalid type for %s", id);
+					goto _err;
+				}
+				continue;
+			}
+			if (strcmp(id, "open") == 0) {
+				err = snd_config_get_string(n, &open_name);
+				if (err < 0) {
+					SNDERR("Invalid type for %s", id);
+					goto _err;
+				}
+				continue;
+			}
+			SNDERR("Unknown field %s", id);
+			err = -EINVAL;
+			goto _err;
+		}
+	}
+	if (!open_name) {
+		open_name = buf;
+		snprintf(buf, sizeof(buf), "_snd_pcm_scope_%s_open", str);
+	}
+	h = snd_dlopen(lib, RTLD_NOW);
+	open_func = h ? dlsym(h, open_name) : NULL;
+	err = 0;
+	if (!h) {
+		SNDERR("Cannot open shared library %s", lib);
+		err = -ENOENT;
+	} else if (!open_func) {
+		SNDERR("symbol %s is not defined inside %s", open_name, lib);
+		snd_dlclose(h);
+		err = -ENXIO;
+	}
+       _err:
+	if (type_conf)
+		snd_config_delete(type_conf);
+	if (! err) {
+		err = open_func(pcm, name, root, conf);
+		if (err < 0)
+			snd_dlclose(h);
+		else
+			meter->dl_handle = h;
+	}
+	return err;
+}
+
+/*! \page pcm_plugins
+
+\section pcm_plugins_meter Plugin: Meter
+
+Show meter (visual waveform representation).
+
+\code
+pcm_scope_type.NAME {
+	[lib STR]		# Library file (default libasound.so)
+	[open STR]		# Open function (default _snd_pcm_scope_NAME_open)
+}
+
+pcm_scope.name {
+	type STR		# Scope type
+	...
+}
+
+pcm.name {
+        type meter              # Meter PCM
+        slave STR               # Slave name
+        # or
+        slave {                 # Slave definition
+                pcm STR         # Slave PCM name
+                # or
+                pcm { }         # Slave PCM definition
+        }
+	[frequency INT]		# Updates per second
+	scopes {
+		ID STR		# Scope name (see pcm_scope)
+		# or
+		ID { }		# Scope definition (see pcm_scope)
+	}
+}
+\endcode
+
+\subsection pcm_plugins_meter_funcref Function reference
+
+<UL>
+  <LI>snd_pcm_meter_open()
+  <LI>_snd_pcm_meter_open()
+</UL>
+
+*/
+
+/**
+ * \brief Creates a new Meter PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param root Root configuration node
+ * \param conf Configuration node with Meter PCM description
+ * \param stream Stream type
+ * \param mode Stream mode
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int _snd_pcm_meter_open(snd_pcm_t **pcmp, const char *name,
+			snd_config_t *root, snd_config_t *conf, 
+			snd_pcm_stream_t stream, int mode)
+{
+	snd_config_iterator_t i, next;
+	int err;
+	snd_pcm_t *spcm;
+	snd_config_t *slave = NULL, *sconf;
+	long frequency = -1;
+	snd_config_t *scopes = NULL;
+	snd_config_for_each(i, next, conf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (snd_pcm_conf_generic_id(id))
+			continue;
+		if (strcmp(id, "slave") == 0) {
+			slave = n;
+			continue;
+		}
+		if (strcmp(id, "frequency") == 0) {
+			err = snd_config_get_integer(n, &frequency);
+			if (err < 0) {
+				SNDERR("Invalid type for %s", id);
+				return -EINVAL;
+			}
+			continue;
+		}
+		if (strcmp(id, "scopes") == 0) {
+			if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) {
+				SNDERR("Invalid type for %s", id);
+				return -EINVAL;
+			}
+			scopes = n;
+			continue;
+		}
+		SNDERR("Unknown field %s", id);
+		return -EINVAL;
+	}
+	if (!slave) {
+		SNDERR("slave is not defined");
+		return -EINVAL;
+	}
+	err = snd_pcm_slave_conf(root, slave, &sconf, 0);
+	if (err < 0)
+		return err;
+	err = snd_pcm_open_slave(&spcm, root, sconf, stream, mode, conf);
+	snd_config_delete(sconf);
+	if (err < 0)
+		return err;
+	err = snd_pcm_meter_open(pcmp, name, frequency > 0 ? (unsigned int) frequency : FREQUENCY, spcm, 1);
+	if (err < 0) {
+		snd_pcm_close(spcm);
+		return err;
+	}
+	if (!scopes)
+		return 0;
+	snd_config_for_each(i, next, scopes) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id, *str;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (snd_config_get_string(n, &str) >= 0) {
+			err = snd_config_search_definition(root, "pcm_scope", str, &n);
+			if (err < 0) {
+				SNDERR("unknown pcm_scope %s", str);
+			} else {
+				err = snd_pcm_meter_add_scope_conf(*pcmp, id, root, n);
+				snd_config_delete(n);
+			}
+		} else
+			err = snd_pcm_meter_add_scope_conf(*pcmp, id, root, n);
+		if (err < 0) {
+			snd_pcm_close(*pcmp);
+			return err;
+		}
+	}
+	return 0;
+}
+SND_DLSYM_BUILD_VERSION(_snd_pcm_meter_open, SND_PCM_DLSYM_VERSION);
+
+#endif
+
+/**
+ * \brief Add a scope to a #SND_PCM_TYPE_METER PCM
+ * \param pcm PCM handle
+ * \param scope Scope handle
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_pcm_meter_add_scope(snd_pcm_t *pcm, snd_pcm_scope_t *scope)
+{
+	snd_pcm_meter_t *meter;
+	assert(pcm->type == SND_PCM_TYPE_METER);
+	meter = pcm->private_data;
+	list_add_tail(&scope->list, &meter->scopes);
+	return 0;
+}
+
+/**
+ * \brief Search an installed scope inside a #SND_PCM_TYPE_METER PCM
+ * \param pcm PCM handle
+ * \param name scope name
+ * \return pointer to found scope or NULL if none is found
+ */
+snd_pcm_scope_t *snd_pcm_meter_search_scope(snd_pcm_t *pcm, const char *name)
+{
+	snd_pcm_meter_t *meter;
+	struct list_head *pos;
+	assert(pcm->type == SND_PCM_TYPE_METER);
+	meter = pcm->private_data;
+	list_for_each(pos, &meter->scopes) {
+		snd_pcm_scope_t *scope;
+		scope = list_entry(pos, snd_pcm_scope_t, list);
+		if (scope->name && strcmp(scope->name, name) == 0)
+			return scope;
+	}
+	return NULL;
+}
+
+/**
+ * \brief Get meter buffer size from a #SND_PCM_TYPE_METER PCM
+ * \param pcm PCM handle
+ * \return meter buffer size in frames
+ */
+snd_pcm_uframes_t snd_pcm_meter_get_bufsize(snd_pcm_t *pcm)
+{
+	snd_pcm_meter_t *meter;
+	assert(pcm->type == SND_PCM_TYPE_METER);
+	meter = pcm->private_data;
+	assert(meter->gen.slave->setup);
+	return meter->buf_size;
+}
+
+/**
+ * \brief Get meter channels from a #SND_PCM_TYPE_METER PCM
+ * \param pcm PCM handle
+ * \return meter channels count
+ */
+unsigned int snd_pcm_meter_get_channels(snd_pcm_t *pcm)
+{
+	snd_pcm_meter_t *meter;
+	assert(pcm->type == SND_PCM_TYPE_METER);
+	meter = pcm->private_data;
+	assert(meter->gen.slave->setup);
+	return meter->gen.slave->channels;
+}
+
+/**
+ * \brief Get meter rate from a #SND_PCM_TYPE_METER PCM
+ * \param pcm PCM handle
+ * \return approximate rate
+ */
+unsigned int snd_pcm_meter_get_rate(snd_pcm_t *pcm)
+{
+	snd_pcm_meter_t *meter;
+	assert(pcm->type == SND_PCM_TYPE_METER);
+	meter = pcm->private_data;
+	assert(meter->gen.slave->setup);
+	return meter->gen.slave->rate;
+}
+
+/**
+ * \brief Get meter "now" frame pointer from a #SND_PCM_TYPE_METER PCM
+ * \param pcm PCM handle
+ * \return "now" frame pointer in frames (0 ... boundary - 1) see #snd_pcm_meter_get_boundary
+ */
+snd_pcm_uframes_t snd_pcm_meter_get_now(snd_pcm_t *pcm)
+{
+	snd_pcm_meter_t *meter;
+	assert(pcm->type == SND_PCM_TYPE_METER);
+	meter = pcm->private_data;
+	assert(meter->gen.slave->setup);
+	return meter->now;
+}
+
+/**
+ * \brief Get boundary for frame pointers from a #SND_PCM_TYPE_METER PCM
+ * \param pcm PCM handle
+ * \return boundary in frames
+ */
+snd_pcm_uframes_t snd_pcm_meter_get_boundary(snd_pcm_t *pcm)
+{
+	snd_pcm_meter_t *meter;
+	assert(pcm->type == SND_PCM_TYPE_METER);
+	meter = pcm->private_data;
+	assert(meter->gen.slave->setup);
+	return meter->gen.slave->boundary;
+}
+
+/**
+ * \brief Set name of a #SND_PCM_TYPE_METER PCM scope
+ * \param scope PCM meter scope
+ * \param val scope name
+ */
+void snd_pcm_scope_set_name(snd_pcm_scope_t *scope, const char *val)
+{
+	scope->name = strdup(val);
+}
+
+/**
+ * \brief Get name of a #SND_PCM_TYPE_METER PCM scope
+ * \param scope PCM meter scope
+ * \return scope name
+ */
+const char *snd_pcm_scope_get_name(snd_pcm_scope_t *scope)
+{
+	return scope->name;
+}
+
+/**
+ * \brief Set callbacks for a #SND_PCM_TYPE_METER PCM scope
+ * \param scope PCM meter scope
+ * \param val callbacks
+ */
+void snd_pcm_scope_set_ops(snd_pcm_scope_t *scope, const snd_pcm_scope_ops_t *val)
+{
+	scope->ops = val;
+}
+
+/**
+ * \brief Get callbacks private value for a #SND_PCM_TYPE_METER PCM scope
+ * \param scope PCM meter scope
+ * \return Private data value
+ */
+void *snd_pcm_scope_get_callback_private(snd_pcm_scope_t *scope)
+{
+	return scope->private_data;
+}
+
+/**
+ * \brief Get callbacks private value for a #SND_PCM_TYPE_METER PCM scope
+ * \param scope PCM meter scope
+ * \param val Private data value
+ */
+void snd_pcm_scope_set_callback_private(snd_pcm_scope_t *scope, void *val)
+{
+	scope->private_data = val;
+}
+
+#ifndef DOC_HIDDEN
+typedef struct _snd_pcm_scope_s16 {
+	snd_pcm_t *pcm;
+	snd_pcm_adpcm_state_t *adpcm_states;
+	unsigned int index;
+	snd_pcm_uframes_t old;
+	int16_t *buf;
+	snd_pcm_channel_area_t *buf_areas;
+} snd_pcm_scope_s16_t;
+
+static int s16_enable(snd_pcm_scope_t *scope)
+{
+	snd_pcm_scope_s16_t *s16 = scope->private_data;
+	snd_pcm_meter_t *meter = s16->pcm->private_data;
+	snd_pcm_t *spcm = meter->gen.slave;
+	snd_pcm_channel_area_t *a;
+	unsigned int c;
+	int idx;
+	if (spcm->format == SND_PCM_FORMAT_S16 &&
+	    spcm->access == SND_PCM_ACCESS_MMAP_NONINTERLEAVED) {
+		s16->buf = (int16_t *) meter->buf;
+		return -EINVAL;
+	}
+	switch (spcm->format) {
+	case SND_PCM_FORMAT_A_LAW:
+	case SND_PCM_FORMAT_MU_LAW:
+	case SND_PCM_FORMAT_IMA_ADPCM:
+		idx = snd_pcm_linear_put_index(SND_PCM_FORMAT_S16, SND_PCM_FORMAT_S16);
+		break;
+	case SND_PCM_FORMAT_S8:
+	case SND_PCM_FORMAT_S16_LE:
+	case SND_PCM_FORMAT_S16_BE:
+	case SND_PCM_FORMAT_S24_LE:
+	case SND_PCM_FORMAT_S24_BE:
+	case SND_PCM_FORMAT_S32_LE:
+	case SND_PCM_FORMAT_S32_BE:
+	case SND_PCM_FORMAT_U8:
+	case SND_PCM_FORMAT_U16_LE:
+	case SND_PCM_FORMAT_U16_BE:
+	case SND_PCM_FORMAT_U24_LE:
+	case SND_PCM_FORMAT_U24_BE:
+	case SND_PCM_FORMAT_U32_LE:
+	case SND_PCM_FORMAT_U32_BE:
+		idx = snd_pcm_linear_convert_index(spcm->format, SND_PCM_FORMAT_S16);
+		break;
+	default:
+		return -EINVAL;
+	}
+	s16->index = idx;
+	if (spcm->format == SND_PCM_FORMAT_IMA_ADPCM) {
+		s16->adpcm_states = calloc(spcm->channels, sizeof(*s16->adpcm_states));
+		if (!s16->adpcm_states)
+			return -ENOMEM;
+	}
+	s16->buf = malloc(meter->buf_size * 2 * spcm->channels);
+	if (!s16->buf) {
+		free(s16->adpcm_states);
+		return -ENOMEM;
+	}
+	a = calloc(spcm->channels, sizeof(*a));
+	if (!a) {
+		free(s16->buf);
+		free(s16->adpcm_states);
+		return -ENOMEM;
+	}
+	s16->buf_areas = a;
+	for (c = 0; c < spcm->channels; c++, a++) {
+		a->addr = s16->buf + c * meter->buf_size;
+		a->first = 0;
+		a->step = 16;
+	}
+	return 0;
+}
+
+static void s16_disable(snd_pcm_scope_t *scope)
+{
+	snd_pcm_scope_s16_t *s16 = scope->private_data;
+	free(s16->adpcm_states);
+	s16->adpcm_states = NULL;
+	free(s16->buf);
+	s16->buf = NULL;
+	free(s16->buf_areas);
+	s16->buf_areas = 0;
+}
+
+static void s16_close(snd_pcm_scope_t *scope)
+{
+	snd_pcm_scope_s16_t *s16 = scope->private_data;
+	free(s16);
+}
+
+static void s16_start(snd_pcm_scope_t *scope ATTRIBUTE_UNUSED)
+{
+}
+
+static void s16_stop(snd_pcm_scope_t *scope ATTRIBUTE_UNUSED)
+{
+}
+
+static void s16_update(snd_pcm_scope_t *scope)
+{
+	snd_pcm_scope_s16_t *s16 = scope->private_data;
+	snd_pcm_meter_t *meter = s16->pcm->private_data;
+	snd_pcm_t *spcm = meter->gen.slave;
+	snd_pcm_sframes_t size;
+	snd_pcm_uframes_t offset;
+	size = meter->now - s16->old;
+	if (size < 0)
+		size += spcm->boundary;
+	offset = s16->old % meter->buf_size;
+	while (size > 0) {
+		snd_pcm_uframes_t frames = size;
+		snd_pcm_uframes_t cont = meter->buf_size - offset;
+		if (frames > cont)
+			frames = cont;
+		switch (spcm->format) {
+		case SND_PCM_FORMAT_A_LAW:
+			snd_pcm_alaw_decode(s16->buf_areas, offset,
+					    meter->buf_areas, offset,
+					    spcm->channels, frames,
+					    s16->index);
+			break;
+		case SND_PCM_FORMAT_MU_LAW:
+			snd_pcm_mulaw_decode(s16->buf_areas, offset,
+					     meter->buf_areas, offset,
+					     spcm->channels, frames,
+					     s16->index);
+			break;
+		case SND_PCM_FORMAT_IMA_ADPCM:
+			snd_pcm_adpcm_decode(s16->buf_areas, offset,
+					     meter->buf_areas, offset,
+					     spcm->channels, frames,
+					     s16->index,
+					     s16->adpcm_states);
+			break;
+		default:
+			snd_pcm_linear_convert(s16->buf_areas, offset,
+					       meter->buf_areas, offset,
+					       spcm->channels, frames,
+					       s16->index);
+			break;
+		}
+		if (frames == cont)
+			offset = 0;
+		else
+			offset += frames;
+		size -= frames;
+	}
+	s16->old = meter->now;
+}
+
+static void s16_reset(snd_pcm_scope_t *scope)
+{
+	snd_pcm_scope_s16_t *s16 = scope->private_data;
+	snd_pcm_meter_t *meter = s16->pcm->private_data;
+	s16->old = meter->now;
+}
+
+static const snd_pcm_scope_ops_t s16_ops = {
+	.enable = s16_enable,
+	.disable = s16_disable,
+	.close = s16_close,
+	.start = s16_start,
+	.stop = s16_stop,
+	.update = s16_update,
+	.reset = s16_reset,
+};
+
+#endif
+
+/**
+ * \brief Add a s16 pseudo scope to a #SND_PCM_TYPE_METER PCM
+ * \param pcm The pcm handle
+ * \param name Scope name
+ * \param scopep Pointer to newly created and added scope
+ * \return 0 on success otherwise a negative error code
+ *
+ * s16 pseudo scope convert #SND_PCM_TYPE_METER PCM frames in CPU endian 
+ * 16 bit frames for use with other scopes. Don't forget to insert it before
+ * and to not insert it more time (see #snd_pcm_meter_search_scope)
+ */
+int snd_pcm_scope_s16_open(snd_pcm_t *pcm, const char *name,
+			   snd_pcm_scope_t **scopep)
+{
+	snd_pcm_meter_t *meter;
+	snd_pcm_scope_t *scope;
+	snd_pcm_scope_s16_t *s16;
+	assert(pcm->type == SND_PCM_TYPE_METER);
+	meter = pcm->private_data;
+	scope = calloc(1, sizeof(*scope));
+	if (!scope)
+		return -ENOMEM;
+	s16 = calloc(1, sizeof(*s16));
+	if (!s16) {
+		free(scope);
+		return -ENOMEM;
+	}
+	if (name)
+		scope->name = strdup(name);
+	s16->pcm = pcm;
+	scope->ops = &s16_ops;
+	scope->private_data = s16;
+	list_add_tail(&scope->list, &meter->scopes);
+	*scopep = scope;
+	return 0;
+}
+
+/**
+ * \brief Get s16 pseudo scope frames buffer for a channel
+ * \param scope s16 pseudo scope handle
+ * \param channel Channel
+ * \return Pointer to channel buffer
+ */
+int16_t *snd_pcm_scope_s16_get_channel_buffer(snd_pcm_scope_t *scope,
+					      unsigned int channel)
+{
+	snd_pcm_scope_s16_t *s16;
+	snd_pcm_meter_t *meter;
+	assert(scope->ops == &s16_ops);
+	s16 = scope->private_data;
+	meter = s16->pcm->private_data;
+	assert(meter->gen.slave->setup);
+	assert(s16->buf_areas);
+	assert(channel < meter->gen.slave->channels);
+	return s16->buf_areas[channel].addr;
+}
+
+/**
+ * \brief allocate an invalid #snd_pcm_scope_t using standard malloc
+ * \param ptr returned pointer
+ * \return 0 on success otherwise negative error code
+ */
+int snd_pcm_scope_malloc(snd_pcm_scope_t **ptr)
+{
+	assert(ptr);
+	*ptr = calloc(1, sizeof(snd_pcm_scope_t));
+	if (!*ptr)
+		return -ENOMEM;
+	return 0;
+}
+
diff --git a/src/pcm/pcm_misc.c b/src/pcm/pcm_misc.c
new file mode 100644
index 0000000..d52160c
--- /dev/null
+++ b/src/pcm/pcm_misc.c
@@ -0,0 +1,833 @@
+/*
+ *  PCM Interface - misc routines
+ *  Copyright (c) 1998 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <byteswap.h>
+#include "pcm_local.h"
+
+
+/**
+ * \brief Return sign info for a PCM sample linear format
+ * \param format Format
+ * \return 0 unsigned, 1 signed, a negative error code if format is not linear
+ */
+int snd_pcm_format_signed(snd_pcm_format_t format)
+{
+	switch (format) {
+	case SNDRV_PCM_FORMAT_S8:
+	case SNDRV_PCM_FORMAT_S16_LE:
+	case SNDRV_PCM_FORMAT_S16_BE:
+	case SNDRV_PCM_FORMAT_S24_LE:
+	case SNDRV_PCM_FORMAT_S24_BE:
+	case SNDRV_PCM_FORMAT_S32_LE:
+	case SNDRV_PCM_FORMAT_S32_BE:
+	case SNDRV_PCM_FORMAT_S24_3LE:
+	case SNDRV_PCM_FORMAT_S24_3BE:
+	case SNDRV_PCM_FORMAT_S20_3LE:
+	case SNDRV_PCM_FORMAT_S20_3BE:
+	case SNDRV_PCM_FORMAT_S18_3LE:
+	case SNDRV_PCM_FORMAT_S18_3BE:
+		return 1;
+	case SNDRV_PCM_FORMAT_U8:
+	case SNDRV_PCM_FORMAT_U16_LE:
+	case SNDRV_PCM_FORMAT_U16_BE:
+	case SNDRV_PCM_FORMAT_U24_LE:
+	case SNDRV_PCM_FORMAT_U24_BE:
+	case SNDRV_PCM_FORMAT_U32_LE:
+	case SNDRV_PCM_FORMAT_U32_BE:
+	case SNDRV_PCM_FORMAT_U24_3LE:
+	case SNDRV_PCM_FORMAT_U24_3BE:
+	case SNDRV_PCM_FORMAT_U20_3LE:
+	case SNDRV_PCM_FORMAT_U20_3BE:
+	case SNDRV_PCM_FORMAT_U18_3LE:
+	case SNDRV_PCM_FORMAT_U18_3BE:
+		return 0;
+	default:
+		return -EINVAL;
+	}
+}
+
+/**
+ * \brief Return sign info for a PCM sample linear format
+ * \param format Format
+ * \return 0 signed, 1 unsigned, a negative error code if format is not linear
+ */
+int snd_pcm_format_unsigned(snd_pcm_format_t format)
+{
+	int val;
+
+	val = snd_pcm_format_signed(format);
+	if (val < 0)
+		return val;
+	return !val;
+}
+
+/**
+ * \brief Return linear info for a PCM sample format
+ * \param format Format
+ * \return 0 non linear, 1 linear
+ */
+int snd_pcm_format_linear(snd_pcm_format_t format)
+{
+	return snd_pcm_format_signed(format) >= 0;
+}
+
+/**
+ * \brief Return float info for a PCM sample format
+ * \param format Format
+ * \return 0 non float, 1 float
+ */
+int snd_pcm_format_float(snd_pcm_format_t format)
+{
+	switch (format) {
+	case SNDRV_PCM_FORMAT_FLOAT_LE:
+	case SNDRV_PCM_FORMAT_FLOAT_BE:
+	case SNDRV_PCM_FORMAT_FLOAT64_LE:
+	case SNDRV_PCM_FORMAT_FLOAT64_BE:
+		return 1;
+	default:
+		return 0;
+	}
+}
+
+/**
+ * \brief Return endian info for a PCM sample format
+ * \param format Format
+ * \return 0 big endian, 1 little endian, a negative error code if endian independent
+ */
+int snd_pcm_format_little_endian(snd_pcm_format_t format)
+{
+	switch (format) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+	case SNDRV_PCM_FORMAT_U16_LE:
+	case SNDRV_PCM_FORMAT_S24_LE:
+	case SNDRV_PCM_FORMAT_U24_LE:
+	case SNDRV_PCM_FORMAT_S32_LE:
+	case SNDRV_PCM_FORMAT_U32_LE:
+	case SNDRV_PCM_FORMAT_FLOAT_LE:
+	case SNDRV_PCM_FORMAT_FLOAT64_LE:
+	case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE:
+	case SNDRV_PCM_FORMAT_S24_3LE:
+	case SNDRV_PCM_FORMAT_S20_3LE:
+	case SNDRV_PCM_FORMAT_S18_3LE:
+	case SNDRV_PCM_FORMAT_U24_3LE:
+	case SNDRV_PCM_FORMAT_U20_3LE:
+	case SNDRV_PCM_FORMAT_U18_3LE:
+		return 1;
+	case SNDRV_PCM_FORMAT_S16_BE:
+	case SNDRV_PCM_FORMAT_U16_BE:
+	case SNDRV_PCM_FORMAT_S24_BE:
+	case SNDRV_PCM_FORMAT_U24_BE:
+	case SNDRV_PCM_FORMAT_S32_BE:
+	case SNDRV_PCM_FORMAT_U32_BE:
+	case SNDRV_PCM_FORMAT_FLOAT_BE:
+	case SNDRV_PCM_FORMAT_FLOAT64_BE:
+	case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE:
+	case SNDRV_PCM_FORMAT_S24_3BE:
+	case SNDRV_PCM_FORMAT_S20_3BE:
+	case SNDRV_PCM_FORMAT_S18_3BE:
+	case SNDRV_PCM_FORMAT_U24_3BE:
+	case SNDRV_PCM_FORMAT_U20_3BE:
+	case SNDRV_PCM_FORMAT_U18_3BE:
+		return 0;
+	default:
+		return -EINVAL;
+	}
+}
+
+/**
+ * \brief Return endian info for a PCM sample format
+ * \param format Format
+ * \return 0 little endian, 1 big endian, a negative error code if endian independent
+ */
+int snd_pcm_format_big_endian(snd_pcm_format_t format)
+{
+	int val;
+
+	val = snd_pcm_format_little_endian(format);
+	if (val < 0)
+		return val;
+	return !val;
+}
+
+/**
+ * \brief Return endian info for a PCM sample format
+ * \param format Format
+ * \return 0 swapped, 1 CPU endian, a negative error code if endian independent
+ */
+int snd_pcm_format_cpu_endian(snd_pcm_format_t format)
+{
+#ifdef SNDRV_LITTLE_ENDIAN
+	return snd_pcm_format_little_endian(format);
+#else
+	return snd_pcm_format_big_endian(format);
+#endif
+}
+
+/**
+ * \brief Return nominal bits per a PCM sample
+ * \param format Sample format
+ * \return bits per sample, a negative error code if not applicable
+ */
+int snd_pcm_format_width(snd_pcm_format_t format)
+{
+	switch (format) {
+	case SNDRV_PCM_FORMAT_S8:
+	case SNDRV_PCM_FORMAT_U8:
+		return 8;
+	case SNDRV_PCM_FORMAT_S16_LE:
+	case SNDRV_PCM_FORMAT_S16_BE:
+	case SNDRV_PCM_FORMAT_U16_LE:
+	case SNDRV_PCM_FORMAT_U16_BE:
+		return 16;
+	case SNDRV_PCM_FORMAT_S18_3LE:
+	case SNDRV_PCM_FORMAT_S18_3BE:
+	case SNDRV_PCM_FORMAT_U18_3LE:
+	case SNDRV_PCM_FORMAT_U18_3BE:
+		return 18;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+	case SNDRV_PCM_FORMAT_S20_3BE:
+	case SNDRV_PCM_FORMAT_U20_3LE:
+	case SNDRV_PCM_FORMAT_U20_3BE:
+		return 20;
+	case SNDRV_PCM_FORMAT_S24_LE:
+	case SNDRV_PCM_FORMAT_S24_BE:
+	case SNDRV_PCM_FORMAT_U24_LE:
+	case SNDRV_PCM_FORMAT_U24_BE:
+	case SNDRV_PCM_FORMAT_S24_3LE:
+	case SNDRV_PCM_FORMAT_S24_3BE:
+	case SNDRV_PCM_FORMAT_U24_3LE:
+	case SNDRV_PCM_FORMAT_U24_3BE:
+		return 24;
+	case SNDRV_PCM_FORMAT_S32_LE:
+	case SNDRV_PCM_FORMAT_S32_BE:
+	case SNDRV_PCM_FORMAT_U32_LE:
+	case SNDRV_PCM_FORMAT_U32_BE:
+	case SNDRV_PCM_FORMAT_FLOAT_LE:
+	case SNDRV_PCM_FORMAT_FLOAT_BE:
+		return 32;
+	case SNDRV_PCM_FORMAT_FLOAT64_LE:
+	case SNDRV_PCM_FORMAT_FLOAT64_BE:
+		return 64;
+	case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE:
+	case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE:
+		return 32;
+	case SNDRV_PCM_FORMAT_MU_LAW:
+	case SNDRV_PCM_FORMAT_A_LAW:
+		return 8;
+	case SNDRV_PCM_FORMAT_IMA_ADPCM:
+		return 4;
+	default:
+		return -EINVAL;
+	}
+}
+
+/**
+ * \brief Return bits needed to store a PCM sample
+ * \param format Sample format
+ * \return bits per sample, a negative error code if not applicable
+ */
+int snd_pcm_format_physical_width(snd_pcm_format_t format)
+{
+	switch (format) {
+	case SNDRV_PCM_FORMAT_S8:
+	case SNDRV_PCM_FORMAT_U8:
+		return 8;
+	case SNDRV_PCM_FORMAT_S16_LE:
+	case SNDRV_PCM_FORMAT_S16_BE:
+	case SNDRV_PCM_FORMAT_U16_LE:
+	case SNDRV_PCM_FORMAT_U16_BE:
+		return 16;
+	case SNDRV_PCM_FORMAT_S18_3LE:
+	case SNDRV_PCM_FORMAT_S18_3BE:
+	case SNDRV_PCM_FORMAT_U18_3LE:
+	case SNDRV_PCM_FORMAT_U18_3BE:
+	case SNDRV_PCM_FORMAT_S20_3LE:
+	case SNDRV_PCM_FORMAT_S20_3BE:
+	case SNDRV_PCM_FORMAT_U20_3LE:
+	case SNDRV_PCM_FORMAT_U20_3BE:
+	case SNDRV_PCM_FORMAT_S24_3LE:
+	case SNDRV_PCM_FORMAT_S24_3BE:
+	case SNDRV_PCM_FORMAT_U24_3LE:
+	case SNDRV_PCM_FORMAT_U24_3BE:
+		return 24;
+	case SNDRV_PCM_FORMAT_S24_LE:
+	case SNDRV_PCM_FORMAT_S24_BE:
+	case SNDRV_PCM_FORMAT_U24_LE:
+	case SNDRV_PCM_FORMAT_U24_BE:
+	case SNDRV_PCM_FORMAT_S32_LE:
+	case SNDRV_PCM_FORMAT_S32_BE:
+	case SNDRV_PCM_FORMAT_U32_LE:
+	case SNDRV_PCM_FORMAT_U32_BE:
+	case SNDRV_PCM_FORMAT_FLOAT_LE:
+	case SNDRV_PCM_FORMAT_FLOAT_BE:
+	case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE:
+	case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE:
+		return 32;
+	case SNDRV_PCM_FORMAT_FLOAT64_LE:
+	case SNDRV_PCM_FORMAT_FLOAT64_BE:
+		return 64;
+	case SNDRV_PCM_FORMAT_MU_LAW:
+	case SNDRV_PCM_FORMAT_A_LAW:
+		return 8;
+	case SNDRV_PCM_FORMAT_IMA_ADPCM:
+		return 4;
+	default:
+		return -EINVAL;
+	}
+}
+
+/**
+ * \brief Return bytes needed to store a quantity of PCM sample
+ * \param format Sample format
+ * \param samples Samples count
+ * \return bytes needed, a negative error code if not integer or unknown
+ */
+ssize_t snd_pcm_format_size(snd_pcm_format_t format, size_t samples)
+{
+	switch (format) {
+	case SNDRV_PCM_FORMAT_S8:
+	case SNDRV_PCM_FORMAT_U8:
+		return samples;
+	case SNDRV_PCM_FORMAT_S16_LE:
+	case SNDRV_PCM_FORMAT_S16_BE:
+	case SNDRV_PCM_FORMAT_U16_LE:
+	case SNDRV_PCM_FORMAT_U16_BE:
+		return samples * 2;
+	case SNDRV_PCM_FORMAT_S18_3LE:
+	case SNDRV_PCM_FORMAT_S18_3BE:
+	case SNDRV_PCM_FORMAT_U18_3LE:
+	case SNDRV_PCM_FORMAT_U18_3BE:
+	case SNDRV_PCM_FORMAT_S20_3LE:
+	case SNDRV_PCM_FORMAT_S20_3BE:
+	case SNDRV_PCM_FORMAT_U20_3LE:
+	case SNDRV_PCM_FORMAT_U20_3BE:
+	case SNDRV_PCM_FORMAT_S24_3LE:
+	case SNDRV_PCM_FORMAT_S24_3BE:
+	case SNDRV_PCM_FORMAT_U24_3LE:
+	case SNDRV_PCM_FORMAT_U24_3BE:
+		return samples * 3;
+	case SNDRV_PCM_FORMAT_S24_LE:
+	case SNDRV_PCM_FORMAT_S24_BE:
+	case SNDRV_PCM_FORMAT_U24_LE:
+	case SNDRV_PCM_FORMAT_U24_BE:
+	case SNDRV_PCM_FORMAT_S32_LE:
+	case SNDRV_PCM_FORMAT_S32_BE:
+	case SNDRV_PCM_FORMAT_U32_LE:
+	case SNDRV_PCM_FORMAT_U32_BE:
+	case SNDRV_PCM_FORMAT_FLOAT_LE:
+	case SNDRV_PCM_FORMAT_FLOAT_BE:
+		return samples * 4;
+	case SNDRV_PCM_FORMAT_FLOAT64_LE:
+	case SNDRV_PCM_FORMAT_FLOAT64_BE:
+		return samples * 8;
+	case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE:
+	case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE:
+		return samples * 4;
+	case SNDRV_PCM_FORMAT_MU_LAW:
+	case SNDRV_PCM_FORMAT_A_LAW:
+		return samples;
+	case SNDRV_PCM_FORMAT_IMA_ADPCM:
+		if (samples & 1)
+			return -EINVAL;
+		return samples / 2;
+	default:
+		assert(0);
+		return -EINVAL;
+	}
+}
+
+/**
+ * \brief Return 64 bit expressing silence for a PCM sample format
+ * \param format Sample format
+ * \return silence 64 bit word
+ */
+u_int64_t snd_pcm_format_silence_64(snd_pcm_format_t format)
+{
+	switch (format) {
+	case SNDRV_PCM_FORMAT_S8:
+	case SNDRV_PCM_FORMAT_S16_LE:
+	case SNDRV_PCM_FORMAT_S16_BE:
+	case SNDRV_PCM_FORMAT_S24_LE:
+	case SNDRV_PCM_FORMAT_S24_BE:
+	case SNDRV_PCM_FORMAT_S32_LE:
+	case SNDRV_PCM_FORMAT_S32_BE:
+	case SNDRV_PCM_FORMAT_S24_3LE:
+	case SNDRV_PCM_FORMAT_S24_3BE:
+	case SNDRV_PCM_FORMAT_S20_3LE:
+	case SNDRV_PCM_FORMAT_S20_3BE:
+	case SNDRV_PCM_FORMAT_S18_3LE:
+	case SNDRV_PCM_FORMAT_S18_3BE:
+		return 0;
+	case SNDRV_PCM_FORMAT_U8:
+		return 0x8080808080808080ULL;
+#ifdef SNDRV_LITTLE_ENDIAN
+	case SNDRV_PCM_FORMAT_U16_LE:
+		return 0x8000800080008000ULL;
+	case SNDRV_PCM_FORMAT_U24_LE:
+		return 0x0080000000800000ULL;
+	case SNDRV_PCM_FORMAT_U32_LE:
+		return 0x8000000080000000ULL;
+	case SNDRV_PCM_FORMAT_U16_BE:
+		return 0x0080008000800080ULL;
+	case SNDRV_PCM_FORMAT_U24_BE:
+		return 0x0000800000008000ULL;
+	case SNDRV_PCM_FORMAT_U32_BE:
+		return 0x0000008000000080ULL;
+	case SNDRV_PCM_FORMAT_U24_3LE:
+		return 0x0000800000800000ULL;
+	case SNDRV_PCM_FORMAT_U24_3BE:
+		return 0x0080000080000080ULL;
+	case SNDRV_PCM_FORMAT_U20_3LE:
+		return 0x0000080000080000ULL;
+	case SNDRV_PCM_FORMAT_U20_3BE:
+		return 0x0008000008000008ULL;
+	case SNDRV_PCM_FORMAT_U18_3LE:
+		return 0x0000020000020000ULL;
+	case SNDRV_PCM_FORMAT_U18_3BE:
+		return 0x0002000002000002ULL;
+#else
+	case SNDRV_PCM_FORMAT_U16_LE:
+		return 0x0080008000800080ULL;
+	case SNDRV_PCM_FORMAT_U24_LE:
+		return 0x0000800000008000ULL;
+	case SNDRV_PCM_FORMAT_U32_LE:
+		return 0x0000008000000080ULL;
+	case SNDRV_PCM_FORMAT_U16_BE:
+		return 0x8000800080008000ULL;
+	case SNDRV_PCM_FORMAT_U24_BE:
+		return 0x0080000000800000ULL;
+	case SNDRV_PCM_FORMAT_U32_BE:
+		return 0x8000000080000000ULL;
+	case SNDRV_PCM_FORMAT_U24_3LE:
+		return 0x0080000080000080ULL;
+	case SNDRV_PCM_FORMAT_U24_3BE:
+		return 0x0000800000800000ULL;
+	case SNDRV_PCM_FORMAT_U20_3LE:
+		return 0x0008000008000008ULL;
+	case SNDRV_PCM_FORMAT_U20_3BE:
+		return 0x0000080000080000ULL;
+	case SNDRV_PCM_FORMAT_U18_3LE:
+		return 0x0002000002000002ULL;
+	case SNDRV_PCM_FORMAT_U18_3BE:
+		return 0x0000020000020000ULL;
+#endif
+	case SNDRV_PCM_FORMAT_FLOAT_LE:
+	{
+		union {
+			float f[2];
+			u_int64_t i;
+		} u;
+		u.f[0] = u.f[1] = 0.0;
+#ifdef SNDRV_LITTLE_ENDIAN
+		return u.i;
+#else
+		return bswap_64(u.i);
+#endif
+	}
+	case SNDRV_PCM_FORMAT_FLOAT64_LE:
+	{
+		union {
+			double f;
+			u_int64_t i;
+		} u;
+		u.f = 0.0;
+#ifdef SNDRV_LITTLE_ENDIAN
+		return u.i;
+#else
+		return bswap_64(u.i);
+#endif
+	}
+	case SNDRV_PCM_FORMAT_FLOAT_BE:		
+	{
+		union {
+			float f[2];
+			u_int64_t i;
+		} u;
+		u.f[0] = u.f[1] = 0.0;
+#ifdef SNDRV_LITTLE_ENDIAN
+		return bswap_64(u.i);
+#else
+		return u.i;
+#endif
+	}
+	case SNDRV_PCM_FORMAT_FLOAT64_BE:
+	{
+		union {
+			double f;
+			u_int64_t i;
+		} u;
+		u.f = 0.0;
+#ifdef SNDRV_LITTLE_ENDIAN
+		return bswap_64(u.i);
+#else
+		return u.i;
+#endif
+	}
+	case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE:
+	case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE:
+		return 0;	
+	case SNDRV_PCM_FORMAT_MU_LAW:
+		return 0x7f7f7f7f7f7f7f7fULL;
+	case SNDRV_PCM_FORMAT_A_LAW:
+		return 0x5555555555555555ULL;
+	case SNDRV_PCM_FORMAT_IMA_ADPCM:	/* special case */
+	case SNDRV_PCM_FORMAT_MPEG:
+	case SNDRV_PCM_FORMAT_GSM:
+	case SNDRV_PCM_FORMAT_SPECIAL:
+		return 0;
+	default:
+		assert(0);
+		return 0;
+	}
+	return 0;
+}
+
+/**
+ * \brief Return 32 bit expressing silence for a PCM sample format
+ * \param format Sample format
+ * \return silence 32 bit word
+ */
+u_int32_t snd_pcm_format_silence_32(snd_pcm_format_t format)
+{
+	assert(snd_pcm_format_physical_width(format) <= 32);
+	return (u_int32_t)snd_pcm_format_silence_64(format);
+}
+
+/**
+ * \brief Return 16 bit expressing silence for a PCM sample format
+ * \param format Sample format
+ * \return silence 16 bit word
+ */
+u_int16_t snd_pcm_format_silence_16(snd_pcm_format_t format)
+{
+	assert(snd_pcm_format_physical_width(format) <= 16);
+	return (u_int16_t)snd_pcm_format_silence_64(format);
+}
+
+/**
+ * \brief Return 8 bit expressing silence for a PCM sample format
+ * \param format Sample format
+ * \return silence 8 bit word
+ */
+u_int8_t snd_pcm_format_silence(snd_pcm_format_t format)
+{
+	assert(snd_pcm_format_physical_width(format) <= 8);
+	return (u_int8_t)snd_pcm_format_silence_64(format);
+}
+
+/**
+ * \brief Silence a PCM samples buffer
+ * \param format Sample format
+ * \param data Buffer
+ * \param samples Samples count
+ * \return 0 if successful or a negative error code
+ */
+int snd_pcm_format_set_silence(snd_pcm_format_t format, void *data, unsigned int samples)
+{
+	if (samples == 0)
+		return 0;
+	switch (snd_pcm_format_physical_width(format)) {
+	case 4: {
+		u_int8_t silence = snd_pcm_format_silence_64(format);
+		unsigned int samples1;
+		if (samples % 2 != 0)
+			return -EINVAL;
+		samples1 = samples / 2;
+		memset(data, silence, samples1);
+		break;
+	}
+	case 8: {
+		u_int8_t silence = snd_pcm_format_silence_64(format);
+		memset(data, silence, samples);
+		break;
+	}
+	case 16: {
+		u_int16_t silence = snd_pcm_format_silence_64(format);
+		u_int16_t *pdata = (u_int16_t *)data;
+		if (! silence)
+			memset(data, 0, samples * 2);
+		else {
+			while (samples-- > 0)
+				*pdata++ = silence;
+		}
+		break;
+	}
+	case 24: {
+		u_int32_t silence = snd_pcm_format_silence_64(format);
+		u_int8_t *pdata = (u_int8_t *)data;
+		if (! silence)
+			memset(data, 0, samples * 3);
+		else {
+			while (samples-- > 0) {
+#ifdef SNDRV_LITTLE_ENDIAN
+				*pdata++ = silence >> 0;
+				*pdata++ = silence >> 8;
+				*pdata++ = silence >> 16;
+#else
+				*pdata++ = silence >> 16;
+				*pdata++ = silence >> 8;
+				*pdata++ = silence >> 0;
+#endif
+			}
+		}
+		break;
+	}
+	case 32: {
+		u_int32_t silence = snd_pcm_format_silence_64(format);
+		u_int32_t *pdata = (u_int32_t *)data;
+		if (! silence)
+			memset(data, 0, samples * 4);
+		else {
+			while (samples-- > 0)
+				*pdata++ = silence;
+		}
+		break;
+	}
+	case 64: {
+		u_int64_t silence = snd_pcm_format_silence_64(format);
+		u_int64_t *pdata = (u_int64_t *)data;
+		if (! silence)
+			memset(data, 0, samples * 8);
+		else {
+			while (samples-- > 0)
+				*pdata++ = silence;
+		}
+		break;
+	}
+	default:
+		assert(0);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static const int linear_formats[4][2][2] = {
+	{ { SNDRV_PCM_FORMAT_S8, SNDRV_PCM_FORMAT_S8 },
+	  { SNDRV_PCM_FORMAT_U8, SNDRV_PCM_FORMAT_U8 } },
+	{ { SNDRV_PCM_FORMAT_S16_LE, SNDRV_PCM_FORMAT_S16_BE },
+	  { SNDRV_PCM_FORMAT_U16_LE, SNDRV_PCM_FORMAT_U16_BE } },
+	{ { SNDRV_PCM_FORMAT_S24_LE, SNDRV_PCM_FORMAT_S24_BE },
+	  { SNDRV_PCM_FORMAT_U24_LE, SNDRV_PCM_FORMAT_U24_BE } },
+	{ { SNDRV_PCM_FORMAT_S32_LE, SNDRV_PCM_FORMAT_S32_BE },
+	  { SNDRV_PCM_FORMAT_U32_LE, SNDRV_PCM_FORMAT_U32_BE } }
+};
+
+static const int linear24_formats[3][2][2] = {
+	{ { SNDRV_PCM_FORMAT_S24_3LE, SNDRV_PCM_FORMAT_S24_3BE },
+	  { SNDRV_PCM_FORMAT_U24_3LE, SNDRV_PCM_FORMAT_U24_3BE } },
+	{ { SNDRV_PCM_FORMAT_S20_3LE, SNDRV_PCM_FORMAT_S20_3BE },
+	  { SNDRV_PCM_FORMAT_U20_3LE, SNDRV_PCM_FORMAT_U20_3BE } },
+	{ { SNDRV_PCM_FORMAT_S18_3LE, SNDRV_PCM_FORMAT_S18_3BE },
+	  { SNDRV_PCM_FORMAT_U18_3LE, SNDRV_PCM_FORMAT_U18_3BE } },
+};
+
+/**
+ * \brief Compose a PCM sample linear format
+ * \param width Nominal bits per sample
+ * \param pwidth Physical bit width of the format
+ * \param unsignd Sign: 0 signed, 1 unsigned
+ * \param big_endian Endian: 0 little endian, 1 big endian
+ * \return The matching format type, or #SND_PCM_FORMAT_UNKNOWN if no match
+ */
+snd_pcm_format_t snd_pcm_build_linear_format(int width, int pwidth, int unsignd, int big_endian)
+{
+	if (pwidth == 24) {
+		switch (width) {
+		case 24:
+			width = 0;
+			break;
+		case 20:
+			width = 1;
+			break;
+		case 18:
+			width = 2;
+			break;
+		default:
+			return SND_PCM_FORMAT_UNKNOWN;
+		}
+		return linear24_formats[width][!!unsignd][!!big_endian];
+	} else {
+		switch (width) {
+		case 8:
+			width = 0;
+			break;
+		case 16:
+			width = 1;
+			break;
+		case 24:
+			width = 2;
+			break;
+		case 32:
+			width = 3;
+			break;
+		default:
+			return SND_PCM_FORMAT_UNKNOWN;
+		}
+		return linear_formats[width][!!unsignd][!!big_endian];
+	}
+}
+
+/**
+ * \brief Parse control element id from the config
+ * \param conf the config tree to parse
+ * \param ctl_id the pointer to store the resultant control element id
+ * \param cardp the pointer to store the card index
+ * \param cchannelsp the pointer to store the number of channels (optional)
+ * \param hwctlp the pointer to store the h/w control flag (optional)
+ * \return 0 if successful, or a negative error code
+ *
+ * This function parses the given config tree to retrieve the control element id
+ * and the card index.  It's used by softvol.  External PCM plugins can use this
+ * function for creating or assigining their controls.
+ *
+ * cchannelsp and hwctlp arguments are optional.  Set NULL if not necessary.
+ */
+int snd_pcm_parse_control_id(snd_config_t *conf, snd_ctl_elem_id_t *ctl_id, int *cardp,
+			     int *cchannelsp, int *hwctlp)
+{
+	snd_config_iterator_t i, next;
+	int iface = SND_CTL_ELEM_IFACE_MIXER;
+	const char *name = NULL;
+	long index = 0;
+	long device = -1;
+	long subdevice = -1;
+	int err;
+
+	assert(ctl_id && cardp);
+
+	*cardp = -1;
+	if (cchannelsp)
+		*cchannelsp = 2;
+	snd_config_for_each(i, next, conf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (strcmp(id, "comment") == 0)
+			continue;
+		if (strcmp(id, "card") == 0) {
+			const char *str;
+			long v;
+			if ((err = snd_config_get_integer(n, &v)) < 0) {
+				if ((err = snd_config_get_string(n, &str)) < 0) {
+					SNDERR("Invalid field %s", id);
+					goto _err;
+				}
+				*cardp = snd_card_get_index(str);
+				if (*cardp < 0) {
+					SNDERR("Cannot get index for %s", str);
+					err = *cardp;
+					goto _err;
+				}
+			} else
+				*cardp = v;
+			continue;
+		}
+		if (strcmp(id, "iface") == 0 || strcmp(id, "interface") == 0) {
+			const char *ptr;
+			if ((err = snd_config_get_string(n, &ptr)) < 0) {
+				SNDERR("field %s is not a string", id);
+				goto _err;
+			}
+			if ((err = snd_config_get_ctl_iface_ascii(ptr)) < 0) {
+				SNDERR("Invalid value for '%s'", id);
+				goto _err;
+			}
+			iface = err;
+			continue;
+		}
+		if (strcmp(id, "name") == 0) {
+			if ((err = snd_config_get_string(n, &name)) < 0) {
+				SNDERR("field %s is not a string", id);
+				goto _err;
+			}
+			continue;
+		}
+		if (strcmp(id, "index") == 0) {
+			if ((err = snd_config_get_integer(n, &index)) < 0) {
+				SNDERR("field %s is not an integer", id);
+				goto _err;
+			}
+			continue;
+		}
+		if (strcmp(id, "device") == 0) {
+			if ((err = snd_config_get_integer(n, &device)) < 0) {
+				SNDERR("field %s is not an integer", id);
+				goto _err;
+			}
+			continue;
+		}
+		if (strcmp(id, "subdevice") == 0) {
+			if ((err = snd_config_get_integer(n, &subdevice)) < 0) {
+				SNDERR("field %s is not an integer", id);
+				goto _err;
+			}
+			continue;
+		}
+		if (cchannelsp && strcmp(id, "count") == 0) {
+			long v;
+			if ((err = snd_config_get_integer(n, &v)) < 0) {
+				SNDERR("field %s is not an integer", id);
+				goto _err;
+			}
+			if (v < 1 || v > 2) {
+				SNDERR("Invalid count %ld", v);
+				goto _err;
+			}
+			*cchannelsp = v;
+			continue;
+		}
+		if (hwctlp && strcmp(id, "hwctl") == 0) {
+			if ((err = snd_config_get_bool(n)) < 0) {
+				SNDERR("The field %s must be a boolean type", id);
+				return err;
+			}
+			*hwctlp = err;
+			continue;
+		}
+		SNDERR("Unknown field %s", id);
+		return -EINVAL;
+	}
+	if (name == NULL) {
+		SNDERR("Missing control name");
+		err = -EINVAL;
+		goto _err;
+	}
+	if (device < 0)
+		device = 0;
+	if (subdevice < 0)
+		subdevice = 0;
+
+	snd_ctl_elem_id_set_interface(ctl_id, iface);
+	snd_ctl_elem_id_set_name(ctl_id, name);
+	snd_ctl_elem_id_set_index(ctl_id, index);
+	snd_ctl_elem_id_set_device(ctl_id, device);
+	snd_ctl_elem_id_set_subdevice(ctl_id, subdevice);
+
+	return 0;
+
+ _err:
+	return err;
+}
diff --git a/src/pcm/pcm_mmap.c b/src/pcm/pcm_mmap.c
new file mode 100644
index 0000000..6b44050
--- /dev/null
+++ b/src/pcm/pcm_mmap.c
@@ -0,0 +1,638 @@
+/*
+ *  PCM Interface - mmap
+ *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+#include <stdio.h>
+#include <malloc.h>
+#include <string.h>
+#include <sys/poll.h>
+#include <sys/mman.h>
+#include <sys/shm.h>
+#include "pcm_local.h"
+
+size_t page_size(void)
+{
+	long s = sysconf(_SC_PAGE_SIZE);
+	assert(s > 0);
+	return s;
+}
+
+size_t page_align(size_t size)
+{
+	size_t r;
+	long psz = page_size();
+	r = size % psz;
+	if (r)
+		return size + psz - r;
+	return size;
+}
+
+size_t page_ptr(size_t object_offset, size_t object_size, size_t *offset, size_t *mmap_offset)
+{
+	size_t r;
+	long psz = page_size();
+	assert(offset);
+	assert(mmap_offset);
+	*mmap_offset = object_offset;
+	object_offset %= psz;
+	*mmap_offset -= object_offset;
+	object_size += object_offset;
+	r = object_size % psz;
+	if (r)
+		r = object_size + psz - r;
+	else
+		r = object_size;
+	*offset = object_offset;
+	return r;
+}
+
+void snd_pcm_mmap_appl_backward(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
+{
+	snd_pcm_sframes_t appl_ptr = *pcm->appl.ptr;
+	appl_ptr -= frames;
+	if (appl_ptr < 0)
+		appl_ptr += pcm->boundary;
+	*pcm->appl.ptr = appl_ptr;
+}
+
+void snd_pcm_mmap_appl_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
+{
+	snd_pcm_uframes_t appl_ptr = *pcm->appl.ptr;
+	appl_ptr += frames;
+	if (appl_ptr >= pcm->boundary)
+		appl_ptr -= pcm->boundary;
+	*pcm->appl.ptr = appl_ptr;
+}
+
+void snd_pcm_mmap_hw_backward(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
+{
+	snd_pcm_sframes_t hw_ptr = *pcm->hw.ptr;
+	hw_ptr -= frames;
+	if (hw_ptr < 0)
+		hw_ptr += pcm->boundary;
+	*pcm->hw.ptr = hw_ptr;
+}
+
+void snd_pcm_mmap_hw_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
+{
+	snd_pcm_uframes_t hw_ptr = *pcm->hw.ptr;
+	hw_ptr += frames;
+	if (hw_ptr >= pcm->boundary)
+		hw_ptr -= pcm->boundary;
+	*pcm->hw.ptr = hw_ptr;
+}
+
+static snd_pcm_sframes_t snd_pcm_mmap_write_areas(snd_pcm_t *pcm,
+						  const snd_pcm_channel_area_t *areas,
+						  snd_pcm_uframes_t offset,
+						  snd_pcm_uframes_t size)
+{
+	snd_pcm_uframes_t xfer = 0;
+
+	if (snd_pcm_mmap_playback_avail(pcm) < size) {
+		SNDMSG("too short avail %ld to size %ld", snd_pcm_mmap_playback_avail(pcm), size);
+		return -EPIPE;
+	}
+	while (size > 0) {
+		const snd_pcm_channel_area_t *pcm_areas;
+		snd_pcm_uframes_t pcm_offset;
+		snd_pcm_uframes_t frames = size;
+		snd_pcm_sframes_t result;
+
+		snd_pcm_mmap_begin(pcm, &pcm_areas, &pcm_offset, &frames);
+		snd_pcm_areas_copy(pcm_areas, pcm_offset,
+				   areas, offset, 
+				   pcm->channels, 
+				   frames, pcm->format);
+		result = snd_pcm_mmap_commit(pcm, pcm_offset, frames);
+		if (result < 0)
+			return xfer > 0 ? (snd_pcm_sframes_t)xfer : result;
+		offset += result;
+		xfer += result;
+		size -= result;
+	}
+	return (snd_pcm_sframes_t)xfer;
+}
+
+static snd_pcm_sframes_t snd_pcm_mmap_read_areas(snd_pcm_t *pcm,
+						 const snd_pcm_channel_area_t *areas,
+						 snd_pcm_uframes_t offset,
+						 snd_pcm_uframes_t size)
+{
+	snd_pcm_uframes_t xfer = 0;
+
+	if (snd_pcm_mmap_capture_avail(pcm) < size) {
+		SNDMSG("too short avail %ld to size %ld", snd_pcm_mmap_capture_avail(pcm), size);
+		return -EPIPE;
+	}
+	while (size > 0) {
+		const snd_pcm_channel_area_t *pcm_areas;
+		snd_pcm_uframes_t pcm_offset;
+		snd_pcm_uframes_t frames = size;
+		snd_pcm_sframes_t result;
+		
+		snd_pcm_mmap_begin(pcm, &pcm_areas, &pcm_offset, &frames);
+		snd_pcm_areas_copy(areas, offset,
+				   pcm_areas, pcm_offset,
+				   pcm->channels, 
+				   frames, pcm->format);
+		result = snd_pcm_mmap_commit(pcm, pcm_offset, frames);
+		if (result < 0)
+			return xfer > 0 ? (snd_pcm_sframes_t)xfer : result;
+		offset += result;
+		xfer += result;
+		size -= result;
+	}
+	return (snd_pcm_sframes_t)xfer;
+}
+
+/**
+ * \brief Write interleaved frames to a PCM using direct buffer (mmap)
+ * \param pcm PCM handle
+ * \param buffer frames containing buffer
+ * \param size frames to be written
+ * \return a positive number of frames actually written otherwise a
+ * negative error code
+ * \retval -EBADFD PCM is not in the right state (#SND_PCM_STATE_PREPARED or #SND_PCM_STATE_RUNNING)
+ * \retval -EPIPE an underrun occurred
+ * \retval -ESTRPIPE a suspend event occurred (stream is suspended and waiting for an application recovery)
+ *
+ * If the blocking behaviour is selected, then routine waits until
+ * all requested bytes are played or put to the playback ring buffer.
+ * The count of bytes can be less only if a signal or underrun occurred.
+ *
+ * If the non-blocking behaviour is selected, then routine doesn't wait at all.
+ */
+snd_pcm_sframes_t snd_pcm_mmap_writei(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size)
+{
+	snd_pcm_channel_area_t areas[pcm->channels];
+	snd_pcm_areas_from_buf(pcm, areas, (void*)buffer);
+	return snd_pcm_write_areas(pcm, areas, 0, size,
+				   snd_pcm_mmap_write_areas);
+}
+
+/**
+ * \brief Write non interleaved frames to a PCM using direct buffer (mmap)
+ * \param pcm PCM handle
+ * \param bufs frames containing buffers (one for each channel)
+ * \param size frames to be written
+ * \return a positive number of frames actually written otherwise a
+ * negative error code
+ * \retval -EBADFD PCM is not in the right state (#SND_PCM_STATE_PREPARED or #SND_PCM_STATE_RUNNING)
+ * \retval -EPIPE an underrun occurred
+ * \retval -ESTRPIPE a suspend event occurred (stream is suspended and waiting for an application recovery)
+ *
+ * If the blocking behaviour is selected, then routine waits until
+ * all requested bytes are played or put to the playback ring buffer.
+ * The count of bytes can be less only if a signal or underrun occurred.
+ *
+ * If the non-blocking behaviour is selected, then routine doesn't wait at all.
+ */
+snd_pcm_sframes_t snd_pcm_mmap_writen(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size)
+{
+	snd_pcm_channel_area_t areas[pcm->channels];
+	snd_pcm_areas_from_bufs(pcm, areas, bufs);
+	return snd_pcm_write_areas(pcm, areas, 0, size,
+				   snd_pcm_mmap_write_areas);
+}
+
+/**
+ * \brief Read interleaved frames from a PCM using direct buffer (mmap)
+ * \param pcm PCM handle
+ * \param buffer frames containing buffer
+ * \param size frames to be written
+ * \return a positive number of frames actually read otherwise a
+ * negative error code
+ * \retval -EBADFD PCM is not in the right state (#SND_PCM_STATE_PREPARED or #SND_PCM_STATE_RUNNING)
+ * \retval -EPIPE an overrun occurred
+ * \retval -ESTRPIPE a suspend event occurred (stream is suspended and waiting for an application recovery)
+ *
+ * If the blocking behaviour was selected, then routine waits until
+ * all requested bytes are filled. The count of bytes can be less only
+ * if a signal or underrun occurred.
+ *
+ * If the non-blocking behaviour is selected, then routine doesn't wait at all.
+ */
+snd_pcm_sframes_t snd_pcm_mmap_readi(snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t size)
+{
+	snd_pcm_channel_area_t areas[pcm->channels];
+	snd_pcm_areas_from_buf(pcm, areas, buffer);
+	return snd_pcm_read_areas(pcm, areas, 0, size,
+				  snd_pcm_mmap_read_areas);
+}
+
+/**
+ * \brief Read non interleaved frames to a PCM using direct buffer (mmap)
+ * \param pcm PCM handle
+ * \param bufs frames containing buffers (one for each channel)
+ * \param size frames to be written
+ * \return a positive number of frames actually read otherwise a
+ * negative error code
+ * \retval -EBADFD PCM is not in the right state (#SND_PCM_STATE_PREPARED or #SND_PCM_STATE_RUNNING)
+ * \retval -EPIPE an overrun occurred
+ * \retval -ESTRPIPE a suspend event occurred (stream is suspended and waiting for an application recovery)
+ *
+ * If the blocking behaviour was selected, then routine waits until
+ * all requested bytes are filled. The count of bytes can be less only
+ * if a signal or underrun occurred.
+ *
+ * If the non-blocking behaviour is selected, then routine doesn't wait at all.
+ */
+snd_pcm_sframes_t snd_pcm_mmap_readn(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size)
+{
+	snd_pcm_channel_area_t areas[pcm->channels];
+	snd_pcm_areas_from_bufs(pcm, areas, bufs);
+	return snd_pcm_read_areas(pcm, areas, 0, size,
+				  snd_pcm_mmap_read_areas);
+}
+
+int snd_pcm_channel_info_shm(snd_pcm_t *pcm, snd_pcm_channel_info_t *info, int shmid)
+{
+	switch (pcm->access) {
+	case SND_PCM_ACCESS_MMAP_INTERLEAVED:
+	case SND_PCM_ACCESS_RW_INTERLEAVED:
+		info->first = info->channel * pcm->sample_bits;
+		info->step = pcm->frame_bits;
+		break;
+	case SND_PCM_ACCESS_MMAP_NONINTERLEAVED:
+	case SND_PCM_ACCESS_RW_NONINTERLEAVED:
+		info->first = 0;
+		info->step = pcm->sample_bits;
+		break;
+	default:
+		SNDMSG("invalid access type %d", pcm->access);
+		return -EINVAL;
+	}
+	info->addr = 0;
+	if (pcm->hw_flags & SND_PCM_HW_PARAMS_EXPORT_BUFFER) {
+		info->type = SND_PCM_AREA_SHM;
+		info->u.shm.shmid = shmid;
+		info->u.shm.area = NULL;
+	} else
+		info->type = SND_PCM_AREA_LOCAL;
+	return 0;
+}	
+
+int snd_pcm_mmap(snd_pcm_t *pcm)
+{
+	int err;
+	unsigned int c;
+	assert(pcm);
+	if (CHECK_SANITY(! pcm->setup)) {
+		SNDMSG("PCM not set up");
+		return -EIO;
+	}
+	if (CHECK_SANITY(pcm->mmap_channels || pcm->running_areas)) {
+		SNDMSG("Already mmapped");
+		return -EBUSY;
+	}
+	err = pcm->ops->mmap(pcm);
+	if (err < 0)
+		return err;
+	if (pcm->mmap_shadow)
+		return 0;
+	pcm->mmap_channels = calloc(pcm->channels, sizeof(pcm->mmap_channels[0]));
+	if (!pcm->mmap_channels)
+		return -ENOMEM;
+	pcm->running_areas = calloc(pcm->channels, sizeof(pcm->running_areas[0]));
+	if (!pcm->running_areas) {
+		free(pcm->mmap_channels);
+		pcm->mmap_channels = NULL;
+		return -ENOMEM;
+	}
+	for (c = 0; c < pcm->channels; ++c) {
+		snd_pcm_channel_info_t *i = &pcm->mmap_channels[c];
+		i->channel = c;
+		err = snd_pcm_channel_info(pcm, i);
+		if (err < 0) {
+			free(pcm->mmap_channels);
+			free(pcm->running_areas);
+			pcm->mmap_channels = NULL;
+			pcm->running_areas = NULL;
+			return err;
+		}
+	}
+	for (c = 0; c < pcm->channels; ++c) {
+		snd_pcm_channel_info_t *i = &pcm->mmap_channels[c];
+		snd_pcm_channel_area_t *a = &pcm->running_areas[c];
+		char *ptr;
+		size_t size;
+		unsigned int c1;
+		if (i->addr) {
+        		a->addr = i->addr;
+        		a->first = i->first;
+        		a->step = i->step;
+		        continue;
+                }
+                size = i->first + i->step * (pcm->buffer_size - 1) + pcm->sample_bits;
+		for (c1 = c + 1; c1 < pcm->channels; ++c1) {
+			snd_pcm_channel_info_t *i1 = &pcm->mmap_channels[c1];
+			size_t s;
+			if (i1->type != i->type)
+				continue;
+			switch (i1->type) {
+			case SND_PCM_AREA_MMAP:
+				if (i1->u.mmap.fd != i->u.mmap.fd ||
+				    i1->u.mmap.offset != i->u.mmap.offset)
+					continue;
+				break;
+			case SND_PCM_AREA_SHM:
+				if (i1->u.shm.shmid != i->u.shm.shmid)
+					continue;
+				break;
+			case SND_PCM_AREA_LOCAL:
+				break;
+			default:
+				assert(0);
+			}
+			s = i1->first + i1->step * (pcm->buffer_size - 1) + pcm->sample_bits;
+			if (s > size)
+				size = s;
+		}
+		size = (size + 7) / 8;
+		size = page_align(size);
+		switch (i->type) {
+		case SND_PCM_AREA_MMAP:
+			ptr = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_FILE|MAP_SHARED, i->u.mmap.fd, i->u.mmap.offset);
+			if (ptr == MAP_FAILED) {
+				SYSERR("mmap failed");
+				return -errno;
+			}
+			i->addr = ptr;
+			break;
+		case SND_PCM_AREA_SHM:
+			if (i->u.shm.shmid < 0) {
+				int id;
+				/* FIXME: safer permission? */
+				id = shmget(IPC_PRIVATE, size, 0666);
+				if (id < 0) {
+					SYSERR("shmget failed");
+					return -errno;
+				}
+				i->u.shm.shmid = id;
+				ptr = shmat(i->u.shm.shmid, 0, 0);
+				if (ptr == (void *) -1) {
+					SYSERR("shmat failed");
+					return -errno;
+				}
+				/* automatically remove segment if not used */
+				if (shmctl(id, IPC_RMID, NULL) < 0){
+					SYSERR("shmctl mark remove failed");
+					return -errno;
+				}
+				i->u.shm.area = snd_shm_area_create(id, ptr);
+				if (i->u.shm.area == NULL) {
+					SYSERR("snd_shm_area_create failed");
+					return -ENOMEM;
+				}
+				if (pcm->access == SND_PCM_ACCESS_MMAP_INTERLEAVED ||
+				    pcm->access == SND_PCM_ACCESS_RW_INTERLEAVED) {
+					unsigned int c1;
+					for (c1 = c + 1; c1 < pcm->channels; c1++) {
+						snd_pcm_channel_info_t *i1 = &pcm->mmap_channels[c1];
+						if (i1->u.shm.shmid < 0) {
+							i1->u.shm.shmid = id;
+							i1->u.shm.area = snd_shm_area_share(i->u.shm.area);
+						}
+					}
+				}
+			} else {
+				ptr = shmat(i->u.shm.shmid, 0, 0);
+				if (ptr == (void*) -1) {
+					SYSERR("shmat failed");
+					return -errno;
+				}
+			}
+			i->addr = ptr;
+			break;
+		case SND_PCM_AREA_LOCAL:
+			ptr = malloc(size);
+			if (ptr == NULL) {
+				SYSERR("malloc failed");
+				return -errno;
+			}
+			i->addr = ptr;
+			break;
+		default:
+			assert(0);
+		}
+		for (c1 = c + 1; c1 < pcm->channels; ++c1) {
+			snd_pcm_channel_info_t *i1 = &pcm->mmap_channels[c1];
+			if (i1->type != i->type)
+				continue;
+			switch (i1->type) {
+			case SND_PCM_AREA_MMAP:
+				if (i1->u.mmap.fd != i->u.mmap.fd ||
+                                    i1->u.mmap.offset != i->u.mmap.offset)
+					continue;
+				break;
+			case SND_PCM_AREA_SHM:
+				if (i1->u.shm.shmid != i->u.shm.shmid)
+					continue;
+				/* follow thru */
+			case SND_PCM_AREA_LOCAL:
+				if (pcm->access != SND_PCM_ACCESS_MMAP_INTERLEAVED &&
+				    pcm->access != SND_PCM_ACCESS_RW_INTERLEAVED)
+				        continue;
+				break;
+			default:
+				assert(0);
+			}
+			i1->addr = i->addr;
+		}
+		a->addr = i->addr;
+		a->first = i->first;
+		a->step = i->step;
+	}
+	return 0;
+}
+
+int snd_pcm_munmap(snd_pcm_t *pcm)
+{
+	int err;
+	unsigned int c;
+	assert(pcm);
+	if (CHECK_SANITY(! pcm->mmap_channels)) {
+		SNDMSG("Not mmapped");
+		return -ENXIO;
+	}
+	if (pcm->mmap_shadow)
+		return pcm->ops->munmap(pcm);
+	for (c = 0; c < pcm->channels; ++c) {
+		snd_pcm_channel_info_t *i = &pcm->mmap_channels[c];
+		unsigned int c1;
+		size_t size = i->first + i->step * (pcm->buffer_size - 1) + pcm->sample_bits;
+		if (!i->addr)
+			continue;
+		for (c1 = c + 1; c1 < pcm->channels; ++c1) {
+			snd_pcm_channel_info_t *i1 = &pcm->mmap_channels[c1];
+			size_t s;
+			if (i1->addr != i->addr)
+				continue;
+			i1->addr = NULL;
+			s = i1->first + i1->step * (pcm->buffer_size - 1) + pcm->sample_bits;
+			if (s > size)
+				size = s;
+		}
+		size = (size + 7) / 8;
+		size = page_align(size);
+		switch (i->type) {
+		case SND_PCM_AREA_MMAP:
+			err = munmap(i->addr, size);
+			if (err < 0) {
+				SYSERR("mmap failed");
+				return -errno;
+			}
+			errno = 0;
+			break;
+		case SND_PCM_AREA_SHM:
+			if (i->u.shm.area) {
+				snd_shm_area_destroy(i->u.shm.area);
+				i->u.shm.area = NULL;
+				if (pcm->access == SND_PCM_ACCESS_MMAP_INTERLEAVED ||
+				    pcm->access == SND_PCM_ACCESS_RW_INTERLEAVED) {
+					unsigned int c1;
+					for (c1 = c + 1; c1 < pcm->channels; c1++) {
+						snd_pcm_channel_info_t *i1 = &pcm->mmap_channels[c1];
+						if (i1->u.shm.area) {
+							snd_shm_area_destroy(i1->u.shm.area);
+							i1->u.shm.area = NULL;
+						}
+					}
+				}
+			}
+			break;
+		case SND_PCM_AREA_LOCAL:
+			free(i->addr);
+			break;
+		default:
+			assert(0);
+		}
+		i->addr = NULL;
+	}
+	err = pcm->ops->munmap(pcm);
+	if (err < 0)
+		return err;
+	free(pcm->mmap_channels);
+	free(pcm->running_areas);
+	pcm->mmap_channels = NULL;
+	pcm->running_areas = NULL;
+	return 0;
+}
+
+snd_pcm_sframes_t snd_pcm_write_mmap(snd_pcm_t *pcm, snd_pcm_uframes_t offset,
+				     snd_pcm_uframes_t size)
+{
+	snd_pcm_uframes_t xfer = 0;
+	snd_pcm_sframes_t err = 0;
+	if (! size)
+		return 0;
+	while (xfer < size) {
+		snd_pcm_uframes_t frames = size - xfer;
+		snd_pcm_uframes_t cont = pcm->buffer_size - offset;
+		if (cont < frames)
+			frames = cont;
+		switch (pcm->access) {
+		case SND_PCM_ACCESS_MMAP_INTERLEAVED:
+		{
+			const snd_pcm_channel_area_t *a = snd_pcm_mmap_areas(pcm);
+			const char *buf = snd_pcm_channel_area_addr(a, offset);
+			err = _snd_pcm_writei(pcm, buf, frames);
+			if (err >= 0)
+				frames = err;
+			break;
+		}
+		case SND_PCM_ACCESS_MMAP_NONINTERLEAVED:
+		{
+			unsigned int channels = pcm->channels;
+			unsigned int c;
+			void *bufs[channels];
+			const snd_pcm_channel_area_t *areas = snd_pcm_mmap_areas(pcm);
+			for (c = 0; c < channels; ++c) {
+				const snd_pcm_channel_area_t *a = &areas[c];
+				bufs[c] = snd_pcm_channel_area_addr(a, offset);
+			}
+			err = _snd_pcm_writen(pcm, bufs, frames);
+			if (err >= 0)
+				frames = err;
+			break;
+		}
+		default:
+			SNDMSG("invalid access type %d", pcm->access);
+			return -EINVAL;
+		}
+		if (err < 0)
+			break;
+		xfer += frames;
+		offset = (offset + frames) % pcm->buffer_size;
+	}
+	if (xfer > 0)
+		return xfer;
+	return err;
+}
+
+snd_pcm_sframes_t snd_pcm_read_mmap(snd_pcm_t *pcm, snd_pcm_uframes_t offset,
+				    snd_pcm_uframes_t size)
+{
+	snd_pcm_uframes_t xfer = 0;
+	snd_pcm_sframes_t err = 0;
+	if (! size)
+		return 0;
+	while (xfer < size) {
+		snd_pcm_uframes_t frames = size - xfer;
+		snd_pcm_uframes_t cont = pcm->buffer_size - offset;
+		if (cont < frames)
+			frames = cont;
+		switch (pcm->access) {
+		case SND_PCM_ACCESS_MMAP_INTERLEAVED:
+		{
+			const snd_pcm_channel_area_t *a = snd_pcm_mmap_areas(pcm);
+			char *buf = snd_pcm_channel_area_addr(a, offset);
+			err = _snd_pcm_readi(pcm, buf, frames);
+			if (err >= 0)
+				frames = err;
+			break;
+		}
+		case SND_PCM_ACCESS_MMAP_NONINTERLEAVED:
+		{
+			snd_pcm_uframes_t channels = pcm->channels;
+			unsigned int c;
+			void *bufs[channels];
+			const snd_pcm_channel_area_t *areas = snd_pcm_mmap_areas(pcm);
+			for (c = 0; c < channels; ++c) {
+				const snd_pcm_channel_area_t *a = &areas[c];
+				bufs[c] = snd_pcm_channel_area_addr(a, offset);
+			}
+			err = _snd_pcm_readn(pcm->fast_op_arg, bufs, frames);
+			if (err >= 0)
+				frames = err;
+		}
+		default:
+			SNDMSG("invalid access type %d", pcm->access);
+			return -EINVAL;
+		}
+		if (err < 0)
+			break;
+		xfer += frames;
+		offset = (offset + frames) % pcm->buffer_size;
+	}
+	if (xfer > 0)
+		return xfer;
+	return err;
+}
diff --git a/src/pcm/pcm_mmap_emul.c b/src/pcm/pcm_mmap_emul.c
new file mode 100644
index 0000000..664e907
--- /dev/null
+++ b/src/pcm/pcm_mmap_emul.c
@@ -0,0 +1,512 @@
+/**
+ * \file pcm/pcm_mmap_emul.c
+ * \ingroup PCM_Plugins
+ * \brief PCM Mmap-Emulation Plugin Interface
+ * \author Takashi Iwai <tiwai@suse.de>
+ * \date 2007
+ */
+/*
+ *  PCM - Mmap-Emulation
+ *  Copyright (c) 2007 by Takashi Iwai <tiwai@suse.de>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+#include "pcm_local.h"
+#include "pcm_generic.h"
+
+#ifndef PIC
+/* entry for static linking */
+const char *_snd_module_pcm_mmap_emul = "";
+#endif
+
+#ifndef DOC_HIDDEN
+/*
+ *
+ */
+
+typedef struct {
+	snd_pcm_generic_t gen;
+	unsigned int mmap_emul :1;
+	snd_pcm_uframes_t hw_ptr;
+	snd_pcm_uframes_t appl_ptr;
+	snd_pcm_uframes_t start_threshold;
+} mmap_emul_t;
+#endif
+
+/*
+ * here goes a really tricky part; hw_refine falls back to ACCESS_RW_* type
+ * when ACCESS_MMAP_* isn't supported by the hardware.
+ */
+static int snd_pcm_mmap_emul_hw_refine(snd_pcm_t *pcm,
+				       snd_pcm_hw_params_t *params)
+{
+	mmap_emul_t *map = pcm->private_data;
+	int err = 0;
+	snd_pcm_access_mask_t oldmask =
+		*snd_pcm_hw_param_get_mask(params, SND_PCM_HW_PARAM_ACCESS);
+	snd_pcm_access_mask_t mask;
+	const snd_mask_t *pmask;
+
+	snd_mask_none(&mask);
+	err = snd_pcm_hw_refine(map->gen.slave, params);
+	if (err < 0) {
+		snd_pcm_hw_params_t new = *params;
+
+		/* try to use RW_* */
+		if (snd_pcm_access_mask_test(&oldmask,
+					     SND_PCM_ACCESS_MMAP_INTERLEAVED) &&
+		    !snd_pcm_access_mask_test(&oldmask,
+					      SND_PCM_ACCESS_RW_INTERLEAVED))
+			snd_pcm_access_mask_set(&mask,
+						SND_PCM_ACCESS_RW_INTERLEAVED);
+		if (snd_pcm_access_mask_test(&oldmask,
+					     SND_PCM_ACCESS_MMAP_NONINTERLEAVED) &&
+		    !snd_pcm_access_mask_test(&oldmask,
+					      SND_PCM_ACCESS_RW_NONINTERLEAVED))
+			snd_pcm_access_mask_set(&mask,
+						SND_PCM_ACCESS_RW_NONINTERLEAVED);
+		if (snd_pcm_access_mask_empty(&mask))
+			return err;
+		pmask = snd_pcm_hw_param_get_mask(&new,
+						  SND_PCM_HW_PARAM_ACCESS);
+		*(snd_mask_t *)pmask = mask;
+		err = snd_pcm_hw_refine(map->gen.slave, &new);
+		if (err < 0)
+			return err;
+		*params = new;
+	}
+
+	pmask = snd_pcm_hw_param_get_mask(params, SND_PCM_HW_PARAM_ACCESS);
+	if (snd_pcm_access_mask_test(pmask, SND_PCM_ACCESS_MMAP_INTERLEAVED) ||
+	    snd_pcm_access_mask_test(pmask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED) ||
+	    snd_pcm_access_mask_test(pmask, SND_PCM_ACCESS_MMAP_COMPLEX))
+		return 0;
+	if (snd_pcm_access_mask_test(&mask, SND_PCM_ACCESS_RW_INTERLEAVED)) {
+		if (snd_pcm_access_mask_test(pmask,
+					     SND_PCM_ACCESS_RW_INTERLEAVED))
+			snd_pcm_access_mask_set((snd_pcm_access_mask_t *)pmask,
+						SND_PCM_ACCESS_MMAP_INTERLEAVED);
+		snd_pcm_access_mask_reset((snd_pcm_access_mask_t *)pmask,
+					  SND_PCM_ACCESS_RW_INTERLEAVED);
+		params->cmask |= 1<<SND_PCM_HW_PARAM_ACCESS;
+	}
+	if (snd_pcm_access_mask_test(&mask, SND_PCM_ACCESS_RW_NONINTERLEAVED)) {
+		if (snd_pcm_access_mask_test(pmask,
+					     SND_PCM_ACCESS_RW_NONINTERLEAVED))
+			snd_pcm_access_mask_set((snd_pcm_access_mask_t *)pmask,
+						SND_PCM_ACCESS_MMAP_NONINTERLEAVED);
+		snd_pcm_access_mask_reset((snd_pcm_access_mask_t *)pmask,
+					  SND_PCM_ACCESS_RW_NONINTERLEAVED);
+		params->cmask |= 1<<SND_PCM_HW_PARAM_ACCESS;
+	}
+	if (snd_pcm_access_mask_test(&oldmask, SND_PCM_ACCESS_MMAP_INTERLEAVED)) {
+		if (snd_pcm_access_mask_test(&oldmask,
+					     SND_PCM_ACCESS_RW_INTERLEAVED)) {
+			if (snd_pcm_access_mask_test(pmask,
+						     SND_PCM_ACCESS_RW_INTERLEAVED)) {
+				snd_pcm_access_mask_set((snd_pcm_access_mask_t *)pmask,
+							SND_PCM_ACCESS_MMAP_INTERLEAVED);
+				params->cmask |= 1<<SND_PCM_HW_PARAM_ACCESS;
+			}
+		}
+	}
+	if (snd_pcm_access_mask_test(&oldmask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED)) {
+		if (snd_pcm_access_mask_test(&oldmask,
+					     SND_PCM_ACCESS_RW_NONINTERLEAVED)) {
+			if (snd_pcm_access_mask_test(pmask,
+						     SND_PCM_ACCESS_RW_NONINTERLEAVED)) {
+				snd_pcm_access_mask_set((snd_pcm_access_mask_t *)pmask,
+							SND_PCM_ACCESS_MMAP_NONINTERLEAVED);
+				params->cmask |= 1<<SND_PCM_HW_PARAM_ACCESS;
+			}
+		}
+	}
+	return 0;
+}
+
+/*
+ * hw_params needs a similar hack like hw_refine, but it's much simpler
+ * because now snd_pcm_hw_params_t takes only one choice for each item.
+ *
+ * Here, when the normal hw_params call fails, it turns on the mmap_emul
+ * flag and tries to use ACCESS_RW_* mode.
+ *
+ * In mmap_emul mode, the appl_ptr and hw_ptr are handled individually
+ * from the layering slave PCM, and they are sync'ed appropriately in
+ * each read/write or avail_update/commit call.
+ */
+static int snd_pcm_mmap_emul_hw_params(snd_pcm_t *pcm,
+				       snd_pcm_hw_params_t *params)
+{
+	mmap_emul_t *map = pcm->private_data;
+	snd_pcm_hw_params_t old = *params;
+	snd_pcm_access_t access;
+	snd_pcm_access_mask_t oldmask;
+	snd_pcm_access_mask_t *pmask;
+	int err;
+
+	err = _snd_pcm_hw_params(map->gen.slave, params);
+	if (err >= 0) {
+		map->mmap_emul = 0;
+		return err;
+	}
+
+	*params = old;
+	pmask = (snd_pcm_access_mask_t *)snd_pcm_hw_param_get_mask(params, SND_PCM_HW_PARAM_ACCESS);
+	oldmask = *pmask;
+	if (INTERNAL(snd_pcm_hw_params_get_access)(params, &access) < 0)
+		goto _err;
+	switch (access) {
+	case SND_PCM_ACCESS_MMAP_INTERLEAVED:
+		snd_pcm_access_mask_reset(pmask,
+					  SND_PCM_ACCESS_MMAP_INTERLEAVED);
+		snd_pcm_access_mask_set(pmask, SND_PCM_ACCESS_RW_INTERLEAVED);
+		break;
+	case SND_PCM_ACCESS_MMAP_NONINTERLEAVED:
+		snd_pcm_access_mask_reset(pmask,
+					  SND_PCM_ACCESS_MMAP_NONINTERLEAVED);
+		snd_pcm_access_mask_set(pmask,
+					SND_PCM_ACCESS_RW_NONINTERLEAVED);
+		break;
+	default:
+		goto _err;
+	}
+	err = _snd_pcm_hw_params(map->gen.slave, params);
+	if (err < 0)
+		goto _err;
+
+	/* need to back the access type to relieve apps */
+	*pmask = oldmask;
+
+	/* OK, we do fake */
+	map->mmap_emul = 1;
+	map->appl_ptr = 0;
+	map->hw_ptr = 0;
+	snd_pcm_set_hw_ptr(pcm, &map->hw_ptr, -1, 0);
+	snd_pcm_set_appl_ptr(pcm, &map->appl_ptr, -1, 0);
+	return 0;
+
+ _err:
+	err = -errno;
+	return err;
+}
+
+static int snd_pcm_mmap_emul_sw_params(snd_pcm_t *pcm,
+				       snd_pcm_sw_params_t *params)
+{
+	mmap_emul_t *map = pcm->private_data;
+	int err;
+
+	if (!map->mmap_emul)
+		return snd_pcm_generic_sw_params(pcm, params);
+
+	map->start_threshold = params->start_threshold;
+
+	/* HACK: don't auto-start in the slave PCM */
+	params->start_threshold = pcm->boundary;
+	err = snd_pcm_generic_sw_params(pcm, params);
+	if (err < 0)
+		return err;
+	/* restore the value for this PCM */
+	params->start_threshold = map->start_threshold;
+	return err;
+}
+
+static int snd_pcm_mmap_emul_prepare(snd_pcm_t *pcm)
+{
+	mmap_emul_t *map = pcm->private_data;
+	int err;
+
+	err = snd_pcm_generic_prepare(pcm);
+	if (err < 0)
+		return err;
+	map->hw_ptr = map->appl_ptr = 0;
+	return err;
+}
+
+static int snd_pcm_mmap_emul_reset(snd_pcm_t *pcm)
+{
+	mmap_emul_t *map = pcm->private_data;
+	int err;
+
+	err = snd_pcm_generic_reset(pcm);
+	if (err < 0)
+		return err;
+	map->hw_ptr = map->appl_ptr = 0;
+	return err;
+}
+
+static snd_pcm_sframes_t
+snd_pcm_mmap_emul_rewind(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
+{
+	frames = snd_pcm_generic_rewind(pcm, frames);
+	if (frames > 0)
+		snd_pcm_mmap_appl_backward(pcm, frames);
+	return frames;
+}
+
+static snd_pcm_sframes_t
+snd_pcm_mmap_emul_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
+{
+	frames = snd_pcm_generic_forward(pcm, frames);
+	if (frames > 0)
+		snd_pcm_mmap_appl_forward(pcm, frames);
+	return frames;
+}
+
+/* write out the uncommitted chunk on mmap buffer to the slave PCM */
+static snd_pcm_sframes_t
+sync_slave_write(snd_pcm_t *pcm)
+{
+	mmap_emul_t *map = pcm->private_data;
+	snd_pcm_t *slave = map->gen.slave;
+	snd_pcm_uframes_t offset;
+	snd_pcm_sframes_t size;
+
+	/* HACK: don't start stream automatically at commit in mmap mode */
+	pcm->start_threshold = pcm->boundary;
+
+	size = map->appl_ptr - *slave->appl.ptr;
+	if (size < 0)
+		size += pcm->boundary;
+	if (size) {
+		offset = *slave->appl.ptr % pcm->buffer_size;
+		size = snd_pcm_write_mmap(pcm, offset, size);
+	}
+	pcm->start_threshold = map->start_threshold; /* restore */
+	return size;
+}
+
+/* read the available chunk on the slave PCM to mmap buffer */
+static snd_pcm_sframes_t
+sync_slave_read(snd_pcm_t *pcm)
+{
+	mmap_emul_t *map = pcm->private_data;
+	snd_pcm_t *slave = map->gen.slave;
+	snd_pcm_uframes_t offset;
+	snd_pcm_sframes_t size;
+
+	size = *slave->hw.ptr - map->hw_ptr;
+	if (size < 0)
+		size += pcm->boundary;
+	if (!size)
+		return 0;
+	offset = map->hw_ptr % pcm->buffer_size;
+	size = snd_pcm_read_mmap(pcm, offset, size);
+	if (size > 0)
+		snd_pcm_mmap_hw_forward(pcm, size);
+	return 0;
+}
+
+static snd_pcm_sframes_t
+snd_pcm_mmap_emul_mmap_commit(snd_pcm_t *pcm, snd_pcm_uframes_t offset,
+			      snd_pcm_uframes_t size)
+{
+	mmap_emul_t *map = pcm->private_data;
+	snd_pcm_t *slave = map->gen.slave;
+
+	snd_pcm_mmap_appl_forward(pcm, size);
+	if (!map->mmap_emul)
+		return snd_pcm_mmap_commit(slave, offset, size);
+	if (pcm->stream == SND_PCM_STREAM_PLAYBACK)
+		sync_slave_write(pcm);
+	return size;
+}
+
+static snd_pcm_sframes_t snd_pcm_mmap_emul_avail_update(snd_pcm_t *pcm)
+{
+	mmap_emul_t *map = pcm->private_data;
+	snd_pcm_t *slave = map->gen.slave;
+	snd_pcm_sframes_t avail;
+
+	avail = snd_pcm_avail_update(slave);
+	if (!map->mmap_emul || pcm->stream == SND_PCM_STREAM_PLAYBACK)
+		map->hw_ptr = *slave->hw.ptr;
+	else
+		sync_slave_read(pcm);
+	return snd_pcm_mmap_avail(pcm);
+}
+
+static void snd_pcm_mmap_emul_dump(snd_pcm_t *pcm, snd_output_t *out)
+{
+	mmap_emul_t *map = pcm->private_data;
+
+	snd_output_printf(out, "Mmap emulation PCM\n");
+	if (pcm->setup) {
+		snd_output_printf(out, "Its setup is:\n");
+		snd_pcm_dump_setup(pcm, out);
+	}
+	snd_output_printf(out, "Slave: ");
+	snd_pcm_dump(map->gen.slave, out);
+}
+
+static const snd_pcm_ops_t snd_pcm_mmap_emul_ops = {
+	.close = snd_pcm_generic_close,
+	.info = snd_pcm_generic_info,
+	.hw_refine = snd_pcm_mmap_emul_hw_refine,
+	.hw_params = snd_pcm_mmap_emul_hw_params,
+	.hw_free = snd_pcm_generic_hw_free,
+	.sw_params = snd_pcm_mmap_emul_sw_params,
+	.channel_info = snd_pcm_generic_channel_info,
+	.dump = snd_pcm_mmap_emul_dump,
+	.nonblock = snd_pcm_generic_nonblock,
+	.async = snd_pcm_generic_async,
+	.mmap = snd_pcm_generic_mmap,
+	.munmap = snd_pcm_generic_munmap,
+};
+
+static const snd_pcm_fast_ops_t snd_pcm_mmap_emul_fast_ops = {
+	.status = snd_pcm_generic_status,
+	.state = snd_pcm_generic_state,
+	.hwsync = snd_pcm_generic_hwsync,
+	.delay = snd_pcm_generic_delay,
+	.prepare = snd_pcm_mmap_emul_prepare,
+	.reset = snd_pcm_mmap_emul_reset,
+	.start = snd_pcm_generic_start,
+	.drop = snd_pcm_generic_drop,
+	.drain = snd_pcm_generic_drain,
+	.pause = snd_pcm_generic_pause,
+	.rewindable = snd_pcm_generic_rewindable,
+	.rewind = snd_pcm_mmap_emul_rewind,
+	.forwardable = snd_pcm_generic_forwardable,
+	.forward = snd_pcm_mmap_emul_forward,
+	.resume = snd_pcm_generic_resume,
+	.link = snd_pcm_generic_link,
+	.link_slaves = snd_pcm_generic_link_slaves,
+	.unlink = snd_pcm_generic_unlink,
+	.writei = snd_pcm_generic_writei,
+	.writen = snd_pcm_generic_writen,
+	.readi = snd_pcm_generic_readi,
+	.readn = snd_pcm_generic_readn,
+	.avail_update = snd_pcm_mmap_emul_avail_update,
+	.mmap_commit = snd_pcm_mmap_emul_mmap_commit,
+	.htimestamp = snd_pcm_generic_htimestamp,
+	.poll_descriptors = snd_pcm_generic_poll_descriptors,
+	.poll_descriptors_count = snd_pcm_generic_poll_descriptors_count,
+	.poll_revents = snd_pcm_generic_poll_revents,
+};
+
+#ifndef DOC_HIDDEN
+int __snd_pcm_mmap_emul_open(snd_pcm_t **pcmp, const char *name,
+			     snd_pcm_t *slave, int close_slave)
+{
+	snd_pcm_t *pcm;
+	mmap_emul_t *map;
+	int err;
+
+	map = calloc(1, sizeof(*map));
+	if (!map)
+		return -ENOMEM;
+	map->gen.slave = slave;
+	map->gen.close_slave = close_slave;
+
+	err = snd_pcm_new(&pcm, SND_PCM_TYPE_MMAP_EMUL, name,
+			  slave->stream, slave->mode);
+	if (err < 0) {
+		free(map);
+		return err;
+	}
+	pcm->ops = &snd_pcm_mmap_emul_ops;
+	pcm->fast_ops = &snd_pcm_mmap_emul_fast_ops;
+	pcm->private_data = map;
+	pcm->poll_fd = slave->poll_fd;
+	pcm->poll_events = slave->poll_events;
+	pcm->monotonic = slave->monotonic;
+	snd_pcm_set_hw_ptr(pcm, &map->hw_ptr, -1, 0);
+	snd_pcm_set_appl_ptr(pcm, &map->appl_ptr, -1, 0);
+	*pcmp = pcm;
+
+	return 0;
+}
+#endif
+
+/*! \page pcm_plugins
+
+\section pcm_plugins_mmap_emul Plugin: mmap_emul
+
+\code
+pcm.name {
+	type mmap_emul
+	slave PCM
+}
+\endcode
+
+\subsection pcm_plugins_mmap_emul_funcref Function reference
+
+<UL>
+  <LI>_snd_pcm_hw_open()
+</UL>
+
+*/
+
+/**
+ * \brief Creates a new mmap_emul PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param root Root configuration node
+ * \param conf Configuration node with hw PCM description
+ * \param stream PCM Stream
+ * \param mode PCM Mode
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int _snd_pcm_mmap_emul_open(snd_pcm_t **pcmp, const char *name,
+			    snd_config_t *root ATTRIBUTE_UNUSED,
+			    snd_config_t *conf,
+			    snd_pcm_stream_t stream, int mode)
+{
+	snd_config_iterator_t i, next;
+	int err;
+	snd_pcm_t *spcm;
+	snd_config_t *slave = NULL, *sconf;
+
+	snd_config_for_each(i, next, conf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (snd_pcm_conf_generic_id(id))
+			continue;
+		if (strcmp(id, "slave") == 0) {
+			slave = n;
+			continue;
+		}
+		SNDERR("Unknown field %s", id);
+		return -EINVAL;
+	}
+	if (!slave) {
+		SNDERR("slave is not defined");
+		return -EINVAL;
+	}
+	err = snd_pcm_slave_conf(root, slave, &sconf, 0);
+	if (err < 0)
+		return err;
+	err = snd_pcm_open_slave(&spcm, root, sconf, stream, mode, conf);
+	snd_config_delete(sconf);
+	if (err < 0)
+		return err;
+	err = __snd_pcm_mmap_emul_open(pcmp, name, spcm, 1);
+	if (err < 0)
+		snd_pcm_close(spcm);
+	return err;
+}
+
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(_snd_pcm_mmap_emul_open, SND_PCM_DLSYM_VERSION);
+#endif
diff --git a/src/pcm/pcm_mulaw.c b/src/pcm/pcm_mulaw.c
new file mode 100644
index 0000000..22e7d96
--- /dev/null
+++ b/src/pcm/pcm_mulaw.c
@@ -0,0 +1,568 @@
+/**
+ * \file pcm/pcm_mulaw.c
+ * \ingroup PCM_Plugins
+ * \brief PCM Mu-Law Conversion Plugin Interface
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \date 2000-2001
+ */
+/*
+ *  PCM - Mu-Law conversion
+ *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+#include <byteswap.h>
+#include "pcm_local.h"
+#include "pcm_plugin.h"
+
+#include "plugin_ops.h"
+
+#ifndef PIC
+/* entry for static linking */
+const char *_snd_module_pcm_mulaw = "";
+#endif
+
+#ifndef DOC_HIDDEN
+
+typedef void (*mulaw_f)(const snd_pcm_channel_area_t *src_areas,
+			snd_pcm_uframes_t src_offset,
+			const snd_pcm_channel_area_t *dst_areas,
+			snd_pcm_uframes_t dst_offset,
+			unsigned int channels, snd_pcm_uframes_t frames,
+			unsigned int getputidx);
+
+typedef struct {
+	/* This field need to be the first */
+	snd_pcm_plugin_t plug;
+	unsigned int getput_idx;
+	mulaw_f func;
+	snd_pcm_format_t sformat;
+} snd_pcm_mulaw_t;
+
+#endif
+
+static inline int val_seg(int val)
+{
+	int r = 0;
+	val >>= 7;
+	if (val & 0xf0) {
+		val >>= 4;
+		r += 4;
+	}
+	if (val & 0x0c) {
+		val >>= 2;
+		r += 2;
+	}
+	if (val & 0x02)
+		r += 1;
+	return r;
+}
+
+/*
+ * s16_to_ulaw() - Convert a linear PCM value to u-law
+ *
+ * In order to simplify the encoding process, the original linear magnitude
+ * is biased by adding 33 which shifts the encoding range from (0 - 8158) to
+ * (33 - 8191). The result can be seen in the following encoding table:
+ *
+ *	Biased Linear Input Code	Compressed Code
+ *	------------------------	---------------
+ *	00000001wxyza			000wxyz
+ *	0000001wxyzab			001wxyz
+ *	000001wxyzabc			010wxyz
+ *	00001wxyzabcd			011wxyz
+ *	0001wxyzabcde			100wxyz
+ *	001wxyzabcdef			101wxyz
+ *	01wxyzabcdefg			110wxyz
+ *	1wxyzabcdefgh			111wxyz
+ *
+ * Each biased linear code has a leading 1 which identifies the segment
+ * number. The value of the segment number is equal to 7 minus the number
+ * of leading 0's. The quantization interval is directly available as the
+ * four bits wxyz.  * The trailing bits (a - h) are ignored.
+ *
+ * Ordinarily the complement of the resulting code word is used for
+ * transmission, and so the code word is complemented before it is returned.
+ *
+ * For further information see John C. Bellamy's Digital Telephony, 1982,
+ * John Wiley & Sons, pps 98-111 and 472-476.
+ */
+
+static unsigned char s16_to_ulaw(int pcm_val)	/* 2's complement (16-bit range) */
+{
+	int mask;
+	int seg;
+	unsigned char uval;
+
+	if (pcm_val < 0) {
+		pcm_val = 0x84 - pcm_val;
+		mask = 0x7f;
+	} else {
+		pcm_val += 0x84;
+		mask = 0xff;
+	}
+	if (pcm_val > 0x7fff)
+		pcm_val = 0x7fff;
+
+	/* Convert the scaled magnitude to segment number. */
+	seg = val_seg(pcm_val);
+
+	/*
+	 * Combine the sign, segment, quantization bits;
+	 * and complement the code word.
+	 */
+	uval = (seg << 4) | ((pcm_val >> (seg + 3)) & 0x0f);
+	return uval ^ mask;
+}
+
+/*
+ * ulaw_to_s16() - Convert a u-law value to 16-bit linear PCM
+ *
+ * First, a biased linear code is derived from the code word. An unbiased
+ * output can then be obtained by subtracting 33 from the biased code.
+ *
+ * Note that this function expects to be passed the complement of the
+ * original code word. This is in keeping with ISDN conventions.
+ */
+static int ulaw_to_s16(unsigned char u_val)
+{
+	int t;
+
+	/* Complement to obtain normal u-law value. */
+	u_val = ~u_val;
+
+	/*
+	 * Extract and bias the quantization bits. Then
+	 * shift up by the segment number and subtract out the bias.
+	 */
+	t = ((u_val & 0x0f) << 3) + 0x84;
+	t <<= (u_val & 0x70) >> 4;
+
+	return ((u_val & 0x80) ? (0x84 - t) : (t - 0x84));
+}
+
+#ifndef DOC_HIDDEN
+
+void snd_pcm_mulaw_decode(const snd_pcm_channel_area_t *dst_areas,
+			  snd_pcm_uframes_t dst_offset,
+			  const snd_pcm_channel_area_t *src_areas,
+			  snd_pcm_uframes_t src_offset,
+			  unsigned int channels, snd_pcm_uframes_t frames,
+			  unsigned int putidx)
+{
+#define PUT16_LABELS
+#include "plugin_ops.h"
+#undef PUT16_LABELS
+	void *put = put16_labels[putidx];
+	unsigned int channel;
+	for (channel = 0; channel < channels; ++channel) {
+		const unsigned char *src;
+		char *dst;
+		int src_step, dst_step;
+		snd_pcm_uframes_t frames1;
+		const snd_pcm_channel_area_t *src_area = &src_areas[channel];
+		const snd_pcm_channel_area_t *dst_area = &dst_areas[channel];
+		src = snd_pcm_channel_area_addr(src_area, src_offset);
+		dst = snd_pcm_channel_area_addr(dst_area, dst_offset);
+		src_step = snd_pcm_channel_area_step(src_area);
+		dst_step = snd_pcm_channel_area_step(dst_area);
+		frames1 = frames;
+		while (frames1-- > 0) {
+			int16_t sample = ulaw_to_s16(*src);
+			goto *put;
+#define PUT16_END after
+#include "plugin_ops.h"
+#undef PUT16_END
+		after:
+			src += src_step;
+			dst += dst_step;
+		}
+	}
+}
+
+void snd_pcm_mulaw_encode(const snd_pcm_channel_area_t *dst_areas,
+			  snd_pcm_uframes_t dst_offset,
+			  const snd_pcm_channel_area_t *src_areas,
+			  snd_pcm_uframes_t src_offset,
+			  unsigned int channels, snd_pcm_uframes_t frames,
+			  unsigned int getidx)
+{
+#define GET16_LABELS
+#include "plugin_ops.h"
+#undef GET16_LABELS
+	void *get = get16_labels[getidx];
+	unsigned int channel;
+	int16_t sample = 0;
+	for (channel = 0; channel < channels; ++channel) {
+		const char *src;
+		char *dst;
+		int src_step, dst_step;
+		snd_pcm_uframes_t frames1;
+		const snd_pcm_channel_area_t *src_area = &src_areas[channel];
+		const snd_pcm_channel_area_t *dst_area = &dst_areas[channel];
+		src = snd_pcm_channel_area_addr(src_area, src_offset);
+		dst = snd_pcm_channel_area_addr(dst_area, dst_offset);
+		src_step = snd_pcm_channel_area_step(src_area);
+		dst_step = snd_pcm_channel_area_step(dst_area);
+		frames1 = frames;
+		while (frames1-- > 0) {
+			goto *get;
+#define GET16_END after
+#include "plugin_ops.h"
+#undef GET16_END
+		after:
+			*dst = s16_to_ulaw(sample);
+			src += src_step;
+			dst += dst_step;
+		}
+	}
+}
+
+#endif /* DOC_HIDDEN */
+
+static int snd_pcm_mulaw_hw_refine_cprepare(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	snd_pcm_mulaw_t *mulaw = pcm->private_data;
+	int err;
+	snd_pcm_access_mask_t access_mask = { SND_PCM_ACCBIT_SHM };
+	err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_ACCESS,
+					 &access_mask);
+	if (err < 0)
+		return err;
+	if (mulaw->sformat == SND_PCM_FORMAT_MU_LAW) {
+		snd_pcm_format_mask_t format_mask= { SND_PCM_FMTBIT_LINEAR };
+		err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_FORMAT,
+						 &format_mask);
+	} else {
+		err = _snd_pcm_hw_params_set_format(params,
+						   SND_PCM_FORMAT_MU_LAW);
+	}
+	err = _snd_pcm_hw_params_set_subformat(params, SND_PCM_SUBFORMAT_STD);
+	if (err < 0)
+		return err;
+	params->info &= ~(SND_PCM_INFO_MMAP | SND_PCM_INFO_MMAP_VALID);
+	return 0;
+}
+
+static int snd_pcm_mulaw_hw_refine_sprepare(snd_pcm_t *pcm, snd_pcm_hw_params_t *sparams)
+{
+	snd_pcm_mulaw_t *mulaw = pcm->private_data;
+	snd_pcm_access_mask_t saccess_mask = { SND_PCM_ACCBIT_MMAP };
+	_snd_pcm_hw_params_any(sparams);
+	_snd_pcm_hw_param_set_mask(sparams, SND_PCM_HW_PARAM_ACCESS,
+				   &saccess_mask);
+	_snd_pcm_hw_params_set_format(sparams, mulaw->sformat);
+	_snd_pcm_hw_params_set_subformat(sparams, SND_PCM_SUBFORMAT_STD);
+	return 0;
+}
+
+static int snd_pcm_mulaw_hw_refine_schange(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params,
+					    snd_pcm_hw_params_t *sparams)
+{
+	int err;
+	unsigned int links = (SND_PCM_HW_PARBIT_CHANNELS |
+			      SND_PCM_HW_PARBIT_RATE |
+			      SND_PCM_HW_PARBIT_PERIOD_SIZE |
+			      SND_PCM_HW_PARBIT_BUFFER_SIZE |
+			      SND_PCM_HW_PARBIT_PERIODS |
+			      SND_PCM_HW_PARBIT_PERIOD_TIME |
+			      SND_PCM_HW_PARBIT_BUFFER_TIME |
+			      SND_PCM_HW_PARBIT_TICK_TIME);
+	err = _snd_pcm_hw_params_refine(sparams, links, params);
+	if (err < 0)
+		return err;
+	return 0;
+}
+	
+static int snd_pcm_mulaw_hw_refine_cchange(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params,
+					    snd_pcm_hw_params_t *sparams)
+{
+	int err;
+	unsigned int links = (SND_PCM_HW_PARBIT_CHANNELS |
+			      SND_PCM_HW_PARBIT_RATE |
+			      SND_PCM_HW_PARBIT_PERIOD_SIZE |
+			      SND_PCM_HW_PARBIT_BUFFER_SIZE |
+			      SND_PCM_HW_PARBIT_PERIODS |
+			      SND_PCM_HW_PARBIT_PERIOD_TIME |
+			      SND_PCM_HW_PARBIT_BUFFER_TIME |
+			      SND_PCM_HW_PARBIT_TICK_TIME);
+	err = _snd_pcm_hw_params_refine(params, links, sparams);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static int snd_pcm_mulaw_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	return snd_pcm_hw_refine_slave(pcm, params,
+				       snd_pcm_mulaw_hw_refine_cprepare,
+				       snd_pcm_mulaw_hw_refine_cchange,
+				       snd_pcm_mulaw_hw_refine_sprepare,
+				       snd_pcm_mulaw_hw_refine_schange,
+				       snd_pcm_generic_hw_refine);
+}
+
+static int snd_pcm_mulaw_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t * params)
+{
+	snd_pcm_mulaw_t *mulaw = pcm->private_data;
+	snd_pcm_format_t format;
+	int err = snd_pcm_hw_params_slave(pcm, params,
+					  snd_pcm_mulaw_hw_refine_cchange,
+					  snd_pcm_mulaw_hw_refine_sprepare,
+					  snd_pcm_mulaw_hw_refine_schange,
+					  snd_pcm_generic_hw_params);
+	if (err < 0)
+		return err;
+
+	err = INTERNAL(snd_pcm_hw_params_get_format)(params, &format);
+	if (err < 0)
+		return err;
+
+	if (pcm->stream == SND_PCM_STREAM_PLAYBACK) {
+		if (mulaw->sformat == SND_PCM_FORMAT_MU_LAW) {
+			mulaw->getput_idx = snd_pcm_linear_get_index(format, SND_PCM_FORMAT_S16);
+			mulaw->func = snd_pcm_mulaw_encode;
+		} else {
+			mulaw->getput_idx = snd_pcm_linear_put_index(SND_PCM_FORMAT_S16, mulaw->sformat);
+			mulaw->func = snd_pcm_mulaw_decode;
+		}
+	} else {
+		if (mulaw->sformat == SND_PCM_FORMAT_MU_LAW) {
+			mulaw->getput_idx = snd_pcm_linear_put_index(SND_PCM_FORMAT_S16, format);
+			mulaw->func = snd_pcm_mulaw_decode;
+		} else {
+			mulaw->getput_idx = snd_pcm_linear_get_index(mulaw->sformat, SND_PCM_FORMAT_S16);
+			mulaw->func = snd_pcm_mulaw_encode;
+		}
+	}
+	return 0;
+}
+
+static snd_pcm_uframes_t
+snd_pcm_mulaw_write_areas(snd_pcm_t *pcm,
+			  const snd_pcm_channel_area_t *areas,
+			  snd_pcm_uframes_t offset,
+			  snd_pcm_uframes_t size,
+			  const snd_pcm_channel_area_t *slave_areas,
+			  snd_pcm_uframes_t slave_offset,
+			  snd_pcm_uframes_t *slave_sizep)
+{
+	snd_pcm_mulaw_t *mulaw = pcm->private_data;
+	if (size > *slave_sizep)
+		size = *slave_sizep;
+	mulaw->func(slave_areas, slave_offset,
+		    areas, offset, 
+		    pcm->channels, size,
+		    mulaw->getput_idx);
+	*slave_sizep = size;
+	return size;
+}
+
+static snd_pcm_uframes_t
+snd_pcm_mulaw_read_areas(snd_pcm_t *pcm,
+			 const snd_pcm_channel_area_t *areas,
+			 snd_pcm_uframes_t offset,
+			 snd_pcm_uframes_t size,
+			 const snd_pcm_channel_area_t *slave_areas,
+			 snd_pcm_uframes_t slave_offset,
+			 snd_pcm_uframes_t *slave_sizep)
+{
+	snd_pcm_mulaw_t *mulaw = pcm->private_data;
+	if (size > *slave_sizep)
+		size = *slave_sizep;
+	mulaw->func(areas, offset, 
+		    slave_areas, slave_offset,
+		    pcm->channels, size,
+		    mulaw->getput_idx);
+	*slave_sizep = size;
+	return size;
+}
+
+static void snd_pcm_mulaw_dump(snd_pcm_t *pcm, snd_output_t *out)
+{
+	snd_pcm_mulaw_t *mulaw = pcm->private_data;
+	snd_output_printf(out, "Mu-Law conversion PCM (%s)\n", 
+		snd_pcm_format_name(mulaw->sformat));
+	if (pcm->setup) {
+		snd_output_printf(out, "Its setup is:\n");
+		snd_pcm_dump_setup(pcm, out);
+	}
+	snd_output_printf(out, "Slave: ");
+	snd_pcm_dump(mulaw->plug.gen.slave, out);
+}
+
+static const snd_pcm_ops_t snd_pcm_mulaw_ops = {
+	.close = snd_pcm_generic_close,
+	.info = snd_pcm_generic_info,
+	.hw_refine = snd_pcm_mulaw_hw_refine,
+	.hw_params = snd_pcm_mulaw_hw_params,
+	.hw_free = snd_pcm_generic_hw_free,
+	.sw_params = snd_pcm_generic_sw_params,
+	.channel_info = snd_pcm_generic_channel_info,
+	.dump = snd_pcm_mulaw_dump,
+	.nonblock = snd_pcm_generic_nonblock,
+	.async = snd_pcm_generic_async,
+	.mmap = snd_pcm_generic_mmap,
+	.munmap = snd_pcm_generic_munmap,
+};
+
+/**
+ * \brief Creates a new Mu-Law conversion PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param sformat Slave (destination) format
+ * \param slave Slave PCM handle
+ * \param close_slave When set, the slave PCM handle is closed with copy PCM
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int snd_pcm_mulaw_open(snd_pcm_t **pcmp, const char *name, snd_pcm_format_t sformat, snd_pcm_t *slave, int close_slave)
+{
+	snd_pcm_t *pcm;
+	snd_pcm_mulaw_t *mulaw;
+	int err;
+	assert(pcmp && slave);
+	if (snd_pcm_format_linear(sformat) != 1 &&
+	    sformat != SND_PCM_FORMAT_MU_LAW)
+		return -EINVAL;
+	mulaw = calloc(1, sizeof(snd_pcm_mulaw_t));
+	if (!mulaw) {
+		return -ENOMEM;
+	}
+	snd_pcm_plugin_init(&mulaw->plug);
+	mulaw->sformat = sformat;
+	mulaw->plug.read = snd_pcm_mulaw_read_areas;
+	mulaw->plug.write = snd_pcm_mulaw_write_areas;
+	mulaw->plug.undo_read = snd_pcm_plugin_undo_read_generic;
+	mulaw->plug.undo_write = snd_pcm_plugin_undo_write_generic;
+	mulaw->plug.gen.slave = slave;
+	mulaw->plug.gen.close_slave = close_slave;
+
+	err = snd_pcm_new(&pcm, SND_PCM_TYPE_MULAW, name, slave->stream, slave->mode);
+	if (err < 0) {
+		free(mulaw);
+		return err;
+	}
+	pcm->ops = &snd_pcm_mulaw_ops;
+	pcm->fast_ops = &snd_pcm_plugin_fast_ops;
+	pcm->private_data = mulaw;
+	pcm->poll_fd = slave->poll_fd;
+	pcm->poll_events = slave->poll_events;
+	pcm->monotonic = slave->monotonic;
+	snd_pcm_set_hw_ptr(pcm, &mulaw->plug.hw_ptr, -1, 0);
+	snd_pcm_set_appl_ptr(pcm, &mulaw->plug.appl_ptr, -1, 0);
+	*pcmp = pcm;
+
+	return 0;
+}
+
+/*! \page pcm_plugins
+
+\section pcm_plugins_mulaw Plugin: Mu-Law
+
+This plugin converts Mu-Law samples to linear or linear to Mu-Law samples
+from master Mu-Law conversion PCM to given slave PCM. The channel count,
+format and rate must match for both of them.
+
+\code
+pcm.name {
+        type mulaw              # Mu-Law conversion PCM
+        slave STR               # Slave name
+        # or
+        slave {                 # Slave definition
+                pcm STR         # Slave PCM name
+                # or
+                pcm { }         # Slave PCM definition
+                format STR      # Slave format
+        }
+}
+\endcode
+
+\subsection pcm_plugins_mulaw_funcref Function reference
+
+<UL>
+  <LI>snd_pcm_mulaw_open()
+  <LI>_snd_pcm_mulaw_open()
+</UL>
+
+*/
+
+/**
+ * \brief Creates a new Mu-Law conversion PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param root Root configuration node
+ * \param conf Configuration node with copy PCM description
+ * \param stream Stream type
+ * \param mode Stream mode
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int _snd_pcm_mulaw_open(snd_pcm_t **pcmp, const char *name,
+			snd_config_t *root, snd_config_t *conf, 
+			snd_pcm_stream_t stream, int mode)
+{
+	snd_config_iterator_t i, next;
+	int err;
+	snd_pcm_t *spcm;
+	snd_config_t *slave = NULL, *sconf;
+	snd_pcm_format_t sformat;
+	snd_config_for_each(i, next, conf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (snd_pcm_conf_generic_id(id))
+			continue;
+		if (strcmp(id, "slave") == 0) {
+			slave = n;
+			continue;
+		}
+		SNDERR("Unknown field %s", id);
+		return -EINVAL;
+	}
+	if (!slave) {
+		SNDERR("slave is not defined");
+		return -EINVAL;
+	}
+	err = snd_pcm_slave_conf(root, slave, &sconf, 1,
+				 SND_PCM_HW_PARAM_FORMAT, SCONF_MANDATORY, &sformat);
+	if (err < 0)
+		return err;
+	if (snd_pcm_format_linear(sformat) != 1 &&
+	    sformat != SND_PCM_FORMAT_MU_LAW) {
+		snd_config_delete(sconf);
+		SNDERR("invalid slave format");
+		return -EINVAL;
+	}
+	err = snd_pcm_open_slave(&spcm, root, sconf, stream, mode, conf);
+	snd_config_delete(sconf);
+	if (err < 0)
+		return err;
+	err = snd_pcm_mulaw_open(pcmp, name, sformat, spcm, 1);
+	if (err < 0)
+		snd_pcm_close(spcm);
+	return err;
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(_snd_pcm_mulaw_open, SND_PCM_DLSYM_VERSION);
+#endif
diff --git a/src/pcm/pcm_multi.c b/src/pcm/pcm_multi.c
new file mode 100644
index 0000000..6b39c7a
--- /dev/null
+++ b/src/pcm/pcm_multi.c
@@ -0,0 +1,1216 @@
+/**
+ * \file pcm/pcm_multi.c
+ * \ingroup PCM_Plugins
+ * \brief PCM Multi Streams to One Conversion Plugin Interface
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \date 2000-2001
+ */
+/*
+ *  PCM - Multi
+ *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <math.h>
+#include "pcm_local.h"
+#include "pcm_generic.h"
+
+#ifndef PIC
+/* entry for static linking */
+const char *_snd_module_pcm_multi = "";
+#endif
+
+#ifndef DOC_HIDDEN
+
+typedef struct {
+	snd_pcm_t *pcm;
+	unsigned int channels_count;
+	int close_slave;
+	snd_pcm_t *linked;
+} snd_pcm_multi_slave_t;
+
+typedef struct {
+	int slave_idx;
+	unsigned int slave_channel;
+} snd_pcm_multi_channel_t;
+
+typedef struct {
+	unsigned int slaves_count;
+	unsigned int master_slave;
+	snd_pcm_multi_slave_t *slaves;
+	unsigned int channels_count;
+	snd_pcm_multi_channel_t *channels;
+} snd_pcm_multi_t;
+
+#endif
+
+static int snd_pcm_multi_close(snd_pcm_t *pcm)
+{
+	snd_pcm_multi_t *multi = pcm->private_data;
+	unsigned int i;
+	int ret = 0;
+	for (i = 0; i < multi->slaves_count; ++i) {
+		snd_pcm_multi_slave_t *slave = &multi->slaves[i];
+		if (slave->close_slave) {
+			int err = snd_pcm_close(slave->pcm);
+			if (err < 0)
+				ret = err;
+		}
+	}
+	free(multi->slaves);
+	free(multi->channels);
+	free(multi);
+	return ret;
+}
+
+static int snd_pcm_multi_nonblock(snd_pcm_t *pcm ATTRIBUTE_UNUSED, int nonblock ATTRIBUTE_UNUSED)
+{
+	return 0;
+}
+
+static int snd_pcm_multi_async(snd_pcm_t *pcm, int sig, pid_t pid)
+{
+	snd_pcm_multi_t *multi = pcm->private_data;
+	snd_pcm_t *slave_0 = multi->slaves[multi->master_slave].pcm;
+	return snd_pcm_async(slave_0, sig, pid);
+}
+
+static int snd_pcm_multi_poll_descriptors_count(snd_pcm_t *pcm)
+{
+	snd_pcm_multi_t *multi = pcm->private_data;
+	snd_pcm_t *slave_0 = multi->slaves[multi->master_slave].pcm;
+	return snd_pcm_poll_descriptors_count(slave_0);
+}
+
+static int snd_pcm_multi_poll_descriptors(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int space)
+{
+	snd_pcm_multi_t *multi = pcm->private_data;
+	snd_pcm_t *slave;
+	snd_pcm_t *slave_0 = multi->slaves[multi->master_slave].pcm;
+	int err;
+	unsigned int i;
+
+	for (i = 0; i < multi->slaves_count; ++i) {
+		slave = multi->slaves[i].pcm;
+		if (slave == slave_0)
+			continue;
+		err = snd_pcm_poll_descriptors(slave, pfds, space);
+		if (err < 0)
+			return err;
+	}
+	/* finally overwrite with master's pfds */
+	return snd_pcm_poll_descriptors(slave_0, pfds, space);
+}
+
+static int snd_pcm_multi_poll_revents(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int nfds, unsigned short *revents)
+{
+	snd_pcm_multi_t *multi = pcm->private_data;
+	snd_pcm_t *slave_0 = multi->slaves[multi->master_slave].pcm;
+	return snd_pcm_poll_descriptors_revents(slave_0, pfds, nfds, revents);
+}
+
+static int snd_pcm_multi_info(snd_pcm_t *pcm, snd_pcm_info_t *info)
+{
+	snd_pcm_multi_t *multi = pcm->private_data;
+	int err, n;
+	assert(info->subdevice < multi->slaves_count);
+	n = info->subdevice;
+	info->subdevice = 0;
+	err = snd_pcm_info(multi->slaves[n].pcm, info);
+	if (err < 0)
+		return err;
+	info->subdevices_count = multi->slaves_count;
+	return 0;
+}
+
+static int snd_pcm_multi_hw_refine_cprepare(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	snd_pcm_multi_t *multi = pcm->private_data;
+	snd_pcm_access_mask_t access_mask;
+	int err;
+	snd_pcm_access_mask_any(&access_mask);
+	snd_pcm_access_mask_reset(&access_mask, SND_PCM_ACCESS_MMAP_INTERLEAVED);
+	err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_ACCESS,
+					 &access_mask);
+	if (err < 0)
+		return err;
+	err = _snd_pcm_hw_param_set(params, SND_PCM_HW_PARAM_CHANNELS,
+				    multi->channels_count, 0);
+	if (err < 0)
+		return err;
+	params->info = ~0U;
+	return 0;
+}
+
+static int snd_pcm_multi_hw_refine_sprepare(snd_pcm_t *pcm, unsigned int slave_idx,
+					    snd_pcm_hw_params_t *sparams)
+{
+	snd_pcm_multi_t *multi = pcm->private_data;
+	snd_pcm_multi_slave_t *slave = &multi->slaves[slave_idx];
+	snd_pcm_access_mask_t saccess_mask = { SND_PCM_ACCBIT_MMAP };
+	_snd_pcm_hw_params_any(sparams);
+	_snd_pcm_hw_param_set_mask(sparams, SND_PCM_HW_PARAM_ACCESS,
+				   &saccess_mask);
+	_snd_pcm_hw_param_set(sparams, SND_PCM_HW_PARAM_CHANNELS,
+			      slave->channels_count, 0);
+	return 0;
+}
+
+static int snd_pcm_multi_hw_refine_schange(snd_pcm_t *pcm ATTRIBUTE_UNUSED,
+					   unsigned int slave_idx ATTRIBUTE_UNUSED,
+					   snd_pcm_hw_params_t *params,
+					   snd_pcm_hw_params_t *sparams)
+{
+	int err;
+	unsigned int links = (SND_PCM_HW_PARBIT_FORMAT |
+			      SND_PCM_HW_PARBIT_SUBFORMAT |
+			      SND_PCM_HW_PARBIT_RATE |
+			      SND_PCM_HW_PARBIT_PERIOD_SIZE |
+			      SND_PCM_HW_PARBIT_PERIOD_TIME |
+			      SND_PCM_HW_PARBIT_PERIODS |
+			      SND_PCM_HW_PARBIT_BUFFER_SIZE |
+			      SND_PCM_HW_PARBIT_BUFFER_TIME |
+			      SND_PCM_HW_PARBIT_TICK_TIME);
+	const snd_pcm_access_mask_t *access_mask = snd_pcm_hw_param_get_mask(params, SND_PCM_HW_PARAM_ACCESS);
+	if (!snd_pcm_access_mask_test(access_mask, SND_PCM_ACCESS_RW_INTERLEAVED) &&
+	    !snd_pcm_access_mask_test(access_mask, SND_PCM_ACCESS_RW_NONINTERLEAVED) &&
+	    !snd_pcm_access_mask_test(access_mask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED)) {
+		snd_pcm_access_mask_t saccess_mask;
+		snd_pcm_access_mask_any(&saccess_mask);
+		snd_pcm_access_mask_reset(&saccess_mask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED);
+		err = _snd_pcm_hw_param_set_mask(sparams, SND_PCM_HW_PARAM_ACCESS,
+						 &saccess_mask);
+		if (err < 0)
+			return err;
+	}
+	err = _snd_pcm_hw_params_refine(sparams, links, params);
+	if (err < 0)
+		return err;
+	return 0;
+}
+	
+static int snd_pcm_multi_hw_refine_cchange(snd_pcm_t *pcm ATTRIBUTE_UNUSED,
+					   unsigned int slave_idx ATTRIBUTE_UNUSED,
+					   snd_pcm_hw_params_t *params,
+					   snd_pcm_hw_params_t *sparams)
+{
+	int err;
+	unsigned int links = (SND_PCM_HW_PARBIT_FORMAT |
+			      SND_PCM_HW_PARBIT_SUBFORMAT |
+			      SND_PCM_HW_PARBIT_RATE |
+			      SND_PCM_HW_PARBIT_PERIOD_SIZE |
+			      SND_PCM_HW_PARBIT_PERIOD_TIME |
+			      SND_PCM_HW_PARBIT_PERIODS |
+			      SND_PCM_HW_PARBIT_BUFFER_SIZE |
+			      SND_PCM_HW_PARBIT_BUFFER_TIME |
+			      SND_PCM_HW_PARBIT_TICK_TIME);
+	snd_pcm_access_mask_t access_mask;
+	const snd_pcm_access_mask_t *saccess_mask = snd_pcm_hw_param_get_mask(sparams, SND_PCM_HW_PARAM_ACCESS);
+	snd_pcm_access_mask_any(&access_mask);
+	snd_pcm_access_mask_reset(&access_mask, SND_PCM_ACCESS_MMAP_INTERLEAVED);
+	if (!snd_pcm_access_mask_test(saccess_mask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED))
+		snd_pcm_access_mask_reset(&access_mask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED);
+	if (!snd_pcm_access_mask_test(saccess_mask, SND_PCM_ACCESS_MMAP_COMPLEX) &&
+	    !snd_pcm_access_mask_test(saccess_mask, SND_PCM_ACCESS_MMAP_INTERLEAVED))
+		snd_pcm_access_mask_reset(&access_mask, SND_PCM_ACCESS_MMAP_COMPLEX);
+	err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_ACCESS,
+					 &access_mask);
+	if (err < 0)
+		return err;
+	err = _snd_pcm_hw_params_refine(params, links, sparams);
+	if (err < 0)
+		return err;
+	params->info &= sparams->info;
+	return 0;
+}
+
+static int snd_pcm_multi_hw_refine_slave(snd_pcm_t *pcm,
+					 unsigned int slave_idx,
+					 snd_pcm_hw_params_t *sparams)
+{
+	snd_pcm_multi_t *multi = pcm->private_data;
+	snd_pcm_t *slave = multi->slaves[slave_idx].pcm;
+	return snd_pcm_hw_refine(slave, sparams);
+}
+
+static int snd_pcm_multi_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	snd_pcm_multi_t *multi = pcm->private_data;
+	unsigned int k;
+	snd_pcm_hw_params_t sparams[multi->slaves_count];
+	int err;
+	unsigned int cmask, changed;
+	err = snd_pcm_multi_hw_refine_cprepare(pcm, params);
+	if (err < 0)
+		return err;
+	for (k = 0; k < multi->slaves_count; ++k) {
+		err = snd_pcm_multi_hw_refine_sprepare(pcm, k, &sparams[k]);
+		if (err < 0) {
+			SNDERR("Slave PCM #%d not usable", k);
+			return err;
+		}
+	}
+	do {
+		cmask = params->cmask;
+		params->cmask = 0;
+		for (k = 0; k < multi->slaves_count; ++k) {
+			err = snd_pcm_multi_hw_refine_schange(pcm, k, params, &sparams[k]);
+			if (err >= 0)
+				err = snd_pcm_multi_hw_refine_slave(pcm, k, &sparams[k]);
+			if (err < 0) {
+				snd_pcm_multi_hw_refine_cchange(pcm, k, params, &sparams[k]);
+				return err;
+			}
+			err = snd_pcm_multi_hw_refine_cchange(pcm, k, params, &sparams[k]);
+			if (err < 0)
+				return err;
+		}
+		err = snd_pcm_hw_refine_soft(pcm, params);
+		changed = params->cmask;
+		params->cmask |= cmask;
+		if (err < 0)
+			return err;
+	} while (changed);
+	return 0;
+}
+
+static int snd_pcm_multi_hw_params_slave(snd_pcm_t *pcm,
+					 unsigned int slave_idx,
+					 snd_pcm_hw_params_t *sparams)
+{
+	snd_pcm_multi_t *multi = pcm->private_data;
+	snd_pcm_t *slave = multi->slaves[slave_idx].pcm;
+	int err = snd_pcm_hw_params(slave, sparams);
+	if (err < 0)
+		return err;
+	err = snd_pcm_areas_silence(slave->running_areas, 0, slave->channels, slave->buffer_size, slave->format);
+	if (err < 0)
+		return err;
+	if (slave->stopped_areas) {
+		err = snd_pcm_areas_silence(slave->stopped_areas, 0, slave->channels, slave->buffer_size, slave->format);
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+/* reset links to the normal state
+ * slave #0 = trigger master
+ * slave #1-(N-1) = trigger slaves, linked is set to #0
+ */
+static void reset_links(snd_pcm_multi_t *multi)
+{
+	unsigned int i;
+
+	for (i = 0; i < multi->slaves_count; ++i) {
+		if (multi->slaves[i].linked)
+			snd_pcm_unlink(multi->slaves[i].linked);
+		multi->slaves[0].linked = NULL;
+		if (! i)
+			continue;
+		if (snd_pcm_link(multi->slaves[0].pcm, multi->slaves[i].pcm) >= 0)
+			multi->slaves[i].linked = multi->slaves[0].pcm;
+	}
+}
+
+static int snd_pcm_multi_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	snd_pcm_multi_t *multi = pcm->private_data;
+	unsigned int i;
+	snd_pcm_hw_params_t sparams[multi->slaves_count];
+	int err;
+	for (i = 0; i < multi->slaves_count; ++i) {
+		err = snd_pcm_multi_hw_refine_sprepare(pcm, i, &sparams[i]);
+		assert(err >= 0);
+		err = snd_pcm_multi_hw_refine_schange(pcm, i, params, &sparams[i]);
+		assert(err >= 0);
+		err = snd_pcm_multi_hw_params_slave(pcm, i, &sparams[i]);
+		if (err < 0) {
+			snd_pcm_multi_hw_refine_cchange(pcm, i, params, &sparams[i]);
+			return err;
+		}
+	}
+	reset_links(multi);
+	return 0;
+}
+
+static int snd_pcm_multi_hw_free(snd_pcm_t *pcm)
+{
+	snd_pcm_multi_t *multi = pcm->private_data;
+	unsigned int i;
+	int err = 0;
+	for (i = 0; i < multi->slaves_count; ++i) {
+		snd_pcm_t *slave = multi->slaves[i].pcm;
+		int e = snd_pcm_hw_free(slave);
+		if (e < 0)
+			err = e;
+		if (!multi->slaves[i].linked)
+			continue;
+		e = snd_pcm_unlink(slave);
+		if (e < 0)
+			err = e;
+		multi->slaves[i].linked = NULL;
+	}
+	return err;
+}
+
+static int snd_pcm_multi_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t *params)
+{
+	snd_pcm_multi_t *multi = pcm->private_data;
+	unsigned int i;
+	int err;
+	for (i = 0; i < multi->slaves_count; ++i) {
+		snd_pcm_t *slave = multi->slaves[i].pcm;
+		err = snd_pcm_sw_params(slave, params);
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+static int snd_pcm_multi_status(snd_pcm_t *pcm, snd_pcm_status_t *status)
+{
+	snd_pcm_multi_t *multi = pcm->private_data;
+	snd_pcm_t *slave = multi->slaves[multi->master_slave].pcm;
+	return snd_pcm_status(slave, status);
+}
+
+static snd_pcm_state_t snd_pcm_multi_state(snd_pcm_t *pcm)
+{
+	snd_pcm_multi_t *multi = pcm->private_data;
+	snd_pcm_t *slave = multi->slaves[multi->master_slave].pcm;
+	return snd_pcm_state(slave);
+}
+
+static int snd_pcm_multi_hwsync(snd_pcm_t *pcm)
+{
+	snd_pcm_multi_t *multi = pcm->private_data;
+	snd_pcm_t *slave = multi->slaves[multi->master_slave].pcm;
+	return snd_pcm_hwsync(slave);
+}
+
+static int snd_pcm_multi_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp)
+{
+	snd_pcm_multi_t *multi = pcm->private_data;
+	snd_pcm_t *slave = multi->slaves[multi->master_slave].pcm;
+	return snd_pcm_delay(slave, delayp);
+}
+
+static snd_pcm_sframes_t snd_pcm_multi_avail_update(snd_pcm_t *pcm)
+{
+	snd_pcm_multi_t *multi = pcm->private_data;
+	snd_pcm_sframes_t ret = LONG_MAX;
+	unsigned int i;
+	for (i = 0; i < multi->slaves_count; ++i) {
+		snd_pcm_sframes_t avail;
+		avail = snd_pcm_avail_update(multi->slaves[i].pcm);
+		if (avail < 0)
+			return avail;
+		if (ret > avail)
+			ret = avail;
+	}
+	return ret;
+}
+
+static int snd_pcm_multi_htimestamp(snd_pcm_t *pcm, snd_pcm_uframes_t *avail,
+				    snd_htimestamp_t *tstamp)
+{
+	snd_pcm_multi_t *multi = pcm->private_data;
+	snd_pcm_t *slave = multi->slaves[multi->master_slave].pcm;
+	return snd_pcm_htimestamp(slave, avail, tstamp);
+}
+
+static int snd_pcm_multi_prepare(snd_pcm_t *pcm)
+{
+	snd_pcm_multi_t *multi = pcm->private_data;
+	int result = 0, err;
+	unsigned int i;
+	for (i = 0; i < multi->slaves_count; ++i) {
+		/* We call prepare to each slave even if it's linked.
+		 * This is to make sure to sync non-mmaped control/status.
+		 */
+		err = snd_pcm_prepare(multi->slaves[i].pcm);
+		if (err < 0)
+			result = err;
+	}
+	return result;
+}
+
+static int snd_pcm_multi_reset(snd_pcm_t *pcm)
+{
+	snd_pcm_multi_t *multi = pcm->private_data;
+	int result = 0, err;
+	unsigned int i;
+	for (i = 0; i < multi->slaves_count; ++i) {
+		/* Reset each slave, as well as in prepare */
+		err = snd_pcm_reset(multi->slaves[i].pcm);
+		if (err < 0) 
+			result = err;
+	}
+	return result;
+}
+
+/* when the first slave PCM is linked, it means that the whole multi
+ * plugin instance is linked manually to another PCM.  in this case,
+ * we need to trigger the master.
+ */
+static int snd_pcm_multi_start(snd_pcm_t *pcm)
+{
+	snd_pcm_multi_t *multi = pcm->private_data;
+	int err = 0;
+	unsigned int i;
+	if (multi->slaves[0].linked)
+		return snd_pcm_start(multi->slaves[0].linked);
+	for (i = 0; i < multi->slaves_count; ++i) {
+		if (multi->slaves[i].linked)
+			continue;
+		err = snd_pcm_start(multi->slaves[i].pcm);
+		if (err < 0)
+			return err;
+	}
+	return err;
+}
+
+static int snd_pcm_multi_drop(snd_pcm_t *pcm)
+{
+	snd_pcm_multi_t *multi = pcm->private_data;
+	int err = 0;
+	unsigned int i;
+	if (multi->slaves[0].linked)
+		return snd_pcm_drop(multi->slaves[0].linked);
+	for (i = 0; i < multi->slaves_count; ++i) {
+		if (multi->slaves[i].linked)
+			continue;
+		err = snd_pcm_drop(multi->slaves[i].pcm);
+		if (err < 0)
+			return err;
+	}
+	return err;
+}
+
+static int snd_pcm_multi_drain(snd_pcm_t *pcm)
+{
+	snd_pcm_multi_t *multi = pcm->private_data;
+	int err = 0;
+	unsigned int i;
+	if (multi->slaves[0].linked)
+		return snd_pcm_drain(multi->slaves[0].linked);
+	for (i = 0; i < multi->slaves_count; ++i) {
+		if (multi->slaves[i].linked)
+			continue;
+		err = snd_pcm_drain(multi->slaves[i].pcm);
+		if (err < 0)
+			return err;
+	}
+	return err;
+}
+
+static int snd_pcm_multi_pause(snd_pcm_t *pcm, int enable)
+{
+	snd_pcm_multi_t *multi = pcm->private_data;
+	int err = 0;
+	unsigned int i;
+	if (multi->slaves[0].linked)
+		return snd_pcm_pause(multi->slaves[0].linked, enable);
+	for (i = 0; i < multi->slaves_count; ++i) {
+		if (multi->slaves[i].linked)
+			continue;
+		err = snd_pcm_pause(multi->slaves[i].pcm, enable);
+		if (err < 0)
+			return err;
+	}
+	return err;
+}
+
+static int snd_pcm_multi_channel_info(snd_pcm_t *pcm, snd_pcm_channel_info_t *info)
+{
+	snd_pcm_multi_t *multi = pcm->private_data;
+	unsigned int channel = info->channel;
+	snd_pcm_multi_channel_t *c = &multi->channels[channel];
+	int err;
+	if (c->slave_idx < 0)
+		return -ENXIO;
+	info->channel = c->slave_channel;
+	err = snd_pcm_channel_info(multi->slaves[c->slave_idx].pcm, info);
+	info->channel = channel;
+	return err;
+}
+
+static snd_pcm_sframes_t snd_pcm_multi_rewind(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
+{
+	snd_pcm_multi_t *multi = pcm->private_data;
+	unsigned int i;
+	snd_pcm_uframes_t pos[multi->slaves_count];
+	memset(pos, 0, sizeof(pos));
+	for (i = 0; i < multi->slaves_count; ++i) {
+		snd_pcm_t *slave_i = multi->slaves[i].pcm;
+		snd_pcm_sframes_t f = snd_pcm_rewind(slave_i, frames);
+		if (f < 0)
+			return f;
+		pos[i] = f;
+		frames = f;
+	}
+	/* Realign the pointers */
+	for (i = 0; i < multi->slaves_count; ++i) {
+		snd_pcm_t *slave_i = multi->slaves[i].pcm;
+		snd_pcm_uframes_t f = pos[i] - frames;
+		snd_pcm_sframes_t result;
+		if (f > 0) {
+			result = INTERNAL(snd_pcm_forward)(slave_i, f);
+			if (result < 0)
+				return result;
+			if ((snd_pcm_uframes_t)result != f)
+				return -EIO;
+		}
+	}
+	return frames;
+}
+
+static snd_pcm_sframes_t snd_pcm_multi_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
+{
+	snd_pcm_multi_t *multi = pcm->private_data;
+	unsigned int i;
+	snd_pcm_uframes_t pos[multi->slaves_count];
+	memset(pos, 0, sizeof(pos));
+	for (i = 0; i < multi->slaves_count; ++i) {
+		snd_pcm_t *slave_i = multi->slaves[i].pcm;
+		snd_pcm_sframes_t f = INTERNAL(snd_pcm_forward)(slave_i, frames);
+		if (f < 0)
+			return f;
+		pos[i] = f;
+		frames = f;
+	}
+	/* Realign the pointers */
+	for (i = 0; i < multi->slaves_count; ++i) {
+		snd_pcm_t *slave_i = multi->slaves[i].pcm;
+		snd_pcm_uframes_t f = pos[i] - frames;
+		snd_pcm_sframes_t result;
+		if (f > 0) {
+			result = snd_pcm_rewind(slave_i, f);
+			if (result < 0)
+				return result;
+			if ((snd_pcm_uframes_t)result != f)
+				return -EIO;
+		}
+	}
+	return frames;
+}
+
+static int snd_pcm_multi_resume(snd_pcm_t *pcm)
+{
+	snd_pcm_multi_t *multi = pcm->private_data;
+	int err = 0;
+	unsigned int i;
+	if (multi->slaves[0].linked)
+		return snd_pcm_resume(multi->slaves[0].linked);
+	for (i = 0; i < multi->slaves_count; ++i) {
+		if (multi->slaves[i].linked)
+			continue;
+		err = snd_pcm_resume(multi->slaves[i].pcm);
+		if (err < 0)
+			return err;
+	}
+	return err;
+}
+
+/* if a multi plugin instance is linked as slaves, every slave PCMs
+ * including the first one has to be relinked to the given master.
+ */
+static int snd_pcm_multi_link_slaves(snd_pcm_t *pcm, snd_pcm_t *master)
+{ 
+	snd_pcm_multi_t *multi = pcm->private_data;
+	unsigned int i;
+	int err;
+
+	for (i = 0; i < multi->slaves_count; ++i) {
+		snd_pcm_unlink(multi->slaves[i].pcm);
+		multi->slaves[i].linked = NULL;
+		err = snd_pcm_link(master, multi->slaves[i].pcm);
+		if (err < 0) {
+			reset_links(multi);
+			return err;
+		}
+		multi->slaves[i].linked = master;
+	}
+	return 0;
+}
+
+/* linking to a multi as a master is easy - simply link to the first
+ * slave element as its own slaves are already linked.
+ */
+static int snd_pcm_multi_link(snd_pcm_t *pcm1, snd_pcm_t *pcm2)
+{
+	snd_pcm_multi_t *multi = pcm1->private_data;
+	if (multi->slaves[0].pcm->fast_ops->link)
+		return multi->slaves[0].pcm->fast_ops->link(multi->slaves[0].pcm, pcm2);
+	return -ENOSYS;
+}
+
+static int snd_pcm_multi_unlink(snd_pcm_t *pcm)
+{
+	snd_pcm_multi_t *multi = pcm->private_data;
+	unsigned int i;
+
+	for (i = 0; i < multi->slaves_count; ++i) {
+		if (multi->slaves[i].linked)
+			snd_pcm_unlink(multi->slaves[i].linked);
+		multi->slaves[0].linked = NULL;
+	}
+	return 0;
+}
+
+static snd_pcm_sframes_t snd_pcm_multi_mmap_commit(snd_pcm_t *pcm,
+						   snd_pcm_uframes_t offset,
+						   snd_pcm_uframes_t size)
+{
+	snd_pcm_multi_t *multi = pcm->private_data;
+	snd_pcm_t *slave;
+	unsigned int i;
+	snd_pcm_sframes_t result;
+
+	for (i = 0; i < multi->slaves_count; ++i) {
+		slave = multi->slaves[i].pcm;
+		result = snd_pcm_mmap_commit(slave, offset, size);
+		if (result < 0)
+			return result;
+		if ((snd_pcm_uframes_t)result != size)
+			return -EIO;
+	}
+	return size;
+}
+
+static int snd_pcm_multi_munmap(snd_pcm_t *pcm)
+{
+	free(pcm->mmap_channels);
+	free(pcm->running_areas);
+	pcm->mmap_channels = NULL;
+	pcm->running_areas = NULL;
+	return 0;
+}
+
+static int snd_pcm_multi_mmap(snd_pcm_t *pcm)
+{
+	snd_pcm_multi_t *multi = pcm->private_data;
+	unsigned int c;
+
+	pcm->mmap_channels = calloc(pcm->channels,
+				    sizeof(pcm->mmap_channels[0]));
+	pcm->running_areas = calloc(pcm->channels,
+				    sizeof(pcm->running_areas[0]));
+	if (!pcm->mmap_channels || !pcm->running_areas) {
+		snd_pcm_multi_munmap(pcm);
+		return -ENOMEM;
+	}
+
+	/* Copy the slave mmapped buffer data */
+	for (c = 0; c < pcm->channels; c++) {
+		snd_pcm_multi_channel_t *chan = &multi->channels[c];
+		snd_pcm_t *slave;
+		if (chan->slave_idx < 0) {
+			snd_pcm_multi_munmap(pcm);
+			return -ENXIO;
+		}
+		slave = multi->slaves[chan->slave_idx].pcm;
+		pcm->mmap_channels[c] =
+			slave->mmap_channels[chan->slave_channel];
+		pcm->mmap_channels[c].channel = c;
+		pcm->running_areas[c] =
+			slave->running_areas[chan->slave_channel];
+	}
+	return 0;
+}
+
+static void snd_pcm_multi_dump(snd_pcm_t *pcm, snd_output_t *out)
+{
+	snd_pcm_multi_t *multi = pcm->private_data;
+	unsigned int k;
+	snd_output_printf(out, "Multi PCM\n");
+	snd_output_printf(out, "  Channel bindings:\n");
+	for (k = 0; k < multi->channels_count; ++k) {
+		snd_pcm_multi_channel_t *c = &multi->channels[k];
+		if (c->slave_idx < 0)
+			continue;
+		snd_output_printf(out, "    %d: slave %d, channel %d\n", 
+			k, c->slave_idx, c->slave_channel);
+	}
+	if (pcm->setup) {
+		snd_output_printf(out, "Its setup is:\n");
+		snd_pcm_dump_setup(pcm, out);
+	}
+	for (k = 0; k < multi->slaves_count; ++k) {
+		snd_output_printf(out, "Slave #%d: ", k);
+		snd_pcm_dump(multi->slaves[k].pcm, out);
+	}
+}
+
+static const snd_pcm_ops_t snd_pcm_multi_ops = {
+	.close = snd_pcm_multi_close,
+	.info = snd_pcm_multi_info,
+	.hw_refine = snd_pcm_multi_hw_refine,
+	.hw_params = snd_pcm_multi_hw_params,
+	.hw_free = snd_pcm_multi_hw_free,
+	.sw_params = snd_pcm_multi_sw_params,
+	.channel_info = snd_pcm_multi_channel_info,
+	.dump = snd_pcm_multi_dump,
+	.nonblock = snd_pcm_multi_nonblock,
+	.async = snd_pcm_multi_async,
+	.mmap = snd_pcm_multi_mmap,
+	.munmap = snd_pcm_multi_munmap,
+};
+
+static const snd_pcm_fast_ops_t snd_pcm_multi_fast_ops = {
+	.status = snd_pcm_multi_status,
+	.state = snd_pcm_multi_state,
+	.hwsync = snd_pcm_multi_hwsync,
+	.delay = snd_pcm_multi_delay,
+	.prepare = snd_pcm_multi_prepare,
+	.reset = snd_pcm_multi_reset,
+	.start = snd_pcm_multi_start,
+	.drop = snd_pcm_multi_drop,
+	.drain = snd_pcm_multi_drain,
+	.pause = snd_pcm_multi_pause,
+	.writei = snd_pcm_mmap_writei,
+	.writen = snd_pcm_mmap_writen,
+	.readi = snd_pcm_mmap_readi,
+	.readn = snd_pcm_mmap_readn,
+	.rewind = snd_pcm_multi_rewind,
+	.forward = snd_pcm_multi_forward,
+	.resume = snd_pcm_multi_resume,
+	.link = snd_pcm_multi_link,
+	.link_slaves = snd_pcm_multi_link_slaves,
+	.unlink = snd_pcm_multi_unlink,
+	.avail_update = snd_pcm_multi_avail_update,
+	.mmap_commit = snd_pcm_multi_mmap_commit,
+	.htimestamp = snd_pcm_multi_htimestamp,
+	.poll_descriptors_count = snd_pcm_multi_poll_descriptors_count,
+	.poll_descriptors = snd_pcm_multi_poll_descriptors,
+	.poll_revents = snd_pcm_multi_poll_revents,
+};
+
+/**
+ * \brief Creates a new Multi PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param slaves_count Count of slaves
+ * \param master_slave Master slave number
+ * \param slaves_pcm Array with slave PCMs
+ * \param schannels_count Array with slave channel counts
+ * \param channels_count Count of channels
+ * \param sidxs Array with channels indexes to slaves
+ * \param schannels Array with slave channels
+ * \param close_slaves When set, the slave PCM handle is closed
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int snd_pcm_multi_open(snd_pcm_t **pcmp, const char *name,
+		       unsigned int slaves_count, unsigned int master_slave,
+		       snd_pcm_t **slaves_pcm, unsigned int *schannels_count,
+		       unsigned int channels_count,
+		       int *sidxs, unsigned int *schannels,
+		       int close_slaves)
+{
+	snd_pcm_t *pcm;
+	snd_pcm_multi_t *multi;
+	unsigned int i;
+	snd_pcm_stream_t stream;
+	char slave_map[64][64] = { { 0 } };
+	int err;
+
+	assert(pcmp);
+	assert(slaves_count > 0 && slaves_pcm && schannels_count);
+	assert(channels_count > 0 && sidxs && schannels);
+	assert(master_slave < slaves_count);
+
+	multi = calloc(1, sizeof(snd_pcm_multi_t));
+	if (!multi) {
+		return -ENOMEM;
+	}
+
+	stream = slaves_pcm[0]->stream;
+	
+	multi->slaves_count = slaves_count;
+	multi->master_slave = master_slave;
+	multi->slaves = calloc(slaves_count, sizeof(*multi->slaves));
+	if (!multi->slaves) {
+		free(multi);
+		return -ENOMEM;
+	}
+	multi->channels_count = channels_count;
+	multi->channels = calloc(channels_count, sizeof(*multi->channels));
+	if (!multi->channels) {
+		free(multi->slaves);
+		free(multi);
+		return -ENOMEM;
+	}
+	for (i = 0; i < slaves_count; ++i) {
+		snd_pcm_multi_slave_t *slave = &multi->slaves[i];
+		assert(slaves_pcm[i]->stream == stream);
+		slave->pcm = slaves_pcm[i];
+		slave->channels_count = schannels_count[i];
+		slave->close_slave = close_slaves;
+	}
+	for (i = 0; i < channels_count; ++i) {
+		snd_pcm_multi_channel_t *bind = &multi->channels[i];
+		assert(sidxs[i] < (int)slaves_count);
+		assert(schannels[i] < schannels_count[sidxs[i]]);
+		bind->slave_idx = sidxs[i];
+		bind->slave_channel = schannels[i];
+		if (sidxs[i] < 0)
+			continue;
+		assert(!slave_map[sidxs[i]][schannels[i]]);
+		slave_map[sidxs[i]][schannels[i]] = 1;
+	}
+	multi->channels_count = channels_count;
+
+	err = snd_pcm_new(&pcm, SND_PCM_TYPE_MULTI, name, stream,
+			  multi->slaves[0].pcm->mode);
+	if (err < 0) {
+		free(multi->slaves);
+		free(multi->channels);
+		free(multi);
+		return err;
+	}
+	pcm->mmap_rw = 1;
+	pcm->mmap_shadow = 1; /* has own mmap method */
+	pcm->ops = &snd_pcm_multi_ops;
+	pcm->fast_ops = &snd_pcm_multi_fast_ops;
+	pcm->private_data = multi;
+	pcm->poll_fd = multi->slaves[master_slave].pcm->poll_fd;
+	pcm->poll_events = multi->slaves[master_slave].pcm->poll_events;
+	pcm->monotonic = multi->slaves[master_slave].pcm->monotonic;
+	snd_pcm_link_hw_ptr(pcm, multi->slaves[master_slave].pcm);
+	snd_pcm_link_appl_ptr(pcm, multi->slaves[master_slave].pcm);
+	*pcmp = pcm;
+	return 0;
+}
+
+/*! \page pcm_plugins
+
+\section pcm_plugins_multi Plugin: Multiple streams to One
+
+This plugin converts multiple streams to one.
+
+\code
+pcm.name {
+        type multi              # Multiple streams conversion PCM
+        slaves {		# Slaves definition
+		ID STR		# Slave PCM name
+		# or
+		ID {
+			pcm STR		# Slave PCM name
+			# or
+			pcm { } 	# Slave PCM definition
+			channels INT	# Slave channels
+		}
+        }
+	bindings {		# Bindings table
+		N {
+			slave STR	# Slave key
+			channel INT	# Slave channel
+		}
+	}
+	[master INT]		# Define the master slave
+}
+\endcode
+
+For example, to bind two PCM streams with two-channel stereo (hw:0,0 and
+hw:0,1) as one 4-channel stereo PCM stream, define like this:
+\code
+pcm.quad {
+	type multi
+
+	slaves.a.pcm "hw:0,0"
+	slaves.a.channels 2
+	slaves.b.pcm "hw:0,1"
+	slaves.b.channels 2
+
+	bindings.0.slave a
+	bindings.0.channel 0
+	bindings.1.slave a
+	bindings.1.channel 1
+	bindings.2.slave b
+	bindings.2.channel 0
+	bindings.3.slave b
+	bindings.3.channel 1
+}
+\endcode
+Note that the resultant pcm "quad" is not in the interleaved format
+but in the "complex" format.  Hence, it's not accessible by applications
+which can handle only the interleaved (or the non-interleaved) format.
+In such a case, wrap this PCM with \ref pcm_plugins_route "route" or
+\ref pcm_plugins_plug "plug" plugin.
+\code
+pcm.quad2 {
+	type route
+	slave.pcm "quad"
+	ttable.0.0 1
+	ttable.1.1 1
+	ttable.2.2 1
+	ttable.3.3 1
+}
+\endcode
+
+\subsection pcm_plugins_multi_funcref Function reference
+
+<UL>
+  <LI>snd_pcm_multi_open()
+  <LI>_snd_pcm_multi_open()
+</UL>
+
+*/
+
+/**
+ * \brief Creates a new Multi PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param root Root configuration node
+ * \param conf Configuration node with Multi PCM description
+ * \param stream Stream type
+ * \param mode Stream mode
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int _snd_pcm_multi_open(snd_pcm_t **pcmp, const char *name,
+			snd_config_t *root, snd_config_t *conf,
+			snd_pcm_stream_t stream, int mode)
+{
+	snd_config_iterator_t i, inext, j, jnext;
+	snd_config_t *slaves = NULL;
+	snd_config_t *bindings = NULL;
+	int err;
+	unsigned int idx;
+	const char **slaves_id = NULL;
+	snd_config_t **slaves_conf = NULL;
+	snd_pcm_t **slaves_pcm = NULL;
+	unsigned int *slaves_channels = NULL;
+	int *channels_sidx = NULL;
+	unsigned int *channels_schannel = NULL;
+	unsigned int slaves_count = 0;
+	long master_slave = 0;
+	unsigned int channels_count = 0;
+	snd_config_for_each(i, inext, conf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (snd_pcm_conf_generic_id(id))
+			continue;
+		if (strcmp(id, "slaves") == 0) {
+			if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) {
+				SNDERR("Invalid type for %s", id);
+				return -EINVAL;
+			}
+			slaves = n;
+			continue;
+		}
+		if (strcmp(id, "bindings") == 0) {
+			if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) {
+				SNDERR("Invalid type for %s", id);
+				return -EINVAL;
+			}
+			bindings = n;
+			continue;
+		}
+		if (strcmp(id, "master") == 0) {
+			if (snd_config_get_integer(n, &master_slave) < 0) {
+				SNDERR("Invalid type for %s", id);
+				return -EINVAL;
+			}
+			continue;
+		}
+		SNDERR("Unknown field %s", id);
+		return -EINVAL;
+	}
+	if (!slaves) {
+		SNDERR("slaves is not defined");
+		return -EINVAL;
+	}
+	if (!bindings) {
+		SNDERR("bindings is not defined");
+		return -EINVAL;
+	}
+	snd_config_for_each(i, inext, slaves) {
+		++slaves_count;
+	}
+	if (master_slave < 0 || master_slave >= (long)slaves_count) {
+		SNDERR("Master slave is out of range (0-%u)\n", slaves_count-1);
+		return -EINVAL;
+	}
+	snd_config_for_each(i, inext, bindings) {
+		long cchannel;
+		snd_config_t *m = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(m, &id) < 0)
+			continue;
+		err = safe_strtol(id, &cchannel);
+		if (err < 0 || cchannel < 0) {
+			SNDERR("Invalid channel number: %s", id);
+			return -EINVAL;
+		}
+		if ((unsigned long)cchannel >= channels_count)
+			channels_count = cchannel + 1;
+	}
+	if (channels_count == 0) {
+		SNDERR("No channels defined");
+		return -EINVAL;
+	}
+	slaves_id = calloc(slaves_count, sizeof(*slaves_id));
+	slaves_conf = calloc(slaves_count, sizeof(*slaves_conf));
+	slaves_pcm = calloc(slaves_count, sizeof(*slaves_pcm));
+	slaves_channels = calloc(slaves_count, sizeof(*slaves_channels));
+	channels_sidx = calloc(channels_count, sizeof(*channels_sidx));
+	channels_schannel = calloc(channels_count, sizeof(*channels_schannel));
+	if (!slaves_id || !slaves_conf || !slaves_pcm || !slaves_channels ||
+	    !channels_sidx || !channels_schannel) {
+		err = -ENOMEM;
+		goto _free;
+	}
+	idx = 0;
+	for (idx = 0; idx < channels_count; ++idx)
+		channels_sidx[idx] = -1;
+	idx = 0;
+	snd_config_for_each(i, inext, slaves) {
+		snd_config_t *m = snd_config_iterator_entry(i);
+		const char *id;
+		int channels;
+		if (snd_config_get_id(m, &id) < 0)
+			continue;
+		slaves_id[idx] = id;
+		err = snd_pcm_slave_conf(root, m, &slaves_conf[idx], 1,
+					 SND_PCM_HW_PARAM_CHANNELS, SCONF_MANDATORY, &channels);
+		if (err < 0)
+			goto _free;
+		slaves_channels[idx] = channels;
+		++idx;
+	}
+
+	snd_config_for_each(i, inext, bindings) {
+		snd_config_t *m = snd_config_iterator_entry(i);
+		long cchannel = -1;
+		long schannel = -1;
+		int slave = -1;
+		long val;
+		const char *str;
+		const char *id;
+		if (snd_config_get_id(m, &id) < 0)
+			continue;
+		err = safe_strtol(id, &cchannel);
+		if (err < 0 || cchannel < 0) {
+			SNDERR("Invalid channel number: %s", id);
+			err = -EINVAL;
+			goto _free;
+		}
+		snd_config_for_each(j, jnext, m) {
+			snd_config_t *n = snd_config_iterator_entry(j);
+			const char *id;
+			if (snd_config_get_id(n, &id) < 0)
+				continue;
+			if (strcmp(id, "comment") == 0)
+				continue;
+			if (strcmp(id, "slave") == 0) {
+				char buf[32];
+				unsigned int k;
+				err = snd_config_get_string(n, &str);
+				if (err < 0) {
+					err = snd_config_get_integer(n, &val);
+					if (err < 0) {
+						SNDERR("Invalid value for %s", id);
+						goto _free;
+					}
+					sprintf(buf, "%ld", val);
+					str = buf;
+				}
+				for (k = 0; k < slaves_count; ++k) {
+					if (strcmp(slaves_id[k], str) == 0)
+						slave = k;
+				}
+				continue;
+			}
+			if (strcmp(id, "channel") == 0) {
+				err = snd_config_get_integer(n, &schannel);
+				if (err < 0) {
+					SNDERR("Invalid type for %s", id);
+					goto _free;
+				}
+				continue;
+			}
+			SNDERR("Unknown field %s", id);
+			err = -EINVAL;
+			goto _free;
+		}
+		if (slave < 0 || (unsigned int)slave >= slaves_count) {
+			SNDERR("Invalid or missing sidx for channel %s", id);
+			err = -EINVAL;
+			goto _free;
+		}
+		if (schannel < 0 || 
+		    (unsigned int) schannel >= slaves_channels[slave]) {
+			SNDERR("Invalid or missing schannel for channel %s", id);
+			err = -EINVAL;
+			goto _free;
+		}
+		channels_sidx[cchannel] = slave;
+		channels_schannel[cchannel] = schannel;
+	}
+	
+	for (idx = 0; idx < slaves_count; ++idx) {
+		err = snd_pcm_open_slave(&slaves_pcm[idx], root,
+					 slaves_conf[idx], stream, mode,
+					 conf);
+		if (err < 0)
+			goto _free;
+		snd_config_delete(slaves_conf[idx]);
+		slaves_conf[idx] = NULL;
+	}
+	err = snd_pcm_multi_open(pcmp, name, slaves_count, master_slave,
+				 slaves_pcm, slaves_channels,
+				 channels_count,
+				 channels_sidx, channels_schannel,
+				 1);
+_free:
+	if (err < 0) {
+		for (idx = 0; idx < slaves_count; ++idx) {
+			if (slaves_pcm[idx])
+				snd_pcm_close(slaves_pcm[idx]);
+		}
+	}
+	if (slaves_conf) {
+		for (idx = 0; idx < slaves_count; ++idx) {
+			if (slaves_conf[idx])
+				snd_config_delete(slaves_conf[idx]);
+		}
+		free(slaves_conf);
+	}
+	free(slaves_pcm);
+	free(slaves_channels);
+	free(channels_sidx);
+	free(channels_schannel);
+	free(slaves_id);
+	return err;
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(_snd_pcm_multi_open, SND_PCM_DLSYM_VERSION);
+#endif
diff --git a/src/pcm/pcm_null.c b/src/pcm/pcm_null.c
new file mode 100644
index 0000000..692254a
--- /dev/null
+++ b/src/pcm/pcm_null.c
@@ -0,0 +1,432 @@
+/**
+ * \file pcm/pcm_null.c
+ * \ingroup PCM_Plugins
+ * \brief PCM Null Plugin Interface
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \date 2000-2001
+ */
+/*
+ *  PCM - Null plugin
+ *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+#include <byteswap.h>
+#include <limits.h>
+#include <sys/shm.h>
+#include "pcm_local.h"
+#include "pcm_plugin.h"
+
+#ifndef PIC
+/* entry for static linking */
+const char *_snd_module_pcm_null = "";
+#endif
+
+#ifndef DOC_HIDDEN
+typedef struct {
+	snd_htimestamp_t trigger_tstamp;
+	snd_pcm_state_t state;
+	snd_pcm_uframes_t appl_ptr;
+	snd_pcm_uframes_t hw_ptr;
+	int poll_fd;
+} snd_pcm_null_t;
+#endif
+
+static int snd_pcm_null_close(snd_pcm_t *pcm)
+{
+	snd_pcm_null_t *null = pcm->private_data;
+	close(null->poll_fd);
+	free(null);
+	return 0;
+}
+
+static int snd_pcm_null_nonblock(snd_pcm_t *pcm ATTRIBUTE_UNUSED, int nonblock ATTRIBUTE_UNUSED)
+{
+	return 0;
+}
+
+static int snd_pcm_null_async(snd_pcm_t *pcm ATTRIBUTE_UNUSED, int sig ATTRIBUTE_UNUSED, pid_t pid ATTRIBUTE_UNUSED)
+{
+	return -ENOSYS;
+}
+
+static int snd_pcm_null_info(snd_pcm_t *pcm, snd_pcm_info_t * info)
+{
+	memset(info, 0, sizeof(*info));
+	info->stream = pcm->stream;
+	info->card = -1;
+	if (pcm->name) {
+		strncpy((char *)info->id, pcm->name, sizeof(info->id));
+		strncpy((char *)info->name, pcm->name, sizeof(info->name));
+		strncpy((char *)info->subname, pcm->name, sizeof(info->subname));
+	}
+	info->subdevices_count = 1;
+	return 0;
+}
+
+static int snd_pcm_null_status(snd_pcm_t *pcm, snd_pcm_status_t * status)
+{
+	snd_pcm_null_t *null = pcm->private_data;
+	memset(status, 0, sizeof(*status));
+	status->state = null->state;
+	status->trigger_tstamp = null->trigger_tstamp;
+	gettimestamp(&status->tstamp, pcm->monotonic);
+	status->avail = pcm->buffer_size;
+	status->avail_max = status->avail;
+	return 0;
+}
+
+static snd_pcm_state_t snd_pcm_null_state(snd_pcm_t *pcm)
+{
+	snd_pcm_null_t *null = pcm->private_data;
+	return null->state;
+}
+
+static int snd_pcm_null_hwsync(snd_pcm_t *pcm ATTRIBUTE_UNUSED)
+{
+	return 0;
+}
+
+static int snd_pcm_null_delay(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_sframes_t *delayp)
+{
+	*delayp = 0;
+	return 0;
+}
+
+static int snd_pcm_null_prepare(snd_pcm_t *pcm)
+{
+	snd_pcm_null_t *null = pcm->private_data;
+	null->state = SND_PCM_STATE_PREPARED;
+	*pcm->appl.ptr = 0;
+	*pcm->hw.ptr = 0;
+	return 0;
+}
+
+static int snd_pcm_null_reset(snd_pcm_t *pcm)
+{
+	*pcm->appl.ptr = 0;
+	*pcm->hw.ptr = 0;
+	return 0;
+}
+
+static int snd_pcm_null_start(snd_pcm_t *pcm)
+{
+	snd_pcm_null_t *null = pcm->private_data;
+	assert(null->state == SND_PCM_STATE_PREPARED);
+	null->state = SND_PCM_STATE_RUNNING;
+	if (pcm->stream == SND_PCM_STREAM_CAPTURE)
+		*pcm->hw.ptr = *pcm->appl.ptr + pcm->buffer_size;
+	else
+		*pcm->hw.ptr = *pcm->appl.ptr;
+	return 0;
+}
+
+static int snd_pcm_null_drop(snd_pcm_t *pcm)
+{
+	snd_pcm_null_t *null = pcm->private_data;
+	assert(null->state != SND_PCM_STATE_OPEN);
+	null->state = SND_PCM_STATE_SETUP;
+	return 0;
+}
+
+static int snd_pcm_null_drain(snd_pcm_t *pcm)
+{
+	snd_pcm_null_t *null = pcm->private_data;
+	assert(null->state != SND_PCM_STATE_OPEN);
+	null->state = SND_PCM_STATE_SETUP;
+	return 0;
+}
+
+static int snd_pcm_null_pause(snd_pcm_t *pcm, int enable)
+{
+	snd_pcm_null_t *null = pcm->private_data;
+	if (enable) {
+		if (null->state != SND_PCM_STATE_RUNNING)
+			return -EBADFD;
+		null->state = SND_PCM_STATE_PAUSED;
+	} else {
+		if (null->state != SND_PCM_STATE_PAUSED)
+			return -EBADFD;
+		null->state = SND_PCM_STATE_RUNNING;
+	}
+	return 0;
+}
+
+static snd_pcm_sframes_t snd_pcm_null_rewind(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
+{
+	snd_pcm_null_t *null = pcm->private_data;
+	switch (null->state) {
+	case SND_PCM_STATE_RUNNING:
+		snd_pcm_mmap_hw_backward(pcm, frames);
+		/* Fall through */
+	case SND_PCM_STATE_PREPARED:
+		snd_pcm_mmap_appl_backward(pcm, frames);
+		return frames;
+	default:
+		return -EBADFD;
+	}
+}
+
+static snd_pcm_sframes_t snd_pcm_null_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
+{
+	snd_pcm_null_t *null = pcm->private_data;
+	switch (null->state) {
+	case SND_PCM_STATE_RUNNING:
+		snd_pcm_mmap_hw_forward(pcm, frames);
+		/* Fall through */
+	case SND_PCM_STATE_PREPARED:
+		snd_pcm_mmap_appl_forward(pcm, frames);
+		return frames;
+	default:
+		return -EBADFD;
+	}
+}
+
+static int snd_pcm_null_resume(snd_pcm_t *pcm ATTRIBUTE_UNUSED)
+{
+	return 0;
+}
+
+static snd_pcm_sframes_t snd_pcm_null_xfer_areas(snd_pcm_t *pcm,
+						 const snd_pcm_channel_area_t *areas ATTRIBUTE_UNUSED,
+						 snd_pcm_uframes_t offset ATTRIBUTE_UNUSED,
+						 snd_pcm_uframes_t size)
+{
+	snd_pcm_mmap_appl_forward(pcm, size);
+	snd_pcm_mmap_hw_forward(pcm, size);
+	return size;
+}
+
+static snd_pcm_sframes_t snd_pcm_null_writei(snd_pcm_t *pcm, const void *buffer ATTRIBUTE_UNUSED, snd_pcm_uframes_t size)
+{
+	return snd_pcm_write_areas(pcm, NULL, 0, size, snd_pcm_null_xfer_areas);
+}
+
+static snd_pcm_sframes_t snd_pcm_null_writen(snd_pcm_t *pcm, void **bufs ATTRIBUTE_UNUSED, snd_pcm_uframes_t size)
+{
+	return snd_pcm_write_areas(pcm, NULL, 0, size, snd_pcm_null_xfer_areas);
+}
+
+static snd_pcm_sframes_t snd_pcm_null_readi(snd_pcm_t *pcm, void *buffer ATTRIBUTE_UNUSED, snd_pcm_uframes_t size)
+{
+	return snd_pcm_read_areas(pcm, NULL, 0, size, snd_pcm_null_xfer_areas);
+}
+
+static snd_pcm_sframes_t snd_pcm_null_readn(snd_pcm_t *pcm, void **bufs ATTRIBUTE_UNUSED, snd_pcm_uframes_t size)
+{
+	return snd_pcm_read_areas(pcm, NULL, 0, size, snd_pcm_null_xfer_areas);
+}
+
+static snd_pcm_sframes_t snd_pcm_null_mmap_commit(snd_pcm_t *pcm,
+						  snd_pcm_uframes_t offset ATTRIBUTE_UNUSED,
+						  snd_pcm_uframes_t size)
+{
+	return snd_pcm_null_forward(pcm, size);
+}
+
+static snd_pcm_sframes_t snd_pcm_null_avail_update(snd_pcm_t *pcm)
+{
+	return pcm->buffer_size;
+}
+
+static int snd_pcm_null_hw_refine(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params)
+{
+	int err = snd_pcm_hw_refine_soft(pcm, params);
+	params->info = SND_PCM_INFO_MMAP | SND_PCM_INFO_MMAP_VALID |
+		       SND_PCM_INFO_RESUME | SND_PCM_INFO_PAUSE;
+	params->fifo_size = 0;
+	return err;
+}
+
+static int snd_pcm_null_hw_params(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t * params ATTRIBUTE_UNUSED)
+{
+	return 0;
+}
+
+static int snd_pcm_null_hw_free(snd_pcm_t *pcm ATTRIBUTE_UNUSED)
+{
+	return 0;
+}
+
+static int snd_pcm_null_sw_params(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_sw_params_t * params ATTRIBUTE_UNUSED)
+{
+	return 0;
+}
+
+static void snd_pcm_null_dump(snd_pcm_t *pcm, snd_output_t *out)
+{
+	snd_output_printf(out, "Null PCM\n");
+	if (pcm->setup) {
+		snd_output_printf(out, "Its setup is:\n");
+		snd_pcm_dump_setup(pcm, out);
+	}
+}
+
+static const snd_pcm_ops_t snd_pcm_null_ops = {
+	.close = snd_pcm_null_close,
+	.info = snd_pcm_null_info,
+	.hw_refine = snd_pcm_null_hw_refine,
+	.hw_params = snd_pcm_null_hw_params,
+	.hw_free = snd_pcm_null_hw_free,
+	.sw_params = snd_pcm_null_sw_params,
+	.channel_info = snd_pcm_generic_channel_info,
+	.dump = snd_pcm_null_dump,
+	.nonblock = snd_pcm_null_nonblock,
+	.async = snd_pcm_null_async,
+	.mmap = snd_pcm_generic_mmap,
+	.munmap = snd_pcm_generic_munmap,
+};
+
+static const snd_pcm_fast_ops_t snd_pcm_null_fast_ops = {
+	.status = snd_pcm_null_status,
+	.state = snd_pcm_null_state,
+	.hwsync = snd_pcm_null_hwsync,
+	.delay = snd_pcm_null_delay,
+	.prepare = snd_pcm_null_prepare,
+	.reset = snd_pcm_null_reset,
+	.start = snd_pcm_null_start,
+	.drop = snd_pcm_null_drop,
+	.drain = snd_pcm_null_drain,
+	.pause = snd_pcm_null_pause,
+	.rewind = snd_pcm_null_rewind,
+	.forward = snd_pcm_null_forward,
+	.resume = snd_pcm_null_resume,
+	.writei = snd_pcm_null_writei,
+	.writen = snd_pcm_null_writen,
+	.readi = snd_pcm_null_readi,
+	.readn = snd_pcm_null_readn,
+	.avail_update = snd_pcm_null_avail_update,
+	.mmap_commit = snd_pcm_null_mmap_commit,
+	.htimestamp = snd_pcm_generic_real_htimestamp,
+};
+
+/**
+ * \brief Creates a new null PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param stream Stream type
+ * \param mode Stream mode
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int snd_pcm_null_open(snd_pcm_t **pcmp, const char *name, snd_pcm_stream_t stream, int mode)
+{
+	snd_pcm_t *pcm;
+	snd_pcm_null_t *null;
+	int fd;
+	int err;
+	assert(pcmp);
+	if (stream == SND_PCM_STREAM_PLAYBACK) {
+		fd = open("/dev/null", O_WRONLY);
+		if (fd < 0) {
+			SYSERR("Cannot open /dev/null");
+			return -errno;
+		}
+	} else {
+		fd = open("/dev/full", O_RDONLY);
+		if (fd < 0) {
+			SYSERR("Cannot open /dev/full");
+			return -errno;
+		}
+	}
+	null = calloc(1, sizeof(snd_pcm_null_t));
+	if (!null) {
+		close(fd);
+		return -ENOMEM;
+	}
+	null->poll_fd = fd;
+	null->state = SND_PCM_STATE_OPEN;
+	
+	err = snd_pcm_new(&pcm, SND_PCM_TYPE_NULL, name, stream, mode);
+	if (err < 0) {
+		close(fd);
+		free(null);
+		return err;
+	}
+	pcm->ops = &snd_pcm_null_ops;
+	pcm->fast_ops = &snd_pcm_null_fast_ops;
+	pcm->private_data = null;
+	pcm->poll_fd = fd;
+	pcm->poll_events = stream == SND_PCM_STREAM_PLAYBACK ? POLLOUT : POLLIN;
+	snd_pcm_set_hw_ptr(pcm, &null->hw_ptr, -1, 0);
+	snd_pcm_set_appl_ptr(pcm, &null->appl_ptr, -1, 0);
+	*pcmp = pcm;
+
+	return 0;
+}
+
+/*! \page pcm_plugins
+
+\section pcm_plugins_null Plugin: Null
+
+This plugin discards contents of a PCM stream or creates a stream with zero
+samples.
+
+Note: This implementation uses devices /dev/null (playback, must be writable)
+and /dev/full (capture, must be readable).
+
+\code
+pcm.name {
+        type null               # Null PCM
+}
+\endcode
+
+\subsection pcm_plugins_null_funcref Function reference
+
+<UL>
+  <LI>snd_pcm_null_open()
+  <LI>_snd_pcm_null_open()
+</UL>
+
+*/
+
+/**
+ * \brief Creates a new Null PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param root Root configuration node
+ * \param conf Configuration node with Null PCM description
+ * \param stream Stream type
+ * \param mode Stream mode
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int _snd_pcm_null_open(snd_pcm_t **pcmp, const char *name,
+		       snd_config_t *root ATTRIBUTE_UNUSED, snd_config_t *conf, 
+		       snd_pcm_stream_t stream, int mode)
+{
+	snd_config_iterator_t i, next;
+	snd_config_for_each(i, next, conf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (snd_pcm_conf_generic_id(id))
+			continue;
+		SNDERR("Unknown field %s", id);
+		return -EINVAL;
+	}
+	return snd_pcm_null_open(pcmp, name, stream, mode);
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(_snd_pcm_null_open, SND_PCM_DLSYM_VERSION);
+#endif
diff --git a/src/pcm/pcm_params.c b/src/pcm/pcm_params.c
new file mode 100644
index 0000000..b085d42
--- /dev/null
+++ b/src/pcm/pcm_params.c
@@ -0,0 +1,2372 @@
+/*
+ *  PCM - Params functions
+ *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+#include "pcm_local.h"
+
+#ifndef NDEBUG
+/*
+ * dump hw_params when $LIBASOUND_DEBUG is set to >= 1
+ */
+static void dump_hw_params(snd_pcm_hw_params_t *params, const char *type,
+			   snd_pcm_hw_param_t var, unsigned int val, int err)
+{
+	const char *verbose = getenv("LIBASOUND_DEBUG");
+	snd_output_t *out;
+
+	if (! verbose || ! *verbose || atoi(verbose) < 1)
+		return;
+	if (snd_output_stdio_attach(&out, stderr, 0) < 0)
+		return;
+	fprintf(stderr, "ALSA ERROR hw_params: %s (%s)\n",
+		type, snd_pcm_hw_param_name(var));
+	fprintf(stderr, "           value = ");
+	switch (var) {
+	case SND_PCM_HW_PARAM_ACCESS:
+		fprintf(stderr, "%s", snd_pcm_access_name(val));
+		break;
+	case SND_PCM_HW_PARAM_FORMAT:
+		fprintf(stderr, "%s", snd_pcm_format_name(val));
+		break;
+	case SND_PCM_HW_PARAM_SUBFORMAT:
+		fprintf(stderr, "%s", snd_pcm_subformat_name(val));
+		break;
+	default:
+		fprintf(stderr, "%u", val);
+	}
+	fprintf(stderr, " : %s\n", snd_strerror(err));
+	snd_pcm_hw_params_dump(params, out);
+	snd_output_close(out);
+}
+#else
+static inline void dump_hw_params(snd_pcm_hw_params_t *params, const char *type,
+				  snd_pcm_hw_param_t var, unsigned int val, int err)
+{
+}
+#endif
+
+static inline int hw_is_mask(snd_pcm_hw_param_t var)
+{
+#if SND_PCM_HW_PARAM_FIRST_MASK == 0
+	return var <= SND_PCM_HW_PARAM_LAST_MASK;
+#else
+	return var >= SND_PCM_HW_PARAM_FIRST_MASK &&
+		var <= SND_PCM_HW_PARAM_LAST_MASK;
+#endif
+}
+
+static inline int hw_is_interval(snd_pcm_hw_param_t var)
+{
+	return var >= SND_PCM_HW_PARAM_FIRST_INTERVAL &&
+		var <= SND_PCM_HW_PARAM_LAST_INTERVAL;
+}
+
+#define hw_param_mask(params,var) \
+	&((params)->masks[(var) - SND_PCM_HW_PARAM_FIRST_MASK])
+
+#define hw_param_interval(params,var) \
+	&((params)->intervals[(var) - SND_PCM_HW_PARAM_FIRST_INTERVAL])
+
+#define hw_param_mask_c hw_param_mask
+#define hw_param_interval_c hw_param_interval
+
+static void _snd_pcm_hw_param_any(snd_pcm_hw_params_t *params, snd_pcm_hw_param_t var)
+{
+	if (hw_is_mask(var)) {
+		snd_mask_any(hw_param_mask(params, var));
+		params->cmask |= 1 << var;
+		params->rmask |= 1 << var;
+		return;
+	}
+	if (hw_is_interval(var)) {
+		snd_interval_any(hw_param_interval(params, var));
+		params->cmask |= 1 << var;
+		params->rmask |= 1 << var;
+		return;
+	}
+	assert(0);
+}
+
+int snd_pcm_hw_param_any(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
+			 snd_pcm_hw_param_t var)
+{
+	_snd_pcm_hw_param_any(params, var);
+	return snd_pcm_hw_refine(pcm, params);
+}
+
+void _snd_pcm_hw_params_any(snd_pcm_hw_params_t *params)
+{
+	unsigned int k;
+	memset(params, 0, sizeof(*params));
+	for (k = SND_PCM_HW_PARAM_FIRST_MASK; k <= SND_PCM_HW_PARAM_LAST_MASK; k++)
+		_snd_pcm_hw_param_any(params, k);
+	for (k = SND_PCM_HW_PARAM_FIRST_INTERVAL; k <= SND_PCM_HW_PARAM_LAST_INTERVAL; k++)
+		_snd_pcm_hw_param_any(params, k);
+	params->rmask = ~0U;
+	params->cmask = 0;
+	params->info = ~0U;
+}
+
+/* Return the value for field PAR if it's fixed in configuration space 
+   defined by PARAMS. Return -EINVAL otherwise
+*/
+int snd_pcm_hw_param_get(const snd_pcm_hw_params_t *params, snd_pcm_hw_param_t var,
+			 unsigned int *val, int *dir)
+{
+	if (hw_is_mask(var)) {
+		const snd_mask_t *mask = hw_param_mask_c(params, var);
+		if (snd_mask_empty(mask) || !snd_mask_single(mask))
+			return -EINVAL;
+		if (dir)
+			*dir = 0;
+		if (val)
+			*val = snd_mask_value(mask);
+		return 0;
+	} else if (hw_is_interval(var)) {
+		const snd_interval_t *i = hw_param_interval_c(params, var);
+		if (snd_interval_empty(i) || !snd_interval_single(i))
+			return -EINVAL;
+		if (dir)
+			*dir = i->openmin;
+		if (val)
+			*val = snd_interval_value(i);
+		return 0;
+	}
+	assert(0);
+	return -EINVAL;
+}
+
+/* Return the minimum value for field PAR. */
+int snd_pcm_hw_param_get_min(const snd_pcm_hw_params_t *params, snd_pcm_hw_param_t var,
+			     unsigned int *val, int *dir)
+{
+	if (hw_is_mask(var)) {
+		const snd_mask_t *m = hw_param_mask_c(params, var);
+		assert(!snd_mask_empty(m));
+		if (dir)
+			*dir = 0;
+		if (val)
+			*val = snd_mask_min(m);
+		return 0;
+	} else if (hw_is_interval(var)) {
+		const snd_interval_t *i = hw_param_interval_c(params, var);
+		assert(!snd_interval_empty(i));
+		if (dir)
+			*dir = i->openmin;
+		if (val)
+			*val = snd_interval_min(i);
+		return 0;
+	}
+	assert(0);
+	return 0;
+}
+
+/* Return the maximum value for field PAR. */
+int snd_pcm_hw_param_get_max(const snd_pcm_hw_params_t *params, snd_pcm_hw_param_t var,
+			     unsigned int *val, int *dir)
+{
+	if (hw_is_mask(var)) {
+		const snd_mask_t *m = hw_param_mask_c(params, var);
+		assert(!snd_mask_empty(m));
+		if (dir)
+			*dir = 0;
+		if (val)
+			*val = snd_mask_max(m);
+		return 0;
+	} else if (hw_is_interval(var)) {
+		const snd_interval_t *i = hw_param_interval_c(params, var);
+		assert(!snd_interval_empty(i));
+		if (dir)
+			*dir = - (int) i->openmax;
+		if (val)
+			*val = snd_interval_max(i);
+		return 0;
+	}
+	assert(0);
+	return 0;
+}
+
+/* Return the mask for field PAR.
+   This function can be called only for SND_PCM_HW_PARAM_ACCESS,
+   SND_PCM_HW_PARAM_FORMAT, SND_PCM_HW_PARAM_SUBFORMAT. */
+const snd_mask_t *snd_pcm_hw_param_get_mask(const snd_pcm_hw_params_t *params,
+					   snd_pcm_hw_param_t var)
+{
+	assert(hw_is_mask(var));
+	return hw_param_mask_c(params, var);
+}
+
+/* Return the interval for field PAR.
+   This function cannot be called for SND_PCM_HW_PARAM_ACCESS,
+   SND_PCM_HW_PARAM_FORMAT, SND_PCM_HW_PARAM_SUBFORMAT. */
+const snd_interval_t *snd_pcm_hw_param_get_interval(const snd_pcm_hw_params_t *params,
+						  snd_pcm_hw_param_t var)
+{
+	assert(hw_is_interval(var));
+	return hw_param_interval_c(params, var);
+}
+
+/* --- Refinement functions --- */
+
+int _snd_pcm_hw_param_set_interval(snd_pcm_hw_params_t *params,
+				   snd_pcm_hw_param_t var,
+				   const snd_interval_t *val)
+{
+	int changed;
+	assert(hw_is_interval(var));
+	changed = snd_interval_refine(hw_param_interval(params, var), val);
+	if (changed) {
+		params->cmask |= 1 << var;
+		params->rmask |= 1 << var;
+	}
+	return changed;
+}
+
+void _snd_pcm_hw_param_set_empty(snd_pcm_hw_params_t *params,
+				 snd_pcm_hw_param_t var)
+{
+	if (hw_is_mask(var)) {
+		snd_mask_none(hw_param_mask(params, var));
+		params->cmask |= 1 << var;
+		params->rmask |= 1 << var;
+	} else if (hw_is_interval(var)) {
+		snd_interval_none(hw_param_interval(params, var));
+		params->cmask |= 1 << var;
+		params->rmask |= 1 << var;
+	} else {
+		assert(0);
+	}
+}
+
+static int _snd_pcm_hw_param_set_integer(snd_pcm_hw_params_t *params,
+					 snd_pcm_hw_param_t var)
+{
+	int changed;
+	assert(hw_is_interval(var));
+	changed = snd_interval_setinteger(hw_param_interval(params, var));
+	if (changed) {
+		params->cmask |= 1 << var;
+		params->rmask |= 1 << var;
+	}
+	return changed;
+}
+	
+/* Inside configuration space defined by PARAMS remove from PAR all 
+   non integer values. Reduce configuration space accordingly.
+   Return -EINVAL if the configuration space is empty
+*/
+int snd_pcm_hw_param_set_integer(snd_pcm_t *pcm, 
+				 snd_pcm_hw_params_t *params,
+				 snd_set_mode_t mode,
+				 snd_pcm_hw_param_t var)
+{
+	snd_pcm_hw_params_t save;
+	int err;
+	switch (mode) {
+	case SND_CHANGE:
+		break;
+	case SND_TRY:
+		save = *params;
+		break;
+	case SND_TEST:
+		save = *params;
+		params = &save;
+		break;
+	default:
+		assert(0);
+		return -EINVAL;
+	}
+	err = _snd_pcm_hw_param_set_integer(params, var);
+	if (err < 0)
+		goto _fail;
+	if (params->rmask) {
+		err = snd_pcm_hw_refine(pcm, params);
+		if (err < 0)
+			goto _fail;
+	}
+	return 0;
+ _fail:
+	if (mode == SND_TRY)
+		*params = save;
+	return err;
+}
+
+static int _snd_pcm_hw_param_set_first(snd_pcm_hw_params_t *params,
+				       snd_pcm_hw_param_t var)
+{
+	int changed;
+	if (hw_is_mask(var))
+		changed = snd_mask_refine_first(hw_param_mask(params, var));
+	else if (hw_is_interval(var))
+		changed = snd_interval_refine_first(hw_param_interval(params, var));
+	else {
+		assert(0);
+		return -EINVAL;
+	}
+	if (changed > 0) {
+		params->cmask |= 1 << var;
+		params->rmask |= 1 << var;
+	}
+	return changed;
+}
+
+
+/* Inside configuration space defined by PARAMS remove from PAR all 
+   values > minimum. Reduce configuration space accordingly.
+   Return the minimum.
+*/
+int snd_pcm_hw_param_set_first(snd_pcm_t *pcm, 
+			       snd_pcm_hw_params_t *params, 
+			       snd_pcm_hw_param_t var,
+			       unsigned int *rval, int *dir)
+{
+	int err;
+
+	err = _snd_pcm_hw_param_set_first(params, var);
+	if (err < 0)
+		return err;
+	if (params->rmask) {
+		err = snd_pcm_hw_refine(pcm, params);
+		if (err < 0)
+			return err;
+	}
+	return snd_pcm_hw_param_get(params, var, rval, dir);
+}
+
+static int _snd_pcm_hw_param_set_last(snd_pcm_hw_params_t *params,
+				      snd_pcm_hw_param_t var)
+{
+	int changed;
+	if (hw_is_mask(var))
+		changed = snd_mask_refine_last(hw_param_mask(params, var));
+	else if (hw_is_interval(var))
+		changed = snd_interval_refine_last(hw_param_interval(params, var));
+	else {
+		assert(0);
+		return -EINVAL;
+	}
+	if (changed > 0) {
+		params->cmask |= 1 << var;
+		params->rmask |= 1 << var;
+	}
+	return changed;
+}
+
+
+/* Inside configuration space defined by PARAMS remove from PAR all 
+   values < maximum. Reduce configuration space accordingly.
+   Return the maximum.
+*/
+int snd_pcm_hw_param_set_last(snd_pcm_t *pcm, 
+			      snd_pcm_hw_params_t *params,
+			      snd_pcm_hw_param_t var,
+			      unsigned int *rval, int *dir)
+{
+	int err;
+
+	err = _snd_pcm_hw_param_set_last(params, var);
+	if (err < 0)
+		return err;
+	if (params->rmask) {
+		err = snd_pcm_hw_refine(pcm, params);
+		if (err < 0)
+			return err;
+	}
+	return snd_pcm_hw_param_get(params, var, rval, dir);
+}
+
+int _snd_pcm_hw_param_set_min(snd_pcm_hw_params_t *params,
+			      snd_pcm_hw_param_t var, unsigned int val, int dir)
+{
+	int changed;
+	int openmin = 0;
+	if (dir) {
+		if (dir > 0) {
+			openmin = 1;
+		} else if (dir < 0) {
+			if (val > 0) {
+				openmin = 1;
+				val--;
+			}
+		}
+	}
+	if (hw_is_mask(var))
+		changed = snd_mask_refine_min(hw_param_mask(params, var), val + !!openmin);
+	else if (hw_is_interval(var))
+		changed = snd_interval_refine_min(hw_param_interval(params, var), val, openmin);
+	else {
+		assert(0);
+		return -EINVAL;
+	}
+	if (changed) {
+		params->cmask |= 1 << var;
+		params->rmask |= 1 << var;
+	}
+	return changed;
+}
+
+/* Inside configuration space defined by PARAMS remove from PAR all 
+   values < VAL. Reduce configuration space accordingly.
+   Return new minimum or -EINVAL if the configuration space is empty
+*/
+int snd_pcm_hw_param_set_min(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
+			     snd_set_mode_t mode,
+			     snd_pcm_hw_param_t var, unsigned int *val, int *dir)
+{
+	snd_pcm_hw_params_t save;
+	int err;
+	switch (mode) {
+	case SND_CHANGE:
+		break;
+	case SND_TRY:
+		save = *params;
+		break;
+	case SND_TEST:
+		save = *params;
+		params = &save;
+		break;
+	default:
+		assert(0);
+		return -EINVAL;
+	}
+	err = _snd_pcm_hw_param_set_min(params, var, *val, dir ? *dir : 0);
+	if (err < 0)
+		goto _fail;
+	if ((mode != SND_TEST || hw_is_interval(var)) && params->rmask) {
+		err = snd_pcm_hw_refine(pcm, params);
+		if (err < 0)
+			goto _fail;
+		if (snd_pcm_hw_param_empty(params, var)) {
+			err = -ENOENT;
+			goto _fail;
+		}
+	}
+	return snd_pcm_hw_param_get_min(params, var, val, dir);
+ _fail:
+	if (mode == SND_TRY)
+		*params = save;
+	if (err < 0 && mode == SND_TRY)
+		dump_hw_params(params, "set_min", var, *val, err);
+	return err;
+}
+
+int _snd_pcm_hw_param_set_max(snd_pcm_hw_params_t *params,
+			      snd_pcm_hw_param_t var, unsigned int val, int dir)
+{
+	int changed;
+	int openmax = 0;
+	if (dir) {
+		if (dir < 0) {
+			openmax = 1;
+		} else if (dir > 0) {
+			openmax = 1;
+			val++;
+		}
+	}
+	if (hw_is_mask(var)) {
+		if (val == 0 && openmax) {
+		snd_mask_none(hw_param_mask(params, var));
+			changed = -EINVAL;
+		} else
+			changed = snd_mask_refine_max(hw_param_mask(params, var), val - !!openmax);
+	} else if (hw_is_interval(var))
+		changed = snd_interval_refine_max(hw_param_interval(params, var), val, openmax);
+	else {
+		assert(0);
+		return -EINVAL;
+	}
+	if (changed) {
+		params->cmask |= 1 << var;
+		params->rmask |= 1 << var;
+	}
+	return changed;
+}
+
+/* Inside configuration space defined by PARAMS remove from PAR all 
+   values >= VAL + 1. Reduce configuration space accordingly.
+   Return new maximum or -EINVAL if the configuration space is empty
+*/
+int snd_pcm_hw_param_set_max(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
+			     snd_set_mode_t mode,
+			     snd_pcm_hw_param_t var, unsigned int *val, int *dir)
+{
+	snd_pcm_hw_params_t save;
+	int err;
+	switch (mode) {
+	case SND_CHANGE:
+		break;
+	case SND_TRY:
+		save = *params;
+		break;
+	case SND_TEST:
+		save = *params;
+		params = &save;
+		break;
+	default:
+		assert(0);
+		return -EINVAL;
+	}
+	err = _snd_pcm_hw_param_set_max(params, var, *val, dir ? *dir : 0);
+	if (err < 0)
+		goto _fail;
+	if ((mode != SND_TEST || hw_is_interval(var)) && params->rmask) {
+		err = snd_pcm_hw_refine(pcm, params);
+		if (err < 0)
+			goto _fail;
+		if (snd_pcm_hw_param_empty(params, var)) {
+			err = -ENOENT;
+			goto _fail;
+		}
+	}
+	return snd_pcm_hw_param_get_max(params, var, val, dir);
+ _fail:
+	if (mode == SND_TRY)
+		*params = save;
+	if (err < 0 && mode == SND_TRY)
+		dump_hw_params(params, "set_max", var, *val, err);
+	return err;
+}
+
+int _snd_pcm_hw_param_set_minmax(snd_pcm_hw_params_t *params,
+				 snd_pcm_hw_param_t var,
+				 unsigned int min, int mindir,
+				 unsigned int max, int maxdir)
+{
+	int changed, c1, c2;
+	int openmin = 0, openmax = 0;
+	if (mindir) {
+		if (mindir > 0) {
+			openmin = 1;
+		} else if (mindir < 0) {
+			if (min > 0) {
+				openmin = 1;
+				min--;
+			}
+		}
+	}
+	if (maxdir) {
+		if (maxdir < 0) {
+			openmax = 1;
+		} else if (maxdir > 0) {
+			openmax = 1;
+			max++;
+		}
+	}
+	if (hw_is_mask(var)) {
+		snd_mask_t *mask = hw_param_mask(params, var);
+		if (max == 0 && openmax) {
+			snd_mask_none(mask);
+			changed = -EINVAL;
+		} else {
+			c1 = snd_mask_refine_min(mask, min + !!openmin);
+			if (c1 < 0)
+				changed = c1;
+			else {
+				c2 = snd_mask_refine_max(mask, max - !!openmax);
+				if (c2 < 0)
+					changed = c2;
+				else
+					changed = (c1 || c2);
+			}
+		}
+	}
+	else if (hw_is_interval(var)) {
+		snd_interval_t *i = hw_param_interval(params, var);
+		c1 = snd_interval_refine_min(i, min, openmin);
+		if (c1 < 0)
+			changed = c1;
+		else {
+			c2 = snd_interval_refine_max(i, max, openmax);
+			if (c2 < 0)
+				changed = c2;
+			else
+				changed = (c1 || c2);
+		}
+	} else {
+		assert(0);
+		return -EINVAL;
+	}
+	if (changed) {
+		params->cmask |= 1 << var;
+		params->rmask |= 1 << var;
+	}
+	return changed;
+}
+
+/* Inside configuration space defined by PARAMS remove from PAR all 
+   values < MIN and all values > MAX. Reduce configuration space accordingly.
+   Return 0 or -EINVAL if the configuration space is empty
+*/
+int snd_pcm_hw_param_set_minmax(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
+				snd_set_mode_t mode,
+				snd_pcm_hw_param_t var,
+				unsigned int *min, int *mindir,
+				unsigned int *max, int *maxdir)
+{
+	snd_pcm_hw_params_t save;
+	int err;
+	switch (mode) {
+	case SND_CHANGE:
+		break;
+	case SND_TRY:
+		save = *params;
+		break;
+	case SND_TEST:
+		save = *params;
+		params = &save;
+		break;
+	default:
+		assert(0);
+		return -EINVAL;
+	}
+	err = _snd_pcm_hw_param_set_minmax(params, var, 
+					   *min, mindir ? *mindir : 0,
+					   *max, maxdir ? *maxdir : 0);
+	if (err < 0)
+		goto _fail;
+	if ((mode != SND_TEST || hw_is_interval(var)) && params->rmask) {
+		err = snd_pcm_hw_refine(pcm, params);
+		if (err < 0)
+			goto _fail;
+	}
+	err = snd_pcm_hw_param_get_min(params, var, min, mindir);
+	if (err < 0)
+		return err;
+	return snd_pcm_hw_param_get_max(params, var, max, maxdir);
+ _fail:
+	if (mode == SND_TRY)
+		*params = save;
+	if (err < 0)
+		dump_hw_params(params, "set_minmax", var, *min, err);
+	return err;
+}
+
+int _snd_pcm_hw_param_set(snd_pcm_hw_params_t *params,
+			  snd_pcm_hw_param_t var, unsigned int val, int dir)
+{
+	int changed;
+	if (hw_is_mask(var)) {
+		snd_mask_t *m = hw_param_mask(params, var);
+		if (val == 0 && dir < 0) {
+			changed = -EINVAL;
+			snd_mask_none(m);
+		} else {
+			if (dir > 0)
+				val++;
+			else if (dir < 0)
+				val--;
+			changed = snd_mask_refine_set(hw_param_mask(params, var), val);
+		}
+	} else if (hw_is_interval(var)) {
+		snd_interval_t *i = hw_param_interval(params, var);
+		if (val == 0 && dir < 0) {
+			changed = -EINVAL;
+			snd_interval_none(i);
+		} else if (dir == 0)
+			changed = snd_interval_refine_set(i, val);
+		else {
+			snd_interval_t t;
+			t.openmin = 1;
+			t.openmax = 1;
+			t.empty = 0;
+			t.integer = 0;
+			if (dir < 0) {
+				t.min = val - 1;
+				t.max = val;
+			} else {
+				t.min = val;
+				t.max = val+1;
+			}
+			changed = snd_interval_refine(i, &t);
+		}
+	} else {
+		assert(0);
+		return -EINVAL;
+	}
+	if (changed) {
+		params->cmask |= 1 << var;
+		params->rmask |= 1 << var;
+	}
+	return changed;
+}
+
+/* Inside configuration space defined by PARAMS remove from PAR all 
+   values != VAL. Reduce configuration space accordingly.
+   Return -EINVAL if the configuration space is empty
+*/
+int snd_pcm_hw_param_set(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
+			 snd_set_mode_t mode,
+			 snd_pcm_hw_param_t var, unsigned int val, int dir)
+{
+	snd_pcm_hw_params_t save;
+	int err;
+	switch (mode) {
+	case SND_CHANGE:
+		break;
+	case SND_TRY:
+		save = *params;
+		break;
+	case SND_TEST:
+		save = *params;
+		params = &save;
+		break;
+	default:
+		assert(0);
+		return -EINVAL;
+	}
+	err = _snd_pcm_hw_param_set(params, var, val, dir);
+	if (err < 0)
+		goto _fail;
+	if ((mode != SND_TEST || hw_is_interval(var)) && params->rmask) {
+		err = snd_pcm_hw_refine(pcm, params);
+		if (err < 0)
+			goto _fail;
+	}
+	return 0;
+ _fail:
+	if (mode == SND_TRY)
+		*params = save;
+	if (err < 0 && mode == SND_TRY)
+		dump_hw_params(params, "set", var, val, err);
+	return err;
+}
+
+int _snd_pcm_hw_param_set_mask(snd_pcm_hw_params_t *params,
+			       snd_pcm_hw_param_t var, const snd_mask_t *val)
+{
+	int changed;
+	assert(hw_is_mask(var));
+	changed = snd_mask_refine(hw_param_mask(params, var), val);
+	if (changed) {
+		params->cmask |= 1 << var;
+		params->rmask |= 1 << var;
+	}
+	return changed;
+}
+
+/* Inside configuration space defined by PARAMS remove from PAR all values
+   not contained in MASK. Reduce configuration space accordingly.
+   This function can be called only for SND_PCM_HW_PARAM_ACCESS,
+   SND_PCM_HW_PARAM_FORMAT, SND_PCM_HW_PARAM_SUBFORMAT.
+   Return 0 on success or -EINVAL
+   if the configuration space is empty
+*/
+int snd_pcm_hw_param_set_mask(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
+			      snd_set_mode_t mode,
+			      snd_pcm_hw_param_t var, const snd_mask_t *val)
+{
+	snd_pcm_hw_params_t save;
+	int err;
+	switch (mode) {
+	case SND_CHANGE:
+		break;
+	case SND_TRY:
+		save = *params;
+		break;
+	case SND_TEST:
+		save = *params;
+		params = &save;
+		break;
+	default:
+		assert(0);
+		return -EINVAL;
+	}
+	err = _snd_pcm_hw_param_set_mask(params, var, val);
+	if (err < 0)
+		goto _fail;
+	if (mode != SND_TEST && params->rmask) {
+		err = snd_pcm_hw_refine(pcm, params);
+		if (err < 0)
+			goto _fail;
+	}
+	return 0;
+ _fail:
+	if (mode == SND_TRY)
+		*params = save;
+	return err;
+}
+
+/* Inside configuration space defined by PARAMS set PAR to the available value
+   nearest to VAL. Reduce configuration space accordingly.
+   This function cannot be called for SND_PCM_HW_PARAM_ACCESS,
+   SND_PCM_HW_PARAM_FORMAT, SND_PCM_HW_PARAM_SUBFORMAT.
+   Return the value found.
+ */
+int snd_pcm_hw_param_set_near(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
+			      snd_pcm_hw_param_t var,
+			      unsigned int *val, int *dir)
+{
+	snd_pcm_hw_params_t save;
+	int err;
+	unsigned int best = *val, saved_min;
+	int last = 0;
+	unsigned int min, max;
+	int mindir, maxdir;
+	int valdir = dir ? *dir : 0;
+	snd_interval_t *i;
+	/* FIXME */
+	if (best > INT_MAX)
+		best = INT_MAX;
+	min = max = best;
+	mindir = maxdir = valdir;
+	if (maxdir > 0)
+		maxdir = 0;
+	else if (maxdir == 0)
+		maxdir = -1;
+	else {
+		maxdir = 1;
+		max--;
+	}
+	save = *params;
+	saved_min = min;
+	err = snd_pcm_hw_param_set_min(pcm, params, SND_CHANGE, var, &min, &mindir);
+
+	i = hw_param_interval(params, var);
+	if (!snd_interval_empty(i) && snd_interval_single(i)) {
+		err = snd_pcm_hw_param_get_min(params, var, val, dir);
+		if (err < 0)
+			dump_hw_params(params, "set_near", var, *val, err);
+		return err;
+	}
+	
+	if (err >= 0) {
+		snd_pcm_hw_params_t params1;
+		if (min == saved_min && mindir == valdir)
+			goto _end;
+		params1 = save;
+		err = snd_pcm_hw_param_set_max(pcm, &params1, SND_CHANGE, var, &max, &maxdir);
+		if (err < 0)
+			goto _end;
+		if (boundary_nearer(max, maxdir, best, valdir, min, mindir)) {
+			*params = params1;
+			last = 1;
+		}
+	} else {
+		*params = save;
+		err = snd_pcm_hw_param_set_max(pcm, params, SND_CHANGE, var, &max, &maxdir);
+		if (err < 0) {
+			dump_hw_params(params, "set_near", var, *val, err);
+			return err;
+		}
+		last = 1;
+	}
+ _end:
+	if (last)
+		err = snd_pcm_hw_param_set_last(pcm, params, var, val, dir);
+	else
+		err = snd_pcm_hw_param_set_first(pcm, params, var, val, dir);
+	if (err < 0)
+		dump_hw_params(params, "set_near", var, *val, err);
+	return err;
+}
+
+#if 0
+/* Inside configuration space defined by PARAMS set PAR to the available value
+   nearest to BEST after VAL (on equal difference values less than BEST are
+   returned first).
+   Reduce configuration space accordingly.
+   This function cannot be called for SND_PCM_HW_PARAM_ACCESS,
+   SND_PCM_HW_PARAM_FORMAT, SND_PCM_HW_PARAM_SUBFORMAT.
+   Return the value found.
+ */
+int snd_pcm_hw_param_set_next(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
+			      snd_pcm_hw_param_t var, 
+			      unsigned int best, int bestdir,
+			      unsigned int val, int *dir)
+{
+	snd_pcm_hw_params_t save;
+	int v, err;
+	int last = 0;
+	int min, max;
+	int mindir, maxdir;
+	int diff, diffdir;
+	int valdir = dir ? *dir : 0;
+	/* FIXME */
+	if (best > INT_MAX)
+		best = INT_MAX;
+	boundary_sub(val, valdir, best, bestdir, &diff, &diffdir);
+	if (diff < 0 || (diff == 0 && diffdir < 0)) {
+		min = best - diff;
+		mindir = bestdir - diffdir;
+		max = val;
+		maxdir = bestdir - 1;
+	} else {
+		min = val;
+		mindir = bestdir + 1;
+		max = best + diff;
+		maxdir = bestdir + diffdir + 1;
+	}
+	min += mindir / 2;
+	mindir %= 2;
+	max += maxdir / 2;
+	maxdir %= 2;
+	save = *params;
+	if (min >= 0 &&
+	    (err = snd_pcm_hw_param_set_min(pcm, params, SND_CHANGE, var, &min, &mindir)) >= 0) {
+		snd_pcm_hw_params_t params1;
+		if (max < 0)
+			goto _end;
+		params1 = save;
+		err = snd_pcm_hw_param_set_max(pcm, &params1, SND_CHANGE, var, &max, &maxdir);
+		if (err < 0)
+			goto _end;
+		if (boundary_nearer(max, maxdir, best, bestdir, min, mindir)) {
+			*params = params1;
+			last = 1;
+		}
+	} else {
+		if (max < 0)
+			return -EINVAL;
+		*params = save;
+		err = snd_pcm_hw_param_set_max(pcm, params, SND_CHANGE, var, &max, &maxdir);
+		if (err < 0)
+			return max;
+		last = 1;
+	}
+ _end:
+	if (last)
+		v = snd_pcm_hw_param_set_last(pcm, params, var, dir);
+	else
+		v = snd_pcm_hw_param_set_first(pcm, params, var, dir);
+	assert(v >= 0);
+	return v;
+}
+#endif
+
+static int snd_pcm_hw_param_set_near_minmax(snd_pcm_t *pcm,
+					    snd_pcm_hw_params_t *params,
+					    snd_pcm_hw_param_t var,
+					    unsigned int min, int *mindir,
+					    unsigned int max, int *maxdir)
+{
+	snd_pcm_hw_params_t tmp;
+	int err;
+	if (!boundary_lt(min, *mindir, max, *maxdir))
+		return snd_pcm_hw_param_set_near(pcm, params, var, &min, mindir);
+	tmp = *params;
+	err = snd_pcm_hw_param_set_near(pcm, &tmp, var, &min, mindir);
+	if (err < 0)
+		return err;
+	if (boundary_lt(min, *mindir, max, *maxdir)) {
+		tmp = *params;
+		err = snd_pcm_hw_param_set_near(pcm, &tmp, var, &max, maxdir);
+	} else {
+		max = min;
+		*maxdir = *mindir;
+	}
+	err = snd_pcm_hw_param_set_minmax(pcm, params, SND_CHANGE, var, &min, mindir,
+					  &max, maxdir);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+int snd_pcm_hw_param_refine_near(snd_pcm_t *pcm,
+				 snd_pcm_hw_params_t *params,
+				 snd_pcm_hw_param_t var,
+				 const snd_pcm_hw_params_t *src)
+{
+	unsigned int min, max;
+	int mindir, maxdir, err;
+
+	if ((err = snd_pcm_hw_param_get_min(src, var, &min, &mindir)) < 0)
+		return err;
+	if ((err = snd_pcm_hw_param_get_max(src, var, &max, &maxdir)) < 0)
+		return err;
+	if ((err = snd_pcm_hw_param_set_near_minmax(pcm, params, var,
+						    min, &mindir, max, &maxdir)) < 0)
+		return err;
+	return 0;
+}
+
+int snd_pcm_hw_param_refine_multiple(snd_pcm_t *pcm,
+				     snd_pcm_hw_params_t *params,
+				     snd_pcm_hw_param_t var,
+				     const snd_pcm_hw_params_t *src)
+{
+	const snd_interval_t *it = hw_param_interval_c(src, var);
+	const snd_interval_t *st = hw_param_interval_c(params, var);
+	if (snd_interval_single(it)) {
+		unsigned int best = snd_interval_min(it), cur, prev;
+		cur = best;
+		for (;;) {
+			if (st->max < cur || (st->max == cur && st->openmax))
+				break;
+			if (it->min <= cur && ! (it->min == cur && st->openmin)) {
+				if (! snd_pcm_hw_param_set(pcm, params, SND_TRY, var, cur, 0))
+					return 0; /* ok */
+			}
+			prev = cur;
+			cur += best;
+			if (cur <= prev)
+				break;
+		}
+	}
+	return snd_pcm_hw_param_refine_near(pcm, params, var, src);
+}
+
+/* ---- end of refinement functions ---- */
+
+int snd_pcm_hw_param_empty(const snd_pcm_hw_params_t *params,
+			   snd_pcm_hw_param_t var)
+{
+	if (hw_is_mask(var))
+		return snd_mask_empty(hw_param_mask_c(params, var));
+	if (hw_is_interval(var))
+		return snd_interval_empty(hw_param_interval_c(params, var));
+	assert(0);
+	return -EINVAL;
+}
+
+int snd_pcm_hw_param_always_eq(const snd_pcm_hw_params_t *params,
+			       snd_pcm_hw_param_t var,
+			       const snd_pcm_hw_params_t *params1)
+{
+	if (hw_is_mask(var))
+		return snd_mask_always_eq(hw_param_mask_c(params, var),
+					  hw_param_mask_c(params1, var));
+	if (hw_is_interval(var))
+		return snd_interval_always_eq(hw_param_interval_c(params, var),
+					      hw_param_interval_c(params1, var));
+	assert(0);
+	return -EINVAL;
+}
+
+int snd_pcm_hw_param_never_eq(const snd_pcm_hw_params_t *params,
+			      snd_pcm_hw_param_t var,
+			      const snd_pcm_hw_params_t *params1)
+{
+	if (hw_is_mask(var))
+		return snd_mask_never_eq(hw_param_mask_c(params, var),
+					 hw_param_mask_c(params1, var));
+	if (hw_is_interval(var))
+		return snd_interval_never_eq(hw_param_interval_c(params, var),
+					     hw_param_interval_c(params1, var));
+	assert(0);
+	return -EINVAL;
+}
+
+#if 0
+#define CHOOSE_DEBUG
+#endif
+
+/* Choose one configuration from configuration space defined by PARAMS
+   The configuration chosen is that obtained fixing in this order:
+   first access
+   first format
+   first subformat
+   min channels
+   min rate
+   min period time
+   max buffer size
+   min tick time
+*/
+static int snd_pcm_hw_params_choose(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	int err;
+#ifdef CHOOSE_DEBUG
+	snd_output_t *log;
+	snd_output_stdio_attach(&log, stderr, 0);
+	snd_output_printf(log, "CHOOSE called:\n");
+	snd_pcm_hw_params_dump(params, log);
+#endif
+
+	err = snd_pcm_hw_param_set_first(pcm, params, SND_PCM_HW_PARAM_ACCESS, NULL, 0);
+	if (err < 0)
+		return err;
+	err = snd_pcm_hw_param_set_first(pcm, params, SND_PCM_HW_PARAM_FORMAT, NULL, 0);
+	if (err < 0)
+		return err;
+	err = snd_pcm_hw_param_set_first(pcm, params, SND_PCM_HW_PARAM_SUBFORMAT, NULL, 0);
+	if (err < 0)
+		return err;
+	err = snd_pcm_hw_param_set_first(pcm, params, SND_PCM_HW_PARAM_CHANNELS, NULL, 0);
+	if (err < 0)
+		return err;
+	err = snd_pcm_hw_param_set_first(pcm, params, SND_PCM_HW_PARAM_RATE, NULL, 0);
+	if (err < 0)
+		return err;
+	if (pcm->minperiodtime > 0) {
+		unsigned int min, max;
+		int dir = 1;
+		err = snd_pcm_hw_param_get_min(params, SND_PCM_HW_PARAM_PERIOD_TIME, &min, &dir);
+		if (err >= 0)
+			err = snd_pcm_hw_param_get_max(params, SND_PCM_HW_PARAM_PERIOD_TIME, &max, &dir);
+		if (err >= 0 && (long)min < pcm->minperiodtime &&
+			        (long)max > pcm->minperiodtime) {
+			min = pcm->minperiodtime; dir = 1;
+			snd_pcm_hw_param_set_min(pcm, params, SND_CHANGE, SND_PCM_HW_PARAM_PERIOD_TIME, &min, &dir);
+		}
+	}
+	if (pcm->compat) {
+		/* old mode */
+		err = snd_pcm_hw_param_set_first(pcm, params, SND_PCM_HW_PARAM_PERIOD_TIME, NULL, 0);
+		if (err < 0)
+			return err;
+		err = snd_pcm_hw_param_set_first(pcm, params, SND_PCM_HW_PARAM_PERIOD_SIZE, NULL, 0);
+		if (err < 0)
+			return err;
+		err = snd_pcm_hw_param_set_last(pcm, params, SND_PCM_HW_PARAM_BUFFER_SIZE, NULL, 0);
+		if (err < 0)
+			return err;
+	} else {
+		/* determine buffer size first */
+		err = snd_pcm_hw_param_set_last(pcm, params, SND_PCM_HW_PARAM_BUFFER_SIZE, NULL, 0);
+		if (err < 0)
+			return err;
+		err = snd_pcm_hw_param_set_first(pcm, params, SND_PCM_HW_PARAM_PERIOD_SIZE, NULL, 0);
+		if (err < 0)
+			return err;
+		err = snd_pcm_hw_param_set_first(pcm, params, SND_PCM_HW_PARAM_PERIOD_TIME, NULL, 0);
+		if (err < 0)
+			return err;
+	}
+	err = snd_pcm_hw_param_set_first(pcm, params, SND_PCM_HW_PARAM_TICK_TIME, NULL, 0);
+	if (err < 0)
+		return err;
+#ifdef CHOOSE_DEBUG
+	snd_output_printf(log, "choose done\n");
+	snd_pcm_hw_params_dump(params, log);
+	snd_output_close(log);
+#endif
+	return 0;
+}
+
+#if 0
+static unsigned int snd_pcm_hw_param_count(const snd_pcm_hw_params_t *params,
+					   snd_pcm_hw_param_t var)
+{
+	if (hw_is_mask(var)) {
+		const snd_mask_t *mask = hw_param_mask_c(params, var);
+		return snd_mask_count(mask);
+	}
+	if (hw_is_interval(var)) {
+		const snd_interval_t *i = hw_param_interval_c(params, var);
+		return snd_interval_max(i) - snd_interval_min(i) + 1;
+	}
+	assert(0);
+	return 0;
+}
+#endif
+
+int _snd_pcm_hw_param_refine(snd_pcm_hw_params_t *params,
+			     snd_pcm_hw_param_t var,
+			     const snd_pcm_hw_params_t *src)
+{
+	int changed = 0;
+	if (hw_is_mask(var)) {
+		snd_mask_t *d = hw_param_mask(params, var);
+		const snd_mask_t *s = hw_param_mask_c(src, var);
+		changed = snd_mask_refine(d, s);
+	} else if (hw_is_interval(var)) {
+		snd_interval_t *d = hw_param_interval(params, var);
+		const snd_interval_t *s = hw_param_interval_c(src, var);
+		changed = snd_interval_refine(d, s);
+	} else
+		return 0; /* NOP / reserved */
+	if (changed) {
+		params->cmask |= 1 << var;
+		params->rmask |= 1 << var;
+	}
+	return changed;
+}
+			     
+#if 0
+static void _snd_pcm_hw_param_copy(snd_pcm_hw_params_t *params, snd_pcm_hw_param_t var,
+				   const snd_pcm_hw_params_t *src)
+{
+	if (hw_is_mask(var)) {
+		snd_mask_t *d = hw_param_mask(params, var);
+		const snd_mask_t *s = hw_param_mask_c(src, var);
+		snd_mask_copy(d, s);
+		params->cmask |= 1 << var;
+		params->rmask |= 1 << var;
+		return;
+	}
+	if (hw_is_interval(var)) {
+		snd_interval_t *d = hw_param_interval(params, var);
+		const snd_interval_t *s = hw_param_interval_c(src, var);
+		snd_interval_copy(d, s);
+		params->cmask |= 1 << var;
+		params->rmask |= 1 << var;
+		return;
+	}
+	assert(0);
+}
+#endif
+
+void snd_pcm_hw_param_dump(const snd_pcm_hw_params_t *params,
+			   snd_pcm_hw_param_t var, snd_output_t *out)
+{
+	if (hw_is_mask(var)) {
+		const snd_mask_t *mask = hw_param_mask_c(params, var);
+		if (snd_mask_empty(mask))
+			snd_output_puts(out, " NONE");
+		else if (snd_mask_full(mask))
+			snd_output_puts(out, " ALL");
+		else {
+			unsigned int k;
+			for (k = 0; k <= SND_MASK_MAX; ++k) {
+				if (snd_mask_test(mask, k)) {
+					const char *s;
+					switch (var) {
+					case SND_PCM_HW_PARAM_ACCESS:
+						s = snd_pcm_access_name(k);
+						break;
+					case SND_PCM_HW_PARAM_FORMAT:
+						s = snd_pcm_format_name(k);
+						break;
+					case SND_PCM_HW_PARAM_SUBFORMAT:
+						s = snd_pcm_subformat_name(k);
+						break;
+					default:
+						assert(0);
+						s = NULL;
+					}
+					if (s) {
+						snd_output_putc(out, ' ');
+						snd_output_puts(out, s);
+					}
+				}
+			}
+		}
+		return;
+	}
+	if (hw_is_interval(var)) {
+		snd_interval_print(hw_param_interval_c(params, var), out);
+		return;
+	}
+	assert(0);
+}
+
+#define HW_PARAM(v) [SND_PCM_HW_PARAM_##v] = #v
+
+static const char *const snd_pcm_hw_param_names[] = {
+	HW_PARAM(ACCESS),
+	HW_PARAM(FORMAT),
+	HW_PARAM(SUBFORMAT),
+	HW_PARAM(SAMPLE_BITS),
+	HW_PARAM(FRAME_BITS),
+	HW_PARAM(CHANNELS),
+	HW_PARAM(RATE),
+	HW_PARAM(PERIOD_TIME),
+	HW_PARAM(PERIOD_SIZE),
+	HW_PARAM(PERIOD_BYTES),
+	HW_PARAM(PERIODS),
+	HW_PARAM(BUFFER_TIME),
+	HW_PARAM(BUFFER_SIZE),
+	HW_PARAM(BUFFER_BYTES),
+	HW_PARAM(TICK_TIME),
+};
+
+const char *snd_pcm_hw_param_name(snd_pcm_hw_param_t param)
+{
+	assert(param <= SND_PCM_HW_PARAM_LAST_INTERVAL);
+	return snd_pcm_hw_param_names[param];
+}
+
+#if 0
+/* Strategies */
+
+struct _snd_pcm_hw_strategy {
+	unsigned int badness_min, badness_max;
+	int (*choose_param)(const snd_pcm_hw_params_t *params,
+			    snd_pcm_t *pcm,
+			    const snd_pcm_hw_strategy_t *strategy);
+	int (*next_value)(snd_pcm_hw_params_t *params,
+			  unsigned int param,
+			  int value, int *dir,
+			  snd_pcm_t *pcm,
+			  const snd_pcm_hw_strategy_t *strategy);
+	int (*min_badness)(const snd_pcm_hw_params_t *params,
+			   unsigned int max_badness,
+			   snd_pcm_t *pcm,
+			   const snd_pcm_hw_strategy_t *strategy);
+	void *private_data;
+	void (*free)(snd_pcm_hw_strategy_t *strategy);
+};
+
+/* Independent badness */
+typedef struct _snd_pcm_hw_strategy_simple snd_pcm_hw_strategy_simple_t;
+
+struct _snd_pcm_hw_strategy_simple {
+	int valid;
+	unsigned int order;
+	int (*next_value)(snd_pcm_hw_params_t *params,
+			  unsigned int param,
+			  int value, int *dir,
+			  snd_pcm_t *pcm,
+			  const snd_pcm_hw_strategy_simple_t *par);
+	unsigned int (*min_badness)(const snd_pcm_hw_params_t *params,
+				    unsigned int param,
+				    snd_pcm_t *pcm,
+				    const snd_pcm_hw_strategy_simple_t *par);
+	void *private_data;
+	void (*free)(snd_pcm_hw_strategy_simple_t *strategy);
+};
+
+typedef struct _snd_pcm_hw_strategy_simple_near {
+	int best;
+	unsigned int mul;
+} snd_pcm_hw_strategy_simple_near_t;
+
+typedef struct _snd_pcm_hw_strategy_simple_choices {
+	unsigned int count;
+	/* choices need to be sorted on ascending badness */
+	snd_pcm_hw_strategy_simple_choices_list_t *choices;
+} snd_pcm_hw_strategy_simple_choices_t;
+
+int snd_pcm_hw_params_strategy(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
+			       const snd_pcm_hw_strategy_t *strategy,
+			       unsigned int badness_min,
+			       unsigned int badness_max)
+{
+	snd_pcm_hw_params_t best_params;
+	int var;
+	int value, dir;
+	unsigned int best_badness;
+	int badness = strategy->min_badness(params, badness_max, pcm, strategy);
+	snd_pcm_hw_params_t params1;
+#if 0
+	printf("\nBadness: %d\n", badness);
+	snd_pcm_hw_params_dump(params, stdout);
+#endif
+	if (badness < 0)
+		return badness;
+	if ((unsigned int)badness > badness_min)
+		badness_min = badness_min;
+	var = strategy->choose_param(params, pcm, strategy);
+	if (var < 0)
+		return badness;
+	best_badness = UINT_MAX;
+	value = -1;
+	while (1) {
+		params1 = *params;
+		value = strategy->next_value(&params1, var, value, &dir, pcm, strategy);
+		if (value < 0)
+			break;
+		badness = snd_pcm_hw_params_strategy(pcm, &params1, strategy, badness_min, badness_max);
+		if (badness >= 0) {
+			if ((unsigned int) badness <= badness_min) {
+				*params = params1;
+				return badness;
+			}
+			best_badness = badness;
+			best_params = params1;
+			badness_max = badness - 1;
+		}
+	}
+	if (best_badness == UINT_MAX) {
+		return -EINVAL;
+	}
+	*params = best_params;
+	return best_badness;
+}
+
+void snd_pcm_hw_strategy_simple_free(snd_pcm_hw_strategy_t *strategy)
+{
+	snd_pcm_hw_strategy_simple_t *pars = strategy->private_data;
+	int k;
+	for (k = 0; k <= SND_PCM_HW_PARAM_LAST_INTERVAL; ++k) {
+		if (pars[k].valid && pars[k].free)
+			pars[k].free(&pars[k]);
+	}
+	free(pars);
+}
+
+int snd_pcm_hw_strategy_simple_choose_param(const snd_pcm_hw_params_t *params,
+					 snd_pcm_t *pcm ATTRIBUTE_UNUSED,
+					 const snd_pcm_hw_strategy_t *strategy)
+{
+	snd_pcm_hw_param_t var;
+	int best_var = -1;
+	const snd_pcm_hw_strategy_simple_t *pars = strategy->private_data;
+	unsigned int min_choices = UINT_MAX;
+	unsigned int min_order = UINT_MAX;
+	for (var = 0; var <= SND_PCM_HW_PARAM_LAST_INTERVAL; ++var) {
+		const snd_pcm_hw_strategy_simple_t *p = &pars[var];
+		unsigned int choices;
+		if (!p->valid)
+			continue;
+		choices = snd_pcm_hw_param_count(params, var);
+		if (choices == 1)
+			continue;
+		assert(choices != 0);
+		if (p->order < min_order ||
+		    (p->order == min_order &&
+		     choices < min_choices)) {
+			min_order = p->order;
+			min_choices = choices;
+			best_var = var;
+		}
+	}
+	return best_var;
+}
+
+int snd_pcm_hw_strategy_simple_next_value(snd_pcm_hw_params_t *params,
+					  snd_pcm_hw_param_t var,
+					  int value, int *dir,
+					  snd_pcm_t *pcm,
+					  const snd_pcm_hw_strategy_t *strategy)
+{
+	const snd_pcm_hw_strategy_simple_t *pars = strategy->private_data;
+	assert(pars[var].valid);
+	return pars[var].next_value(params, var, value, dir, pcm, &pars[var]);
+}
+
+
+int snd_pcm_hw_strategy_simple_min_badness(const snd_pcm_hw_params_t *params,
+					unsigned int max_badness,
+					snd_pcm_t *pcm,
+					const snd_pcm_hw_strategy_t *strategy)
+{
+	snd_pcm_hw_param_t var;
+	unsigned int badness = 0;
+	const snd_pcm_hw_strategy_simple_t *pars = strategy->private_data;
+	for (var = 0; var <= SND_PCM_HW_PARAM_LAST_INTERVAL; ++var) {
+		unsigned int b;
+		if (!pars[var].valid)
+			continue;
+		b = pars[var].min_badness(params, var, pcm, &pars[var]);
+		if (b > max_badness || max_badness - b < badness)
+			return -E2BIG;
+		badness += b;
+	}
+	return badness;
+}
+
+
+void snd_pcm_hw_strategy_simple_near_free(snd_pcm_hw_strategy_simple_t *par)
+{
+	snd_pcm_hw_strategy_simple_near_t *p = par->private_data;
+	free(p);
+}
+
+unsigned int snd_pcm_hw_strategy_simple_near_min_badness(const snd_pcm_hw_params_t *params,
+						      snd_pcm_hw_param_t var,
+						      snd_pcm_t *pcm,
+						      const snd_pcm_hw_strategy_simple_t *par)
+{
+	const snd_pcm_hw_strategy_simple_near_t *p = par->private_data;
+	snd_pcm_hw_params_t params1 = *params;
+	int value = snd_pcm_hw_param_set_near(pcm, &params1, var, p->best, 0);
+	int diff;
+	assert(value >= 0);
+	diff = p->best - value;
+	if (diff < 0)
+		diff = -diff;
+	return diff * p->mul;
+}
+	
+int snd_pcm_hw_strategy_simple_near_next_value(snd_pcm_hw_params_t *params,
+					       snd_pcm_hw_param_t var,
+					       int value, int *dir,
+					       snd_pcm_t *pcm,
+					       const snd_pcm_hw_strategy_simple_t *par)
+{
+	const snd_pcm_hw_strategy_simple_near_t *p = par->private_data;
+	if (value < 0) {
+		*dir = 0;
+		return snd_pcm_hw_param_set_near(pcm, params, var, p->best, dir);
+	} else
+		return snd_pcm_hw_param_set_next(pcm, params, var, p->best, 0, value, dir);
+}
+
+void snd_pcm_hw_strategy_simple_choices_free(snd_pcm_hw_strategy_simple_t *par)
+{
+	snd_pcm_hw_strategy_simple_choices_t *p = par->private_data;
+//	free(p->choices);
+	free(p);
+}
+
+unsigned int snd_pcm_hw_strategy_simple_choices_min_badness(const snd_pcm_hw_params_t *params,
+							 snd_pcm_hw_param_t var,
+							 snd_pcm_t *pcm,
+							 const snd_pcm_hw_strategy_simple_t *par)
+{
+	const snd_pcm_hw_strategy_simple_choices_t *p = par->private_data;
+	unsigned int k;
+	for (k = 0; k < p->count; ++k) {
+		if (snd_pcm_hw_param_set(pcm, (snd_pcm_hw_params_t *) params, SND_TEST, var, p->choices[k].value, 0))
+			return p->choices[k].badness;
+	}
+	assert(0);
+	return UINT_MAX;
+}
+	
+int snd_pcm_hw_strategy_simple_choices_next_value(snd_pcm_hw_params_t *params,
+						  snd_pcm_hw_param_t var,
+						  int value, int *dir,
+						  snd_pcm_t *pcm,
+						  const snd_pcm_hw_strategy_simple_t *par)
+{
+	const snd_pcm_hw_strategy_simple_choices_t *p = par->private_data;
+	unsigned int k = 0;
+	if (value >= 0) {
+		for (; k < p->count; ++k) {
+			if (p->choices[k].value == (unsigned int) value) {
+				k++;
+				break;
+			}
+		}
+	}
+	for (; k < p->count; ++k) {
+		unsigned int v = p->choices[k].value;
+		int err = snd_pcm_hw_param_set(pcm, params, SND_TRY, var, v, 0);
+		if (err < 0)
+			continue;
+		*dir = 0;
+		return v;
+	}
+	return -1;
+}
+
+void snd_pcm_hw_strategy_free(snd_pcm_hw_strategy_t *strategy)
+{
+	if (strategy->free)
+		strategy->free(strategy);
+	free(strategy);
+}
+
+int snd_pcm_hw_strategy_simple(snd_pcm_hw_strategy_t **strategyp,
+			    unsigned int badness_min,
+			    unsigned int badness_max)
+{
+	snd_pcm_hw_strategy_simple_t *data;
+	snd_pcm_hw_strategy_t *s;
+	assert(strategyp);
+	data = calloc(SND_PCM_HW_PARAM_LAST_INTERVAL + 1, sizeof(*data));
+	if (!data)
+		return -ENOMEM;
+	s = calloc(1, sizeof(*s));
+	if (!s) {
+		free(data);
+		return -ENOMEM;
+	}
+	s->choose_param = snd_pcm_hw_strategy_simple_choose_param;
+	s->next_value = snd_pcm_hw_strategy_simple_next_value;
+	s->min_badness = snd_pcm_hw_strategy_simple_min_badness;
+	s->badness_min = badness_min;
+	s->badness_max = badness_max;
+	s->private_data = data;
+	s->free = snd_pcm_hw_strategy_simple_free;
+	*strategyp = s;
+	return 0;
+}
+
+int snd_pcm_hw_strategy_simple_near(snd_pcm_hw_strategy_t *strategy,
+				 int order,
+				 snd_pcm_hw_param_t var,
+				 unsigned int best,
+				 unsigned int mul)
+{
+	snd_pcm_hw_strategy_simple_t *s = strategy->private_data;
+	snd_pcm_hw_strategy_simple_near_t *data;
+	assert(strategy);
+	assert(var <= SND_PCM_HW_PARAM_LAST_INTERVAL);
+	assert(!s->valid);
+	data = calloc(1, sizeof(*data));
+	if (!data)
+		return -ENOMEM;
+	data->best = best;
+	data->mul = mul;
+	s += var;
+	s->order = order;
+	s->valid = 1;
+	s->next_value = snd_pcm_hw_strategy_simple_near_next_value;
+	s->min_badness = snd_pcm_hw_strategy_simple_near_min_badness;
+	s->private_data = data;
+	s->free = snd_pcm_hw_strategy_simple_near_free;
+	return 0;
+}
+
+int snd_pcm_hw_strategy_simple_choices(snd_pcm_hw_strategy_t *strategy,
+				    int order,
+				    snd_pcm_hw_param_t var,
+				    unsigned int count,
+				    snd_pcm_hw_strategy_simple_choices_list_t *choices)
+{
+	snd_pcm_hw_strategy_simple_t *s = strategy->private_data;
+	snd_pcm_hw_strategy_simple_choices_t *data;
+	assert(strategy);
+	assert(var <= SND_PCM_HW_PARAM_LAST_INTERVAL);
+	assert(!s->valid);
+	data = calloc(1, sizeof(*data));
+	if (!data)
+		return -ENOMEM;
+	data->count = count;
+	data->choices = choices;
+	s += var;
+	s->valid = 1;
+	s->order = order;
+	s->next_value = snd_pcm_hw_strategy_simple_choices_next_value;
+	s->min_badness = snd_pcm_hw_strategy_simple_choices_min_badness;
+	s->private_data = data;
+	s->free = snd_pcm_hw_strategy_simple_choices_free;
+	return 0;
+}
+
+int snd_pcm_hw_params_try_explain_failure1(snd_pcm_t *pcm,
+					   snd_pcm_hw_params_t *fail,
+					   snd_pcm_hw_params_t *success,
+					   unsigned int depth,
+					   snd_output_t *out)
+{
+	snd_pcm_hw_param_t var;
+	snd_pcm_hw_params_t i;
+	if (depth < 1)
+		return -ENOENT;
+	for (var = 0; var <= SND_PCM_HW_PARAM_LAST_INTERVAL; var++) {
+		int err;
+		i = *success;
+		_snd_pcm_hw_param_copy(&i, var, fail);
+		err = snd_pcm_hw_refine(pcm, &i);
+		if (err == 0 && 
+		    snd_pcm_hw_params_try_explain_failure1(pcm, fail, &i, depth - 1, out) < 0)
+			continue;
+		snd_output_printf(out, "%s: ", snd_pcm_hw_param_name(var));
+		snd_pcm_hw_param_dump(fail, var, out);
+		snd_output_putc(out, '\n');
+		return 0;
+	}
+	return -ENOENT;
+}
+
+int snd_pcm_hw_params_try_explain_failure(snd_pcm_t *pcm,
+					  snd_pcm_hw_params_t *fail,
+					  snd_pcm_hw_params_t *success,
+					  unsigned int depth,
+					  snd_output_t *out)
+{
+	snd_pcm_hw_params_t i, any;
+	int err;
+	snd_pcm_hw_param_t var;
+	int done = 0;
+	assert(pcm && fail);
+	for (var = 0; var <= SND_PCM_HW_PARAM_LAST_INTERVAL; var++) {
+		if (!snd_pcm_hw_param_empty(fail, var))
+			continue;
+		snd_output_printf(out, "%s is empty\n", snd_pcm_hw_param_name(var));
+		done = 1;
+	}
+	if (done)
+		return 0;
+	i = *fail;
+	err = snd_pcm_hw_refine(pcm, &i);
+	if (err == 0) {
+		snd_output_printf(out, "Configuration is virtually correct\n");
+		return 0;
+	}
+	if (!success) {
+		snd_pcm_hw_params_any(pcm, &any);
+		success = &any;
+	}
+	return snd_pcm_hw_params_try_explain_failure1(pcm, fail, success, depth, out);
+}
+
+#endif
+
+typedef struct _snd_pcm_hw_rule snd_pcm_hw_rule_t;
+
+typedef int (*snd_pcm_hw_rule_func_t)(snd_pcm_hw_params_t *params,
+				      const snd_pcm_hw_rule_t *rule);
+
+struct _snd_pcm_hw_rule {
+	int var;
+	snd_pcm_hw_rule_func_t func;
+	int deps[4];
+	void *private_data;
+};
+
+static int snd_pcm_hw_rule_mul(snd_pcm_hw_params_t *params,
+			       const snd_pcm_hw_rule_t *rule)
+{
+	snd_interval_t t;
+	snd_interval_mul(hw_param_interval_c(params, rule->deps[0]),
+		     hw_param_interval_c(params, rule->deps[1]), &t);
+	return snd_interval_refine(hw_param_interval(params, rule->var), &t);
+}
+
+static int snd_pcm_hw_rule_div(snd_pcm_hw_params_t *params,
+			const snd_pcm_hw_rule_t *rule)
+{
+	snd_interval_t t;
+	snd_interval_div(hw_param_interval_c(params, rule->deps[0]),
+		     hw_param_interval_c(params, rule->deps[1]), &t);
+	return snd_interval_refine(hw_param_interval(params, rule->var), &t);
+}
+
+static int snd_pcm_hw_rule_muldivk(snd_pcm_hw_params_t *params,
+				   const snd_pcm_hw_rule_t *rule)
+{
+	snd_interval_t t;
+	snd_interval_muldivk(hw_param_interval_c(params, rule->deps[0]),
+			 hw_param_interval_c(params, rule->deps[1]),
+			 (unsigned long) rule->private_data, &t);
+	return snd_interval_refine(hw_param_interval(params, rule->var), &t);
+}
+
+static int snd_pcm_hw_rule_mulkdiv(snd_pcm_hw_params_t *params,
+				   const snd_pcm_hw_rule_t *rule)
+{
+	snd_interval_t t;
+	snd_interval_mulkdiv(hw_param_interval_c(params, rule->deps[0]),
+			 (unsigned long) rule->private_data,
+			 hw_param_interval_c(params, rule->deps[1]), &t);
+	return snd_interval_refine(hw_param_interval(params, rule->var), &t);
+}
+
+static int snd_pcm_hw_rule_format(snd_pcm_hw_params_t *params,
+				  const snd_pcm_hw_rule_t *rule)
+{
+	int changed = 0;
+	snd_pcm_format_t k;
+	snd_mask_t *mask = hw_param_mask(params, rule->var);
+	snd_interval_t *i = hw_param_interval(params, rule->deps[0]);
+	for (k = 0; k <= SND_PCM_FORMAT_LAST; k++) {
+		int bits;
+		if (!snd_pcm_format_mask_test(mask, k))
+			continue;
+		bits = snd_pcm_format_physical_width(k);
+		if (bits < 0)
+			continue;
+		if (!snd_interval_test(i, (unsigned int) bits)) {
+			snd_pcm_format_mask_reset(mask, k);
+			if (snd_mask_empty(mask))
+				return -EINVAL;
+			changed = 1;
+		}
+	}
+	return changed;
+}
+
+
+static int snd_pcm_hw_rule_sample_bits(snd_pcm_hw_params_t *params,
+				       const snd_pcm_hw_rule_t *rule)
+{
+	unsigned int min, max;
+	snd_pcm_format_t k;
+	snd_interval_t *i = hw_param_interval(params, rule->var);
+	snd_mask_t *mask = hw_param_mask(params, rule->deps[0]);
+	int c, changed = 0;
+	min = UINT_MAX;
+	max = 0;
+	for (k = 0; k <= SND_PCM_FORMAT_LAST; k++) {
+		int bits;
+		if (!snd_pcm_format_mask_test(mask, k))
+			continue;
+		bits = snd_pcm_format_physical_width(k);
+		if (bits < 0)
+			continue;
+		if (min > (unsigned)bits)
+			min = bits;
+		if (max < (unsigned)bits)
+			max = bits;
+	}
+	c = snd_interval_refine_min(i, min, 0);
+	if (c < 0)
+		return c;
+	if (c)
+		changed = 1;
+	c = snd_interval_refine_max(i, max, 0);
+	if (c < 0)
+		return c;
+	if (c)
+		changed = 1;
+	return changed;
+}
+
+static const snd_pcm_hw_rule_t refine_rules[] = {
+	{
+		.var = SND_PCM_HW_PARAM_FORMAT,
+		.func = snd_pcm_hw_rule_format,
+		.deps = { SND_PCM_HW_PARAM_SAMPLE_BITS, -1 },
+		.private_data = 0,
+	},
+	{
+		.var = SND_PCM_HW_PARAM_SAMPLE_BITS, 
+		.func = snd_pcm_hw_rule_sample_bits,
+		.deps = { SND_PCM_HW_PARAM_FORMAT, 
+			SND_PCM_HW_PARAM_SAMPLE_BITS, -1 },
+		.private_data = 0,
+	},
+	{
+		.var = SND_PCM_HW_PARAM_SAMPLE_BITS, 
+		.func = snd_pcm_hw_rule_div,
+		.deps = { SND_PCM_HW_PARAM_FRAME_BITS,
+			SND_PCM_HW_PARAM_CHANNELS, -1 },
+		.private_data = 0,
+	},
+	{
+		.var = SND_PCM_HW_PARAM_FRAME_BITS, 
+		.func = snd_pcm_hw_rule_mul,
+		.deps = { SND_PCM_HW_PARAM_SAMPLE_BITS,
+			SND_PCM_HW_PARAM_CHANNELS, -1 },
+		.private_data = 0,
+	},
+	{
+		.var = SND_PCM_HW_PARAM_FRAME_BITS, 
+		.func = snd_pcm_hw_rule_mulkdiv,
+		.deps = { SND_PCM_HW_PARAM_PERIOD_BYTES,
+			SND_PCM_HW_PARAM_PERIOD_SIZE, -1 },
+		.private_data = (void*) 8,
+	},
+	{
+		.var = SND_PCM_HW_PARAM_FRAME_BITS, 
+		.func = snd_pcm_hw_rule_mulkdiv,
+		.deps = { SND_PCM_HW_PARAM_BUFFER_BYTES,
+			SND_PCM_HW_PARAM_BUFFER_SIZE, -1 },
+		.private_data = (void*) 8,
+	},
+	{
+		.var = SND_PCM_HW_PARAM_CHANNELS, 
+		.func = snd_pcm_hw_rule_div,
+		.deps = { SND_PCM_HW_PARAM_FRAME_BITS,
+			SND_PCM_HW_PARAM_SAMPLE_BITS, -1 },
+		.private_data = 0,
+	},
+	{
+		.var = SND_PCM_HW_PARAM_RATE, 
+		.func = snd_pcm_hw_rule_mulkdiv,
+		.deps = { SND_PCM_HW_PARAM_PERIOD_SIZE,
+			SND_PCM_HW_PARAM_PERIOD_TIME, -1 },
+		.private_data = (void*) 1000000,
+	},
+	{
+		.var = SND_PCM_HW_PARAM_RATE, 
+		.func = snd_pcm_hw_rule_mulkdiv,
+		.deps = { SND_PCM_HW_PARAM_BUFFER_SIZE,
+			SND_PCM_HW_PARAM_BUFFER_TIME, -1 },
+		.private_data = (void*) 1000000,
+	},
+	{
+		.var = SND_PCM_HW_PARAM_PERIODS, 
+		.func = snd_pcm_hw_rule_div,
+		.deps = { SND_PCM_HW_PARAM_BUFFER_SIZE,
+			SND_PCM_HW_PARAM_PERIOD_SIZE, -1 },
+		.private_data = 0,
+	},
+	{
+		.var = SND_PCM_HW_PARAM_PERIOD_SIZE, 
+		.func = snd_pcm_hw_rule_div,
+		.deps = { SND_PCM_HW_PARAM_BUFFER_SIZE,
+			SND_PCM_HW_PARAM_PERIODS, -1 },
+		.private_data = 0,
+	},
+	{
+		.var = SND_PCM_HW_PARAM_PERIOD_SIZE, 
+		.func = snd_pcm_hw_rule_mulkdiv,
+		.deps = { SND_PCM_HW_PARAM_PERIOD_BYTES,
+			SND_PCM_HW_PARAM_FRAME_BITS, -1 },
+		.private_data = (void*) 8,
+	},
+	{
+		.var = SND_PCM_HW_PARAM_PERIOD_SIZE, 
+		.func = snd_pcm_hw_rule_muldivk,
+		.deps = { SND_PCM_HW_PARAM_PERIOD_TIME,
+			SND_PCM_HW_PARAM_RATE, -1 },
+		.private_data = (void*) 1000000,
+	},
+	{
+		.var = SND_PCM_HW_PARAM_BUFFER_SIZE, 
+		.func = snd_pcm_hw_rule_mul,
+		.deps = { SND_PCM_HW_PARAM_PERIOD_SIZE,
+			SND_PCM_HW_PARAM_PERIODS, -1 },
+		.private_data = 0,
+	},
+	{
+		.var = SND_PCM_HW_PARAM_BUFFER_SIZE, 
+		.func = snd_pcm_hw_rule_mulkdiv,
+		.deps = { SND_PCM_HW_PARAM_BUFFER_BYTES,
+			SND_PCM_HW_PARAM_FRAME_BITS, -1 },
+		.private_data = (void*) 8,
+	},
+	{
+		.var = SND_PCM_HW_PARAM_BUFFER_SIZE, 
+		.func = snd_pcm_hw_rule_muldivk,
+		.deps = { SND_PCM_HW_PARAM_BUFFER_TIME,
+			SND_PCM_HW_PARAM_RATE, -1 },
+		.private_data = (void*) 1000000,
+	},
+	{
+		.var = SND_PCM_HW_PARAM_PERIOD_BYTES, 
+		.func = snd_pcm_hw_rule_muldivk,
+		.deps = { SND_PCM_HW_PARAM_PERIOD_SIZE,
+			SND_PCM_HW_PARAM_FRAME_BITS, -1 },
+		.private_data = (void*) 8,
+	},
+	{
+		.var = SND_PCM_HW_PARAM_BUFFER_BYTES, 
+		.func = snd_pcm_hw_rule_muldivk,
+		.deps = { SND_PCM_HW_PARAM_BUFFER_SIZE,
+			SND_PCM_HW_PARAM_FRAME_BITS, -1 },
+		.private_data = (void*) 8,
+	},
+	{
+		.var = SND_PCM_HW_PARAM_PERIOD_TIME, 
+		.func = snd_pcm_hw_rule_mulkdiv,
+		.deps = { SND_PCM_HW_PARAM_PERIOD_SIZE,
+			SND_PCM_HW_PARAM_RATE, -1 },
+		.private_data = (void*) 1000000,
+	},
+	{
+		.var = SND_PCM_HW_PARAM_BUFFER_TIME, 
+		.func = snd_pcm_hw_rule_mulkdiv,
+		.deps = { SND_PCM_HW_PARAM_BUFFER_SIZE,
+			SND_PCM_HW_PARAM_RATE, -1 },
+		.private_data = (void*) 1000000,
+	},
+};
+
+#define RULES (sizeof(refine_rules) / sizeof(refine_rules[0]))
+
+static const snd_mask_t refine_masks[SND_PCM_HW_PARAM_LAST_MASK - SND_PCM_HW_PARAM_FIRST_MASK + 1] = {
+	[SND_PCM_HW_PARAM_ACCESS - SND_PCM_HW_PARAM_FIRST_MASK] = {
+		.bits = { 0x1f },
+	},
+	[SND_PCM_HW_PARAM_FORMAT - SND_PCM_HW_PARAM_FIRST_MASK] = {
+		.bits = { 0x81ffffff, 0xfff},
+	},
+	[SND_PCM_HW_PARAM_SUBFORMAT - SND_PCM_HW_PARAM_FIRST_MASK] = {
+		.bits = { 0x1 },
+	},
+};
+  
+static const snd_interval_t refine_intervals[SND_PCM_HW_PARAM_LAST_INTERVAL - SND_PCM_HW_PARAM_FIRST_INTERVAL + 1] = {
+	[SND_PCM_HW_PARAM_SAMPLE_BITS - SND_PCM_HW_PARAM_FIRST_INTERVAL] = {
+		.min = 1, .max = UINT_MAX,
+		.openmin = 0, .openmax = 0, .integer = 1, .empty = 0,
+	},
+	[SND_PCM_HW_PARAM_FRAME_BITS - SND_PCM_HW_PARAM_FIRST_INTERVAL] = {
+		.min = 1, .max = UINT_MAX,
+		.openmin = 0, .openmax = 0, .integer = 1, .empty = 0,
+	},
+	[SND_PCM_HW_PARAM_CHANNELS - SND_PCM_HW_PARAM_FIRST_INTERVAL] = {
+		.min = 1, .max = UINT_MAX,
+		.openmin = 0, .openmax = 0, .integer = 1, .empty = 0,
+	},
+	[SND_PCM_HW_PARAM_RATE - SND_PCM_HW_PARAM_FIRST_INTERVAL] = {
+		.min = 1, .max = UINT_MAX,
+		.openmin = 0, .openmax = 0, .integer = 0, .empty = 0,
+	},
+	[SND_PCM_HW_PARAM_PERIOD_TIME - SND_PCM_HW_PARAM_FIRST_INTERVAL] = {
+		.min = 0, .max = UINT_MAX,
+		.openmin = 0, .openmax = 0, .integer = 0, .empty = 0,
+	},
+	[SND_PCM_HW_PARAM_PERIOD_SIZE - SND_PCM_HW_PARAM_FIRST_INTERVAL] = {
+		.min = 0, .max = UINT_MAX,
+		.openmin = 0, .openmax = 0, .integer = 0, .empty = 0,
+	},
+	[SND_PCM_HW_PARAM_PERIOD_BYTES - SND_PCM_HW_PARAM_FIRST_INTERVAL] = {
+		.min = 0, .max = UINT_MAX,
+		.openmin = 0, .openmax = 0, .integer = 0, .empty = 0,
+	},
+	[SND_PCM_HW_PARAM_PERIODS - SND_PCM_HW_PARAM_FIRST_INTERVAL] = {
+		.min = 0, .max = UINT_MAX,
+		.openmin = 0, .openmax = 0, .integer = 0, .empty = 0,
+	},
+	[SND_PCM_HW_PARAM_BUFFER_TIME - SND_PCM_HW_PARAM_FIRST_INTERVAL] = {
+		.min = 1, .max = UINT_MAX,
+		.openmin = 0, .openmax = 0, .integer = 0, .empty = 0,
+	},
+	[SND_PCM_HW_PARAM_BUFFER_SIZE - SND_PCM_HW_PARAM_FIRST_INTERVAL] = {
+		.min = 1, .max = UINT_MAX,
+		.openmin = 0, .openmax = 0, .integer = 1, .empty = 0,
+	},
+	[SND_PCM_HW_PARAM_BUFFER_BYTES - SND_PCM_HW_PARAM_FIRST_INTERVAL] = {
+		.min = 1, .max = UINT_MAX,
+		.openmin = 0, .openmax = 0, .integer = 1, .empty = 0,
+	},
+	[SND_PCM_HW_PARAM_TICK_TIME - SND_PCM_HW_PARAM_FIRST_INTERVAL] = {
+		.min = 0, .max = UINT_MAX,
+		.openmin = 0, .openmax = 0, .integer = 0, .empty = 0,
+	},
+};
+
+#if 0
+#define RULES_DEBUG
+#endif
+
+int snd_pcm_hw_refine_soft(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params)
+{
+	unsigned int k;
+	snd_interval_t *i;
+	unsigned int rstamps[RULES];
+	unsigned int vstamps[SND_PCM_HW_PARAM_LAST_INTERVAL + 1];
+	unsigned int stamp = 2;
+	int changed, again;
+#ifdef RULES_DEBUG
+	snd_output_t *log;
+	snd_output_stdio_attach(&log, stderr, 0);
+	snd_output_printf(log, "refine_soft '%s' (begin)\n", pcm->name);
+	snd_pcm_hw_params_dump(params, log);
+#endif
+
+	for (k = SND_PCM_HW_PARAM_FIRST_MASK; k <= SND_PCM_HW_PARAM_LAST_MASK; k++) {
+		if (!(params->rmask & (1 << k)))
+			continue;
+		changed = snd_mask_refine(hw_param_mask(params, k),
+					  &refine_masks[k - SND_PCM_HW_PARAM_FIRST_MASK]);
+		if (changed)
+			params->cmask |= 1 << k;
+		if (changed < 0)
+			goto _err;
+	}
+
+	for (k = SND_PCM_HW_PARAM_FIRST_INTERVAL; k <= SND_PCM_HW_PARAM_LAST_INTERVAL; k++) {
+		if (!(params->rmask & (1 << k)))
+			continue;
+		changed = snd_interval_refine(hw_param_interval(params, k),
+				      &refine_intervals[k - SND_PCM_HW_PARAM_FIRST_INTERVAL]);
+		if (changed)
+			params->cmask |= 1 << k;
+		if (changed < 0)
+			goto _err;
+	}
+
+	for (k = 0; k < RULES; k++)
+		rstamps[k] = 0;
+	for (k = 0; k <= SND_PCM_HW_PARAM_LAST_INTERVAL; k++)
+		vstamps[k] = (params->rmask & (1 << k)) ? 1 : 0;
+	do {
+		again = 0;
+		for (k = 0; k < RULES; k++) {
+			const snd_pcm_hw_rule_t *r = &refine_rules[k];
+			unsigned int d;
+			int doit = 0;
+			for (d = 0; r->deps[d] >= 0; d++) {
+				if (vstamps[r->deps[d]] > rstamps[k]) {
+					doit = 1;
+					break;
+				}
+			}
+			if (!doit)
+				continue;
+#ifdef RULES_DEBUG
+			snd_output_printf(log, "Rule %d (%p): ", k, r->func);
+			if (r->var >= 0) {
+				snd_output_printf(log, "%s=", snd_pcm_hw_param_name(r->var));
+				snd_pcm_hw_param_dump(params, r->var, log);
+				snd_output_puts(log, " -> ");
+			}
+#endif
+			changed = r->func(params, r);
+#ifdef RULES_DEBUG
+			if (r->var >= 0)
+				snd_pcm_hw_param_dump(params, r->var, log);
+			for (d = 0; r->deps[d] >= 0; d++) {
+				snd_output_printf(log, " %s=", snd_pcm_hw_param_name(r->deps[d]));
+				snd_pcm_hw_param_dump(params, r->deps[d], log);
+			}
+			snd_output_putc(log, '\n');
+#endif
+			rstamps[k] = stamp;
+			if (changed && r->var >= 0) {
+				params->cmask |= 1 << r->var;
+				vstamps[r->var] = stamp;
+				again = 1;
+			}
+			if (changed < 0)
+				goto _err;
+			stamp++;
+		}
+	} while (again);
+	if (!params->msbits) {
+		i = hw_param_interval(params, SND_PCM_HW_PARAM_SAMPLE_BITS);
+		if (snd_interval_single(i))
+			params->msbits = snd_interval_value(i);
+	}
+
+	if (!params->rate_den) {
+		i = hw_param_interval(params, SND_PCM_HW_PARAM_RATE);
+		if (snd_interval_single(i)) {
+			params->rate_num = snd_interval_value(i);
+			params->rate_den = 1;
+		}
+	}
+	params->rmask = 0;
+	return 0;
+ _err:
+#ifdef RULES_DEBUG
+	snd_output_printf(log, "refine_soft '%s' (end-%i)\n", pcm->name, changed);
+	snd_pcm_hw_params_dump(params, log);
+	snd_output_close(log);
+#endif
+	return changed;
+}
+
+int _snd_pcm_hw_params_refine(snd_pcm_hw_params_t *params,
+			      unsigned int vars,
+			      const snd_pcm_hw_params_t *src)
+{
+	int changed, err = 0;
+	unsigned int k;
+	for (k = 0; k <= SND_PCM_HW_PARAM_LAST_INTERVAL; ++k) {
+		if (!(vars & (1 << k)))
+			continue;
+		changed = _snd_pcm_hw_param_refine(params, k, src);
+		if (changed < 0)
+			err = changed;
+	}
+	params->info &= src->info;
+	params->flags = src->flags; /* propagate all flags to slave */
+	return err;
+}
+
+int snd_pcm_hw_refine_slave(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
+			    int (*cprepare)(snd_pcm_t *pcm,
+					    snd_pcm_hw_params_t *params),
+			    int (*cchange)(snd_pcm_t *pcm,
+					   snd_pcm_hw_params_t *params,
+					   snd_pcm_hw_params_t *sparams),
+			    int (*sprepare)(snd_pcm_t *pcm,
+					    snd_pcm_hw_params_t *params),
+			    int (*schange)(snd_pcm_t *pcm,
+					   snd_pcm_hw_params_t *params,
+					   snd_pcm_hw_params_t *sparams),
+			    int (*srefine)(snd_pcm_t *pcm,
+					   snd_pcm_hw_params_t *sparams))
+
+{
+#ifdef RULES_DEBUG
+	snd_output_t *log;
+#endif
+	snd_pcm_hw_params_t sparams;
+	int err;
+	unsigned int cmask, changed;
+#ifdef RULES_DEBUG
+	snd_output_stdio_attach(&log, stderr, 0);
+#endif
+	err = cprepare(pcm, params);
+	if (err < 0)
+		return err;
+	err = sprepare(pcm, &sparams);
+	if (err < 0) {
+		SNDERR("Slave PCM not usable");
+		return err;
+	}
+#ifdef RULES_DEBUG
+	snd_output_printf(log, "hw_refine_slave - enter '%s'\n", pcm->name);
+#endif
+	do {
+		cmask = params->cmask;
+		params->cmask = 0;
+#ifdef RULES_DEBUG
+		snd_output_printf(log, "schange '%s' (client)\n", pcm->name);
+		snd_pcm_hw_params_dump(params, log);
+		snd_output_printf(log, "schange '%s' (slave)\n", pcm->name);
+		snd_pcm_hw_params_dump(&sparams, log);
+#endif
+		err = schange(pcm, params, &sparams);
+		if (err >= 0) {
+#ifdef RULES_DEBUG
+			snd_output_printf(log, "srefine '%s' (client)\n", pcm->name);
+			snd_pcm_hw_params_dump(params, log);
+			snd_output_printf(log, "srefine '%s' (slave)\n", pcm->name);
+			snd_pcm_hw_params_dump(&sparams, log);
+#endif
+			err = srefine(pcm, &sparams);
+			if (err < 0) {
+#ifdef RULES_DEBUG
+				snd_output_printf(log, "srefine '%s', err < 0 (%i) (client)\n", pcm->name, err);
+				snd_pcm_hw_params_dump(params, log);
+				snd_output_printf(log, "srefine '%s', err < 0 (%i) (slave)\n", pcm->name, err);
+				snd_pcm_hw_params_dump(&sparams, log);
+#endif
+				cchange(pcm, params, &sparams);
+				return err;
+			}
+		} else {
+#ifdef RULES_DEBUG
+			snd_output_printf(log, "schange '%s', err < 0 (%i) (client)\n", pcm->name, err);
+			snd_pcm_hw_params_dump(params, log);
+			snd_output_printf(log, "schange '%s', err < 0 (%i) (slave)\n", pcm->name, err);
+			snd_pcm_hw_params_dump(&sparams, log);
+#endif
+			cchange(pcm, params, &sparams);
+			return err;
+		}
+#ifdef RULES_DEBUG
+		snd_output_printf(log, "cchange '%s'\n", pcm->name);
+#endif
+		err = cchange(pcm, params, &sparams);
+		if (err < 0)
+			return err;
+#ifdef RULES_DEBUG
+		snd_output_printf(log, "refine_soft '%s'\n", pcm->name);
+#endif
+		err = snd_pcm_hw_refine_soft(pcm, params);
+		changed = params->cmask;
+		params->cmask |= cmask;
+		if (err < 0)
+			return err;
+#ifdef RULES_DEBUG
+		snd_output_printf(log, "refine_soft ok '%s'\n", pcm->name);
+#endif
+	} while (changed);
+#ifdef RULES_DEBUG
+	snd_output_printf(log, "refine_slave - leave '%s'\n", pcm->name);
+	snd_output_close(log);
+#endif
+	return 0;
+}
+
+int snd_pcm_hw_params_slave(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
+			    int (*cchange)(snd_pcm_t *pcm,
+					   snd_pcm_hw_params_t *params,
+					   snd_pcm_hw_params_t *sparams),
+			    int (*sprepare)(snd_pcm_t *pcm,
+					    snd_pcm_hw_params_t *params),
+			    int (*schange)(snd_pcm_t *pcm,
+					   snd_pcm_hw_params_t *params,
+					   snd_pcm_hw_params_t *sparams),
+			    int (*sparams)(snd_pcm_t *pcm,
+					   snd_pcm_hw_params_t *sparams))
+
+{
+	snd_pcm_hw_params_t slave_params;
+	int err;
+	err = sprepare(pcm, &slave_params);
+	assert(err >= 0);
+	err = schange(pcm, params, &slave_params);
+	assert(err >= 0);
+	err = sparams(pcm, &slave_params);
+	if (err < 0)
+		cchange(pcm, params, &slave_params);
+	return err;
+}
+
+static int snd_pcm_sw_params_default(snd_pcm_t *pcm, snd_pcm_sw_params_t *params)
+{
+	assert(pcm && params);
+	assert(pcm->setup);
+	params->tstamp_mode = SND_PCM_TSTAMP_NONE;
+	params->period_step = 1;
+	params->sleep_min = 0;
+	params->avail_min = pcm->period_size;
+	params->xfer_align = 1;
+	params->start_threshold = 1;
+	params->stop_threshold = pcm->buffer_size;
+	params->silence_threshold = 0;
+	params->silence_size = 0;
+	params->boundary = pcm->buffer_size;
+	while (params->boundary * 2 <= LONG_MAX - pcm->buffer_size)
+		params->boundary *= 2;
+	return 0;
+}
+
+#if 0
+#define REFINE_DEBUG
+#endif
+
+int snd_pcm_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	int res;
+#ifdef REFINE_DEBUG
+	snd_output_t *log;
+	snd_output_stdio_attach(&log, stderr, 0);
+#endif
+	assert(pcm && params);
+#ifdef REFINE_DEBUG
+	snd_output_printf(log, "REFINE called:\n");
+	snd_pcm_hw_params_dump(params, log);
+#endif
+	res = pcm->ops->hw_refine(pcm->op_arg, params);
+#ifdef REFINE_DEBUG
+	snd_output_printf(log, "refine done - result = %i\n", res);
+	snd_pcm_hw_params_dump(params, log);
+	snd_output_close(log);
+#endif
+	return res;
+}
+
+/* Install one of the configurations present in configuration
+   space defined by PARAMS.
+   The configuration chosen is that obtained fixing in this order:
+   first access
+   first format
+   first subformat
+   min channels
+   min rate
+   min period_size
+   max periods
+   Return 0 on success otherwise a negative error code
+*/
+int _snd_pcm_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	int err;
+	snd_pcm_sw_params_t sw;
+	int fb, min_align;
+	err = snd_pcm_hw_refine(pcm, params);
+	if (err < 0)
+		return err;
+	snd_pcm_hw_params_choose(pcm, params);
+	if (pcm->setup) {
+		err = snd_pcm_hw_free(pcm);
+		if (err < 0)
+			return err;
+	}
+	err = pcm->ops->hw_params(pcm->op_arg, params);
+	if (err < 0)
+		return err;
+
+	pcm->setup = 1;
+	INTERNAL(snd_pcm_hw_params_get_access)(params, &pcm->access);
+	INTERNAL(snd_pcm_hw_params_get_format)(params, &pcm->format);
+	INTERNAL(snd_pcm_hw_params_get_subformat)(params, &pcm->subformat);
+	INTERNAL(snd_pcm_hw_params_get_channels)(params, &pcm->channels);
+	INTERNAL(snd_pcm_hw_params_get_rate)(params, &pcm->rate, 0);
+	INTERNAL(snd_pcm_hw_params_get_period_time)(params, &pcm->period_time, 0);
+	INTERNAL(snd_pcm_hw_params_get_period_size)(params, &pcm->period_size, 0);
+	INTERNAL(snd_pcm_hw_params_get_buffer_size)(params, &pcm->buffer_size);
+	pcm->sample_bits = snd_pcm_format_physical_width(pcm->format);
+	pcm->frame_bits = pcm->sample_bits * pcm->channels;
+	fb = pcm->frame_bits;
+	min_align = 1;
+	while (fb % 8) {
+		fb *= 2;
+		min_align *= 2;
+	}
+	pcm->min_align = min_align;
+	
+	pcm->hw_flags = params->flags;
+	pcm->info = params->info;
+	pcm->msbits = params->msbits;
+	pcm->rate_num = params->rate_num;
+	pcm->rate_den = params->rate_den;
+	pcm->fifo_size = params->fifo_size;
+	
+	/* Default sw params */
+	memset(&sw, 0, sizeof(sw));
+	snd_pcm_sw_params_default(pcm, &sw);
+	err = snd_pcm_sw_params(pcm, &sw);
+	assert(err >= 0);
+
+	if (pcm->mmap_rw || 
+	    pcm->access == SND_PCM_ACCESS_MMAP_INTERLEAVED ||
+	    pcm->access == SND_PCM_ACCESS_MMAP_NONINTERLEAVED ||
+	    pcm->access == SND_PCM_ACCESS_MMAP_COMPLEX) {
+		err = snd_pcm_mmap(pcm);
+	}
+	if (err < 0)
+		return err;
+	return 0;
+}
+
diff --git a/src/pcm/pcm_plug.c b/src/pcm/pcm_plug.c
new file mode 100644
index 0000000..e9d2923
--- /dev/null
+++ b/src/pcm/pcm_plug.c
@@ -0,0 +1,1323 @@
+/*
+ * \file pcm/pcm_plug.c
+ * \ingroup PCM_Plugins
+ * \brief PCM Route & Volume Plugin Interface
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \date 2000-2001
+ */
+/*
+ *  PCM - Plug
+ *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+#include "pcm_local.h"
+#include "pcm_plugin.h"
+
+#ifndef PIC
+/* entry for static linking */
+const char *_snd_module_pcm_plug = "";
+#endif
+
+#ifndef DOC_HIDDEN
+
+enum snd_pcm_plug_route_policy {
+	PLUG_ROUTE_POLICY_NONE,
+	PLUG_ROUTE_POLICY_DEFAULT,
+	PLUG_ROUTE_POLICY_COPY,
+	PLUG_ROUTE_POLICY_AVERAGE,
+	PLUG_ROUTE_POLICY_DUP,
+};
+
+typedef struct {
+	snd_pcm_generic_t gen;
+	snd_pcm_t *req_slave;
+	snd_pcm_format_t sformat;
+	int schannels;
+	int srate;
+	const snd_config_t *rate_converter;
+	enum snd_pcm_plug_route_policy route_policy;
+	snd_pcm_route_ttable_entry_t *ttable;
+	int ttable_ok, ttable_last;
+	unsigned int tt_ssize, tt_cused, tt_sused;
+} snd_pcm_plug_t;
+
+#endif
+
+static int snd_pcm_plug_close(snd_pcm_t *pcm)
+{
+	snd_pcm_plug_t *plug = pcm->private_data;
+	int err, result = 0;
+	free(plug->ttable);
+	assert(plug->gen.slave == plug->req_slave);
+	if (plug->gen.close_slave) {
+		snd_pcm_unlink_hw_ptr(pcm, plug->req_slave);
+		snd_pcm_unlink_appl_ptr(pcm, plug->req_slave);
+		err = snd_pcm_close(plug->req_slave);
+		if (err < 0)
+			result = err;
+	}
+	free(plug);
+	return result;
+}
+
+static int snd_pcm_plug_info(snd_pcm_t *pcm, snd_pcm_info_t *info)
+{
+	snd_pcm_plug_t *plug = pcm->private_data;
+	snd_pcm_t *slave = plug->req_slave;
+	int err;
+	
+	if ((err = snd_pcm_info(slave, info)) < 0)
+		return err;
+	return 0;
+}
+
+static const snd_pcm_format_t linear_preferred_formats[] = {
+#ifdef SND_LITTLE_ENDIAN
+	SND_PCM_FORMAT_S16_LE,
+	SND_PCM_FORMAT_U16_LE,
+	SND_PCM_FORMAT_S16_BE,
+	SND_PCM_FORMAT_U16_BE,
+#else
+	SND_PCM_FORMAT_S16_BE,
+	SND_PCM_FORMAT_U16_BE,
+	SND_PCM_FORMAT_S16_LE,
+	SND_PCM_FORMAT_U16_LE,
+#endif
+#ifdef SND_LITTLE_ENDIAN
+	SND_PCM_FORMAT_S32_LE,
+	SND_PCM_FORMAT_U32_LE,
+	SND_PCM_FORMAT_S32_BE,
+	SND_PCM_FORMAT_U32_BE,
+#else
+	SND_PCM_FORMAT_S32_BE,
+	SND_PCM_FORMAT_U32_BE,
+	SND_PCM_FORMAT_S32_LE,
+	SND_PCM_FORMAT_U32_LE,
+#endif
+	SND_PCM_FORMAT_S8,
+	SND_PCM_FORMAT_U8,
+#ifdef SND_LITTLE_ENDIAN
+	SND_PCM_FORMAT_FLOAT_LE,
+	SND_PCM_FORMAT_FLOAT64_LE,
+	SND_PCM_FORMAT_FLOAT_BE,
+	SND_PCM_FORMAT_FLOAT64_BE,
+#else
+	SND_PCM_FORMAT_FLOAT_BE,
+	SND_PCM_FORMAT_FLOAT64_BE,
+	SND_PCM_FORMAT_FLOAT_LE,
+	SND_PCM_FORMAT_FLOAT64_LE,
+#endif
+#ifdef SND_LITTLE_ENDIAN
+	SND_PCM_FORMAT_S24_LE,
+	SND_PCM_FORMAT_U24_LE,
+	SND_PCM_FORMAT_S24_BE,
+	SND_PCM_FORMAT_U24_BE,
+#else
+	SND_PCM_FORMAT_S24_BE,
+	SND_PCM_FORMAT_U24_BE,
+	SND_PCM_FORMAT_S24_LE,
+	SND_PCM_FORMAT_U24_LE,
+#endif
+#ifdef SND_LITTLE_ENDIAN
+	SND_PCM_FORMAT_S24_3LE,
+	SND_PCM_FORMAT_U24_3LE,
+	SND_PCM_FORMAT_S24_3BE,
+	SND_PCM_FORMAT_U24_3BE,
+#else
+	SND_PCM_FORMAT_S24_3BE,
+	SND_PCM_FORMAT_U24_3BE,
+	SND_PCM_FORMAT_S24_3LE,
+	SND_PCM_FORMAT_U24_3LE,
+#endif
+#ifdef SND_LITTLE_ENDIAN
+	SND_PCM_FORMAT_S20_3LE,
+	SND_PCM_FORMAT_U20_3LE,
+	SND_PCM_FORMAT_S20_3BE,
+	SND_PCM_FORMAT_U20_3BE,
+#else
+	SND_PCM_FORMAT_S20_3BE,
+	SND_PCM_FORMAT_U20_3BE,
+	SND_PCM_FORMAT_S20_3LE,
+	SND_PCM_FORMAT_U20_3LE,
+#endif
+#ifdef SND_LITTLE_ENDIAN
+	SND_PCM_FORMAT_S18_3LE,
+	SND_PCM_FORMAT_U18_3LE,
+	SND_PCM_FORMAT_S18_3BE,
+	SND_PCM_FORMAT_U18_3BE,
+#else
+	SND_PCM_FORMAT_S18_3BE,
+	SND_PCM_FORMAT_U18_3BE,
+	SND_PCM_FORMAT_S18_3LE,
+	SND_PCM_FORMAT_U18_3LE,
+#endif
+};
+
+#if defined(BUILD_PCM_PLUGIN_MULAW) || \
+	defined(BUILD_PCM_PLUGIN_ALAW) || \
+	defined(BUILD_PCM_PLUGIN_ADPCM)
+#define BUILD_PCM_NONLINEAR
+#endif
+
+#ifdef BUILD_PCM_NONLINEAR
+static const snd_pcm_format_t nonlinear_preferred_formats[] = {
+#ifdef BUILD_PCM_PLUGIN_MULAW
+	SND_PCM_FORMAT_MU_LAW,
+#endif
+#ifdef BUILD_PCM_PLUGIN_ALAW
+	SND_PCM_FORMAT_A_LAW,
+#endif
+#ifdef BUILD_PCM_PLUGIN_ADPCM
+	SND_PCM_FORMAT_IMA_ADPCM,
+#endif
+};
+#endif
+
+#ifdef BUILD_PCM_PLUGIN_LFLOAT
+static const snd_pcm_format_t float_preferred_formats[] = {
+#ifdef SND_LITTLE_ENDIAN
+	SND_PCM_FORMAT_FLOAT_LE,
+	SND_PCM_FORMAT_FLOAT64_LE,
+	SND_PCM_FORMAT_FLOAT_BE,
+	SND_PCM_FORMAT_FLOAT64_BE,
+#else
+	SND_PCM_FORMAT_FLOAT_BE,
+	SND_PCM_FORMAT_FLOAT64_BE,
+	SND_PCM_FORMAT_FLOAT_LE,
+	SND_PCM_FORMAT_FLOAT64_LE,
+#endif
+};
+#endif
+
+static const char linear_format_widths[32] = {
+	0, 0, 0, 0, 0, 0, 0, 1,
+	0, 0, 0, 0, 0, 0, 0, 1,
+	0, 1, 0, 1, 0, 0, 0, 1,
+	0, 0, 0, 0, 0, 0, 0, 1,
+};
+
+static int check_linear_format(const snd_pcm_format_mask_t *format_mask, int wid, int sgn, int ed)
+{
+	int e, s;
+	if (! linear_format_widths[wid - 1])
+		return SND_PCM_FORMAT_UNKNOWN;
+	for (e = 0; e < 2; e++) {
+		for (s = 0; s < 2; s++) {
+			int pw = ((wid + 7) / 8) * 8;
+			for (; pw <= 32; pw += 8) {
+				snd_pcm_format_t f;
+				f = snd_pcm_build_linear_format(wid, pw, sgn, ed);
+				if (f != SND_PCM_FORMAT_UNKNOWN &&
+				    snd_pcm_format_mask_test(format_mask, f))
+					return f;
+			}
+			sgn = !sgn;
+		}
+		ed = !ed;
+	}
+	return SND_PCM_FORMAT_UNKNOWN;
+}
+
+static snd_pcm_format_t snd_pcm_plug_slave_format(snd_pcm_format_t format, const snd_pcm_format_mask_t *format_mask)
+{
+	int w, w1, u, e;
+	snd_pcm_format_t f;
+	snd_pcm_format_mask_t lin = { SND_PCM_FMTBIT_LINEAR };
+	snd_pcm_format_mask_t fl = {
+#ifdef BUILD_PCM_PLUGIN_LFLOAT
+		SND_PCM_FMTBIT_FLOAT
+#else
+		{ 0 }
+#endif
+	};
+	if (snd_pcm_format_mask_test(format_mask, format))
+		return format;
+	if (!snd_pcm_format_mask_test(&lin, format) &&
+	    !snd_pcm_format_mask_test(&fl, format)) {
+		unsigned int i;
+		switch (format) {
+#ifdef BUILD_PCM_PLUGIN_MULAW
+		case SND_PCM_FORMAT_MU_LAW:
+#endif
+#ifdef BUILD_PCM_PLUGIN_ALAW
+		case SND_PCM_FORMAT_A_LAW:
+#endif
+#ifdef BUILD_PCM_PLUGIN_ADPCM
+		case SND_PCM_FORMAT_IMA_ADPCM:
+#endif
+			for (i = 0; i < sizeof(linear_preferred_formats) / sizeof(linear_preferred_formats[0]); ++i) {
+				snd_pcm_format_t f = linear_preferred_formats[i];
+				if (snd_pcm_format_mask_test(format_mask, f))
+					return f;
+			}
+			/* Fall through */
+		default:
+			return SND_PCM_FORMAT_UNKNOWN;
+		}
+
+	}
+	snd_mask_intersect(&lin, format_mask);
+	snd_mask_intersect(&fl, format_mask);
+	if (snd_mask_empty(&lin) && snd_mask_empty(&fl)) {
+#ifdef BUILD_PCM_NONLINEAR
+		unsigned int i;
+		for (i = 0; i < sizeof(nonlinear_preferred_formats) / sizeof(nonlinear_preferred_formats[0]); ++i) {
+			snd_pcm_format_t f = nonlinear_preferred_formats[i];
+			if (snd_pcm_format_mask_test(format_mask, f))
+				return f;
+		}
+#endif
+		return SND_PCM_FORMAT_UNKNOWN;
+	}
+#ifdef BUILD_PCM_PLUGIN_LFLOAT
+	if (snd_pcm_format_float(format)) {
+		if (snd_pcm_format_mask_test(&fl, format)) {
+			unsigned int i;
+			for (i = 0; i < sizeof(float_preferred_formats) / sizeof(float_preferred_formats[0]); ++i) {
+				snd_pcm_format_t f = float_preferred_formats[i];
+				if (snd_pcm_format_mask_test(format_mask, f))
+					return f;
+			}
+		}
+		w = 32;
+		u = 0;
+		e = snd_pcm_format_big_endian(format);
+	} else
+#endif
+	if (snd_mask_empty(&lin)) {
+#ifdef BUILD_PCM_PLUGIN_LFLOAT
+		unsigned int i;
+		for (i = 0; i < sizeof(float_preferred_formats) / sizeof(float_preferred_formats[0]); ++i) {
+			snd_pcm_format_t f = float_preferred_formats[i];
+			if (snd_pcm_format_mask_test(format_mask, f))
+				return f;
+		}
+#endif
+		return SND_PCM_FORMAT_UNKNOWN;
+	} else {
+		w = snd_pcm_format_width(format);
+		u = snd_pcm_format_unsigned(format);
+		e = snd_pcm_format_big_endian(format);
+	}
+	for (w1 = w; w1 <= 32; w1++) {
+		f = check_linear_format(format_mask, w1, u, e);
+		if (f != SND_PCM_FORMAT_UNKNOWN)
+			return f;
+	}
+	for (w1 = w - 1; w1 > 0; w1--) {
+		f = check_linear_format(format_mask, w1, u, e);
+		if (f != SND_PCM_FORMAT_UNKNOWN)
+			return f;
+	}
+	return SND_PCM_FORMAT_UNKNOWN;
+}
+
+static void snd_pcm_plug_clear(snd_pcm_t *pcm)
+{
+	snd_pcm_plug_t *plug = pcm->private_data;
+	snd_pcm_t *slave = plug->req_slave;
+	/* Clear old plugins */
+	if (plug->gen.slave != slave) {
+		snd_pcm_unlink_hw_ptr(pcm, plug->gen.slave);
+		snd_pcm_unlink_appl_ptr(pcm, plug->gen.slave);
+		snd_pcm_close(plug->gen.slave);
+		plug->gen.slave = slave;
+		pcm->fast_ops = slave->fast_ops;
+		pcm->fast_op_arg = slave->fast_op_arg;
+	}
+}
+
+#ifndef DOC_HIDDEN
+typedef struct {
+	snd_pcm_access_t access;
+	snd_pcm_format_t format;
+	unsigned int channels;
+	unsigned int rate;
+} snd_pcm_plug_params_t;
+#endif
+
+#ifdef BUILD_PCM_PLUGIN_RATE
+static int snd_pcm_plug_change_rate(snd_pcm_t *pcm, snd_pcm_t **new, snd_pcm_plug_params_t *clt, snd_pcm_plug_params_t *slv)
+{
+	snd_pcm_plug_t *plug = pcm->private_data;
+	int err;
+	if (clt->rate == slv->rate)
+		return 0;
+	assert(snd_pcm_format_linear(slv->format));
+	err = snd_pcm_rate_open(new, NULL, slv->format, slv->rate, plug->rate_converter,
+				plug->gen.slave, plug->gen.slave != plug->req_slave);
+	if (err < 0)
+		return err;
+	slv->access = clt->access;
+	slv->rate = clt->rate;
+	if (snd_pcm_format_linear(clt->format))
+		slv->format = clt->format;
+	return 1;
+}
+#endif
+
+#ifdef BUILD_PCM_PLUGIN_ROUTE
+static int snd_pcm_plug_change_channels(snd_pcm_t *pcm, snd_pcm_t **new, snd_pcm_plug_params_t *clt, snd_pcm_plug_params_t *slv)
+{
+	snd_pcm_plug_t *plug = pcm->private_data;
+	unsigned int tt_ssize, tt_cused, tt_sused;
+	snd_pcm_route_ttable_entry_t *ttable;
+	int err;
+	if (clt->channels == slv->channels &&
+	    (!plug->ttable || !plug->ttable_last))
+		return 0;
+	if (clt->rate != slv->rate &&
+	    clt->channels > slv->channels)
+		return 0;
+	assert(snd_pcm_format_linear(slv->format));
+	tt_ssize = slv->channels;
+	tt_cused = clt->channels;
+	tt_sused = slv->channels;
+	ttable = alloca(tt_cused * tt_sused * sizeof(*ttable));
+	if (plug->ttable) {	/* expand or shrink table */
+		unsigned int c = 0, s = 0;
+		for (c = 0; c < tt_cused; c++) {
+			for (s = 0; s < tt_sused; s++) {
+				snd_pcm_route_ttable_entry_t v;
+				if (c >= plug->tt_cused)
+					v = 0;
+				else if (s >= plug->tt_sused)
+					v = 0;
+				else
+					v = plug->ttable[c * plug->tt_ssize + s];
+				ttable[c * tt_ssize + s] = v;
+			}
+		}
+		plug->ttable_ok = 1;
+	} else {
+		unsigned int k;
+		unsigned int c = 0, s = 0;
+		enum snd_pcm_plug_route_policy rpolicy = plug->route_policy;
+		int n;
+		for (k = 0; k < tt_cused * tt_sused; ++k)
+			ttable[k] = 0;
+		if (rpolicy == PLUG_ROUTE_POLICY_DEFAULT) {
+			rpolicy = PLUG_ROUTE_POLICY_COPY;
+			/* it's hack for mono conversion */
+			if (clt->channels == 1 || slv->channels == 1)
+				rpolicy = PLUG_ROUTE_POLICY_AVERAGE;
+		}
+		switch (rpolicy) {
+		case PLUG_ROUTE_POLICY_AVERAGE:
+		case PLUG_ROUTE_POLICY_DUP:
+			if (clt->channels > slv->channels) {
+				n = clt->channels;
+			} else {
+				n = slv->channels;
+			}
+			while (n-- > 0) {
+				snd_pcm_route_ttable_entry_t v = SND_PCM_PLUGIN_ROUTE_FULL;
+				if (rpolicy == PLUG_ROUTE_POLICY_AVERAGE) {
+					if (pcm->stream == SND_PCM_STREAM_PLAYBACK &&
+					    clt->channels > slv->channels) {
+						int srcs = clt->channels / slv->channels;
+						if (s < clt->channels % slv->channels)
+							srcs++;
+						v /= srcs;
+					} else if (pcm->stream == SND_PCM_STREAM_CAPTURE &&
+						   slv->channels > clt->channels) {
+							int srcs = slv->channels / clt->channels;
+						if (s < slv->channels % clt->channels)
+							srcs++;
+						v /= srcs;
+					}
+				}
+				ttable[c * tt_ssize + s] = v;
+				if (++c == clt->channels)
+					c = 0;
+				if (++s == slv->channels)
+					s = 0;
+			}
+			break;
+		case PLUG_ROUTE_POLICY_COPY:
+			if (clt->channels < slv->channels) {
+				n = clt->channels;
+			} else {
+				n = slv->channels;
+			}
+			for (c = 0; (int)c < n; c++)
+				ttable[c * tt_ssize + c] = SND_PCM_PLUGIN_ROUTE_FULL;
+			break;
+		default:
+			SNDERR("Invalid route policy");
+			break;
+		}
+	}
+	err = snd_pcm_route_open(new, NULL, slv->format, (int) slv->channels, ttable, tt_ssize, tt_cused, tt_sused, plug->gen.slave, plug->gen.slave != plug->req_slave);
+	if (err < 0)
+		return err;
+	slv->channels = clt->channels;
+	slv->access = clt->access;
+	if (snd_pcm_format_linear(clt->format))
+		slv->format = clt->format;
+	return 1;
+}
+#endif
+
+static int snd_pcm_plug_change_format(snd_pcm_t *pcm, snd_pcm_t **new, snd_pcm_plug_params_t *clt, snd_pcm_plug_params_t *slv)
+{
+	snd_pcm_plug_t *plug = pcm->private_data;
+	int err;
+	snd_pcm_format_t cfmt;
+	int (*f)(snd_pcm_t **_pcm, const char *name, snd_pcm_format_t sformat, snd_pcm_t *slave, int close_slave);
+
+	/* No conversion is needed */
+	if (clt->format == slv->format &&
+	    clt->rate == slv->rate &&
+	    clt->channels == slv->channels)
+		return 0;
+
+	if (snd_pcm_format_linear(slv->format)) {
+		/* Conversion is done in another plugin */
+		if (clt->rate != slv->rate ||
+		    clt->channels != slv->channels)
+			return 0;
+		cfmt = clt->format;
+		switch (clt->format) {
+#ifdef BUILD_PCM_PLUGIN_MULAW
+		case SND_PCM_FORMAT_MU_LAW:
+			f = snd_pcm_mulaw_open;
+			break;
+#endif
+#ifdef BUILD_PCM_PLUGIN_ALAW
+		case SND_PCM_FORMAT_A_LAW:
+			f = snd_pcm_alaw_open;
+			break;
+#endif
+#ifdef BUILD_PCM_PLUGIN_ADPCM
+		case SND_PCM_FORMAT_IMA_ADPCM:
+			f = snd_pcm_adpcm_open;
+			break;
+#endif
+		default:
+#ifdef BUILD_PCM_PLUGIN_LFLOAT
+			if (snd_pcm_format_float(clt->format))
+				f = snd_pcm_lfloat_open;
+
+			else
+#endif
+				f = snd_pcm_linear_open;
+			break;
+		}
+#ifdef BUILD_PCM_PLUGIN_LFLOAT
+	} else if (snd_pcm_format_float(slv->format)) {
+		/* Conversion is done in another plugin */
+		if (clt->format == slv->format &&
+		    clt->rate == slv->rate &&
+		    clt->channels == slv->channels)
+			return 0;
+		cfmt = clt->format;
+		if (snd_pcm_format_linear(clt->format))
+			f = snd_pcm_lfloat_open;
+		else
+			return -EINVAL;
+#endif
+#ifdef BUILD_PCM_NONLINEAR
+	} else {
+		switch (slv->format) {
+#ifdef BUILD_PCM_PLUGIN_MULAW
+		case SND_PCM_FORMAT_MU_LAW:
+			f = snd_pcm_mulaw_open;
+			break;
+#endif
+#ifdef BUILD_PCM_PLUGIN_ALAW
+		case SND_PCM_FORMAT_A_LAW:
+			f = snd_pcm_alaw_open;
+			break;
+#endif
+#ifdef BUILD_PCM_PLUGIN_ADPCM
+		case SND_PCM_FORMAT_IMA_ADPCM:
+			f = snd_pcm_adpcm_open;
+			break;
+#endif
+		default:
+			return -EINVAL;
+		}
+		if (snd_pcm_format_linear(clt->format))
+			cfmt = clt->format;
+		else
+			cfmt = SND_PCM_FORMAT_S16;
+#endif /* NONLINEAR */
+	}
+	err = f(new, NULL, slv->format, plug->gen.slave, plug->gen.slave != plug->req_slave);
+	if (err < 0)
+		return err;
+	slv->format = cfmt;
+	slv->access = clt->access;
+	return 1;
+}
+
+static int snd_pcm_plug_change_access(snd_pcm_t *pcm, snd_pcm_t **new, snd_pcm_plug_params_t *clt, snd_pcm_plug_params_t *slv)
+{
+	snd_pcm_plug_t *plug = pcm->private_data;
+	int err;
+	if (clt->access == slv->access)
+		return 0;
+	err = snd_pcm_copy_open(new, NULL, plug->gen.slave, plug->gen.slave != plug->req_slave);
+	if (err < 0)
+		return err;
+	slv->access = clt->access;
+	return 1;
+}
+
+#ifdef BUILD_PCM_PLUGIN_MMAP_EMUL
+static int snd_pcm_plug_change_mmap(snd_pcm_t *pcm, snd_pcm_t **new,
+				    snd_pcm_plug_params_t *clt,
+				    snd_pcm_plug_params_t *slv)
+{
+	snd_pcm_plug_t *plug = pcm->private_data;
+	int err;
+
+	if (clt->access == slv->access)
+		return 0;
+
+	switch (slv->access) {
+	case SND_PCM_ACCESS_MMAP_INTERLEAVED:
+	case SND_PCM_ACCESS_MMAP_NONINTERLEAVED:
+	case SND_PCM_ACCESS_MMAP_COMPLEX:
+		return 0;
+	default:
+		break;
+	}
+
+	err = __snd_pcm_mmap_emul_open(new, NULL, plug->gen.slave,
+				       plug->gen.slave != plug->req_slave);
+	if (err < 0)
+		return err;
+	switch (slv->access) {
+	case SND_PCM_ACCESS_RW_INTERLEAVED:
+		slv->access = SND_PCM_ACCESS_MMAP_INTERLEAVED;
+		break;
+	case SND_PCM_ACCESS_RW_NONINTERLEAVED:
+		slv->access = SND_PCM_ACCESS_MMAP_NONINTERLEAVED;
+		break;
+	default:
+		break;
+	}
+	return 1;
+}
+#endif
+
+static int snd_pcm_plug_insert_plugins(snd_pcm_t *pcm,
+				       snd_pcm_plug_params_t *client,
+				       snd_pcm_plug_params_t *slave)
+{
+	snd_pcm_plug_t *plug = pcm->private_data;
+	static int (*const funcs[])(snd_pcm_t *_pcm, snd_pcm_t **new, snd_pcm_plug_params_t *s, snd_pcm_plug_params_t *d) = {
+#ifdef BUILD_PCM_PLUGIN_MMAP_EMUL
+		snd_pcm_plug_change_mmap,
+#endif
+		snd_pcm_plug_change_format,
+#ifdef BUILD_PCM_PLUGIN_ROUTE
+		snd_pcm_plug_change_channels,
+#endif
+#ifdef BUILD_PCM_PLUGIN_RATE
+		snd_pcm_plug_change_rate,
+#endif
+#ifdef BUILD_PCM_PLUGIN_ROUTE
+		snd_pcm_plug_change_channels,
+#endif
+		snd_pcm_plug_change_format,
+		snd_pcm_plug_change_access
+	};
+	snd_pcm_plug_params_t p = *slave;
+	unsigned int k = 0;
+	plug->ttable_ok = plug->ttable_last = 0;
+	while (client->format != p.format ||
+	       client->channels != p.channels ||
+	       client->rate != p.rate ||
+	       client->access != p.access) {
+		snd_pcm_t *new;
+		int err;
+		if (k >= sizeof(funcs)/sizeof(*funcs))
+			return -EINVAL;
+		err = funcs[k](pcm, &new, client, &p);
+		if (err < 0) {
+			snd_pcm_plug_clear(pcm);
+			return err;
+		}
+		if (err) {
+			plug->gen.slave = new;
+			pcm->fast_ops = new->fast_ops;
+			pcm->fast_op_arg = new->fast_op_arg;
+		}
+		k++;
+	}
+#ifdef BUILD_PCM_PLUGIN_ROUTE
+	/* it's exception, user specified ttable, but no reduction/expand */
+	if (plug->ttable && !plug->ttable_ok) {
+		snd_pcm_t *new;
+		int err;
+		plug->ttable_last = 1;
+		err = snd_pcm_plug_change_channels(pcm, &new, client, &p);
+		if (err < 0) {
+			snd_pcm_plug_clear(pcm);
+			return err;
+		}
+		assert(err);
+		assert(plug->ttable_ok);
+		plug->gen.slave = new;
+		pcm->fast_ops = new->fast_ops;
+		pcm->fast_op_arg = new->fast_op_arg;
+	}
+#endif
+	return 0;
+}
+
+static int snd_pcm_plug_hw_refine_cprepare(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params)
+{
+	unsigned int rate_min, channels_max;
+	int err;
+
+	/* HACK: to avoid overflow in PARTBIT_RATE code */
+	err = snd_pcm_hw_param_get_min(params, SND_PCM_HW_PARAM_RATE, &rate_min, NULL);
+	if (err < 0)
+		return err;
+	if (rate_min < 4000) {
+		_snd_pcm_hw_param_set_min(params, SND_PCM_HW_PARAM_RATE, 4000, 0);
+		if (snd_pcm_hw_param_empty(params, SND_PCM_HW_PARAM_RATE))
+			return -EINVAL;
+	}
+	/* HACK: to avoid overflow in PERIOD_SIZE code */
+	err = snd_pcm_hw_param_get_max(params, SND_PCM_HW_PARAM_CHANNELS, &channels_max, NULL);
+	if (err < 0)
+		return err;
+	if (channels_max > 10000) {
+		_snd_pcm_hw_param_set_max(params, SND_PCM_HW_PARAM_CHANNELS, 10000, 0);
+		if (snd_pcm_hw_param_empty(params, SND_PCM_HW_PARAM_CHANNELS))
+			return -EINVAL;
+	}
+	return 0;
+}
+
+static int snd_pcm_plug_hw_refine_sprepare(snd_pcm_t *pcm, snd_pcm_hw_params_t *sparams)
+{
+	snd_pcm_plug_t *plug = pcm->private_data;
+	int err;
+	
+	_snd_pcm_hw_params_any(sparams);
+	if (plug->sformat >= 0) {
+		_snd_pcm_hw_params_set_format(sparams, plug->sformat);
+		_snd_pcm_hw_params_set_subformat(sparams, SND_PCM_SUBFORMAT_STD);
+	}
+	if (plug->schannels > 0)
+		_snd_pcm_hw_param_set(sparams, SND_PCM_HW_PARAM_CHANNELS,
+				      plug->schannels, 0);
+	if (plug->srate > 0)
+		_snd_pcm_hw_param_set_minmax(sparams, SND_PCM_HW_PARAM_RATE,
+					      plug->srate, 0, plug->srate + 1, -1);
+	/* reduce the available configurations */
+	err = snd_pcm_hw_refine(plug->req_slave, sparams);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static int check_access_change(snd_pcm_hw_params_t *cparams,
+			       snd_pcm_hw_params_t *sparams)
+{
+	snd_pcm_access_mask_t *smask;
+#ifdef BUILD_PCM_PLUGIN_MMAP_EMUL
+	const snd_pcm_access_mask_t *cmask;
+	snd_pcm_access_mask_t mask;
+#endif
+
+	smask = (snd_pcm_access_mask_t *)
+		snd_pcm_hw_param_get_mask(sparams,
+					  SND_PCM_HW_PARAM_ACCESS);
+	if (snd_pcm_access_mask_test(smask, SND_PCM_ACCESS_MMAP_INTERLEAVED) ||
+	    snd_pcm_access_mask_test(smask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED) ||
+	    snd_pcm_access_mask_test(smask, SND_PCM_ACCESS_MMAP_COMPLEX))
+		return 0; /* OK, we have mmap support */
+#ifdef BUILD_PCM_PLUGIN_MMAP_EMUL
+	/* no mmap support - we need mmap emulation */
+
+	if (!snd_pcm_access_mask_test(smask, SND_PCM_ACCESS_RW_INTERLEAVED) &&
+	    !snd_pcm_access_mask_test(smask, SND_PCM_ACCESS_RW_NONINTERLEAVED)) 
+		return -EINVAL; /* even no RW access?  no way! */
+
+	cmask = (const snd_pcm_access_mask_t *)
+		snd_pcm_hw_param_get_mask(cparams,
+					  SND_PCM_HW_PARAM_ACCESS);
+	snd_mask_none(&mask);
+	if (snd_pcm_access_mask_test(cmask, SND_PCM_ACCESS_RW_INTERLEAVED) ||
+	    snd_pcm_access_mask_test(cmask, SND_PCM_ACCESS_MMAP_INTERLEAVED)) {
+		if (snd_pcm_access_mask_test(smask, SND_PCM_ACCESS_RW_INTERLEAVED))
+			snd_pcm_access_mask_set(&mask,
+						SND_PCM_ACCESS_RW_INTERLEAVED);
+	}
+	if (snd_pcm_access_mask_test(cmask, SND_PCM_ACCESS_RW_NONINTERLEAVED) ||
+	    snd_pcm_access_mask_test(cmask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED)) {
+		if (snd_pcm_access_mask_test(smask, SND_PCM_ACCESS_RW_NONINTERLEAVED))
+			snd_pcm_access_mask_set(&mask,
+						SND_PCM_ACCESS_RW_NONINTERLEAVED);
+	}
+	if (!snd_mask_empty(&mask))
+		*smask = mask; /* prefer the straight conversion */
+	return 0;
+#else
+	return -EINVAL;
+#endif
+}
+
+static int snd_pcm_plug_hw_refine_schange(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
+					  snd_pcm_hw_params_t *sparams)
+{
+	snd_pcm_plug_t *plug = pcm->private_data;
+	snd_pcm_t *slave = plug->req_slave;
+	unsigned int links = (SND_PCM_HW_PARBIT_PERIOD_TIME |
+			      SND_PCM_HW_PARBIT_TICK_TIME);
+	const snd_pcm_format_mask_t *format_mask, *sformat_mask;
+	snd_pcm_format_mask_t sfmt_mask;
+	int err;
+	snd_pcm_format_t format;
+	snd_interval_t t, buffer_size;
+	const snd_interval_t *srate, *crate;
+
+	if (plug->srate == -2 ||
+	    (pcm->mode & SND_PCM_NO_AUTO_RESAMPLE) ||
+	    (params->flags & SND_PCM_HW_PARAMS_NORESAMPLE))
+		links |= SND_PCM_HW_PARBIT_RATE;
+	else {
+		err = snd_pcm_hw_param_refine_multiple(slave, sparams, SND_PCM_HW_PARAM_RATE, params);
+		if (err < 0)
+			return err;
+	}
+	
+	if (plug->schannels == -2 || (pcm->mode & SND_PCM_NO_AUTO_CHANNELS))
+		links |= SND_PCM_HW_PARBIT_CHANNELS;
+	else {
+		err = snd_pcm_hw_param_refine_near(slave, sparams, SND_PCM_HW_PARAM_CHANNELS, params);
+		if (err < 0)
+			return err;
+	}
+	if (plug->sformat == -2 || (pcm->mode & SND_PCM_NO_AUTO_FORMAT))
+		links |= SND_PCM_HW_PARBIT_FORMAT;
+	else {
+		format_mask = snd_pcm_hw_param_get_mask(params, SND_PCM_HW_PARAM_FORMAT);
+		sformat_mask = snd_pcm_hw_param_get_mask(sparams, SND_PCM_HW_PARAM_FORMAT);
+		snd_mask_none(&sfmt_mask);
+		for (format = 0; format <= SND_PCM_FORMAT_LAST; format++) {
+			snd_pcm_format_t f;
+			if (!snd_pcm_format_mask_test(format_mask, format))
+				continue;
+			if (snd_pcm_format_mask_test(sformat_mask, format))
+				f = format;
+			else {
+				f = snd_pcm_plug_slave_format(format, sformat_mask);
+				if (f == SND_PCM_FORMAT_UNKNOWN)
+					continue;
+			}
+			snd_pcm_format_mask_set(&sfmt_mask, f);
+		}
+
+		if (snd_pcm_format_mask_empty(&sfmt_mask)) {
+			SNDERR("Unable to find an usable slave format for '%s'", pcm->name);
+			for (format = 0; format <= SND_PCM_FORMAT_LAST; format++) {
+				if (!snd_pcm_format_mask_test(format_mask, format))
+					continue;
+				SNDERR("Format: %s", snd_pcm_format_name(format));
+			}
+			for (format = 0; format <= SND_PCM_FORMAT_LAST; format++) {
+				if (!snd_pcm_format_mask_test(sformat_mask, format))
+					continue;
+				SNDERR("Slave format: %s", snd_pcm_format_name(format));
+			}
+			return -EINVAL;
+		}
+		err = snd_pcm_hw_param_set_mask(slave, sparams, SND_CHANGE,
+						SND_PCM_HW_PARAM_FORMAT, &sfmt_mask);
+		if (err < 0)
+			return -EINVAL;
+	}
+
+	if (snd_pcm_hw_param_never_eq(params, SND_PCM_HW_PARAM_ACCESS, sparams)) {
+		err = check_access_change(params, sparams);
+		if (err < 0) {
+			SNDERR("Unable to find an usable access for '%s'",
+			       pcm->name);
+			return err;
+		}
+	}
+
+	if ((links & SND_PCM_HW_PARBIT_RATE) ||
+	    snd_pcm_hw_param_always_eq(params, SND_PCM_HW_PARAM_RATE, sparams))
+		links |= (SND_PCM_HW_PARBIT_PERIOD_SIZE |
+			  SND_PCM_HW_PARBIT_BUFFER_SIZE);
+	else {
+		snd_interval_copy(&buffer_size, snd_pcm_hw_param_get_interval(params, SND_PCM_HW_PARAM_BUFFER_SIZE));
+		snd_interval_unfloor(&buffer_size);
+		crate = snd_pcm_hw_param_get_interval(params, SND_PCM_HW_PARAM_RATE);
+		srate = snd_pcm_hw_param_get_interval(sparams, SND_PCM_HW_PARAM_RATE);
+		snd_interval_muldiv(&buffer_size, srate, crate, &t);
+		err = _snd_pcm_hw_param_set_interval(sparams, SND_PCM_HW_PARAM_BUFFER_SIZE, &t);
+		if (err < 0)
+			return err;
+	}
+	err = _snd_pcm_hw_params_refine(sparams, links, params);
+	if (err < 0)
+		return err;
+	return 0;
+}
+	
+static int snd_pcm_plug_hw_refine_cchange(snd_pcm_t *pcm ATTRIBUTE_UNUSED,
+					  snd_pcm_hw_params_t *params,
+					  snd_pcm_hw_params_t *sparams)
+{
+	snd_pcm_plug_t *plug = pcm->private_data;
+	unsigned int links = (SND_PCM_HW_PARBIT_PERIOD_TIME |
+			      SND_PCM_HW_PARBIT_TICK_TIME);
+	const snd_pcm_format_mask_t *format_mask, *sformat_mask;
+	snd_pcm_format_mask_t fmt_mask;
+	int err;
+	snd_pcm_format_t format;
+	snd_interval_t t;
+	const snd_interval_t *sbuffer_size;
+	const snd_interval_t *srate, *crate;
+
+	if (plug->schannels == -2 || (pcm->mode & SND_PCM_NO_AUTO_CHANNELS))
+		links |= SND_PCM_HW_PARBIT_CHANNELS;
+
+	if (plug->sformat == -2 || (pcm->mode & SND_PCM_NO_AUTO_FORMAT))
+		links |= SND_PCM_HW_PARBIT_FORMAT;
+	else {
+		format_mask = snd_pcm_hw_param_get_mask(params,
+							SND_PCM_HW_PARAM_FORMAT);
+		sformat_mask = snd_pcm_hw_param_get_mask(sparams,
+							 SND_PCM_HW_PARAM_FORMAT);
+		snd_mask_none(&fmt_mask);
+		for (format = 0; format <= SND_PCM_FORMAT_LAST; format++) {
+			snd_pcm_format_t f;
+			if (!snd_pcm_format_mask_test(format_mask, format))
+				continue;
+			if (snd_pcm_format_mask_test(sformat_mask, format))
+				f = format;
+			else {
+				f = snd_pcm_plug_slave_format(format, sformat_mask);
+				if (f == SND_PCM_FORMAT_UNKNOWN)
+					continue;
+			}
+			snd_pcm_format_mask_set(&fmt_mask, format);
+		}
+
+		if (snd_pcm_format_mask_empty(&fmt_mask)) {
+			SNDERR("Unable to find an usable client format");
+			for (format = 0; format <= SND_PCM_FORMAT_LAST; format++) {
+				if (!snd_pcm_format_mask_test(format_mask, format))
+					continue;
+				SNDERR("Format: %s", snd_pcm_format_name(format));
+			}
+			for (format = 0; format <= SND_PCM_FORMAT_LAST; format++) {
+				if (!snd_pcm_format_mask_test(sformat_mask, format))
+					continue;
+				SNDERR("Slave format: %s", snd_pcm_format_name(format));
+			}
+			return -EINVAL;
+		}
+		
+		err = _snd_pcm_hw_param_set_mask(params, 
+						 SND_PCM_HW_PARAM_FORMAT, &fmt_mask);
+		if (err < 0)
+			return err;
+	}
+
+	if (plug->srate == -2 ||
+	    (pcm->mode & SND_PCM_NO_AUTO_RESAMPLE) ||
+	    (params->flags & SND_PCM_HW_PARAMS_NORESAMPLE))
+		links |= SND_PCM_HW_PARBIT_RATE;
+	else {
+		unsigned int rate_min, srate_min;
+		int rate_mindir, srate_mindir;
+		
+		/* This is a temporary hack, waiting for a better solution */
+		err = snd_pcm_hw_param_get_min(params, SND_PCM_HW_PARAM_RATE, &rate_min, &rate_mindir);
+		if (err < 0)
+			return err;
+		err = snd_pcm_hw_param_get_min(sparams, SND_PCM_HW_PARAM_RATE, &srate_min, &srate_mindir);
+		if (err < 0)
+			return err;
+		if (rate_min == srate_min && srate_mindir > rate_mindir) {
+			err = _snd_pcm_hw_param_set_min(params, SND_PCM_HW_PARAM_RATE, srate_min, srate_mindir);
+			if (err < 0)
+				return err;
+		}
+	}
+	if ((links & SND_PCM_HW_PARBIT_RATE) ||
+	    snd_pcm_hw_param_always_eq(params, SND_PCM_HW_PARAM_RATE, sparams))
+		links |= (SND_PCM_HW_PARBIT_PERIOD_SIZE |
+			  SND_PCM_HW_PARBIT_BUFFER_SIZE);
+	else {
+		sbuffer_size = snd_pcm_hw_param_get_interval(sparams, SND_PCM_HW_PARAM_BUFFER_SIZE);
+		crate = snd_pcm_hw_param_get_interval(params, SND_PCM_HW_PARAM_RATE);
+		srate = snd_pcm_hw_param_get_interval(sparams, SND_PCM_HW_PARAM_RATE);
+		snd_interval_muldiv(sbuffer_size, crate, srate, &t);
+		snd_interval_floor(&t);
+		if (snd_interval_empty(&t))
+			return -EINVAL;
+		err = _snd_pcm_hw_param_set_interval(params, SND_PCM_HW_PARAM_BUFFER_SIZE, &t);
+		if (err < 0)
+			return err;
+	}
+	err = _snd_pcm_hw_params_refine(params, links, sparams);
+	if (err < 0)
+		return err;
+	/* FIXME */
+	params->info &= ~(SND_PCM_INFO_MMAP | SND_PCM_INFO_MMAP_VALID);
+	return 0;
+}
+
+static int snd_pcm_plug_hw_refine_slave(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	snd_pcm_plug_t *plug = pcm->private_data;
+	return snd_pcm_hw_refine(plug->req_slave, params);
+}
+
+static int snd_pcm_plug_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	return snd_pcm_hw_refine_slave(pcm, params,
+				       snd_pcm_plug_hw_refine_cprepare,
+				       snd_pcm_plug_hw_refine_cchange,
+				       snd_pcm_plug_hw_refine_sprepare,
+				       snd_pcm_plug_hw_refine_schange,
+				       snd_pcm_plug_hw_refine_slave);
+}
+
+static int snd_pcm_plug_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	snd_pcm_plug_t *plug = pcm->private_data;
+	snd_pcm_t *slave = plug->req_slave;
+	snd_pcm_plug_params_t clt_params, slv_params;
+	snd_pcm_hw_params_t sparams;
+	int err;
+
+	err = snd_pcm_plug_hw_refine_sprepare(pcm, &sparams);
+	if (err < 0)
+		return err;
+	err = snd_pcm_plug_hw_refine_schange(pcm, params, &sparams);
+	if (err < 0)
+		return err;
+	err = snd_pcm_hw_refine_soft(slave, &sparams);
+	if (err < 0)
+		return err;
+
+	INTERNAL(snd_pcm_hw_params_get_access)(params, &clt_params.access);
+	INTERNAL(snd_pcm_hw_params_get_format)(params, &clt_params.format);
+	INTERNAL(snd_pcm_hw_params_get_channels)(params, &clt_params.channels);
+	INTERNAL(snd_pcm_hw_params_get_rate)(params, &clt_params.rate, 0);
+
+	INTERNAL(snd_pcm_hw_params_get_format)(&sparams, &slv_params.format);
+	INTERNAL(snd_pcm_hw_params_get_channels)(&sparams, &slv_params.channels);
+	INTERNAL(snd_pcm_hw_params_get_rate)(&sparams, &slv_params.rate, 0);
+	snd_pcm_plug_clear(pcm);
+	if (!(clt_params.format == slv_params.format &&
+	      clt_params.channels == slv_params.channels &&
+	      clt_params.rate == slv_params.rate &&
+	      !plug->ttable &&
+	      snd_pcm_hw_params_test_access(slave, &sparams,
+					    clt_params.access) >= 0)) {
+		INTERNAL(snd_pcm_hw_params_set_access_first)(slave, &sparams, &slv_params.access);
+		err = snd_pcm_plug_insert_plugins(pcm, &clt_params, &slv_params);
+		if (err < 0)
+			return err;
+	}
+	slave = plug->gen.slave;
+	err = _snd_pcm_hw_params(slave, params);
+	if (err < 0) {
+		snd_pcm_plug_clear(pcm);
+		return err;
+	}
+	snd_pcm_unlink_hw_ptr(pcm, plug->req_slave);
+	snd_pcm_unlink_appl_ptr(pcm, plug->req_slave);
+	snd_pcm_link_hw_ptr(pcm, slave);
+	snd_pcm_link_appl_ptr(pcm, slave);
+	return 0;
+}
+
+static int snd_pcm_plug_hw_free(snd_pcm_t *pcm)
+{
+	snd_pcm_plug_t *plug = pcm->private_data;
+	snd_pcm_t *slave = plug->gen.slave;
+	int err = snd_pcm_hw_free(slave);
+	snd_pcm_plug_clear(pcm);
+	return err;
+}
+
+static void snd_pcm_plug_dump(snd_pcm_t *pcm, snd_output_t *out)
+{
+	snd_pcm_plug_t *plug = pcm->private_data;
+	snd_output_printf(out, "Plug PCM: ");
+	snd_pcm_dump(plug->gen.slave, out);
+}
+
+static const snd_pcm_ops_t snd_pcm_plug_ops = {
+	.close = snd_pcm_plug_close,
+	.info = snd_pcm_plug_info,
+	.hw_refine = snd_pcm_plug_hw_refine,
+	.hw_params = snd_pcm_plug_hw_params,
+	.hw_free = snd_pcm_plug_hw_free,
+	.sw_params = snd_pcm_generic_sw_params,
+	.channel_info = snd_pcm_generic_channel_info,
+	.dump = snd_pcm_plug_dump,
+	.nonblock = snd_pcm_generic_nonblock,
+	.async = snd_pcm_generic_async,
+	.mmap = snd_pcm_generic_mmap,
+	.munmap = snd_pcm_generic_munmap,
+};
+
+/**
+ * \brief Creates a new Plug PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param sformat Slave (destination) format
+ * \param slave Slave PCM handle
+ * \param close_slave When set, the slave PCM handle is closed with copy PCM
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int snd_pcm_plug_open(snd_pcm_t **pcmp,
+		      const char *name,
+		      snd_pcm_format_t sformat, int schannels, int srate,
+		      const snd_config_t *rate_converter,
+		      enum snd_pcm_plug_route_policy route_policy,
+		      snd_pcm_route_ttable_entry_t *ttable,
+		      unsigned int tt_ssize,
+		      unsigned int tt_cused, unsigned int tt_sused,
+		      snd_pcm_t *slave, int close_slave)
+{
+	snd_pcm_t *pcm;
+	snd_pcm_plug_t *plug;
+	int err;
+	assert(pcmp && slave);
+
+	plug = calloc(1, sizeof(snd_pcm_plug_t));
+	if (!plug)
+		return -ENOMEM;
+	plug->sformat = sformat;
+	plug->schannels = schannels;
+	plug->srate = srate;
+	plug->rate_converter = rate_converter;
+	plug->gen.slave = plug->req_slave = slave;
+	plug->gen.close_slave = close_slave;
+	plug->route_policy = route_policy;
+	plug->ttable = ttable;
+	plug->tt_ssize = tt_ssize;
+	plug->tt_cused = tt_cused;
+	plug->tt_sused = tt_sused;
+	
+	err = snd_pcm_new(&pcm, SND_PCM_TYPE_PLUG, name, slave->stream, slave->mode);
+	if (err < 0) {
+		free(plug);
+		return err;
+	}
+	pcm->ops = &snd_pcm_plug_ops;
+	pcm->fast_ops = slave->fast_ops;
+	pcm->fast_op_arg = slave->fast_op_arg;
+	pcm->private_data = plug;
+	pcm->poll_fd = slave->poll_fd;
+	pcm->poll_events = slave->poll_events;
+	pcm->mmap_shadow = 1;
+	pcm->monotonic = slave->monotonic;
+	snd_pcm_link_hw_ptr(pcm, slave);
+	snd_pcm_link_appl_ptr(pcm, slave);
+	*pcmp = pcm;
+
+	return 0;
+}
+
+/*! \page pcm_plugins
+
+\section pcm_plugins_plug Automatic conversion plugin
+
+This plugin converts channels, rate and format on request.
+
+\code
+pcm.name {
+        type plug               # Automatic conversion PCM
+        slave STR               # Slave name
+        # or
+        slave {                 # Slave definition
+                pcm STR         # Slave PCM name
+                # or
+                pcm { }         # Slave PCM definition
+		[format STR]	# Slave format (default nearest) or "unchanged"
+		[channels INT]	# Slave channels (default nearest) or "unchanged"
+		[rate INT]	# Slave rate (default nearest) or "unchanged"
+        }
+	route_policy STR	# route policy for automatic ttable generation
+				# STR can be 'default', 'average', 'copy', 'duplicate'
+				# average: result is average of input channels
+				# copy: only first channels are copied to destination
+				# duplicate: duplicate first set of channels
+				# default: copy policy, except for mono capture - sum
+	ttable {		# Transfer table (bi-dimensional compound of cchannels * schannels numbers)
+		CCHANNEL {
+			SCHANNEL REAL	# route value (0.0 - 1.0)
+		}
+	}
+	rate_converter STR	# type of rate converter
+	# or
+	rate_converter [ STR1 STR2 ... ]
+				# type of rate converter
+				# default value is taken from defaults.pcm.rate_converter
+}
+\endcode
+
+\subsection pcm_plugins_plug_funcref Function reference
+
+<UL>
+  <LI>snd_pcm_plug_open()
+  <LI>_snd_pcm_plug_open()
+</UL>
+
+*/
+
+/**
+ * \brief Creates a new Plug PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param root Root configuration node
+ * \param conf Configuration node with Plug PCM description
+ * \param stream Stream type
+ * \param mode Stream mode
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int _snd_pcm_plug_open(snd_pcm_t **pcmp, const char *name,
+		       snd_config_t *root, snd_config_t *conf, 
+		       snd_pcm_stream_t stream, int mode)
+{
+	snd_config_iterator_t i, next;
+	int err;
+	snd_pcm_t *spcm;
+	snd_config_t *slave = NULL, *sconf;
+	snd_config_t *tt = NULL;
+	enum snd_pcm_plug_route_policy route_policy = PLUG_ROUTE_POLICY_DEFAULT;
+	snd_pcm_route_ttable_entry_t *ttable = NULL;
+	unsigned int csize, ssize;
+	unsigned int cused, sused;
+	snd_pcm_format_t sformat = SND_PCM_FORMAT_UNKNOWN;
+	int schannels = -1, srate = -1;
+	const snd_config_t *rate_converter = NULL;
+
+	snd_config_for_each(i, next, conf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (snd_pcm_conf_generic_id(id))
+			continue;
+		if (strcmp(id, "slave") == 0) {
+			slave = n;
+			continue;
+		}
+#ifdef BUILD_PCM_PLUGIN_ROUTE
+		if (strcmp(id, "ttable") == 0) {
+			route_policy = PLUG_ROUTE_POLICY_NONE;
+			if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) {
+				SNDERR("Invalid type for %s", id);
+				return -EINVAL;
+			}
+			tt = n;
+			continue;
+		}
+		if (strcmp(id, "route_policy") == 0) {
+			const char *str;
+			if ((err = snd_config_get_string(n, &str)) < 0) {
+				SNDERR("Invalid type for %s", id);
+				return -EINVAL;
+			}
+			if (tt != NULL)
+				SNDERR("Table is defined, route policy is ignored");
+			if (!strcmp(str, "default"))
+				route_policy = PLUG_ROUTE_POLICY_DEFAULT;
+			else if (!strcmp(str, "average"))
+				route_policy = PLUG_ROUTE_POLICY_AVERAGE;
+			else if (!strcmp(str, "copy"))
+				route_policy = PLUG_ROUTE_POLICY_COPY;
+			else if (!strcmp(str, "duplicate"))
+				route_policy = PLUG_ROUTE_POLICY_DUP;
+			continue;
+		}
+#endif
+#ifdef BUILD_PCM_PLUGIN_RATE
+		if (strcmp(id, "rate_converter") == 0) {
+			rate_converter = n;
+			continue;
+		}
+#endif
+		SNDERR("Unknown field %s", id);
+		return -EINVAL;
+	}
+	if (!slave) {
+		SNDERR("slave is not defined");
+		return -EINVAL;
+	}
+	err = snd_pcm_slave_conf(root, slave, &sconf, 3,
+				 SND_PCM_HW_PARAM_FORMAT, SCONF_UNCHANGED, &sformat,
+				 SND_PCM_HW_PARAM_CHANNELS, SCONF_UNCHANGED, &schannels,
+				 SND_PCM_HW_PARAM_RATE, SCONF_UNCHANGED, &srate);
+	if (err < 0)
+		return err;
+#ifdef BUILD_PCM_PLUGIN_ROUTE
+	if (tt) {
+		err = snd_pcm_route_determine_ttable(tt, &csize, &ssize);
+		if (err < 0) {
+			snd_config_delete(sconf);
+			return err;
+		}
+		ttable = malloc(csize * ssize * sizeof(*ttable));
+		if (ttable == NULL) {
+			snd_config_delete(sconf);
+			return err;
+		}
+		err = snd_pcm_route_load_ttable(tt, ttable, csize, ssize, &cused, &sused, -1);
+		if (err < 0) {
+			snd_config_delete(sconf);
+			return err;
+		}
+	}
+#endif
+	
+#ifdef BUILD_PCM_PLUGIN_RATE
+	if (! rate_converter)
+		rate_converter = snd_pcm_rate_get_default_converter(root);
+#endif
+
+	err = snd_pcm_open_slave(&spcm, root, sconf, stream, mode, conf);
+	snd_config_delete(sconf);
+	if (err < 0)
+		return err;
+	err = snd_pcm_plug_open(pcmp, name, sformat, schannels, srate, rate_converter,
+				route_policy, ttable, ssize, cused, sused, spcm, 1);
+	if (err < 0)
+		snd_pcm_close(spcm);
+	return err;
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(_snd_pcm_plug_open, SND_PCM_DLSYM_VERSION);
+#endif
diff --git a/src/pcm/pcm_plugin.c b/src/pcm/pcm_plugin.c
new file mode 100644
index 0000000..d88e117
--- /dev/null
+++ b/src/pcm/pcm_plugin.c
@@ -0,0 +1,575 @@
+/**
+ * \file pcm/pcm_plugin.c
+ * \ingroup PCM
+ * \brief PCM Interface
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \date 2000-2001
+ */
+/*
+ *  PCM - Common plugin code
+ *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+/*!
+
+\page pcm_plugins PCM (digital audio) plugins
+
+PCM plugins extends functionality and features of PCM devices.
+The plugins take care about various sample conversions, sample
+copying among channels and so on.
+
+\section pcm_plugins_slave Slave definition
+
+The slave plugin can be specified directly with a string or the definition
+can be entered inside a compound configuration node. Some restrictions can
+be also specified (like static rate or count of channels).
+
+\code
+pcm_slave.NAME {
+	pcm STR		# PCM name
+	# or
+	pcm { }		# PCM definition
+	format STR	# Format or "unchanged"
+	channels INT	# Count of channels or "unchanged" string
+	rate INT	# Rate in Hz or "unchanged" string
+	period_time INT	# Period time in us or "unchanged" string
+	buffer_time INT # Buffer time in us or "unchanged" string
+}
+\endcode
+
+Example:
+
+\code
+pcm_slave.slave_rate44100Hz {
+	pcm "hw:0,0"
+	rate 44100
+}
+
+pcm.rate44100Hz {
+	type plug
+	slave slave_rate44100Hz
+}
+\endcode
+
+The equivalent configuration (in one compound):
+
+\code
+pcm.rate44100Hz {
+	type plug
+	slave {
+		pcm "hw:0,0"
+		rate 44100
+	}
+}
+\endcode
+
+*/
+  
+#include <sys/shm.h>
+#include <limits.h>
+#include "pcm_local.h"
+#include "pcm_plugin.h"
+
+#ifndef DOC_HIDDEN
+
+static snd_pcm_sframes_t
+snd_pcm_plugin_undo_read(snd_pcm_t *pcm ATTRIBUTE_UNUSED,
+			 const snd_pcm_channel_area_t *res_areas ATTRIBUTE_UNUSED,
+			 snd_pcm_uframes_t res_offset ATTRIBUTE_UNUSED,
+			 snd_pcm_uframes_t res_size ATTRIBUTE_UNUSED,
+			 snd_pcm_uframes_t slave_undo_size ATTRIBUTE_UNUSED)
+{
+	return -EIO;
+}
+
+static snd_pcm_sframes_t
+snd_pcm_plugin_undo_write(snd_pcm_t *pcm ATTRIBUTE_UNUSED,
+			  const snd_pcm_channel_area_t *res_areas ATTRIBUTE_UNUSED,
+			  snd_pcm_uframes_t res_offset ATTRIBUTE_UNUSED,
+			  snd_pcm_uframes_t res_size ATTRIBUTE_UNUSED,
+			  snd_pcm_uframes_t slave_undo_size ATTRIBUTE_UNUSED)
+{
+	return -EIO;
+}
+
+snd_pcm_sframes_t
+snd_pcm_plugin_undo_read_generic(snd_pcm_t *pcm ATTRIBUTE_UNUSED,
+				 const snd_pcm_channel_area_t *res_areas ATTRIBUTE_UNUSED,
+				 snd_pcm_uframes_t res_offset ATTRIBUTE_UNUSED,
+				 snd_pcm_uframes_t res_size ATTRIBUTE_UNUSED,
+				 snd_pcm_uframes_t slave_undo_size)
+{
+	return slave_undo_size;
+}
+
+snd_pcm_sframes_t
+snd_pcm_plugin_undo_write_generic(snd_pcm_t *pcm ATTRIBUTE_UNUSED,
+				  const snd_pcm_channel_area_t *res_areas ATTRIBUTE_UNUSED,
+				  snd_pcm_uframes_t res_offset ATTRIBUTE_UNUSED,
+				  snd_pcm_uframes_t res_size ATTRIBUTE_UNUSED,
+				  snd_pcm_uframes_t slave_undo_size)
+{
+	return slave_undo_size;
+}
+
+void snd_pcm_plugin_init(snd_pcm_plugin_t *plugin)
+{
+	memset(plugin, 0, sizeof(snd_pcm_plugin_t));
+	plugin->undo_read = snd_pcm_plugin_undo_read;
+	plugin->undo_write = snd_pcm_plugin_undo_write;
+	snd_atomic_write_init(&plugin->watom);
+}
+
+static int snd_pcm_plugin_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp)
+{
+	snd_pcm_plugin_t *plugin = pcm->private_data;
+	snd_pcm_sframes_t sd;
+	int err = snd_pcm_delay(plugin->gen.slave, &sd);
+	if (err < 0)
+		return err;
+        if (pcm->stream == SND_PCM_STREAM_CAPTURE &&
+	    pcm->access != SND_PCM_ACCESS_RW_INTERLEAVED &&
+	    pcm->access != SND_PCM_ACCESS_RW_NONINTERLEAVED) {
+                sd += snd_pcm_mmap_capture_avail(pcm);
+        }        
+
+	*delayp = sd;
+	return 0;
+}
+
+static int snd_pcm_plugin_prepare(snd_pcm_t *pcm)
+{
+	snd_pcm_plugin_t *plugin = pcm->private_data;
+	int err;
+	snd_atomic_write_begin(&plugin->watom);
+	err = snd_pcm_prepare(plugin->gen.slave);
+	if (err < 0) {
+		snd_atomic_write_end(&plugin->watom);
+		return err;
+	}
+	*pcm->hw.ptr = 0;
+	*pcm->appl.ptr = 0;
+	snd_atomic_write_end(&plugin->watom);
+	if (plugin->init) {
+		err = plugin->init(pcm);
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+static int snd_pcm_plugin_reset(snd_pcm_t *pcm)
+{
+	snd_pcm_plugin_t *plugin = pcm->private_data;
+	int err;
+	snd_atomic_write_begin(&plugin->watom);
+	err = snd_pcm_reset(plugin->gen.slave);
+	if (err < 0) {
+		snd_atomic_write_end(&plugin->watom);
+		return err;
+	}
+	*pcm->hw.ptr = 0;
+	*pcm->appl.ptr = 0;
+	snd_atomic_write_end(&plugin->watom);
+	if (plugin->init) {
+		err = plugin->init(pcm);
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+static snd_pcm_sframes_t snd_pcm_plugin_rewindable(snd_pcm_t *pcm)
+{
+	return snd_pcm_mmap_hw_avail(pcm);
+}
+
+static snd_pcm_sframes_t snd_pcm_plugin_rewind(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
+{
+	snd_pcm_plugin_t *plugin = pcm->private_data;
+	snd_pcm_sframes_t n = snd_pcm_mmap_hw_avail(pcm);
+	snd_pcm_sframes_t sframes;
+
+	if ((snd_pcm_uframes_t)n < frames)
+		frames = n;
+	if (frames == 0)
+		return 0;
+	
+        sframes = frames;
+	snd_atomic_write_begin(&plugin->watom);
+	sframes = snd_pcm_rewind(plugin->gen.slave, sframes);
+	if (sframes < 0) {
+		snd_atomic_write_end(&plugin->watom);
+		return sframes;
+	}
+	snd_pcm_mmap_appl_backward(pcm, (snd_pcm_uframes_t) frames);
+	snd_atomic_write_end(&plugin->watom);
+	return (snd_pcm_sframes_t) frames;
+}
+
+static snd_pcm_sframes_t snd_pcm_plugin_forwardable(snd_pcm_t *pcm)
+{
+	return snd_pcm_mmap_avail(pcm);
+}
+
+static snd_pcm_sframes_t snd_pcm_plugin_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
+{
+	snd_pcm_plugin_t *plugin = pcm->private_data;
+	snd_pcm_sframes_t n = snd_pcm_mmap_avail(pcm);
+	snd_pcm_sframes_t sframes;
+
+	if ((snd_pcm_uframes_t)n < frames)
+		frames = n;
+	if (frames == 0)
+		return 0;
+	
+        sframes = frames;
+	snd_atomic_write_begin(&plugin->watom);
+	sframes = INTERNAL(snd_pcm_forward)(plugin->gen.slave, sframes);
+	if (sframes < 0) {
+		snd_atomic_write_end(&plugin->watom);
+		return sframes;
+	}
+	snd_pcm_mmap_appl_forward(pcm, (snd_pcm_uframes_t) frames);
+	snd_atomic_write_end(&plugin->watom);
+	return (snd_pcm_sframes_t) frames;
+}
+
+static snd_pcm_sframes_t snd_pcm_plugin_write_areas(snd_pcm_t *pcm,
+						    const snd_pcm_channel_area_t *areas,
+						    snd_pcm_uframes_t offset,
+						    snd_pcm_uframes_t size)
+{
+	snd_pcm_plugin_t *plugin = pcm->private_data;
+	snd_pcm_t *slave = plugin->gen.slave;
+	snd_pcm_uframes_t xfer = 0;
+	snd_pcm_sframes_t result;
+	int err;
+
+	while (size > 0) {
+		snd_pcm_uframes_t frames = size;
+		const snd_pcm_channel_area_t *slave_areas;
+		snd_pcm_uframes_t slave_offset;
+		snd_pcm_uframes_t slave_frames = ULONG_MAX;
+		
+		err = snd_pcm_mmap_begin(slave, &slave_areas, &slave_offset, &slave_frames);
+		if (err < 0 || slave_frames == 0)
+			break;
+		frames = plugin->write(pcm, areas, offset, frames,
+				       slave_areas, slave_offset, &slave_frames);
+		if (CHECK_SANITY(slave_frames > snd_pcm_mmap_playback_avail(slave))) {
+			SNDMSG("write overflow %ld > %ld", slave_frames,
+			       snd_pcm_mmap_playback_avail(slave));
+			return -EPIPE;
+		}
+		snd_atomic_write_begin(&plugin->watom);
+		snd_pcm_mmap_appl_forward(pcm, frames);
+		result = snd_pcm_mmap_commit(slave, slave_offset, slave_frames);
+		if (result > 0 && (snd_pcm_uframes_t)result != slave_frames) {
+			snd_pcm_sframes_t res;
+			res = plugin->undo_write(pcm, slave_areas, slave_offset + result, slave_frames, slave_frames - result);
+			if (res < 0)
+				return xfer > 0 ? (snd_pcm_sframes_t)xfer : res;
+			frames -= res;
+		}
+		snd_atomic_write_end(&plugin->watom);
+		if (result <= 0)
+			return xfer > 0 ? (snd_pcm_sframes_t)xfer : result;
+		offset += frames;
+		xfer += frames;
+		size -= frames;
+	}
+	return (snd_pcm_sframes_t)xfer;
+}
+
+static snd_pcm_sframes_t snd_pcm_plugin_read_areas(snd_pcm_t *pcm,
+						   const snd_pcm_channel_area_t *areas,
+						   snd_pcm_uframes_t offset,
+						   snd_pcm_uframes_t size)
+{
+	snd_pcm_plugin_t *plugin = pcm->private_data;
+	snd_pcm_t *slave = plugin->gen.slave;
+	snd_pcm_uframes_t xfer = 0;
+	snd_pcm_sframes_t result;
+	
+	while (size > 0) {
+		snd_pcm_uframes_t frames = size;
+		const snd_pcm_channel_area_t *slave_areas;
+		snd_pcm_uframes_t slave_offset;
+		snd_pcm_uframes_t slave_frames = ULONG_MAX;
+		
+		snd_pcm_mmap_begin(slave, &slave_areas, &slave_offset, &slave_frames);
+		if (slave_frames == 0)
+			break;
+		frames = (plugin->read)(pcm, areas, offset, frames,
+				      slave_areas, slave_offset, &slave_frames);
+		if (CHECK_SANITY(slave_frames > snd_pcm_mmap_capture_avail(slave))) {
+			SNDMSG("read overflow %ld > %ld", slave_frames,
+			       snd_pcm_mmap_playback_avail(slave));
+			return -EPIPE;
+		}
+		snd_atomic_write_begin(&plugin->watom);
+		snd_pcm_mmap_appl_forward(pcm, frames);
+		result = snd_pcm_mmap_commit(slave, slave_offset, slave_frames);
+		if (result > 0 && (snd_pcm_uframes_t)result != slave_frames) {
+			snd_pcm_sframes_t res;
+			
+			res = plugin->undo_read(slave, areas, offset, frames, slave_frames - result);
+			if (res < 0)
+				return xfer > 0 ? (snd_pcm_sframes_t)xfer : res;
+			frames -= res;
+		}
+		snd_atomic_write_end(&plugin->watom);
+		if (result <= 0)
+			return xfer > 0 ? (snd_pcm_sframes_t)xfer : result;
+		offset += frames;
+		xfer += frames;
+		size -= frames;
+	}
+	return (snd_pcm_sframes_t)xfer;
+}
+
+
+static snd_pcm_sframes_t
+snd_pcm_plugin_writei(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size)
+{
+	snd_pcm_channel_area_t areas[pcm->channels];
+	snd_pcm_areas_from_buf(pcm, areas, (void*)buffer);
+	return snd_pcm_write_areas(pcm, areas, 0, size, 
+				   snd_pcm_plugin_write_areas);
+}
+
+static snd_pcm_sframes_t
+snd_pcm_plugin_writen(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size)
+{
+	snd_pcm_channel_area_t areas[pcm->channels];
+	snd_pcm_areas_from_bufs(pcm, areas, bufs);
+	return snd_pcm_write_areas(pcm, areas, 0, size,
+				   snd_pcm_plugin_write_areas);
+}
+
+static snd_pcm_sframes_t
+snd_pcm_plugin_readi(snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t size)
+{
+	snd_pcm_channel_area_t areas[pcm->channels];
+	snd_pcm_areas_from_buf(pcm, areas, buffer);
+	return snd_pcm_read_areas(pcm, areas, 0, size,
+				  snd_pcm_plugin_read_areas);
+}
+
+static snd_pcm_sframes_t
+snd_pcm_plugin_readn(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size)
+{
+	snd_pcm_channel_area_t areas[pcm->channels];
+	snd_pcm_areas_from_bufs(pcm, areas, bufs);
+	return snd_pcm_read_areas(pcm, areas, 0, size,
+				  snd_pcm_plugin_read_areas);
+}
+
+static snd_pcm_sframes_t
+snd_pcm_plugin_mmap_commit(snd_pcm_t *pcm,
+			   snd_pcm_uframes_t offset ATTRIBUTE_UNUSED,
+			   snd_pcm_uframes_t size)
+{
+	snd_pcm_plugin_t *plugin = pcm->private_data;
+	snd_pcm_t *slave = plugin->gen.slave;
+	const snd_pcm_channel_area_t *areas;
+	snd_pcm_uframes_t appl_offset;
+	snd_pcm_sframes_t slave_size;
+	snd_pcm_sframes_t xfer;
+
+	if (pcm->stream == SND_PCM_STREAM_CAPTURE) {
+		snd_atomic_write_begin(&plugin->watom);
+		snd_pcm_mmap_appl_forward(pcm, size);
+		snd_atomic_write_end(&plugin->watom);
+		return size;
+	}
+	slave_size = snd_pcm_avail_update(slave);
+	if (slave_size < 0)
+		return slave_size;
+	areas = snd_pcm_mmap_areas(pcm);
+	appl_offset = snd_pcm_mmap_offset(pcm);
+	xfer = 0;
+	while (size > 0 && slave_size > 0) {
+		snd_pcm_uframes_t frames = size;
+		snd_pcm_uframes_t cont = pcm->buffer_size - appl_offset;
+		const snd_pcm_channel_area_t *slave_areas;
+		snd_pcm_uframes_t slave_offset;
+		snd_pcm_uframes_t slave_frames = ULONG_MAX;
+		snd_pcm_sframes_t result;
+		int err;
+
+		err = snd_pcm_mmap_begin(slave, &slave_areas, &slave_offset, &slave_frames);
+		if (err < 0)
+			return xfer > 0 ? xfer : err;
+		if (frames > cont)
+			frames = cont;
+		frames = plugin->write(pcm, areas, appl_offset, frames,
+				       slave_areas, slave_offset, &slave_frames);
+		snd_atomic_write_begin(&plugin->watom);
+		snd_pcm_mmap_appl_forward(pcm, frames);
+		result = snd_pcm_mmap_commit(slave, slave_offset, slave_frames);
+		snd_atomic_write_end(&plugin->watom);
+		if (result > 0 && (snd_pcm_uframes_t)result != slave_frames) {
+			snd_pcm_sframes_t res;
+			
+			res = plugin->undo_write(pcm, slave_areas, slave_offset + result, slave_frames, slave_frames - result);
+			if (res < 0)
+				return xfer > 0 ? xfer : res;
+			frames -= res;
+		}
+		if (result <= 0)
+			return xfer > 0 ? xfer : result;
+		if (frames == cont)
+			appl_offset = 0;
+		else
+			appl_offset += result;
+		size -= frames;
+		slave_size -= frames;
+		xfer += frames;
+	}
+	if (CHECK_SANITY(size)) {
+		SNDMSG("short commit: %ld", size);
+		return -EPIPE;
+	}
+	return xfer;
+}
+
+static snd_pcm_sframes_t snd_pcm_plugin_avail_update(snd_pcm_t *pcm)
+{
+	snd_pcm_plugin_t *plugin = pcm->private_data;
+	snd_pcm_t *slave = plugin->gen.slave;
+	snd_pcm_sframes_t slave_size;
+
+	slave_size = snd_pcm_avail_update(slave);
+	if (pcm->stream == SND_PCM_STREAM_CAPTURE &&
+	    pcm->access != SND_PCM_ACCESS_RW_INTERLEAVED &&
+	    pcm->access != SND_PCM_ACCESS_RW_NONINTERLEAVED)
+		goto _capture;
+        *pcm->hw.ptr = *slave->hw.ptr;
+        return slave_size;
+ _capture:
+ 	{
+		const snd_pcm_channel_area_t *areas;
+		snd_pcm_uframes_t xfer, hw_offset, size;
+		
+		xfer = snd_pcm_mmap_capture_avail(pcm);
+		size = pcm->buffer_size - xfer;
+		areas = snd_pcm_mmap_areas(pcm);
+		hw_offset = snd_pcm_mmap_hw_offset(pcm);
+		while (size > 0 && slave_size > 0) {
+			snd_pcm_uframes_t frames = size;
+			snd_pcm_uframes_t cont = pcm->buffer_size - hw_offset;
+			const snd_pcm_channel_area_t *slave_areas;
+			snd_pcm_uframes_t slave_offset;
+			snd_pcm_uframes_t slave_frames = ULONG_MAX;
+			snd_pcm_sframes_t result;
+			int err;
+
+			err = snd_pcm_mmap_begin(slave, &slave_areas, &slave_offset, &slave_frames);
+			if (err < 0)
+				return xfer > 0 ? (snd_pcm_sframes_t)xfer : err;
+			if (frames > cont)
+				frames = cont;
+			frames = (plugin->read)(pcm, areas, hw_offset, frames,
+					      slave_areas, slave_offset, &slave_frames);
+			snd_atomic_write_begin(&plugin->watom);
+			snd_pcm_mmap_hw_forward(pcm, frames);
+			result = snd_pcm_mmap_commit(slave, slave_offset, slave_frames);
+			snd_atomic_write_end(&plugin->watom);
+			if (result > 0 && (snd_pcm_uframes_t)result != slave_frames) {
+				snd_pcm_sframes_t res;
+				
+				res = plugin->undo_read(slave, areas, hw_offset, frames, slave_frames - result);
+				if (res < 0)
+					return xfer > 0 ? (snd_pcm_sframes_t)xfer : res;
+				frames -= res;
+			}
+			if (result <= 0)
+				return xfer > 0 ? (snd_pcm_sframes_t)xfer : result;
+			if (frames == cont)
+				hw_offset = 0;
+			else
+				hw_offset += frames;
+			size -= frames;
+			slave_size -= slave_frames;
+			xfer += frames;
+		}
+		return (snd_pcm_sframes_t)xfer;
+	}
+}
+
+static int snd_pcm_plugin_status(snd_pcm_t *pcm, snd_pcm_status_t * status)
+{
+	snd_pcm_plugin_t *plugin = pcm->private_data;
+	snd_pcm_sframes_t err;
+	snd_atomic_read_t ratom;
+	snd_atomic_read_init(&ratom, &plugin->watom);
+ _again:
+	snd_atomic_read_begin(&ratom);
+	/* sync with the latest hw and appl ptrs */
+	snd_pcm_plugin_avail_update(pcm);
+
+	err = snd_pcm_status(plugin->gen.slave, status);
+	if (err < 0) {
+		snd_atomic_read_ok(&ratom);
+		return err;
+	}
+	status->appl_ptr = *pcm->appl.ptr;
+	status->hw_ptr = *pcm->hw.ptr;
+	if (!snd_atomic_read_ok(&ratom)) {
+		snd_atomic_read_wait(&ratom);
+		goto _again;
+	}
+	return 0;
+}
+
+const snd_pcm_fast_ops_t snd_pcm_plugin_fast_ops = {
+	.status = snd_pcm_plugin_status,
+	.state = snd_pcm_generic_state,
+	.hwsync = snd_pcm_generic_hwsync,
+	.delay = snd_pcm_plugin_delay,
+	.prepare = snd_pcm_plugin_prepare,
+	.reset = snd_pcm_plugin_reset,
+	.start = snd_pcm_generic_start,
+	.drop = snd_pcm_generic_drop,
+	.drain = snd_pcm_generic_drain,
+	.pause = snd_pcm_generic_pause,
+	.rewindable = snd_pcm_plugin_rewindable,
+	.rewind = snd_pcm_plugin_rewind,
+	.forwardable = snd_pcm_plugin_forwardable,
+	.forward = snd_pcm_plugin_forward,
+	.resume = snd_pcm_generic_resume,
+	.link = snd_pcm_generic_link,
+	.link_slaves = snd_pcm_generic_link_slaves,
+	.unlink = snd_pcm_generic_unlink,
+	.writei = snd_pcm_plugin_writei,
+	.writen = snd_pcm_plugin_writen,
+	.readi = snd_pcm_plugin_readi,
+	.readn = snd_pcm_plugin_readn,
+	.avail_update = snd_pcm_plugin_avail_update,
+	.mmap_commit = snd_pcm_plugin_mmap_commit,
+	.htimestamp = snd_pcm_generic_htimestamp,
+	.poll_descriptors_count = snd_pcm_generic_poll_descriptors_count,
+	.poll_descriptors = snd_pcm_generic_poll_descriptors,
+	.poll_revents = snd_pcm_generic_poll_revents,
+};
+
+#endif
diff --git a/src/pcm/pcm_plugin.h b/src/pcm/pcm_plugin.h
new file mode 100644
index 0000000..7ee7c7f
--- /dev/null
+++ b/src/pcm/pcm_plugin.h
@@ -0,0 +1,152 @@
+/*
+ *  PCM - Common plugin code
+ *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+#include "iatomic.h"
+#include "pcm_generic.h"
+
+typedef snd_pcm_uframes_t (*snd_pcm_slave_xfer_areas_func_t)
+     (snd_pcm_t *pcm, 
+      const snd_pcm_channel_area_t *areas,
+      snd_pcm_uframes_t offset, 
+      snd_pcm_uframes_t size,
+      const snd_pcm_channel_area_t *slave_areas,
+      snd_pcm_uframes_t slave_offset, 
+      snd_pcm_uframes_t *slave_sizep);
+
+typedef snd_pcm_sframes_t (*snd_pcm_slave_xfer_areas_undo_func_t)
+     (snd_pcm_t *pcm,
+      const snd_pcm_channel_area_t *res_areas,	/* result areas */
+      snd_pcm_uframes_t res_offset,		/* offset of result areas */
+      snd_pcm_uframes_t res_size,		/* size of result areas */
+      snd_pcm_uframes_t slave_undo_size);
+
+typedef struct {
+	snd_pcm_generic_t gen;
+	snd_pcm_slave_xfer_areas_func_t read;
+	snd_pcm_slave_xfer_areas_func_t write;
+	snd_pcm_slave_xfer_areas_undo_func_t undo_read;
+	snd_pcm_slave_xfer_areas_undo_func_t undo_write;
+	int (*init)(snd_pcm_t *pcm);
+	snd_pcm_uframes_t appl_ptr, hw_ptr;
+	snd_atomic_write_t watom;
+} snd_pcm_plugin_t;	
+
+/* make local functions really local */
+#define snd_pcm_plugin_init \
+	snd1_pcm_plugin_init
+#define snd_pcm_plugin_fast_ops \
+	snd1_pcm_plugin_fast_ops
+#define snd_pcm_plugin_undo_read_generic \
+	snd1_pcm_plugin_undo_read_generic
+#define snd_pcm_plugin_undo_write_generic \
+	snd1_pcm_plugin_undo_write_generic
+
+void snd_pcm_plugin_init(snd_pcm_plugin_t *plugin);
+
+extern const snd_pcm_fast_ops_t snd_pcm_plugin_fast_ops;
+
+snd_pcm_sframes_t snd_pcm_plugin_undo_read_generic
+     (snd_pcm_t *pcm,
+      const snd_pcm_channel_area_t *res_areas,	/* result areas */
+      snd_pcm_uframes_t res_offset,		/* offset of result areas */
+      snd_pcm_uframes_t res_size,		/* size of result areas */
+      snd_pcm_uframes_t slave_undo_size);
+
+snd_pcm_sframes_t snd_pcm_plugin_undo_write_generic
+     (snd_pcm_t *pcm,
+      const snd_pcm_channel_area_t *res_areas,	/* result areas */
+      snd_pcm_uframes_t res_offset,		/* offset of result areas */
+      snd_pcm_uframes_t res_size,		/* size of result areas */
+      snd_pcm_uframes_t slave_undo_size);
+
+/* make local functions really local */
+#define snd_pcm_linear_get_index	snd1_pcm_linear_get_index
+#define snd_pcm_linear_put_index	snd1_pcm_linear_put_index
+#define snd_pcm_linear_get32_index	snd1_pcm_linear_get32_index
+#define snd_pcm_linear_put32_index	snd1_pcm_linear_put32_index
+#define snd_pcm_linear_convert_index	snd1_pcm_linear_convert_index
+#define snd_pcm_linear_convert	snd1_pcm_linear_convert
+#define snd_pcm_linear_getput	snd1_pcm_linear_getput
+#define snd_pcm_alaw_decode	snd1_pcm_alaw_decode
+#define snd_pcm_alaw_encode	snd1_pcm_alaw_encode
+#define snd_pcm_mulaw_decode	snd1_pcm_mulaw_decode
+#define snd_pcm_mulaw_encode	snd1_pcm_mulaw_encode
+#define snd_pcm_adpcm_decode	snd1_pcm_adpcm_decode
+#define snd_pcm_adpcm_encode	snd1_pcm_adpcm_encode
+
+int snd_pcm_linear_get_index(snd_pcm_format_t src_format, snd_pcm_format_t dst_format);
+int snd_pcm_linear_put_index(snd_pcm_format_t src_format, snd_pcm_format_t dst_format);
+int snd_pcm_linear_get32_index(snd_pcm_format_t src_format, snd_pcm_format_t dst_format);
+int snd_pcm_linear_put32_index(snd_pcm_format_t src_format, snd_pcm_format_t dst_format);
+int snd_pcm_linear_convert_index(snd_pcm_format_t src_format, snd_pcm_format_t dst_format);
+
+void snd_pcm_linear_convert(const snd_pcm_channel_area_t *dst_areas, snd_pcm_uframes_t dst_offset,
+			    const snd_pcm_channel_area_t *src_areas, snd_pcm_uframes_t src_offset,
+			    unsigned int channels, snd_pcm_uframes_t frames,
+			    unsigned int convidx);
+void snd_pcm_linear_getput(const snd_pcm_channel_area_t *dst_areas, snd_pcm_uframes_t dst_offset,
+			   const snd_pcm_channel_area_t *src_areas, snd_pcm_uframes_t src_offset,
+			   unsigned int channels, snd_pcm_uframes_t frames,
+			   unsigned int get_idx, unsigned int put_idx);
+void snd_pcm_alaw_decode(const snd_pcm_channel_area_t *dst_areas,
+			 snd_pcm_uframes_t dst_offset,
+			 const snd_pcm_channel_area_t *src_areas,
+			 snd_pcm_uframes_t src_offset,
+			 unsigned int channels, snd_pcm_uframes_t frames,
+			 unsigned int putidx);
+void snd_pcm_alaw_encode(const snd_pcm_channel_area_t *dst_areas,
+			 snd_pcm_uframes_t dst_offset,
+			 const snd_pcm_channel_area_t *src_areas,
+			 snd_pcm_uframes_t src_offset,
+			 unsigned int channels, snd_pcm_uframes_t frames,
+			 unsigned int getidx);
+void snd_pcm_mulaw_decode(const snd_pcm_channel_area_t *dst_areas,
+			  snd_pcm_uframes_t dst_offset,
+			  const snd_pcm_channel_area_t *src_areas,
+			  snd_pcm_uframes_t src_offset,
+			  unsigned int channels, snd_pcm_uframes_t frames,
+			  unsigned int putidx);
+void snd_pcm_mulaw_encode(const snd_pcm_channel_area_t *dst_areas,
+			  snd_pcm_uframes_t dst_offset,
+			  const snd_pcm_channel_area_t *src_areas,
+			  snd_pcm_uframes_t src_offset,
+			  unsigned int channels, snd_pcm_uframes_t frames,
+			  unsigned int getidx);
+
+typedef struct _snd_pcm_adpcm_state {
+	int pred_val;		/* Calculated predicted value */
+	int step_idx;		/* Previous StepSize lookup index */
+} snd_pcm_adpcm_state_t;
+
+void snd_pcm_adpcm_decode(const snd_pcm_channel_area_t *dst_areas,
+			  snd_pcm_uframes_t dst_offset,
+			  const snd_pcm_channel_area_t *src_areas,
+			  snd_pcm_uframes_t src_offset,
+			  unsigned int channels, snd_pcm_uframes_t frames,
+			  unsigned int putidx,
+			  snd_pcm_adpcm_state_t *states);
+void snd_pcm_adpcm_encode(const snd_pcm_channel_area_t *dst_areas,
+			  snd_pcm_uframes_t dst_offset,
+			  const snd_pcm_channel_area_t *src_areas,
+			  snd_pcm_uframes_t src_offset,
+			  unsigned int channels, snd_pcm_uframes_t frames,
+			  unsigned int getidx,
+			  snd_pcm_adpcm_state_t *states);
diff --git a/src/pcm/pcm_rate.c b/src/pcm/pcm_rate.c
new file mode 100644
index 0000000..eb35e4a
--- /dev/null
+++ b/src/pcm/pcm_rate.c
@@ -0,0 +1,1543 @@
+/**
+ * \file pcm/pcm_rate.c
+ * \ingroup PCM_Plugins
+ * \brief PCM Rate Plugin Interface
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \date 2000-2004
+ */
+/*
+ *  PCM - Rate conversion
+ *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *                2004 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+#include <inttypes.h>
+#include <byteswap.h>
+#include "pcm_local.h"
+#include "pcm_plugin.h"
+#include "pcm_rate.h"
+#include "iatomic.h"
+
+#include "plugin_ops.h"
+
+#if 0
+#define DEBUG_REFINE
+#endif
+
+#ifndef PIC
+/* entry for static linking */
+const char *_snd_module_pcm_rate = "";
+#endif
+
+#ifndef DOC_HIDDEN
+
+typedef struct _snd_pcm_rate snd_pcm_rate_t;
+
+struct _snd_pcm_rate {
+	snd_pcm_generic_t gen;
+	snd_atomic_write_t watom;
+	snd_pcm_uframes_t appl_ptr, hw_ptr;
+	snd_pcm_uframes_t last_commit_ptr;
+	snd_pcm_uframes_t orig_avail_min;
+	snd_pcm_sw_params_t sw_params;
+	snd_pcm_format_t sformat;
+	unsigned int srate;
+	snd_pcm_channel_area_t *pareas;	/* areas for splitted period (rate pcm) */
+	snd_pcm_channel_area_t *sareas;	/* areas for splitted period (slave pcm) */
+	snd_pcm_rate_info_t info;
+	void *open_func;
+	void *obj;
+	snd_pcm_rate_ops_t ops;
+	unsigned int get_idx;
+	unsigned int put_idx;
+	int16_t *src_buf;
+	int16_t *dst_buf;
+	int start_pending; /* start is triggered but not commited to slave */
+	snd_htimestamp_t trigger_tstamp;
+	unsigned int plugin_version;
+	unsigned int rate_min, rate_max;
+};
+
+#define SND_PCM_RATE_PLUGIN_VERSION_OLD	0x010001	/* old rate plugin */
+
+#endif /* DOC_HIDDEN */
+
+static int snd_pcm_rate_hw_refine_cprepare(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params)
+{
+	snd_pcm_rate_t *rate = pcm->private_data;
+	int err;
+	snd_pcm_access_mask_t access_mask = { SND_PCM_ACCBIT_SHM };
+	snd_pcm_format_mask_t format_mask = { SND_PCM_FMTBIT_LINEAR };
+	err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_ACCESS,
+					 &access_mask);
+	if (err < 0)
+		return err;
+	err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_FORMAT,
+					 &format_mask);
+	if (err < 0)
+		return err;
+	err = _snd_pcm_hw_params_set_subformat(params, SND_PCM_SUBFORMAT_STD);
+	if (err < 0)
+		return err;
+	if (rate->rate_min) {
+		err = _snd_pcm_hw_param_set_min(params, SND_PCM_HW_PARAM_RATE,
+						rate->rate_min, 0);
+		if (err < 0)
+			return err;
+	}
+	if (rate->rate_max) {
+		err = _snd_pcm_hw_param_set_max(params, SND_PCM_HW_PARAM_RATE,
+						rate->rate_max, 0);
+		if (err < 0)
+			return err;
+	}
+	params->info &= ~(SND_PCM_INFO_MMAP | SND_PCM_INFO_MMAP_VALID);
+	return 0;
+}
+
+static int snd_pcm_rate_hw_refine_sprepare(snd_pcm_t *pcm, snd_pcm_hw_params_t *sparams)
+{
+	snd_pcm_rate_t *rate = pcm->private_data;
+	snd_pcm_access_mask_t saccess_mask = { SND_PCM_ACCBIT_MMAP };
+	_snd_pcm_hw_params_any(sparams);
+	_snd_pcm_hw_param_set_mask(sparams, SND_PCM_HW_PARAM_ACCESS,
+				   &saccess_mask);
+	if (rate->sformat != SND_PCM_FORMAT_UNKNOWN) {
+		_snd_pcm_hw_params_set_format(sparams, rate->sformat);
+		_snd_pcm_hw_params_set_subformat(sparams, SND_PCM_SUBFORMAT_STD);
+	}
+	_snd_pcm_hw_param_set_minmax(sparams, SND_PCM_HW_PARAM_RATE,
+				     rate->srate, 0, rate->srate + 1, -1);
+	return 0;
+}
+
+static int snd_pcm_rate_hw_refine_schange(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
+					  snd_pcm_hw_params_t *sparams)
+{
+	snd_pcm_rate_t *rate = pcm->private_data;
+	snd_interval_t t, buffer_size;
+	const snd_interval_t *srate, *crate;
+	int err;
+	unsigned int links = (SND_PCM_HW_PARBIT_CHANNELS |
+			      SND_PCM_HW_PARBIT_PERIOD_TIME |
+			      SND_PCM_HW_PARBIT_TICK_TIME);
+	if (rate->sformat == SND_PCM_FORMAT_UNKNOWN)
+		links |= (SND_PCM_HW_PARBIT_FORMAT |
+			  SND_PCM_HW_PARBIT_SUBFORMAT |
+			  SND_PCM_HW_PARBIT_SAMPLE_BITS |
+			  SND_PCM_HW_PARBIT_FRAME_BITS);
+	snd_interval_copy(&buffer_size, snd_pcm_hw_param_get_interval(params, SND_PCM_HW_PARAM_BUFFER_SIZE));
+	snd_interval_unfloor(&buffer_size);
+	crate = snd_pcm_hw_param_get_interval(params, SND_PCM_HW_PARAM_RATE);
+	srate = snd_pcm_hw_param_get_interval(sparams, SND_PCM_HW_PARAM_RATE);
+	snd_interval_muldiv(&buffer_size, srate, crate, &t);
+	err = _snd_pcm_hw_param_set_interval(sparams, SND_PCM_HW_PARAM_BUFFER_SIZE, &t);
+	if (err < 0)
+		return err;
+	err = _snd_pcm_hw_params_refine(sparams, links, params);
+	if (err < 0)
+		return err;
+	return 0;
+}
+	
+static int snd_pcm_rate_hw_refine_cchange(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
+					  snd_pcm_hw_params_t *sparams)
+{
+	snd_pcm_rate_t *rate = pcm->private_data;
+	snd_interval_t t;
+#ifdef DEBUG_REFINE
+	snd_output_t *out;
+#endif
+	const snd_interval_t *sbuffer_size, *buffer_size;
+	const snd_interval_t *srate, *crate;
+	int err;
+	unsigned int links = (SND_PCM_HW_PARBIT_CHANNELS |
+			      SND_PCM_HW_PARBIT_PERIOD_TIME |
+			      SND_PCM_HW_PARBIT_TICK_TIME);
+	if (rate->sformat == SND_PCM_FORMAT_UNKNOWN)
+		links |= (SND_PCM_HW_PARBIT_FORMAT |
+			  SND_PCM_HW_PARBIT_SUBFORMAT |
+			  SND_PCM_HW_PARBIT_SAMPLE_BITS |
+			  SND_PCM_HW_PARBIT_FRAME_BITS);
+	sbuffer_size = snd_pcm_hw_param_get_interval(sparams, SND_PCM_HW_PARAM_BUFFER_SIZE);
+	crate = snd_pcm_hw_param_get_interval(params, SND_PCM_HW_PARAM_RATE);
+	srate = snd_pcm_hw_param_get_interval(sparams, SND_PCM_HW_PARAM_RATE);
+	snd_interval_muldiv(sbuffer_size, crate, srate, &t);
+	snd_interval_floor(&t);
+	err = _snd_pcm_hw_param_set_interval(params, SND_PCM_HW_PARAM_BUFFER_SIZE, &t);
+	if (err < 0)
+		return err;
+	buffer_size = snd_pcm_hw_param_get_interval(params, SND_PCM_HW_PARAM_BUFFER_SIZE);
+	/*
+	 * this condition probably needs more work:
+	 *   in case when the buffer_size is known and we are looking
+	 *   for best period_size, we should prefer situation when
+	 *   (buffer_size / period_size) * period_size == buffer_size
+	 */
+	if (snd_interval_single(buffer_size) && buffer_size->integer) {
+		snd_interval_t *period_size;
+		period_size = (snd_interval_t *)snd_pcm_hw_param_get_interval(params, SND_PCM_HW_PARAM_PERIOD_SIZE);
+		if (!snd_interval_checkempty(period_size) &&
+		    period_size->openmin && period_size->openmax &&
+		    period_size->min + 1 == period_size->max) {
+			if (period_size->min > 0 && (buffer_size->min / period_size->min) * period_size->min == buffer_size->min) {
+		    		snd_interval_set_value(period_size, period_size->min);
+		    	} else if ((buffer_size->max / period_size->max) * period_size->max == buffer_size->max) {
+		    		snd_interval_set_value(period_size, period_size->max);
+		    	}
+		}
+	}
+#ifdef DEBUG_REFINE
+	snd_output_stdio_attach(&out, stderr, 0);
+	snd_output_printf(out, "REFINE (params):\n");
+	snd_pcm_hw_params_dump(params, out);
+	snd_output_printf(out, "REFINE (slave params):\n");
+	snd_pcm_hw_params_dump(sparams, out);
+	snd_output_close(out);
+#endif
+	err = _snd_pcm_hw_params_refine(params, links, sparams);
+#ifdef DEBUG_REFINE
+	snd_output_stdio_attach(&out, stderr, 0);
+	snd_output_printf(out, "********************\n");
+	snd_output_printf(out, "REFINE (params) (%i):\n", err);
+	snd_pcm_hw_params_dump(params, out);
+	snd_output_printf(out, "REFINE (slave params):\n");
+	snd_pcm_hw_params_dump(sparams, out);
+	snd_output_close(out);
+#endif
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static int snd_pcm_rate_hw_refine(snd_pcm_t *pcm, 
+				  snd_pcm_hw_params_t *params)
+{
+	return snd_pcm_hw_refine_slave(pcm, params,
+				       snd_pcm_rate_hw_refine_cprepare,
+				       snd_pcm_rate_hw_refine_cchange,
+				       snd_pcm_rate_hw_refine_sprepare,
+				       snd_pcm_rate_hw_refine_schange,
+				       snd_pcm_generic_hw_refine);
+}
+
+static int snd_pcm_rate_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t * params)
+{
+	snd_pcm_rate_t *rate = pcm->private_data;
+	snd_pcm_t *slave = rate->gen.slave;
+	snd_pcm_rate_side_info_t *sinfo, *cinfo;
+	unsigned int channels, cwidth, swidth, chn;
+	int err = snd_pcm_hw_params_slave(pcm, params,
+					  snd_pcm_rate_hw_refine_cchange,
+					  snd_pcm_rate_hw_refine_sprepare,
+					  snd_pcm_rate_hw_refine_schange,
+					  snd_pcm_generic_hw_params);
+	if (err < 0)
+		return err;
+
+	if (pcm->stream == SND_PCM_STREAM_PLAYBACK) {
+		cinfo = &rate->info.in;
+		sinfo = &rate->info.out;
+	} else {
+		sinfo = &rate->info.in;
+		cinfo = &rate->info.out;
+	}
+	err = INTERNAL(snd_pcm_hw_params_get_format)(params, &cinfo->format);
+	if (err < 0)
+		return err;
+	err = INTERNAL(snd_pcm_hw_params_get_rate)(params, &cinfo->rate, 0);
+	if (err < 0)
+		return err;
+	err = INTERNAL(snd_pcm_hw_params_get_period_size)(params, &cinfo->period_size, 0);
+	if (err < 0)
+		return err;
+	err = INTERNAL(snd_pcm_hw_params_get_buffer_size)(params, &cinfo->buffer_size);
+	if (err < 0)
+		return err;
+	err = INTERNAL(snd_pcm_hw_params_get_channels)(params, &channels);
+	if (err < 0)
+		return err;
+
+	rate->info.channels = channels;
+	sinfo->format = slave->format;
+	sinfo->rate = slave->rate;
+	sinfo->buffer_size = slave->buffer_size;
+	sinfo->period_size = slave->period_size;
+
+	if (CHECK_SANITY(rate->pareas)) {
+		SNDMSG("rate plugin already in use");
+		return -EBUSY;
+	}
+	err = rate->ops.init(rate->obj, &rate->info);
+	if (err < 0)
+		return err;
+
+	rate->pareas = malloc(2 * channels * sizeof(*rate->pareas));
+	if (rate->pareas == NULL)
+		goto error;
+
+	cwidth = snd_pcm_format_physical_width(cinfo->format);
+	swidth = snd_pcm_format_physical_width(sinfo->format);
+	rate->pareas[0].addr = malloc(((cwidth * channels * cinfo->period_size) / 8) +
+				      ((swidth * channels * sinfo->period_size) / 8));
+	if (rate->pareas[0].addr == NULL)
+		goto error;
+
+	rate->sareas = rate->pareas + channels;
+	rate->sareas[0].addr = (char *)rate->pareas[0].addr + ((cwidth * channels * cinfo->period_size) / 8);
+	for (chn = 0; chn < channels; chn++) {
+		rate->pareas[chn].addr = rate->pareas[0].addr + (cwidth * chn * cinfo->period_size) / 8;
+		rate->pareas[chn].first = 0;
+		rate->pareas[chn].step = cwidth;
+		rate->sareas[chn].addr = rate->sareas[0].addr + (swidth * chn * sinfo->period_size) / 8;
+		rate->sareas[chn].first = 0;
+		rate->sareas[chn].step = swidth;
+	}
+
+	if (rate->ops.convert_s16) {
+		rate->get_idx = snd_pcm_linear_get_index(rate->info.in.format, SND_PCM_FORMAT_S16);
+		rate->put_idx = snd_pcm_linear_put_index(SND_PCM_FORMAT_S16, rate->info.out.format);
+		free(rate->src_buf);
+		rate->src_buf = malloc(channels * rate->info.in.period_size * 2);
+		free(rate->dst_buf);
+		rate->dst_buf = malloc(channels * rate->info.out.period_size * 2);
+		if (! rate->src_buf || ! rate->dst_buf)
+			goto error;
+	}
+
+	return 0;
+
+ error:
+	if (rate->pareas) {
+		free(rate->pareas[0].addr);
+		free(rate->pareas);
+		rate->pareas = NULL;
+	}
+	if (rate->ops.free)
+		rate->ops.free(rate->obj);
+	return -ENOMEM;
+}
+
+static int snd_pcm_rate_hw_free(snd_pcm_t *pcm)
+{
+	snd_pcm_rate_t *rate = pcm->private_data;
+	if (rate->pareas) {
+		free(rate->pareas[0].addr);
+		free(rate->pareas);
+		rate->pareas = NULL;
+		rate->sareas = NULL;
+	}
+	if (rate->ops.free)
+		rate->ops.free(rate->obj);
+	free(rate->src_buf);
+	free(rate->dst_buf);
+	rate->src_buf = rate->dst_buf = NULL;
+	return snd_pcm_hw_free(rate->gen.slave);
+}
+
+static void recalc(snd_pcm_t *pcm, snd_pcm_uframes_t *val)
+{
+	snd_pcm_rate_t *rate = pcm->private_data;
+	snd_pcm_t *slave = rate->gen.slave;
+	unsigned long div;
+
+	if (*val == pcm->buffer_size) {
+		*val = slave->buffer_size;
+	} else {
+		div = *val / pcm->period_size;
+		if (div * pcm->period_size == *val)
+			*val = div * slave->period_size;
+		else
+			*val = muldiv_near(*val, slave->period_size, pcm->period_size);
+	}
+}
+
+static int snd_pcm_rate_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t * params)
+{
+	snd_pcm_rate_t *rate = pcm->private_data;
+	snd_pcm_t *slave = rate->gen.slave;
+	snd_pcm_sw_params_t *sparams;
+	snd_pcm_uframes_t boundary1, boundary2, sboundary;
+	int err;
+
+	sparams = &rate->sw_params;
+	err = snd_pcm_sw_params_current(slave, sparams);
+	if (err < 0)
+		return err;
+	sboundary = sparams->boundary;
+	*sparams = *params;
+	boundary1 = pcm->buffer_size;
+	boundary2 = slave->buffer_size;
+	while (boundary1 * 2 <= LONG_MAX - pcm->buffer_size &&
+	       boundary2 * 2 <= LONG_MAX - slave->buffer_size) {
+		boundary1 *= 2;
+		boundary2 *= 2;
+	}
+	params->boundary = boundary1;
+	sparams->boundary = sboundary;
+
+	if (rate->ops.adjust_pitch)
+		rate->ops.adjust_pitch(rate->obj, &rate->info);
+
+	recalc(pcm, &sparams->avail_min);
+	rate->orig_avail_min = sparams->avail_min;
+	recalc(pcm, &sparams->start_threshold);
+	if (sparams->avail_min < 1) sparams->avail_min = 1;
+	if (sparams->start_threshold <= slave->buffer_size) {
+		if (sparams->start_threshold > (slave->buffer_size / sparams->avail_min) * sparams->avail_min)
+			sparams->start_threshold = (slave->buffer_size / sparams->avail_min) * sparams->avail_min;
+	}
+	if (sparams->stop_threshold >= params->boundary) {
+		sparams->stop_threshold = sparams->boundary;
+	} else {
+		recalc(pcm, &sparams->stop_threshold);
+	}
+	recalc(pcm, &sparams->silence_threshold);
+	if (sparams->silence_size >= params->boundary) {
+		sparams->silence_size = sparams->boundary;
+	} else {
+		recalc(pcm, &sparams->silence_size);
+	}
+	return snd_pcm_sw_params(slave, sparams);
+}
+
+static int snd_pcm_rate_init(snd_pcm_t *pcm)
+{
+	snd_pcm_rate_t *rate = pcm->private_data;
+
+	if (rate->ops.reset)
+		rate->ops.reset(rate->obj);
+	rate->last_commit_ptr = 0;
+	rate->start_pending = 0;
+	return 0;
+}
+
+static void convert_to_s16(snd_pcm_rate_t *rate, int16_t *buf,
+			   const snd_pcm_channel_area_t *areas,
+			   snd_pcm_uframes_t offset, unsigned int frames,
+			   unsigned int channels)
+{
+#ifndef DOC_HIDDEN
+#define GET16_LABELS
+#include "plugin_ops.h"
+#undef GET16_LABELS
+#endif /* DOC_HIDDEN */
+	void *get = get16_labels[rate->get_idx];
+	const char *src;
+	int16_t sample;
+	const char *srcs[channels];
+	int src_step[channels];
+	unsigned int c;
+
+	for (c = 0; c < channels; c++) {
+		srcs[c] = snd_pcm_channel_area_addr(areas + c, offset);
+		src_step[c] = snd_pcm_channel_area_step(areas + c);
+	}
+
+	while (frames--) {
+		for (c = 0; c < channels; c++) {
+			src = srcs[c];
+			goto *get;
+#ifndef DOC_HIDDEN
+#define GET16_END after_get
+#include "plugin_ops.h"
+#undef GET16_END
+#endif /* DOC_HIDDEN */
+		after_get:
+			*buf++ = sample;
+			srcs[c] += src_step[c];
+		}
+	}
+}
+
+static void convert_from_s16(snd_pcm_rate_t *rate, const int16_t *buf,
+			     const snd_pcm_channel_area_t *areas,
+			     snd_pcm_uframes_t offset, unsigned int frames,
+			     unsigned int channels)
+{
+#ifndef DOC_HIDDEN
+#define PUT16_LABELS
+#include "plugin_ops.h"
+#undef PUT16_LABELS
+#endif /* DOC_HIDDEN */
+	void *put = put16_labels[rate->put_idx];
+	char *dst;
+	int16_t sample;
+	char *dsts[channels];
+	int dst_step[channels];
+	unsigned int c;
+
+	for (c = 0; c < channels; c++) {
+		dsts[c] = snd_pcm_channel_area_addr(areas + c, offset);
+		dst_step[c] = snd_pcm_channel_area_step(areas + c);
+	}
+
+	while (frames--) {
+		for (c = 0; c < channels; c++) {
+			dst = dsts[c];
+			sample = *buf++;
+			goto *put;
+#ifndef DOC_HIDDEN
+#define PUT16_END after_put
+#include "plugin_ops.h"
+#undef PUT16_END
+#endif /* DOC_HIDDEN */
+		after_put:
+			dsts[c] += dst_step[c];
+		}
+	}
+}
+
+static void do_convert(const snd_pcm_channel_area_t *dst_areas,
+		       snd_pcm_uframes_t dst_offset, unsigned int dst_frames,
+		       const snd_pcm_channel_area_t *src_areas,
+		       snd_pcm_uframes_t src_offset, unsigned int src_frames,
+		       unsigned int channels,
+		       snd_pcm_rate_t *rate)
+{
+	if (rate->ops.convert_s16) {
+		const int16_t *src;
+		int16_t *dst;
+		if (! rate->src_buf)
+			src = src_areas->addr + src_offset * 2 * channels;
+		else {
+			convert_to_s16(rate, rate->src_buf, src_areas, src_offset,
+				       src_frames, channels);
+			src = rate->src_buf;
+		}
+		if (! rate->dst_buf)
+			dst = dst_areas->addr + dst_offset * 2 * channels;
+		else
+			dst = rate->dst_buf;
+		rate->ops.convert_s16(rate->obj, dst, dst_frames, src, src_frames);
+		if (dst == rate->dst_buf)
+			convert_from_s16(rate, rate->dst_buf, dst_areas, dst_offset,
+					 dst_frames, channels);
+	} else {
+		rate->ops.convert(rate->obj, dst_areas, dst_offset, dst_frames,
+				   src_areas, src_offset, src_frames);
+	}
+}
+
+static inline void
+snd_pcm_rate_write_areas1(snd_pcm_t *pcm,
+			 const snd_pcm_channel_area_t *areas,
+			 snd_pcm_uframes_t offset,
+			 const snd_pcm_channel_area_t *slave_areas,
+			 snd_pcm_uframes_t slave_offset)
+{
+	snd_pcm_rate_t *rate = pcm->private_data;
+	do_convert(slave_areas, slave_offset, rate->gen.slave->period_size,
+		   areas, offset, pcm->period_size,
+		   pcm->channels, rate);
+}
+
+static inline void
+snd_pcm_rate_read_areas1(snd_pcm_t *pcm,
+			 const snd_pcm_channel_area_t *areas,
+			 snd_pcm_uframes_t offset,
+			 const snd_pcm_channel_area_t *slave_areas,
+			 snd_pcm_uframes_t slave_offset)
+{
+	snd_pcm_rate_t *rate = pcm->private_data;
+	do_convert(areas, offset, pcm->period_size,
+		   slave_areas, slave_offset, rate->gen.slave->period_size,
+		   pcm->channels, rate);
+}
+
+static inline snd_pcm_sframes_t snd_pcm_rate_move_applptr(snd_pcm_t *pcm, snd_pcm_sframes_t frames)
+{
+	snd_pcm_rate_t *rate = pcm->private_data;
+	snd_pcm_uframes_t orig_appl_ptr, appl_ptr = rate->appl_ptr, slave_appl_ptr;
+	snd_pcm_sframes_t diff, ndiff;
+	snd_pcm_t *slave = rate->gen.slave;
+
+	orig_appl_ptr = rate->appl_ptr;
+	if (frames > 0)
+		snd_pcm_mmap_appl_forward(pcm, frames);
+	else
+		snd_pcm_mmap_appl_backward(pcm, -frames);
+	slave_appl_ptr =
+		(appl_ptr / pcm->period_size) * rate->gen.slave->period_size;
+	diff = slave_appl_ptr - *slave->appl.ptr;
+	if (diff < -(snd_pcm_sframes_t)(slave->boundary / 2)) {
+		diff = (slave->boundary - *slave->appl.ptr) + slave_appl_ptr;
+	} else if (diff > (snd_pcm_sframes_t)(slave->boundary / 2)) {
+		diff = -((slave->boundary - slave_appl_ptr) + *slave->appl.ptr);
+	}
+	if (diff == 0)
+		return frames;
+	if (diff > 0) {
+		ndiff = snd_pcm_forward(rate->gen.slave, diff);
+	} else {
+		ndiff = snd_pcm_rewind(rate->gen.slave, diff);
+	}
+	if (ndiff < 0)
+		return diff;
+	slave_appl_ptr = *slave->appl.ptr;
+	rate->appl_ptr =
+		(slave_appl_ptr / rate->gen.slave->period_size) * pcm->period_size +
+		orig_appl_ptr % pcm->period_size;
+	if (pcm->stream == SND_PCM_STREAM_PLAYBACK)
+		rate->appl_ptr += rate->ops.input_frames(rate->obj, slave_appl_ptr % rate->gen.slave->period_size);
+	else
+		rate->appl_ptr += rate->ops.output_frames(rate->obj, slave_appl_ptr % rate->gen.slave->period_size);
+
+	diff = orig_appl_ptr - rate->appl_ptr;
+	if (diff < -(snd_pcm_sframes_t)(slave->boundary / 2)) {
+		diff = (slave->boundary - rate->appl_ptr) + orig_appl_ptr;
+	} else if (diff > (snd_pcm_sframes_t)(slave->boundary / 2)) {
+		diff = -((slave->boundary - orig_appl_ptr) + rate->appl_ptr);
+	}
+	if (frames < 0)
+		diff = -diff;
+
+	rate->last_commit_ptr = rate->appl_ptr - rate->appl_ptr % pcm->period_size;
+
+	return diff;
+}
+
+static inline void snd_pcm_rate_sync_hwptr(snd_pcm_t *pcm)
+{
+	snd_pcm_rate_t *rate = pcm->private_data;
+	snd_pcm_uframes_t slave_hw_ptr = *rate->gen.slave->hw.ptr;
+
+	if (pcm->stream != SND_PCM_STREAM_PLAYBACK)
+		return;
+	/* FIXME: boundary overlap of slave hw_ptr isn't evaluated here!
+	 *        e.g. if slave rate is small... 
+	 */
+	rate->hw_ptr =
+		(slave_hw_ptr / rate->gen.slave->period_size) * pcm->period_size +
+		rate->ops.input_frames(rate->obj, slave_hw_ptr % rate->gen.slave->period_size);
+}
+
+static int snd_pcm_rate_hwsync(snd_pcm_t *pcm)
+{
+	snd_pcm_rate_t *rate = pcm->private_data;
+	int err = snd_pcm_hwsync(rate->gen.slave);
+	if (err < 0)
+		return err;
+	snd_atomic_write_begin(&rate->watom);
+	snd_pcm_rate_sync_hwptr(pcm);
+	snd_atomic_write_end(&rate->watom);
+	return 0;
+}
+
+static int snd_pcm_rate_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp)
+{
+	snd_pcm_rate_hwsync(pcm);
+	if (pcm->stream == SND_PCM_STREAM_PLAYBACK)
+		*delayp = snd_pcm_mmap_playback_hw_avail(pcm);
+	else
+		*delayp = snd_pcm_mmap_capture_hw_avail(pcm);
+	return 0;
+}
+
+static int snd_pcm_rate_prepare(snd_pcm_t *pcm)
+{
+	snd_pcm_rate_t *rate = pcm->private_data;
+	int err;
+
+	snd_atomic_write_begin(&rate->watom);
+	err = snd_pcm_prepare(rate->gen.slave);
+	if (err < 0) {
+		snd_atomic_write_end(&rate->watom);
+		return err;
+	}
+	*pcm->hw.ptr = 0;
+	*pcm->appl.ptr = 0;
+	snd_atomic_write_end(&rate->watom);
+	err = snd_pcm_rate_init(pcm);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static int snd_pcm_rate_reset(snd_pcm_t *pcm)
+{
+	snd_pcm_rate_t *rate = pcm->private_data;
+	int err;
+	snd_atomic_write_begin(&rate->watom);
+	err = snd_pcm_reset(rate->gen.slave);
+	if (err < 0) {
+		snd_atomic_write_end(&rate->watom);
+		return err;
+	}
+	*pcm->hw.ptr = 0;
+	*pcm->appl.ptr = 0;
+	snd_atomic_write_end(&rate->watom);
+	err = snd_pcm_rate_init(pcm);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static snd_pcm_sframes_t snd_pcm_rate_rewind(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
+{
+	snd_pcm_rate_t *rate = pcm->private_data;
+	snd_pcm_sframes_t n = snd_pcm_mmap_hw_avail(pcm);
+
+	if ((snd_pcm_uframes_t)n > frames)
+		frames = n;
+	if (frames == 0)
+		return 0;
+	
+	snd_atomic_write_begin(&rate->watom);
+	n = snd_pcm_rate_move_applptr(pcm, -frames);
+	snd_atomic_write_end(&rate->watom);
+	return n;
+}
+
+static snd_pcm_sframes_t snd_pcm_rate_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
+{
+	snd_pcm_rate_t *rate = pcm->private_data;
+	snd_pcm_sframes_t n = snd_pcm_mmap_avail(pcm);
+
+	if ((snd_pcm_uframes_t)n > frames)
+		frames = n;
+	if (frames == 0)
+		return 0;
+	
+	snd_atomic_write_begin(&rate->watom);
+	n = snd_pcm_rate_move_applptr(pcm, frames);
+	snd_atomic_write_end(&rate->watom);
+	return n;
+}
+
+static int snd_pcm_rate_commit_area(snd_pcm_t *pcm, snd_pcm_rate_t *rate,
+				    snd_pcm_uframes_t appl_offset,
+				    snd_pcm_uframes_t size,
+				    snd_pcm_uframes_t slave_size)
+{
+	snd_pcm_uframes_t cont = pcm->buffer_size - appl_offset;
+	const snd_pcm_channel_area_t *areas;
+	const snd_pcm_channel_area_t *slave_areas;
+	snd_pcm_uframes_t slave_offset, xfer;
+	snd_pcm_uframes_t slave_frames = ULONG_MAX;
+	snd_pcm_sframes_t result;
+
+	areas = snd_pcm_mmap_areas(pcm);
+	if (cont >= size) {
+		result = snd_pcm_mmap_begin(rate->gen.slave, &slave_areas, &slave_offset, &slave_frames);
+		if (result < 0)
+			return result;
+		if (slave_frames < slave_size) {
+			snd_pcm_rate_write_areas1(pcm, areas, appl_offset, rate->sareas, 0);
+			goto __partial;
+		}
+		snd_pcm_rate_write_areas1(pcm, areas, appl_offset,
+					  slave_areas, slave_offset);
+		result = snd_pcm_mmap_commit(rate->gen.slave, slave_offset, slave_size);
+		if (result < (snd_pcm_sframes_t)slave_size) {
+			if (result < 0)
+				return result;
+			result = snd_pcm_rewind(rate->gen.slave, result);
+			if (result < 0)
+				return result;
+			return 0;
+		}
+	} else {
+		snd_pcm_areas_copy(rate->pareas, 0,
+				   areas, appl_offset,
+				   pcm->channels, cont,
+				   pcm->format);
+		snd_pcm_areas_copy(rate->pareas, cont,
+				   areas, 0,
+				   pcm->channels, size - cont,
+				   pcm->format);
+
+		snd_pcm_rate_write_areas1(pcm, rate->pareas, 0, rate->sareas, 0);
+
+		/* ok, commit first fragment */
+		result = snd_pcm_mmap_begin(rate->gen.slave, &slave_areas, &slave_offset, &slave_frames);
+		if (result < 0)
+			return result;
+	      __partial:
+		xfer = 0;
+		cont = slave_frames;
+		if (cont > slave_size)
+			cont = slave_size;
+		snd_pcm_areas_copy(slave_areas, slave_offset,
+				   rate->sareas, 0,
+				   pcm->channels, cont,
+				   rate->gen.slave->format);
+		result = snd_pcm_mmap_commit(rate->gen.slave, slave_offset, cont);
+		if (result < (snd_pcm_sframes_t)cont) {
+			if (result < 0)
+				return result;
+			result = snd_pcm_rewind(rate->gen.slave, result);
+			if (result < 0)
+				return result;
+			return 0;
+		}
+		xfer = cont;
+
+		if (xfer == slave_size)
+			goto commit_done;
+		
+		/* commit second fragment */
+		cont = slave_size - cont;
+		slave_frames = cont;
+		result = snd_pcm_mmap_begin(rate->gen.slave, &slave_areas, &slave_offset, &slave_frames);
+		if (result < 0)
+			return result;
+#if 0
+		if (slave_offset) {
+			SNDERR("non-zero slave_offset %ld", slave_offset);
+			return -EIO;
+		}
+#endif
+		snd_pcm_areas_copy(slave_areas, slave_offset,
+				   rate->sareas, xfer,
+				   pcm->channels, cont,
+				   rate->gen.slave->format);
+		result = snd_pcm_mmap_commit(rate->gen.slave, slave_offset, cont);
+		if (result < (snd_pcm_sframes_t)cont) {
+			if (result < 0)
+				return result;
+			result = snd_pcm_rewind(rate->gen.slave, result + xfer);
+			if (result < 0)
+				return result;
+			return 0;
+		}
+	}
+
+ commit_done:
+	if (rate->start_pending) {
+		/* we have pending start-trigger.  let's issue it now */
+		snd_pcm_start(rate->gen.slave);
+		rate->start_pending = 0;
+	}
+	return 1;
+}
+
+static int snd_pcm_rate_commit_next_period(snd_pcm_t *pcm, snd_pcm_uframes_t appl_offset)
+{
+	snd_pcm_rate_t *rate = pcm->private_data;
+
+	return snd_pcm_rate_commit_area(pcm, rate, appl_offset, pcm->period_size,
+					rate->gen.slave->period_size);
+}
+
+static int snd_pcm_rate_grab_next_period(snd_pcm_t *pcm, snd_pcm_uframes_t hw_offset)
+{
+	snd_pcm_rate_t *rate = pcm->private_data;
+	snd_pcm_uframes_t cont = pcm->buffer_size - hw_offset;
+	const snd_pcm_channel_area_t *areas;
+	const snd_pcm_channel_area_t *slave_areas;
+	snd_pcm_uframes_t slave_offset, xfer;
+	snd_pcm_uframes_t slave_frames = ULONG_MAX;
+	snd_pcm_sframes_t result;
+
+	areas = snd_pcm_mmap_areas(pcm);
+	if (cont >= pcm->period_size) {
+		result = snd_pcm_mmap_begin(rate->gen.slave, &slave_areas, &slave_offset, &slave_frames);
+		if (result < 0)
+			return result;
+		if (slave_frames < rate->gen.slave->period_size)
+			goto __partial;
+		snd_pcm_rate_read_areas1(pcm, areas, hw_offset,
+					 slave_areas, slave_offset);
+		result = snd_pcm_mmap_commit(rate->gen.slave, slave_offset, rate->gen.slave->period_size);
+		if (result < (snd_pcm_sframes_t)rate->gen.slave->period_size) {
+			if (result < 0)
+				return result;
+			result = snd_pcm_rewind(rate->gen.slave, result);
+			if (result < 0)
+				return result;
+			return 0;
+		}
+	} else {
+		/* ok, grab first fragment */
+		result = snd_pcm_mmap_begin(rate->gen.slave, &slave_areas, &slave_offset, &slave_frames);
+		if (result < 0)
+			return result;
+	      __partial:
+		xfer = 0;
+		cont = slave_frames;
+		if (cont > rate->gen.slave->period_size)
+			cont = rate->gen.slave->period_size;
+		snd_pcm_areas_copy(rate->sareas, 0,
+				   slave_areas, slave_offset,
+				   pcm->channels, cont,
+				   rate->gen.slave->format);
+		result = snd_pcm_mmap_commit(rate->gen.slave, slave_offset, cont);
+		if (result < (snd_pcm_sframes_t)cont) {
+			if (result < 0)
+				return result;
+			result = snd_pcm_rewind(rate->gen.slave, result);
+			if (result < 0)
+				return result;
+			return 0;
+		}
+		xfer = cont;
+
+		if (xfer == rate->gen.slave->period_size)
+			goto __transfer;
+
+		/* grab second fragment */
+		cont = rate->gen.slave->period_size - cont;
+		slave_frames = cont;
+		result = snd_pcm_mmap_begin(rate->gen.slave, &slave_areas, &slave_offset, &slave_frames);
+		if (result < 0)
+			return result;
+#if 0
+		if (slave_offset) {
+			SNDERR("non-zero slave_offset %ld", slave_offset);
+			return -EIO;
+		}
+#endif
+		snd_pcm_areas_copy(rate->sareas, xfer,
+		                   slave_areas, slave_offset,
+				   pcm->channels, cont,
+				   rate->gen.slave->format);
+		result = snd_pcm_mmap_commit(rate->gen.slave, slave_offset, cont);
+		if (result < (snd_pcm_sframes_t)cont) {
+			if (result < 0)
+				return result;
+			result = snd_pcm_rewind(rate->gen.slave, result + xfer);
+			if (result < 0)
+				return result;
+			return 0;
+		}
+
+	      __transfer:
+		cont = pcm->buffer_size - hw_offset;
+		if (cont >= pcm->period_size) {
+			snd_pcm_rate_read_areas1(pcm, areas, hw_offset,
+						 rate->sareas, 0);
+		} else {
+			snd_pcm_rate_read_areas1(pcm,
+						 rate->pareas, 0,
+						 rate->sareas, 0);
+			snd_pcm_areas_copy(areas, hw_offset,
+					   rate->pareas, 0,
+					   pcm->channels, cont,
+					   pcm->format);
+			snd_pcm_areas_copy(areas, 0,
+					   rate->pareas, cont,
+					   pcm->channels, pcm->period_size - cont,
+					   pcm->format);
+		}
+	}
+	return 1;
+}
+
+static int snd_pcm_rate_sync_playback_area(snd_pcm_t *pcm, snd_pcm_uframes_t appl_ptr)
+{
+	snd_pcm_rate_t *rate = pcm->private_data;
+	snd_pcm_t *slave = rate->gen.slave;
+	snd_pcm_uframes_t xfer;
+	snd_pcm_sframes_t slave_size;
+	int err;
+
+	slave_size = snd_pcm_avail_update(slave);
+	if (slave_size < 0)
+		return slave_size;
+
+	if (appl_ptr < rate->last_commit_ptr)
+		xfer = appl_ptr - rate->last_commit_ptr + pcm->boundary;
+	else
+		xfer = appl_ptr - rate->last_commit_ptr;
+	while (xfer >= pcm->period_size &&
+	       (snd_pcm_uframes_t)slave_size >= rate->gen.slave->period_size) {
+		err = snd_pcm_rate_commit_next_period(pcm, rate->last_commit_ptr % pcm->buffer_size);
+		if (err == 0)
+			break;
+		if (err < 0)
+			return err;
+		xfer -= pcm->period_size;
+		slave_size -= rate->gen.slave->period_size;
+		rate->last_commit_ptr += pcm->period_size;
+		if (rate->last_commit_ptr >= pcm->boundary)
+			rate->last_commit_ptr = 0;
+	}
+	return 0;
+}
+
+static snd_pcm_sframes_t snd_pcm_rate_mmap_commit(snd_pcm_t *pcm,
+						  snd_pcm_uframes_t offset ATTRIBUTE_UNUSED,
+						  snd_pcm_uframes_t size)
+{
+	snd_pcm_rate_t *rate = pcm->private_data;
+	int err;
+
+	if (size == 0)
+		return 0;
+	if (pcm->stream == SND_PCM_STREAM_PLAYBACK) {
+		err = snd_pcm_rate_sync_playback_area(pcm, rate->appl_ptr + size);
+		if (err < 0)
+			return err;
+	}
+	snd_atomic_write_begin(&rate->watom);
+	snd_pcm_mmap_appl_forward(pcm, size);
+	snd_atomic_write_end(&rate->watom);
+	return size;
+}
+
+static snd_pcm_sframes_t snd_pcm_rate_avail_update(snd_pcm_t *pcm)
+{
+	snd_pcm_rate_t *rate = pcm->private_data;
+	snd_pcm_t *slave = rate->gen.slave;
+	snd_pcm_uframes_t slave_size;
+
+	slave_size = snd_pcm_avail_update(slave);
+	if (pcm->stream == SND_PCM_STREAM_CAPTURE)
+		goto _capture;
+	snd_atomic_write_begin(&rate->watom);
+	snd_pcm_rate_sync_hwptr(pcm);
+	snd_atomic_write_end(&rate->watom);
+	snd_pcm_rate_sync_playback_area(pcm, rate->appl_ptr);
+	return snd_pcm_mmap_avail(pcm);
+ _capture: {
+	snd_pcm_uframes_t xfer, hw_offset, size;
+	
+	xfer = snd_pcm_mmap_capture_avail(pcm);
+	size = pcm->buffer_size - xfer;
+	hw_offset = snd_pcm_mmap_hw_offset(pcm);
+	while (size >= pcm->period_size &&
+	       slave_size >= rate->gen.slave->period_size) {
+		int err = snd_pcm_rate_grab_next_period(pcm, hw_offset);
+		if (err < 0)
+			return err;
+		if (err == 0)
+			return (snd_pcm_sframes_t)xfer;
+		xfer += pcm->period_size;
+		size -= pcm->period_size;
+		slave_size -= rate->gen.slave->period_size;
+		hw_offset += pcm->period_size;
+		hw_offset %= pcm->buffer_size;
+		snd_pcm_mmap_hw_forward(pcm, pcm->period_size);
+	}
+	return (snd_pcm_sframes_t)xfer;
+ }
+}
+
+static int snd_pcm_rate_htimestamp(snd_pcm_t *pcm,
+				   snd_pcm_uframes_t *avail,
+				   snd_htimestamp_t *tstamp)
+{
+	snd_pcm_rate_t *rate = pcm->private_data;
+	snd_pcm_sframes_t avail1;
+	snd_pcm_uframes_t tmp;
+	int ok = 0, err;
+
+	while (1) {
+		/* the position is from this plugin itself */
+		avail1 = snd_pcm_avail_update(pcm);
+		if (avail1 < 0)
+			return avail1;
+		if (ok && (snd_pcm_uframes_t)avail1 == *avail)
+			break;
+		*avail = avail1;
+		/* timestamp is taken from the slave PCM */
+		err = snd_pcm_htimestamp(rate->gen.slave, &tmp, tstamp);
+		if (err < 0)
+			return err;
+		ok = 1;
+	}
+	return 0;
+}
+
+static int snd_pcm_rate_poll_revents(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int nfds, unsigned short *revents)
+{
+	snd_pcm_rate_t *rate = pcm->private_data;
+	if (pcm->stream == SND_PCM_STREAM_PLAYBACK) {
+		/* Try to sync as much as possible */
+		snd_pcm_rate_hwsync(pcm);
+		snd_pcm_rate_sync_playback_area(pcm, rate->appl_ptr);
+	}
+	return snd_pcm_poll_descriptors_revents(rate->gen.slave, pfds, nfds, revents);
+}
+
+static int snd_pcm_rate_drain(snd_pcm_t *pcm)
+{
+	snd_pcm_rate_t *rate = pcm->private_data;
+
+	if (pcm->stream == SND_PCM_STREAM_PLAYBACK) {
+		/* commit the remaining fraction (if any) */
+		snd_pcm_uframes_t size, ofs, saved_avail_min;
+		snd_pcm_sw_params_t sw_params;
+
+		/* temporarily set avail_min to one */
+		sw_params = rate->sw_params;
+		saved_avail_min = sw_params.avail_min;
+		sw_params.avail_min = 1;
+		snd_pcm_sw_params(rate->gen.slave, &sw_params);
+
+		size = rate->appl_ptr - rate->last_commit_ptr;
+		ofs = rate->last_commit_ptr % pcm->buffer_size;
+		while (size > 0) {
+			snd_pcm_uframes_t psize, spsize;
+
+			if (snd_pcm_wait(rate->gen.slave, -1) < 0)
+				break;
+			if (size > pcm->period_size) {
+				psize = pcm->period_size;
+				spsize = rate->gen.slave->period_size;
+			} else {
+				psize = size;
+				spsize = rate->ops.output_frames(rate->obj, size);
+				if (! spsize)
+					break;
+			}
+			snd_pcm_rate_commit_area(pcm, rate, ofs,
+						 psize, spsize);
+			ofs = (ofs + psize) % pcm->buffer_size;
+			size -= psize;
+		}
+		sw_params.avail_min = saved_avail_min;
+		snd_pcm_sw_params(rate->gen.slave, &sw_params);
+	}
+	return snd_pcm_drain(rate->gen.slave);
+}
+
+static snd_pcm_state_t snd_pcm_rate_state(snd_pcm_t *pcm)
+{
+	snd_pcm_rate_t *rate = pcm->private_data;
+	if (rate->start_pending) /* pseudo-state */
+		return SND_PCM_STATE_RUNNING;
+	return snd_pcm_state(rate->gen.slave);
+}
+
+
+static int snd_pcm_rate_start(snd_pcm_t *pcm)
+{
+	snd_pcm_rate_t *rate = pcm->private_data;
+	snd_pcm_uframes_t avail;
+		
+	if (pcm->stream == SND_PCM_STREAM_CAPTURE)
+		return snd_pcm_start(rate->gen.slave);
+
+	if (snd_pcm_state(rate->gen.slave) != SND_PCM_STATE_PREPARED)
+		return -EBADFD;
+
+	gettimestamp(&rate->trigger_tstamp, pcm->monotonic);
+
+	avail = snd_pcm_mmap_playback_hw_avail(rate->gen.slave);
+	if (avail == 0) {
+		/* postpone the trigger since we have no data committed yet */
+		rate->start_pending = 1;
+		return 0;
+	}
+	rate->start_pending = 0;
+	return snd_pcm_start(rate->gen.slave);
+}
+
+static int snd_pcm_rate_status(snd_pcm_t *pcm, snd_pcm_status_t * status)
+{
+	snd_pcm_rate_t *rate = pcm->private_data;
+	snd_pcm_sframes_t err;
+	snd_atomic_read_t ratom;
+	snd_atomic_read_init(&ratom, &rate->watom);
+ _again:
+	snd_atomic_read_begin(&ratom);
+	err = snd_pcm_status(rate->gen.slave, status);
+	if (err < 0) {
+		snd_atomic_read_ok(&ratom);
+		return err;
+	}
+	if (pcm->stream == SND_PCM_STREAM_PLAYBACK) {
+		if (rate->start_pending)
+			status->state = SND_PCM_STATE_RUNNING;
+		status->trigger_tstamp = rate->trigger_tstamp;
+	}
+	snd_pcm_rate_sync_hwptr(pcm);
+	status->appl_ptr = *pcm->appl.ptr;
+	status->hw_ptr = *pcm->hw.ptr;
+	if (pcm->stream == SND_PCM_STREAM_PLAYBACK) {
+		status->delay = snd_pcm_mmap_playback_hw_avail(pcm);
+		status->avail = snd_pcm_mmap_playback_avail(pcm);
+		status->avail_max = rate->ops.input_frames(rate->obj, status->avail_max);
+	} else {
+		status->delay = snd_pcm_mmap_capture_hw_avail(pcm);
+		status->avail = snd_pcm_mmap_capture_avail(pcm);
+		status->avail_max = rate->ops.output_frames(rate->obj, status->avail_max);
+	}
+	if (!snd_atomic_read_ok(&ratom)) {
+		snd_atomic_read_wait(&ratom);
+		goto _again;
+	}
+	return 0;
+}
+
+static void snd_pcm_rate_dump(snd_pcm_t *pcm, snd_output_t *out)
+{
+	snd_pcm_rate_t *rate = pcm->private_data;
+	if (rate->sformat == SND_PCM_FORMAT_UNKNOWN)
+		snd_output_printf(out, "Rate conversion PCM (%d)\n", 
+			rate->srate);
+	else
+		snd_output_printf(out, "Rate conversion PCM (%d, sformat=%s)\n", 
+			rate->srate,
+			snd_pcm_format_name(rate->sformat));
+	if (rate->ops.dump)
+		rate->ops.dump(rate->obj, out);
+	snd_output_printf(out, "Protocol version: %x\n", rate->plugin_version);
+	if (pcm->setup) {
+		snd_output_printf(out, "Its setup is:\n");
+		snd_pcm_dump_setup(pcm, out);
+	}
+	snd_output_printf(out, "Slave: ");
+	snd_pcm_dump(rate->gen.slave, out);
+}
+
+static int snd_pcm_rate_close(snd_pcm_t *pcm)
+{
+	snd_pcm_rate_t *rate = pcm->private_data;
+
+	if (rate->ops.close)
+		rate->ops.close(rate->obj);
+	if (rate->open_func)
+		snd_dlobj_cache_put(rate->open_func);
+	return snd_pcm_generic_close(pcm);
+}
+
+static const snd_pcm_fast_ops_t snd_pcm_rate_fast_ops = {
+	.status = snd_pcm_rate_status,
+	.state = snd_pcm_rate_state,
+	.hwsync = snd_pcm_rate_hwsync,
+	.delay = snd_pcm_rate_delay,
+	.prepare = snd_pcm_rate_prepare,
+	.reset = snd_pcm_rate_reset,
+	.start = snd_pcm_rate_start,
+	.drop = snd_pcm_generic_drop,
+	.drain = snd_pcm_rate_drain,
+	.pause = snd_pcm_generic_pause,
+	.rewind = snd_pcm_rate_rewind,
+	.forward = snd_pcm_rate_forward,
+	.resume = snd_pcm_generic_resume,
+	.writei = snd_pcm_mmap_writei,
+	.writen = snd_pcm_mmap_writen,
+	.readi = snd_pcm_mmap_readi,
+	.readn = snd_pcm_mmap_readn,
+	.avail_update = snd_pcm_rate_avail_update,
+	.mmap_commit = snd_pcm_rate_mmap_commit,
+	.htimestamp = snd_pcm_rate_htimestamp,
+	.poll_descriptors_count = snd_pcm_generic_poll_descriptors_count,
+	.poll_descriptors = snd_pcm_generic_poll_descriptors,
+	.poll_revents = snd_pcm_rate_poll_revents,
+};
+
+static const snd_pcm_ops_t snd_pcm_rate_ops = {
+	.close = snd_pcm_rate_close,
+	.info = snd_pcm_generic_info,
+	.hw_refine = snd_pcm_rate_hw_refine,
+	.hw_params = snd_pcm_rate_hw_params,
+	.hw_free = snd_pcm_rate_hw_free,
+	.sw_params = snd_pcm_rate_sw_params,
+	.channel_info = snd_pcm_generic_channel_info,
+	.dump = snd_pcm_rate_dump,
+	.nonblock = snd_pcm_generic_nonblock,
+	.async = snd_pcm_generic_async,
+	.mmap = snd_pcm_generic_mmap,
+	.munmap = snd_pcm_generic_munmap,
+};
+
+/**
+ * \brief Get a default converter string
+ * \param root Root configuration node
+ * \retval A const config item if found, or NULL
+ */
+const snd_config_t *snd_pcm_rate_get_default_converter(snd_config_t *root)
+{
+	snd_config_t *n;
+	/* look for default definition */
+	if (snd_config_search(root, "defaults.pcm.rate_converter", &n) >= 0)
+		return n;
+	return NULL;
+}
+
+#ifdef PIC
+static int is_builtin_plugin(const char *type)
+{
+	return strcmp(type, "linear") == 0;
+}
+
+static const char *const default_rate_plugins[] = {
+	"speexrate", "linear", NULL
+};
+
+static int rate_open_func(snd_pcm_rate_t *rate, const char *type, int verbose)
+{
+	char open_name[64], lib_name[128], *lib = NULL;
+	snd_pcm_rate_open_func_t open_func;
+	int err;
+
+	snprintf(open_name, sizeof(open_name), "_snd_pcm_rate_%s_open", type);
+	if (!is_builtin_plugin(type)) {
+		snprintf(lib_name, sizeof(lib_name),
+				 "%s/libasound_module_rate_%s.so", ALSA_PLUGIN_DIR, type);
+		lib = lib_name;
+	}
+	open_func = snd_dlobj_cache_get(lib, open_name, NULL, verbose);
+	if (!open_func)
+		return -ENOENT;
+
+	rate->open_func = open_func;
+	rate->rate_min = SND_PCM_PLUGIN_RATE_MIN;
+	rate->rate_max = SND_PCM_PLUGIN_RATE_MAX;
+	rate->plugin_version = SND_PCM_RATE_PLUGIN_VERSION;
+
+	err = open_func(SND_PCM_RATE_PLUGIN_VERSION, &rate->obj, &rate->ops);
+	if (!err) {
+		rate->plugin_version = rate->ops.version;
+		if (rate->ops.get_supported_rates)
+			rate->ops.get_supported_rates(rate->obj,
+						      &rate->rate_min,
+						      &rate->rate_max);
+		return 0;
+	}
+
+	/* try to open with the old protocol version */
+	rate->plugin_version = SND_PCM_RATE_PLUGIN_VERSION_OLD;
+	err = open_func(SND_PCM_RATE_PLUGIN_VERSION_OLD,
+			&rate->obj, &rate->ops);
+	if (err) {
+		snd_dlobj_cache_put(open_func);
+		rate->open_func = NULL;
+	}
+	return err;
+}
+#endif
+
+/**
+ * \brief Creates a new rate PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param sformat Slave format
+ * \param srate Slave rate
+ * \param converter SRC type string node
+ * \param slave Slave PCM handle
+ * \param close_slave When set, the slave PCM handle is closed with copy PCM
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int snd_pcm_rate_open(snd_pcm_t **pcmp, const char *name,
+		      snd_pcm_format_t sformat, unsigned int srate,
+		      const snd_config_t *converter,
+		      snd_pcm_t *slave, int close_slave)
+{
+	snd_pcm_t *pcm;
+	snd_pcm_rate_t *rate;
+	const char *type = NULL;
+	int err;
+#ifndef PIC
+	snd_pcm_rate_open_func_t open_func;
+	extern int SND_PCM_RATE_PLUGIN_ENTRY(linear) (unsigned int version, void **objp, snd_pcm_rate_ops_t *ops);
+#endif
+
+	assert(pcmp && slave);
+	if (sformat != SND_PCM_FORMAT_UNKNOWN &&
+	    snd_pcm_format_linear(sformat) != 1)
+		return -EINVAL;
+	rate = calloc(1, sizeof(snd_pcm_rate_t));
+	if (!rate) {
+		return -ENOMEM;
+	}
+	rate->gen.slave = slave;
+	rate->gen.close_slave = close_slave;
+	rate->srate = srate;
+	rate->sformat = sformat;
+	snd_atomic_write_init(&rate->watom);
+
+	err = snd_pcm_new(&pcm, SND_PCM_TYPE_RATE, name, slave->stream, slave->mode);
+	if (err < 0) {
+		free(rate);
+		return err;
+	}
+
+#ifdef PIC
+	err = -ENOENT;
+	if (!converter) {
+		const char *const *types;
+		for (types = default_rate_plugins; *types; types++) {
+			err = rate_open_func(rate, *types, 0);
+			if (!err) {
+				type = *types;
+				break;
+			}
+		}
+	} else if (!snd_config_get_string(converter, &type))
+		err = rate_open_func(rate, type, 1);
+	else if (snd_config_get_type(converter) == SND_CONFIG_TYPE_COMPOUND) {
+		snd_config_iterator_t i, next;
+		snd_config_for_each(i, next, converter) {
+			snd_config_t *n = snd_config_iterator_entry(i);
+			if (snd_config_get_string(n, &type) < 0)
+				break;
+			err = rate_open_func(rate, type, 0);
+			if (!err)
+				break;
+		}
+	} else {
+		SNDERR("Invalid type for rate converter");
+		snd_pcm_close(pcm);
+		free(rate);
+		return -EINVAL;
+	}
+	if (err < 0) {
+		SNDERR("Cannot find rate converter");
+		snd_pcm_close(pcm);
+		free(rate);
+		return -ENOENT;
+	}
+#else
+	type = "linear";
+	open_func = SND_PCM_RATE_PLUGIN_ENTRY(linear);
+	err = open_func(SND_PCM_RATE_PLUGIN_VERSION, &rate->obj, &rate->ops);
+	if (err < 0) {
+		snd_pcm_close(pcm);
+		free(rate);
+		return err;
+	}
+#endif
+
+	if (! rate->ops.init || ! (rate->ops.convert || rate->ops.convert_s16) ||
+	    ! rate->ops.input_frames || ! rate->ops.output_frames) {
+		SNDERR("Inproper rate plugin %s initialization", type);
+		snd_pcm_close(pcm);
+		free(rate);
+		return err;
+	}
+
+	pcm->ops = &snd_pcm_rate_ops;
+	pcm->fast_ops = &snd_pcm_rate_fast_ops;
+	pcm->private_data = rate;
+	pcm->poll_fd = slave->poll_fd;
+	pcm->poll_events = slave->poll_events;
+	pcm->mmap_rw = 1;
+	pcm->monotonic = slave->monotonic;
+	snd_pcm_set_hw_ptr(pcm, &rate->hw_ptr, -1, 0);
+	snd_pcm_set_appl_ptr(pcm, &rate->appl_ptr, -1, 0);
+	*pcmp = pcm;
+
+	return 0;
+}
+
+/*! \page pcm_plugins
+
+\section pcm_plugins_rate Plugin: Rate
+
+This plugin converts a stream rate. The input and output formats must be linear.
+
+\code
+pcm.name {
+	type rate               # Rate PCM
+        slave STR               # Slave name
+        # or
+        slave {                 # Slave definition
+                pcm STR         # Slave PCM name
+                # or
+                pcm { }         # Slave PCM definition
+                rate INT        # Slave rate
+                [format STR]    # Slave format
+        }
+	converter STR			# optional
+	# or
+	converter [ STR1 STR2 ... ]	# optional
+				# Converter type, default is taken from
+				# defaults.pcm.rate_converter
+}
+\endcode
+
+\subsection pcm_plugins_rate_funcref Function reference
+
+<UL>
+  <LI>snd_pcm_rate_open()
+  <LI>_snd_pcm_rate_open()
+</UL>
+
+*/
+
+/**
+ * \brief Creates a new rate PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param root Root configuration node
+ * \param conf Configuration node with rate PCM description
+ * \param stream Stream type
+ * \param mode Stream mode
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int _snd_pcm_rate_open(snd_pcm_t **pcmp, const char *name,
+		       snd_config_t *root, snd_config_t *conf, 
+		       snd_pcm_stream_t stream, int mode)
+{
+	snd_config_iterator_t i, next;
+	int err;
+	snd_pcm_t *spcm;
+	snd_config_t *slave = NULL, *sconf;
+	snd_pcm_format_t sformat = SND_PCM_FORMAT_UNKNOWN;
+	int srate = -1;
+	const snd_config_t *converter = NULL;
+
+	snd_config_for_each(i, next, conf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (snd_pcm_conf_generic_id(id))
+			continue;
+		if (strcmp(id, "slave") == 0) {
+			slave = n;
+			continue;
+		}
+		if (strcmp(id, "converter") == 0) {
+			converter = n;
+			continue;
+		}
+		SNDERR("Unknown field %s", id);
+		return -EINVAL;
+	}
+	if (!slave) {
+		SNDERR("slave is not defined");
+		return -EINVAL;
+	}
+
+	err = snd_pcm_slave_conf(root, slave, &sconf, 2,
+				 SND_PCM_HW_PARAM_FORMAT, 0, &sformat,
+				 SND_PCM_HW_PARAM_RATE, SCONF_MANDATORY, &srate);
+	if (err < 0)
+		return err;
+	if (sformat != SND_PCM_FORMAT_UNKNOWN &&
+	    snd_pcm_format_linear(sformat) != 1) {
+	    	snd_config_delete(sconf);
+		SNDERR("slave format is not linear");
+		return -EINVAL;
+	}
+	err = snd_pcm_open_slave(&spcm, root, sconf, stream, mode, conf);
+	snd_config_delete(sconf);
+	if (err < 0)
+		return err;
+	err = snd_pcm_rate_open(pcmp, name, sformat, (unsigned int) srate,
+				converter, spcm, 1);
+	if (err < 0)
+		snd_pcm_close(spcm);
+	return err;
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(_snd_pcm_rate_open, SND_PCM_DLSYM_VERSION);
+#endif
diff --git a/src/pcm/pcm_rate_linear.c b/src/pcm/pcm_rate_linear.c
new file mode 100644
index 0000000..7481b38
--- /dev/null
+++ b/src/pcm/pcm_rate_linear.c
@@ -0,0 +1,447 @@
+/*
+ *  Linear rate converter plugin
+ * 
+ *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *                2004 by Jaroslav Kysela <perex@perex.cz>
+ *                2006 by Takashi Iwai <tiwai@suse.de>
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <inttypes.h>
+#include <byteswap.h>
+#include "pcm_local.h"
+#include "pcm_plugin.h"
+#include "pcm_rate.h"
+
+#include "plugin_ops.h"
+
+
+/* LINEAR_DIV needs to be large enough to handle resampling from 192000 -> 8000 */
+#define LINEAR_DIV_SHIFT 19
+#define LINEAR_DIV (1<<LINEAR_DIV_SHIFT)
+
+struct rate_linear {
+	unsigned int get_idx;
+	unsigned int put_idx;
+	unsigned int pitch;
+	unsigned int pitch_shift;	/* for expand interpolation */
+	unsigned int channels;
+	int16_t *old_sample;
+	void (*func)(struct rate_linear *rate,
+		     const snd_pcm_channel_area_t *dst_areas,
+		     snd_pcm_uframes_t dst_offset, unsigned int dst_frames,
+		     const snd_pcm_channel_area_t *src_areas,
+		     snd_pcm_uframes_t src_offset, unsigned int src_frames);
+};
+
+static snd_pcm_uframes_t input_frames(void *obj, snd_pcm_uframes_t frames)
+{
+	struct rate_linear *rate = obj;
+	if (frames == 0)
+		return 0;
+	/* Round toward zero */
+	return muldiv_near(frames, LINEAR_DIV, rate->pitch);
+}
+
+static snd_pcm_uframes_t output_frames(void *obj, snd_pcm_uframes_t frames)
+{
+	struct rate_linear *rate = obj;
+	if (frames == 0)
+		return 0;
+	/* Round toward zero */
+	return muldiv_near(frames, rate->pitch, LINEAR_DIV);
+}
+
+static void linear_expand(struct rate_linear *rate,
+			  const snd_pcm_channel_area_t *dst_areas,
+			  snd_pcm_uframes_t dst_offset, unsigned int dst_frames,
+			  const snd_pcm_channel_area_t *src_areas,
+			  snd_pcm_uframes_t src_offset, unsigned int src_frames)
+{
+#define GET16_LABELS
+#define PUT16_LABELS
+#include "plugin_ops.h"
+#undef GET16_LABELS
+#undef PUT16_LABELS
+	void *get = get16_labels[rate->get_idx];
+	void *put = put16_labels[rate->put_idx];
+	unsigned int get_threshold = rate->pitch;
+	unsigned int channel;
+	unsigned int src_frames1;
+	unsigned int dst_frames1;
+	int16_t sample = 0;
+	unsigned int pos;
+	
+	for (channel = 0; channel < rate->channels; ++channel) {
+		const snd_pcm_channel_area_t *src_area = &src_areas[channel];
+		const snd_pcm_channel_area_t *dst_area = &dst_areas[channel];
+		const char *src;
+		char *dst;
+		int src_step, dst_step;
+		int16_t old_sample = 0;
+		int16_t new_sample;
+		int old_weight, new_weight;
+		src = snd_pcm_channel_area_addr(src_area, src_offset);
+		dst = snd_pcm_channel_area_addr(dst_area, dst_offset);
+		src_step = snd_pcm_channel_area_step(src_area);
+		dst_step = snd_pcm_channel_area_step(dst_area);
+		src_frames1 = 0;
+		dst_frames1 = 0;
+		new_sample = rate->old_sample[channel];
+		pos = get_threshold;
+		while (dst_frames1 < dst_frames) {
+			if (pos >= get_threshold) {
+				pos -= get_threshold;
+				old_sample = new_sample;
+				if (src_frames1 < src_frames) {
+					goto *get;
+#define GET16_END after_get
+#include "plugin_ops.h"
+#undef GET16_END
+				after_get:
+					new_sample = sample;
+				}
+			}
+			new_weight = (pos << (16 - rate->pitch_shift)) / (get_threshold >> rate->pitch_shift);
+			old_weight = 0x10000 - new_weight;
+			sample = (old_sample * old_weight + new_sample * new_weight) >> 16;
+			goto *put;
+#define PUT16_END after_put
+#include "plugin_ops.h"
+#undef PUT16_END
+		after_put:
+			dst += dst_step;
+			dst_frames1++;
+			pos += LINEAR_DIV;
+			if (pos >= get_threshold) {
+				src += src_step;
+				src_frames1++;
+			}
+		} 
+		rate->old_sample[channel] = new_sample;
+	}
+}
+
+/* optimized version for S16 format */
+static void linear_expand_s16(struct rate_linear *rate,
+			      const snd_pcm_channel_area_t *dst_areas,
+			      snd_pcm_uframes_t dst_offset, unsigned int dst_frames,
+			      const snd_pcm_channel_area_t *src_areas,
+			      snd_pcm_uframes_t src_offset, unsigned int src_frames)
+{
+	unsigned int channel;
+	unsigned int src_frames1;
+	unsigned int dst_frames1;
+	unsigned int get_threshold = rate->pitch;
+	unsigned int pos;
+	
+	for (channel = 0; channel < rate->channels; ++channel) {
+		const snd_pcm_channel_area_t *src_area = &src_areas[channel];
+		const snd_pcm_channel_area_t *dst_area = &dst_areas[channel];
+		const int16_t *src;
+		int16_t *dst;
+		int src_step, dst_step;
+		int16_t old_sample = 0;
+		int16_t new_sample;
+		int old_weight, new_weight;
+		src = snd_pcm_channel_area_addr(src_area, src_offset);
+		dst = snd_pcm_channel_area_addr(dst_area, dst_offset);
+		src_step = snd_pcm_channel_area_step(src_area) >> 1;
+		dst_step = snd_pcm_channel_area_step(dst_area) >> 1;
+		src_frames1 = 0;
+		dst_frames1 = 0;
+		new_sample = rate->old_sample[channel];
+		pos = get_threshold;
+		while (dst_frames1 < dst_frames) {
+			if (pos >= get_threshold) {
+				pos -= get_threshold;
+				old_sample = new_sample;
+				if (src_frames1 < src_frames)
+					new_sample = *src;
+			}
+			new_weight = (pos << (16 - rate->pitch_shift)) / (get_threshold >> rate->pitch_shift);
+			old_weight = 0x10000 - new_weight;
+			*dst = (old_sample * old_weight + new_sample * new_weight) >> 16;
+			dst += dst_step;
+			dst_frames1++;
+			pos += LINEAR_DIV;
+			if (pos >= get_threshold) {
+				src += src_step;
+				src_frames1++;
+			}
+		} 
+		rate->old_sample[channel] = new_sample;
+	}
+}
+
+static void linear_shrink(struct rate_linear *rate,
+			  const snd_pcm_channel_area_t *dst_areas,
+			  snd_pcm_uframes_t dst_offset, unsigned int dst_frames,
+			  const snd_pcm_channel_area_t *src_areas,
+			  snd_pcm_uframes_t src_offset, unsigned int src_frames)
+{
+#define GET16_LABELS
+#define PUT16_LABELS
+#include "plugin_ops.h"
+#undef GET16_LABELS
+#undef PUT16_LABELS
+	void *get = get16_labels[rate->get_idx];
+	void *put = put16_labels[rate->put_idx];
+	unsigned int get_increment = rate->pitch;
+	unsigned int channel;
+	unsigned int src_frames1;
+	unsigned int dst_frames1;
+	int16_t sample = 0;
+	unsigned int pos;
+
+	for (channel = 0; channel < rate->channels; ++channel) {
+		const snd_pcm_channel_area_t *src_area = &src_areas[channel];
+		const snd_pcm_channel_area_t *dst_area = &dst_areas[channel];
+		const char *src;
+		char *dst;
+		int src_step, dst_step;
+		int16_t old_sample = 0;
+		int16_t new_sample = 0;
+		int old_weight, new_weight;
+		pos = LINEAR_DIV - get_increment; /* Force first sample to be copied */
+		src = snd_pcm_channel_area_addr(src_area, src_offset);
+		dst = snd_pcm_channel_area_addr(dst_area, dst_offset);
+		src_step = snd_pcm_channel_area_step(src_area);
+		dst_step = snd_pcm_channel_area_step(dst_area);
+		src_frames1 = 0;
+		dst_frames1 = 0;
+		while (src_frames1 < src_frames) {
+			
+			goto *get;
+#define GET16_END after_get
+#include "plugin_ops.h"
+#undef GET16_END
+		after_get:
+			new_sample = sample;
+			src += src_step;
+			src_frames1++;
+			pos += get_increment;
+			if (pos >= LINEAR_DIV) {
+				pos -= LINEAR_DIV;
+				old_weight = (pos << (32 - LINEAR_DIV_SHIFT)) / (get_increment >> (LINEAR_DIV_SHIFT - 16));
+				new_weight = 0x10000 - old_weight;
+				sample = (old_sample * old_weight + new_sample * new_weight) >> 16;
+				goto *put;
+#define PUT16_END after_put
+#include "plugin_ops.h"
+#undef PUT16_END
+			after_put:
+				dst += dst_step;
+				dst_frames1++;
+				if (CHECK_SANITY(dst_frames1 > dst_frames)) {
+					SNDERR("dst_frames overflow");
+					break;
+				}
+			}
+			old_sample = new_sample;
+		}
+	}
+}
+
+/* optimized version for S16 format */
+static void linear_shrink_s16(struct rate_linear *rate,
+			      const snd_pcm_channel_area_t *dst_areas,
+			      snd_pcm_uframes_t dst_offset, unsigned int dst_frames,
+			      const snd_pcm_channel_area_t *src_areas,
+			      snd_pcm_uframes_t src_offset, unsigned int src_frames)
+{
+	unsigned int get_increment = rate->pitch;
+	unsigned int channel;
+	unsigned int src_frames1;
+	unsigned int dst_frames1;
+	unsigned int pos = 0;
+
+	for (channel = 0; channel < rate->channels; ++channel) {
+		const snd_pcm_channel_area_t *src_area = &src_areas[channel];
+		const snd_pcm_channel_area_t *dst_area = &dst_areas[channel];
+		const int16_t *src;
+		int16_t *dst;
+		int src_step, dst_step;
+		int16_t old_sample = 0;
+		int16_t new_sample = 0;
+		int old_weight, new_weight;
+		pos = LINEAR_DIV - get_increment; /* Force first sample to be copied */
+		src = snd_pcm_channel_area_addr(src_area, src_offset);
+		dst = snd_pcm_channel_area_addr(dst_area, dst_offset);
+		src_step = snd_pcm_channel_area_step(src_area) >> 1;
+		dst_step = snd_pcm_channel_area_step(dst_area) >> 1 ;
+		src_frames1 = 0;
+		dst_frames1 = 0;
+		while (src_frames1 < src_frames) {
+			
+			new_sample = *src;
+			src += src_step;
+			src_frames1++;
+			pos += get_increment;
+			if (pos >= LINEAR_DIV) {
+				pos -= LINEAR_DIV;
+				old_weight = (pos << (32 - LINEAR_DIV_SHIFT)) / (get_increment >> (LINEAR_DIV_SHIFT - 16));
+				new_weight = 0x10000 - old_weight;
+				*dst = (old_sample * old_weight + new_sample * new_weight) >> 16;
+				dst += dst_step;
+				dst_frames1++;
+				if (CHECK_SANITY(dst_frames1 > dst_frames)) {
+					SNDERR("dst_frames overflow");
+					break;
+				}
+			}
+			old_sample = new_sample;
+		}
+	}
+}
+
+static void linear_convert(void *obj, 
+			   const snd_pcm_channel_area_t *dst_areas,
+			   snd_pcm_uframes_t dst_offset, unsigned int dst_frames,
+			   const snd_pcm_channel_area_t *src_areas,
+			   snd_pcm_uframes_t src_offset, unsigned int src_frames)
+{
+	struct rate_linear *rate = obj;
+	rate->func(rate, dst_areas, dst_offset, dst_frames,
+		   src_areas, src_offset, src_frames);
+}
+
+static void linear_free(void *obj)
+{
+	struct rate_linear *rate = obj;
+
+	free(rate->old_sample);
+	rate->old_sample = NULL;
+}
+
+static int linear_init(void *obj, snd_pcm_rate_info_t *info)
+{
+	struct rate_linear *rate = obj;
+
+	rate->get_idx = snd_pcm_linear_get_index(info->in.format, SND_PCM_FORMAT_S16);
+	rate->put_idx = snd_pcm_linear_put_index(SND_PCM_FORMAT_S16, info->out.format);
+	if (info->in.rate < info->out.rate) {
+		if (info->in.format == info->out.format && info->in.format == SND_PCM_FORMAT_S16)
+			rate->func = linear_expand_s16;
+		else
+			rate->func = linear_expand;
+		/* pitch is get_threshold */
+	} else {
+		if (info->in.format == info->out.format && info->in.format == SND_PCM_FORMAT_S16)
+			rate->func = linear_shrink_s16;
+		else
+			rate->func = linear_shrink;
+		/* pitch is get_increment */
+	}
+	rate->pitch = (((u_int64_t)info->out.rate * LINEAR_DIV) +
+		       (info->in.rate / 2)) / info->in.rate;
+	rate->channels = info->channels;
+
+	free(rate->old_sample);
+	rate->old_sample = malloc(sizeof(*rate->old_sample) * rate->channels);
+	if (! rate->old_sample)
+		return -ENOMEM;
+
+	return 0;
+}
+
+static int linear_adjust_pitch(void *obj, snd_pcm_rate_info_t *info)
+{
+	struct rate_linear *rate = obj;
+	snd_pcm_uframes_t cframes;
+
+	rate->pitch = (((u_int64_t)info->out.period_size * LINEAR_DIV) +
+		       (info->in.period_size/2) ) / info->in.period_size;
+			
+	cframes = input_frames(rate, info->out.period_size);
+	while (cframes != info->in.period_size) {
+		snd_pcm_uframes_t cframes_new;
+		if (cframes > info->in.period_size)
+			rate->pitch++;
+		else
+			rate->pitch--;
+		cframes_new = input_frames(rate, info->out.period_size);
+		if ((cframes > info->in.period_size && cframes_new < info->in.period_size) ||
+		    (cframes < info->in.period_size && cframes_new > info->in.period_size)) {
+			SNDERR("invalid pcm period_size %ld -> %ld",
+			       info->in.period_size, info->out.period_size);
+			return -EIO;
+		}
+		cframes = cframes_new;
+	}
+	if (rate->pitch >= LINEAR_DIV) {
+		/* shift for expand linear interpolation */
+		rate->pitch_shift = 0;
+		while ((rate->pitch >> rate->pitch_shift) >= (1 << 16))
+			rate->pitch_shift++;
+	}
+	return 0;
+}
+
+static void linear_reset(void *obj)
+{
+	struct rate_linear *rate = obj;
+
+	/* for expand */
+	if (rate->old_sample)
+		memset(rate->old_sample, 0, sizeof(*rate->old_sample) * rate->channels);
+}
+
+static void linear_close(void *obj)
+{
+	free(obj);
+}
+
+static int get_supported_rates(ATTRIBUTE_UNUSED void *rate,
+			       unsigned int *rate_min, unsigned int *rate_max)
+{
+	*rate_min = SND_PCM_PLUGIN_RATE_MIN;
+	*rate_max = SND_PCM_PLUGIN_RATE_MAX;
+	return 0;
+}
+
+static void linear_dump(ATTRIBUTE_UNUSED void *rate, snd_output_t *out)
+{
+	snd_output_printf(out, "Converter: linear-interpolation\n");
+}
+
+static const snd_pcm_rate_ops_t linear_ops = {
+	.close = linear_close,
+	.init = linear_init,
+	.free = linear_free,
+	.reset = linear_reset,
+	.adjust_pitch = linear_adjust_pitch,
+	.convert = linear_convert,
+	.input_frames = input_frames,
+	.output_frames = output_frames,
+	.version = SND_PCM_RATE_PLUGIN_VERSION,
+	.get_supported_rates = get_supported_rates,
+	.dump = linear_dump,
+};
+
+int SND_PCM_RATE_PLUGIN_ENTRY(linear) (ATTRIBUTE_UNUSED unsigned int version,
+				       void **objp, snd_pcm_rate_ops_t *ops)
+{
+	struct rate_linear *rate;
+
+	rate = calloc(1, sizeof(*rate));
+	if (! rate)
+		return -ENOMEM;
+
+	*objp = rate;
+	*ops = linear_ops;
+	return 0;
+}
diff --git a/src/pcm/pcm_route.c b/src/pcm/pcm_route.c
new file mode 100644
index 0000000..4da623f
--- /dev/null
+++ b/src/pcm/pcm_route.c
@@ -0,0 +1,1172 @@
+/**
+ * \file pcm/pcm_route.c
+ * \ingroup PCM_Plugins
+ * \brief PCM Route & Volume Plugin Interface
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \date 2000-2001
+ */
+/*
+ *  PCM - Route & Volume Plugin
+ *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+#include <byteswap.h>
+#include <math.h>
+#include "pcm_local.h"
+#include "pcm_plugin.h"
+
+#include "plugin_ops.h"
+
+#ifndef PIC
+/* entry for static linking */
+const char *_snd_module_pcm_route = "";
+#endif
+
+#ifndef DOC_HIDDEN
+
+/* The best possible hack to support missing optimization in gcc 2.7.2.3 */
+#if SND_PCM_PLUGIN_ROUTE_RESOLUTION & (SND_PCM_PLUGIN_ROUTE_RESOLUTION - 1) != 0
+#define div(a) a /= SND_PCM_PLUGIN_ROUTE_RESOLUTION
+#elif SND_PCM_PLUGIN_ROUTE_RESOLUTION == 16
+#define div(a) a >>= 4
+#else
+#error "Add some code here"
+#endif
+
+typedef struct {
+	int channel;
+	int as_int;
+#if SND_PCM_PLUGIN_ROUTE_FLOAT
+	float as_float;
+#endif
+} snd_pcm_route_ttable_src_t;
+
+typedef struct snd_pcm_route_ttable_dst snd_pcm_route_ttable_dst_t;
+
+typedef struct {
+	enum {UINT32=0, UINT64=1, FLOAT=2} sum_idx;
+	unsigned int get_idx;
+	unsigned int put_idx;
+	unsigned int conv_idx;
+	int use_getput;
+	unsigned int src_size;
+	snd_pcm_format_t dst_sfmt;
+	unsigned int ndsts;
+	snd_pcm_route_ttable_dst_t *dsts;
+} snd_pcm_route_params_t;
+
+
+typedef void (*route_f)(const snd_pcm_channel_area_t *dst_area,
+			snd_pcm_uframes_t dst_offset,
+			const snd_pcm_channel_area_t *src_areas,
+			snd_pcm_uframes_t src_offset,
+			unsigned int src_channels,
+			snd_pcm_uframes_t frames,
+			const snd_pcm_route_ttable_dst_t *ttable,
+			const snd_pcm_route_params_t *params);
+
+struct snd_pcm_route_ttable_dst {
+	int att;	/* Attenuated */
+	unsigned int nsrcs;
+	snd_pcm_route_ttable_src_t* srcs;
+	route_f func;
+};
+
+typedef union {
+	int32_t as_sint32;
+	int64_t as_sint64;
+#if SND_PCM_PLUGIN_ROUTE_FLOAT
+	float as_float;
+#endif
+} sum_t;
+
+typedef struct {
+	/* This field need to be the first */
+	snd_pcm_plugin_t plug;
+	snd_pcm_format_t sformat;
+	int schannels;
+	snd_pcm_route_params_t params;
+} snd_pcm_route_t;
+
+#endif /* DOC_HIDDEN */
+
+static void snd_pcm_route_convert1_zero(const snd_pcm_channel_area_t *dst_area,
+					snd_pcm_uframes_t dst_offset,
+					const snd_pcm_channel_area_t *src_areas ATTRIBUTE_UNUSED,
+					snd_pcm_uframes_t src_offset ATTRIBUTE_UNUSED,
+					unsigned int src_channels ATTRIBUTE_UNUSED,
+					snd_pcm_uframes_t frames,
+					const snd_pcm_route_ttable_dst_t* ttable ATTRIBUTE_UNUSED,
+					const snd_pcm_route_params_t *params)
+{
+	snd_pcm_area_silence(dst_area, dst_offset, frames, params->dst_sfmt);
+}
+
+#ifndef DOC_HIDDEN
+
+static void snd_pcm_route_convert1_one(const snd_pcm_channel_area_t *dst_area,
+				       snd_pcm_uframes_t dst_offset,
+				       const snd_pcm_channel_area_t *src_areas,
+				       snd_pcm_uframes_t src_offset,
+				       unsigned int src_channels,
+				       snd_pcm_uframes_t frames,
+				       const snd_pcm_route_ttable_dst_t* ttable,
+				       const snd_pcm_route_params_t *params)
+{
+#define CONV_LABELS
+#include "plugin_ops.h"
+#undef CONV_LABELS
+	void *conv;
+	const snd_pcm_channel_area_t *src_area = 0;
+	unsigned int srcidx;
+	const char *src;
+	char *dst;
+	int src_step, dst_step;
+	for (srcidx = 0; srcidx < ttable->nsrcs && srcidx < src_channels; ++srcidx) {
+		unsigned int channel = ttable->srcs[srcidx].channel;
+		if (channel >= src_channels)
+			continue;
+		src_area = &src_areas[channel];
+		if (src_area->addr != NULL)
+			break;
+	}
+	if (srcidx == ttable->nsrcs || srcidx == src_channels) {
+		snd_pcm_route_convert1_zero(dst_area, dst_offset,
+					    src_areas, src_offset,
+					    src_channels,
+					    frames, ttable, params);
+		return;
+	}
+	
+	conv = conv_labels[params->conv_idx];
+	src = snd_pcm_channel_area_addr(src_area, src_offset);
+	dst = snd_pcm_channel_area_addr(dst_area, dst_offset);
+	src_step = snd_pcm_channel_area_step(src_area);
+	dst_step = snd_pcm_channel_area_step(dst_area);
+	while (frames-- > 0) {
+		goto *conv;
+#define CONV_END after
+#include "plugin_ops.h"
+#undef CONV_END
+	after:
+		src += src_step;
+		dst += dst_step;
+	}
+}
+
+static void snd_pcm_route_convert1_one_getput(const snd_pcm_channel_area_t *dst_area,
+					      snd_pcm_uframes_t dst_offset,
+					      const snd_pcm_channel_area_t *src_areas,
+					      snd_pcm_uframes_t src_offset,
+					      unsigned int src_channels,
+					      snd_pcm_uframes_t frames,
+					      const snd_pcm_route_ttable_dst_t* ttable,
+					      const snd_pcm_route_params_t *params)
+{
+#define CONV24_LABELS
+#include "plugin_ops.h"
+#undef CONV24_LABELS
+	void *get, *put;
+	const snd_pcm_channel_area_t *src_area = 0;
+	unsigned int srcidx;
+	const char *src;
+	char *dst;
+	int src_step, dst_step;
+	u_int32_t sample = 0;
+	for (srcidx = 0; srcidx < ttable->nsrcs && srcidx < src_channels; ++srcidx) {
+		unsigned int channel = ttable->srcs[srcidx].channel;
+		if (channel >= src_channels)
+			continue;
+		src_area = &src_areas[channel];
+		if (src_area->addr != NULL)
+			break;
+	}
+	if (srcidx == ttable->nsrcs || srcidx == src_channels) {
+		snd_pcm_route_convert1_zero(dst_area, dst_offset,
+					    src_areas, src_offset,
+					    src_channels,
+					    frames, ttable, params);
+		return;
+	}
+	
+	get = get32_labels[params->get_idx];
+	put = put32_labels[params->put_idx];
+	src = snd_pcm_channel_area_addr(src_area, src_offset);
+	dst = snd_pcm_channel_area_addr(dst_area, dst_offset);
+	src_step = snd_pcm_channel_area_step(src_area);
+	dst_step = snd_pcm_channel_area_step(dst_area);
+	while (frames-- > 0) {
+		goto *get;
+#define CONV24_END after
+#include "plugin_ops.h"
+#undef CONV24_END
+	after:
+		src += src_step;
+		dst += dst_step;
+	}
+}
+
+static void snd_pcm_route_convert1_many(const snd_pcm_channel_area_t *dst_area,
+					snd_pcm_uframes_t dst_offset,
+					const snd_pcm_channel_area_t *src_areas,
+					snd_pcm_uframes_t src_offset,
+					unsigned int src_channels,
+					snd_pcm_uframes_t frames,
+					const snd_pcm_route_ttable_dst_t* ttable,
+					const snd_pcm_route_params_t *params)
+{
+#define GETS_LABELS
+#define PUT32_LABELS
+#include "plugin_ops.h"
+#undef GETS_LABELS
+#undef PUT32_LABELS
+	static void *const zero_labels[3] = {
+		&&zero_int32, &&zero_int64,
+#if SND_PCM_PLUGIN_ROUTE_FLOAT
+		&&zero_float
+#endif
+	};
+	/* sum_type att */
+	static void *const add_labels[3 * 2] = {
+		&&add_int32_noatt, &&add_int32_att,
+		&&add_int64_noatt, &&add_int64_att,
+#if SND_PCM_PLUGIN_ROUTE_FLOAT
+		&&add_float_noatt, &&add_float_att
+#endif
+	};
+	/* sum_type att shift */
+	static void *const norm_labels[3 * 2 * 4] = {
+		0,
+		&&norm_int32_8_noatt,
+		&&norm_int32_16_noatt,
+		&&norm_int32_24_noatt,
+		0,
+		&&norm_int32_8_att,
+		&&norm_int32_16_att,
+		&&norm_int32_24_att,
+		&&norm_int64_0_noatt,
+		&&norm_int64_8_noatt,
+		&&norm_int64_16_noatt,
+		&&norm_int64_24_noatt,
+		&&norm_int64_0_att,
+		&&norm_int64_8_att,
+		&&norm_int64_16_att,
+		&&norm_int64_24_att,
+#if SND_PCM_PLUGIN_ROUTE_FLOAT
+		&&norm_float_0,
+		&&norm_float_8,
+		&&norm_float_16,
+		&&norm_float_24,
+		&&norm_float_0,
+		&&norm_float_8,
+		&&norm_float_16,
+		&&norm_float_24,
+#endif
+	};
+	void *zero, *get, *add, *norm, *put32;
+	int nsrcs = ttable->nsrcs;
+	char *dst;
+	int dst_step;
+	const char *srcs[nsrcs];
+	int src_steps[nsrcs];
+	snd_pcm_route_ttable_src_t src_tt[nsrcs];
+	int32_t sample = 0;
+	int srcidx, srcidx1 = 0;
+	for (srcidx = 0; srcidx < nsrcs && (unsigned)srcidx < src_channels; ++srcidx) {
+		const snd_pcm_channel_area_t *src_area;
+		unsigned int channel = ttable->srcs[srcidx].channel;
+		if (channel >= src_channels)
+			continue;
+		src_area = &src_areas[channel];
+		srcs[srcidx1] = snd_pcm_channel_area_addr(src_area, src_offset);
+		src_steps[srcidx1] = snd_pcm_channel_area_step(src_area);
+		src_tt[srcidx1] = ttable->srcs[srcidx];
+		srcidx1++;
+	}
+	nsrcs = srcidx1;
+	if (nsrcs == 0) {
+		snd_pcm_route_convert1_zero(dst_area, dst_offset,
+					    src_areas, src_offset,
+					    src_channels,
+					    frames, ttable, params);
+		return;
+	} else if (nsrcs == 1 && src_tt[0].as_int == SND_PCM_PLUGIN_ROUTE_RESOLUTION) {
+		if (params->use_getput)
+			snd_pcm_route_convert1_one_getput(dst_area, dst_offset,
+							  src_areas, src_offset,
+							  src_channels,
+							  frames, ttable, params);
+		else
+			snd_pcm_route_convert1_one(dst_area, dst_offset,
+						   src_areas, src_offset,
+						   src_channels,
+						   frames, ttable, params);
+		return;
+	}
+
+	zero = zero_labels[params->sum_idx];
+	get = gets_labels[params->get_idx];
+	add = add_labels[params->sum_idx * 2 + ttable->att];
+	norm = norm_labels[params->sum_idx * 8 + ttable->att * 4 + 4 - params->src_size];
+	put32 = put32_labels[params->put_idx];
+	dst = snd_pcm_channel_area_addr(dst_area, dst_offset);
+	dst_step = snd_pcm_channel_area_step(dst_area);
+
+	while (frames-- > 0) {
+		snd_pcm_route_ttable_src_t *ttp = src_tt;
+		sum_t sum;
+
+		/* Zero sum */
+		goto *zero;
+	zero_int32:
+		sum.as_sint32 = 0;
+		goto zero_end;
+	zero_int64: 
+		sum.as_sint64 = 0;
+		goto zero_end;
+#if SND_PCM_PLUGIN_ROUTE_FLOAT
+	zero_float:
+		sum.as_float = 0.0;
+		goto zero_end;
+#endif
+	zero_end:
+		for (srcidx = 0; srcidx < nsrcs; ++srcidx) {
+			const char *src = srcs[srcidx];
+			
+			/* Get sample */
+			goto *get;
+#define GETS_END after_get
+#include "plugin_ops.h"
+#undef GETS_END
+		after_get:
+
+			/* Sum */
+			goto *add;
+		add_int32_att:
+			sum.as_sint32 += sample * ttp->as_int;
+			goto after_sum;
+		add_int32_noatt:
+			if (ttp->as_int)
+				sum.as_sint32 += sample;
+			goto after_sum;
+		add_int64_att:
+			sum.as_sint64 += (int64_t) sample * ttp->as_int;
+			goto after_sum;
+		add_int64_noatt:
+			if (ttp->as_int)
+				sum.as_sint64 += sample;
+			goto after_sum;
+#if SND_PCM_PLUGIN_ROUTE_FLOAT
+		add_float_att:
+			sum.as_float += sample * ttp->as_float;
+			goto after_sum;
+		add_float_noatt:
+			if (ttp->as_int)
+				sum.as_float += sample;
+			goto after_sum;
+#endif
+		after_sum:
+			srcs[srcidx] += src_steps[srcidx];
+			ttp++;
+		}
+		
+		/* Normalization */
+		goto *norm;
+	norm_int32_8_att:
+		sum.as_sint64 = sum.as_sint32;
+	norm_int64_8_att:
+		sum.as_sint64 <<= 8;
+	norm_int64_0_att:
+		div(sum.as_sint64);
+		goto norm_int;
+
+	norm_int32_16_att:
+		sum.as_sint64 = sum.as_sint32;
+	norm_int64_16_att:
+		sum.as_sint64 <<= 16;
+		div(sum.as_sint64);
+		goto norm_int;
+
+	norm_int32_24_att:
+		sum.as_sint64 = sum.as_sint32;
+	norm_int64_24_att:
+		sum.as_sint64 <<= 24;
+		div(sum.as_sint64);
+		goto norm_int;
+
+	norm_int32_8_noatt:
+		sum.as_sint64 = sum.as_sint32;
+	norm_int64_8_noatt:
+		sum.as_sint64 <<= 8;
+		goto norm_int;
+
+	norm_int32_16_noatt:
+		sum.as_sint64 = sum.as_sint32;
+	norm_int64_16_noatt:
+		sum.as_sint64 <<= 16;
+		goto norm_int;
+
+	norm_int32_24_noatt:
+		sum.as_sint64 = sum.as_sint32;
+	norm_int64_24_noatt:
+		sum.as_sint64 <<= 24;
+		goto norm_int;
+
+	norm_int64_0_noatt:
+	norm_int:
+		if (sum.as_sint64 > (int64_t)0x7fffffff)
+			sample = 0x7fffffff;	/* maximum positive value */
+		else if (sum.as_sint64 < -(int64_t)0x80000000)
+			sample = 0x80000000;	/* maximum negative value */
+		else
+			sample = sum.as_sint64;
+		goto after_norm;
+
+#if SND_PCM_PLUGIN_ROUTE_FLOAT
+	norm_float_8:
+		sum.as_float *= 1 << 8;
+		goto norm_float;
+	norm_float_16:
+		sum.as_float *= 1 << 16;
+		goto norm_float;
+	norm_float_24:
+		sum.as_float *= 1 << 24;
+		goto norm_float;
+	norm_float_0:
+	norm_float:
+		sum.as_float = rint(sum.as_float);
+		if (sum.as_float > (int64_t)0x7fffffff)
+			sample = 0x7fffffff;	/* maximum positive value */
+		else if (sum.as_float < -(int64_t)0x80000000)
+			sample = 0x80000000;	/* maximum negative value */
+		else
+			sample = sum.as_float;
+		goto after_norm;
+#endif
+	after_norm:
+		
+		/* Put sample */
+		goto *put32;
+#define PUT32_END after_put32
+#include "plugin_ops.h"
+#undef PUT32_END
+	after_put32:
+		
+		dst += dst_step;
+	}
+}
+
+#endif /* DOC_HIDDEN */
+
+static void snd_pcm_route_convert(const snd_pcm_channel_area_t *dst_areas,
+				  snd_pcm_uframes_t dst_offset,
+				  const snd_pcm_channel_area_t *src_areas,
+				  snd_pcm_uframes_t src_offset,
+				  unsigned int src_channels,
+				  unsigned int dst_channels,
+				  snd_pcm_uframes_t frames,
+				  snd_pcm_route_params_t *params)
+{
+	unsigned int dst_channel;
+	snd_pcm_route_ttable_dst_t *dstp;
+	const snd_pcm_channel_area_t *dst_area;
+
+	dstp = params->dsts;
+	dst_area = dst_areas;
+	for (dst_channel = 0; dst_channel < dst_channels; ++dst_channel) {
+		if (dst_channel >= params->ndsts)
+			snd_pcm_route_convert1_zero(dst_area, dst_offset,
+						    src_areas, src_offset,
+						    src_channels,
+						    frames, dstp, params);
+		else
+			dstp->func(dst_area, dst_offset,
+				   src_areas, src_offset,
+				   src_channels,
+				   frames, dstp, params);
+		dstp++;
+		dst_area++;
+	}
+}
+
+static int snd_pcm_route_close(snd_pcm_t *pcm)
+{
+	snd_pcm_route_t *route = pcm->private_data;
+	snd_pcm_route_params_t *params = &route->params;
+	unsigned int dst_channel;
+
+	if (params->dsts) {
+		for (dst_channel = 0; dst_channel < params->ndsts; ++dst_channel) {
+			free(params->dsts[dst_channel].srcs);
+		}
+		free(params->dsts);
+	}
+	return snd_pcm_generic_close(pcm);
+}
+
+static int snd_pcm_route_hw_refine_cprepare(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params)
+{
+	int err;
+	snd_pcm_access_mask_t access_mask = { SND_PCM_ACCBIT_SHM };
+	snd_pcm_format_mask_t format_mask = { SND_PCM_FMTBIT_LINEAR };
+	err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_ACCESS,
+					 &access_mask);
+	if (err < 0)
+		return err;
+	err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_FORMAT,
+					 &format_mask);
+	if (err < 0)
+		return err;
+	err = _snd_pcm_hw_params_set_subformat(params, SND_PCM_SUBFORMAT_STD);
+	if (err < 0)
+		return err;
+	err = _snd_pcm_hw_param_set_min(params, SND_PCM_HW_PARAM_CHANNELS, 1, 0);
+	if (err < 0)
+		return err;
+	params->info &= ~(SND_PCM_INFO_MMAP | SND_PCM_INFO_MMAP_VALID);
+	return 0;
+}
+
+static int snd_pcm_route_hw_refine_sprepare(snd_pcm_t *pcm, snd_pcm_hw_params_t *sparams)
+{
+	snd_pcm_route_t *route = pcm->private_data;
+	snd_pcm_access_mask_t saccess_mask = { SND_PCM_ACCBIT_MMAP };
+	_snd_pcm_hw_params_any(sparams);
+	_snd_pcm_hw_param_set_mask(sparams, SND_PCM_HW_PARAM_ACCESS,
+				   &saccess_mask);
+	if (route->sformat != SND_PCM_FORMAT_UNKNOWN) {
+		_snd_pcm_hw_params_set_format(sparams, route->sformat);
+		_snd_pcm_hw_params_set_subformat(sparams, SND_PCM_SUBFORMAT_STD);
+	}
+	if (route->schannels >= 0) {
+		_snd_pcm_hw_param_set(sparams, SND_PCM_HW_PARAM_CHANNELS,
+				      (unsigned int) route->schannels, 0);
+	}
+	return 0;
+}
+
+static int snd_pcm_route_hw_refine_schange(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
+					    snd_pcm_hw_params_t *sparams)
+{
+	snd_pcm_route_t *route = pcm->private_data;
+	int err;
+	unsigned int links = (SND_PCM_HW_PARBIT_RATE |
+			      SND_PCM_HW_PARBIT_PERIODS |
+			      SND_PCM_HW_PARBIT_PERIOD_SIZE |
+			      SND_PCM_HW_PARBIT_PERIOD_TIME |
+			      SND_PCM_HW_PARBIT_BUFFER_SIZE |
+			      SND_PCM_HW_PARBIT_BUFFER_TIME |
+			      SND_PCM_HW_PARBIT_TICK_TIME);
+	if (route->sformat == SND_PCM_FORMAT_UNKNOWN)
+		links |= (SND_PCM_HW_PARBIT_FORMAT | 
+			  SND_PCM_HW_PARBIT_SUBFORMAT |
+			  SND_PCM_HW_PARBIT_SAMPLE_BITS);
+	if (route->schannels < 0)
+		links |= SND_PCM_HW_PARBIT_CHANNELS;
+	err = _snd_pcm_hw_params_refine(sparams, links, params);
+	if (err < 0)
+		return err;
+	return 0;
+}
+	
+static int snd_pcm_route_hw_refine_cchange(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
+					    snd_pcm_hw_params_t *sparams)
+{
+	snd_pcm_route_t *route = pcm->private_data;
+	int err;
+	unsigned int links = (SND_PCM_HW_PARBIT_RATE |
+			      SND_PCM_HW_PARBIT_PERIODS |
+			      SND_PCM_HW_PARBIT_PERIOD_SIZE |
+			      SND_PCM_HW_PARBIT_PERIOD_TIME |
+			      SND_PCM_HW_PARBIT_BUFFER_SIZE |
+			      SND_PCM_HW_PARBIT_BUFFER_TIME |
+			      SND_PCM_HW_PARBIT_TICK_TIME);
+	if (route->sformat == SND_PCM_FORMAT_UNKNOWN)
+		links |= (SND_PCM_HW_PARBIT_FORMAT | 
+			  SND_PCM_HW_PARBIT_SUBFORMAT |
+			  SND_PCM_HW_PARBIT_SAMPLE_BITS);
+	if (route->schannels < 0)
+		links |= SND_PCM_HW_PARBIT_CHANNELS;
+	err = _snd_pcm_hw_params_refine(params, links, sparams);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static int snd_pcm_route_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	return snd_pcm_hw_refine_slave(pcm, params,
+				       snd_pcm_route_hw_refine_cprepare,
+				       snd_pcm_route_hw_refine_cchange,
+				       snd_pcm_route_hw_refine_sprepare,
+				       snd_pcm_route_hw_refine_schange,
+				       snd_pcm_generic_hw_refine);
+}
+
+static int snd_pcm_route_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t * params)
+{
+	snd_pcm_route_t *route = pcm->private_data;
+	snd_pcm_t *slave = route->plug.gen.slave;
+	snd_pcm_format_t src_format, dst_format;
+	int err = snd_pcm_hw_params_slave(pcm, params,
+					  snd_pcm_route_hw_refine_cchange,
+					  snd_pcm_route_hw_refine_sprepare,
+					  snd_pcm_route_hw_refine_schange,
+					  snd_pcm_generic_hw_params);
+	if (err < 0)
+		return err;
+
+	if (pcm->stream == SND_PCM_STREAM_PLAYBACK) {
+		err = INTERNAL(snd_pcm_hw_params_get_format)(params, &src_format);
+		dst_format = slave->format;
+	} else {
+		src_format = slave->format;
+		err = INTERNAL(snd_pcm_hw_params_get_format)(params, &dst_format);
+	}
+	if (err < 0)
+		return err;
+	route->params.use_getput = snd_pcm_format_physical_width(src_format) == 24 ||
+		snd_pcm_format_physical_width(dst_format) == 24;
+	route->params.get_idx = snd_pcm_linear_get_index(src_format, SND_PCM_FORMAT_S16);
+	route->params.put_idx = snd_pcm_linear_put32_index(SND_PCM_FORMAT_S32, dst_format);
+	route->params.conv_idx = snd_pcm_linear_convert_index(src_format, dst_format);
+	route->params.src_size = snd_pcm_format_width(src_format) / 8;
+	route->params.dst_sfmt = dst_format;
+#if SND_PCM_PLUGIN_ROUTE_FLOAT
+	route->params.sum_idx = FLOAT;
+#else
+	if (snd_pcm_format_width(src_format) == 32)
+		route->params.sum_idx = UINT64;
+	else
+		route->params.sum_idx = UINT32;
+#endif
+	return 0;
+}
+
+static snd_pcm_uframes_t
+snd_pcm_route_write_areas(snd_pcm_t *pcm,
+			  const snd_pcm_channel_area_t *areas,
+			  snd_pcm_uframes_t offset,
+			  snd_pcm_uframes_t size,
+			  const snd_pcm_channel_area_t *slave_areas,
+			  snd_pcm_uframes_t slave_offset,
+			  snd_pcm_uframes_t *slave_sizep)
+{
+	snd_pcm_route_t *route = pcm->private_data;
+	snd_pcm_t *slave = route->plug.gen.slave;
+	if (size > *slave_sizep)
+		size = *slave_sizep;
+	snd_pcm_route_convert(slave_areas, slave_offset,
+			      areas, offset, 
+			      pcm->channels,
+			      slave->channels,
+			      size, &route->params);
+	*slave_sizep = size;
+	return size;
+}
+
+static snd_pcm_uframes_t
+snd_pcm_route_read_areas(snd_pcm_t *pcm,
+			 const snd_pcm_channel_area_t *areas,
+			 snd_pcm_uframes_t offset,
+			 snd_pcm_uframes_t size,
+			 const snd_pcm_channel_area_t *slave_areas,
+			 snd_pcm_uframes_t slave_offset,
+			 snd_pcm_uframes_t *slave_sizep)
+{
+	snd_pcm_route_t *route = pcm->private_data;
+	snd_pcm_t *slave = route->plug.gen.slave;
+	if (size > *slave_sizep)
+		size = *slave_sizep;
+	snd_pcm_route_convert(areas, offset, 
+			      slave_areas, slave_offset,
+			      slave->channels,
+			      pcm->channels,
+			      size, &route->params);
+	*slave_sizep = size;
+	return size;
+}
+
+static void snd_pcm_route_dump(snd_pcm_t *pcm, snd_output_t *out)
+{
+	snd_pcm_route_t *route = pcm->private_data;
+	unsigned int dst;
+	if (route->sformat == SND_PCM_FORMAT_UNKNOWN)
+		snd_output_printf(out, "Route conversion PCM\n");
+	else
+		snd_output_printf(out, "Route conversion PCM (sformat=%s)\n", 
+			snd_pcm_format_name(route->sformat));
+	snd_output_puts(out, "  Transformation table:\n");
+	for (dst = 0; dst < route->params.ndsts; dst++) {
+		snd_pcm_route_ttable_dst_t *d = &route->params.dsts[dst];
+		unsigned int src;
+		snd_output_printf(out, "    %d <- ", dst);
+		if (d->nsrcs == 0) {
+			snd_output_printf(out, "none\n");
+			continue;
+		}
+		src = 0;
+		while (1) {
+			snd_pcm_route_ttable_src_t *s = &d->srcs[src];
+			if (d->att)
+#if SND_PCM_PLUGIN_ROUTE_FLOAT
+				snd_output_printf(out, "%d*%g", s->channel, s->as_float);
+#else
+				snd_output_printf(out, "%d*%g", s->channel, (double)s->as_int / (double)SND_PCM_PLUGIN_ROUTE_RESOLUTION);
+#endif
+			else
+				snd_output_printf(out, "%d", s->channel);
+			src++;
+			if (src == d->nsrcs)
+				break;
+			snd_output_puts(out, " + ");
+		}
+		snd_output_putc(out, '\n');
+	}
+	if (pcm->setup) {
+		snd_output_printf(out, "Its setup is:\n");
+		snd_pcm_dump_setup(pcm, out);
+	}
+	snd_output_printf(out, "Slave: ");
+	snd_pcm_dump(route->plug.gen.slave, out);
+}
+
+static const snd_pcm_ops_t snd_pcm_route_ops = {
+	.close = snd_pcm_route_close,
+	.info = snd_pcm_generic_info,
+	.hw_refine = snd_pcm_route_hw_refine,
+	.hw_params = snd_pcm_route_hw_params,
+	.hw_free = snd_pcm_generic_hw_free,
+	.sw_params = snd_pcm_generic_sw_params,
+	.channel_info = snd_pcm_generic_channel_info,
+	.dump = snd_pcm_route_dump,
+	.nonblock = snd_pcm_generic_nonblock,
+	.async = snd_pcm_generic_async,
+	.mmap = snd_pcm_generic_mmap,
+	.munmap = snd_pcm_generic_munmap,
+};
+
+static int route_load_ttable(snd_pcm_route_params_t *params, snd_pcm_stream_t stream,
+			     unsigned int tt_ssize,
+			     snd_pcm_route_ttable_entry_t *ttable,
+			     unsigned int tt_cused, unsigned int tt_sused)
+{
+	unsigned int src_channel, dst_channel;
+	snd_pcm_route_ttable_dst_t *dptr;
+	unsigned int sused, dused, smul, dmul;
+	if (stream == SND_PCM_STREAM_PLAYBACK) {
+		sused = tt_cused;
+		dused = tt_sused;
+		smul = tt_ssize;
+		dmul = 1;
+	} else {
+		sused = tt_sused;
+		dused = tt_cused;
+		smul = 1;
+		dmul = tt_ssize;
+	}
+	params->ndsts = dused;
+	dptr = calloc(dused, sizeof(*params->dsts));
+	if (!dptr)
+		return -ENOMEM;
+	params->dsts = dptr;
+	for (dst_channel = 0; dst_channel < dused; ++dst_channel) {
+		snd_pcm_route_ttable_entry_t t = 0;
+		int att = 0;
+		int nsrcs = 0;
+		snd_pcm_route_ttable_src_t srcs[sused];
+		for (src_channel = 0; src_channel < sused; ++src_channel) {
+			snd_pcm_route_ttable_entry_t v;
+			v = ttable[src_channel * smul + dst_channel * dmul];
+			if (v != 0) {
+				srcs[nsrcs].channel = src_channel;
+#if SND_PCM_PLUGIN_ROUTE_FLOAT
+				/* Also in user space for non attenuated */
+				srcs[nsrcs].as_int = (v == SND_PCM_PLUGIN_ROUTE_FULL ? SND_PCM_PLUGIN_ROUTE_RESOLUTION : 0);
+				srcs[nsrcs].as_float = v;
+#else
+				assert(v >= 0 && v <= SND_PCM_PLUGIN_ROUTE_FULL);
+				srcs[nsrcs].as_int = v;
+#endif
+				if (v != SND_PCM_PLUGIN_ROUTE_FULL)
+					att = 1;
+				t += v;
+				nsrcs++;
+			}
+		}
+#if 0
+		assert(t <= SND_PCM_PLUGIN_ROUTE_FULL);
+#endif
+		dptr->att = att;
+		dptr->nsrcs = nsrcs;
+		if (nsrcs == 0)
+			dptr->func = snd_pcm_route_convert1_zero;
+		else
+			dptr->func = snd_pcm_route_convert1_many;
+		if (nsrcs > 0) {
+			dptr->srcs = calloc((unsigned int) nsrcs, sizeof(*srcs));
+			if (!dptr->srcs)
+				return -ENOMEM;
+			memcpy(dptr->srcs, srcs, sizeof(*srcs) * nsrcs);
+		} else
+			dptr->srcs = 0;
+		dptr++;
+	}
+	return 0;
+}
+
+/**
+ * \brief Creates a new Route & Volume PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param sformat Slave format
+ * \param schannels Slave channels
+ * \param ttable Attenuation table
+ * \param tt_ssize Attenuation table - slave size
+ * \param tt_cused Attenuation table - client used count
+ * \param tt_sused Attenuation table - slave used count
+ * \param slave Slave PCM handle
+ * \param close_slave When set, the slave PCM handle is closed with copy PCM
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int snd_pcm_route_open(snd_pcm_t **pcmp, const char *name,
+		       snd_pcm_format_t sformat, int schannels,
+		       snd_pcm_route_ttable_entry_t *ttable,
+		       unsigned int tt_ssize,
+		       unsigned int tt_cused, unsigned int tt_sused,
+		       snd_pcm_t *slave, int close_slave)
+{
+	snd_pcm_t *pcm;
+	snd_pcm_route_t *route;
+	int err;
+	assert(pcmp && slave && ttable);
+	if (sformat != SND_PCM_FORMAT_UNKNOWN && 
+	    snd_pcm_format_linear(sformat) != 1)
+		return -EINVAL;
+	route = calloc(1, sizeof(snd_pcm_route_t));
+	if (!route) {
+		return -ENOMEM;
+	}
+	snd_pcm_plugin_init(&route->plug);
+	route->sformat = sformat;
+	route->schannels = schannels;
+	route->plug.read = snd_pcm_route_read_areas;
+	route->plug.write = snd_pcm_route_write_areas;
+	route->plug.undo_read = snd_pcm_plugin_undo_read_generic;
+	route->plug.undo_write = snd_pcm_plugin_undo_write_generic;
+	route->plug.gen.slave = slave;
+	route->plug.gen.close_slave = close_slave;
+
+	err = snd_pcm_new(&pcm, SND_PCM_TYPE_ROUTE, name, slave->stream, slave->mode);
+	if (err < 0) {
+		free(route);
+		return err;
+	}
+	pcm->ops = &snd_pcm_route_ops;
+	pcm->fast_ops = &snd_pcm_plugin_fast_ops;
+	pcm->private_data = route;
+	pcm->poll_fd = slave->poll_fd;
+	pcm->poll_events = slave->poll_events;
+	pcm->monotonic = slave->monotonic;
+	snd_pcm_set_hw_ptr(pcm, &route->plug.hw_ptr, -1, 0);
+	snd_pcm_set_appl_ptr(pcm, &route->plug.appl_ptr, -1, 0);
+	err = route_load_ttable(&route->params, pcm->stream, tt_ssize, ttable, tt_cused, tt_sused);
+	if (err < 0) {
+		snd_pcm_close(pcm);
+		return err;
+	}
+	*pcmp = pcm;
+
+	return 0;
+}
+
+/**
+ * \brief Determine route matrix sizes
+ * \param tt Configuration root describing route matrix
+ * \param tt_csize Returned client size in elements
+ * \param tt_ssize Returned slave size in elements
+ * \retval zero on success otherwise a negative error code
+ */
+int snd_pcm_route_determine_ttable(snd_config_t *tt,
+				   unsigned int *tt_csize,
+				   unsigned int *tt_ssize)
+{
+	snd_config_iterator_t i, inext;
+	long csize = 0, ssize = 0;
+	int err;
+
+	assert(tt && tt_csize && tt_ssize);
+	snd_config_for_each(i, inext, tt) {
+		snd_config_t *in = snd_config_iterator_entry(i);
+		snd_config_iterator_t j, jnext;
+		long cchannel;
+		const char *id;
+		if (!snd_config_get_id(in, &id) < 0)
+			continue;
+		err = safe_strtol(id, &cchannel);
+		if (err < 0) {
+			SNDERR("Invalid client channel: %s", id);
+			return -EINVAL;
+		}
+		if (cchannel + 1 > csize)
+			csize = cchannel + 1;
+		if (snd_config_get_type(in) != SND_CONFIG_TYPE_COMPOUND)
+			return -EINVAL;
+		snd_config_for_each(j, jnext, in) {
+			snd_config_t *jnode = snd_config_iterator_entry(j);
+			long schannel;
+			const char *id;
+			if (snd_config_get_id(jnode, &id) < 0)
+				continue;
+			err = safe_strtol(id, &schannel);
+			if (err < 0) {
+				SNDERR("Invalid slave channel: %s", id);
+				return -EINVAL;
+			}
+			if (schannel + 1 > ssize)
+				ssize = schannel + 1;
+		}
+	}
+	if (csize == 0 || ssize == 0) {
+		SNDERR("Invalid null ttable configuration");
+		return -EINVAL;
+	}
+	*tt_csize = csize;
+	*tt_ssize = ssize;
+	return 0;
+}
+
+/**
+ * \brief Load route matrix
+ * \param tt Configuration root describing route matrix
+ * \param ttable Returned route matrix
+ * \param tt_csize Client size in elements
+ * \param tt_ssize Slave size in elements
+ * \param tt_cused Used client elements
+ * \param tt_sused Used slave elements
+ * \param schannels Slave channels
+ * \retval zero on success otherwise a negative error code
+ */
+int snd_pcm_route_load_ttable(snd_config_t *tt, snd_pcm_route_ttable_entry_t *ttable,
+			      unsigned int tt_csize, unsigned int tt_ssize,
+			      unsigned int *tt_cused, unsigned int *tt_sused,
+			      int schannels)
+{
+	int cused = -1;
+	int sused = -1;
+	snd_config_iterator_t i, inext;
+	unsigned int k;
+	int err;
+	for (k = 0; k < tt_csize * tt_ssize; ++k)
+		ttable[k] = 0.0;
+	snd_config_for_each(i, inext, tt) {
+		snd_config_t *in = snd_config_iterator_entry(i);
+		snd_config_iterator_t j, jnext;
+		long cchannel;
+		const char *id;
+		if (!snd_config_get_id(in, &id) < 0)
+			continue;
+		err = safe_strtol(id, &cchannel);
+		if (err < 0 || 
+		    cchannel < 0 || (unsigned int) cchannel > tt_csize) {
+			SNDERR("Invalid client channel: %s", id);
+			return -EINVAL;
+		}
+		if (snd_config_get_type(in) != SND_CONFIG_TYPE_COMPOUND)
+			return -EINVAL;
+		snd_config_for_each(j, jnext, in) {
+			snd_config_t *jnode = snd_config_iterator_entry(j);
+			double value;
+			long schannel;
+			const char *id;
+			if (snd_config_get_id(jnode, &id) < 0)
+				continue;
+			err = safe_strtol(id, &schannel);
+			if (err < 0 || 
+			    schannel < 0 || (unsigned int) schannel > tt_ssize || 
+			    (schannels > 0 && schannel >= schannels)) {
+				SNDERR("Invalid slave channel: %s", id);
+				return -EINVAL;
+			}
+			err = snd_config_get_real(jnode, &value);
+			if (err < 0) {
+				long v;
+				err = snd_config_get_integer(jnode, &v);
+				if (err < 0) {
+					SNDERR("Invalid type for %s", id);
+					return -EINVAL;
+				}
+				value = v;
+			}
+			ttable[cchannel * tt_ssize + schannel] = value;
+			if (schannel > sused)
+				sused = schannel;
+		}
+		if (cchannel > cused)
+			cused = cchannel;
+	}
+	*tt_sused = sused + 1;
+	*tt_cused = cused + 1;
+	return 0;
+}
+
+/*! \page pcm_plugins
+
+\section pcm_plugins_route Plugin: Route & Volume
+
+This plugin converts channels and applies volume during the conversion.
+The format and rate must match for both of them.
+
+\code
+pcm.name {
+        type route              # Route & Volume conversion PCM
+        slave STR               # Slave name
+        # or
+        slave {                 # Slave definition
+                pcm STR         # Slave PCM name
+                # or
+                pcm { }         # Slave PCM definition
+                [format STR]    # Slave format
+                [channels INT]  # Slave channels
+        }
+        ttable {                # Transfer table (bi-dimensional compound of cchannels * schannels numbers)
+                CCHANNEL {
+                        SCHANNEL REAL   # route value (0.0 - 1.0)
+                }
+        }
+}
+\endcode
+
+\subsection pcm_plugins_route_funcref Function reference
+
+<UL>
+  <LI>snd_pcm_route_open()
+  <LI>_snd_pcm_route_open()
+</UL>
+
+*/
+
+/**
+ * \brief Creates a new Route & Volume PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param root Root configuration node
+ * \param conf Configuration node with Route & Volume PCM description
+ * \param stream Stream type
+ * \param mode Stream mode
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int _snd_pcm_route_open(snd_pcm_t **pcmp, const char *name,
+			snd_config_t *root, snd_config_t *conf, 
+			snd_pcm_stream_t stream, int mode)
+{
+	snd_config_iterator_t i, next;
+	int err;
+	snd_pcm_t *spcm;
+	snd_config_t *slave = NULL, *sconf;
+	snd_pcm_format_t sformat = SND_PCM_FORMAT_UNKNOWN;
+	int schannels = -1;
+	snd_config_t *tt = NULL;
+	snd_pcm_route_ttable_entry_t *ttable = NULL;
+	unsigned int csize, ssize;
+	unsigned int cused, sused;
+	snd_config_for_each(i, next, conf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (snd_pcm_conf_generic_id(id))
+			continue;
+		if (strcmp(id, "slave") == 0) {
+			slave = n;
+			continue;
+		}
+		if (strcmp(id, "ttable") == 0) {
+			if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) {
+				SNDERR("Invalid type for %s", id);
+				return -EINVAL;
+			}
+			tt = n;
+			continue;
+		}
+		SNDERR("Unknown field %s", id);
+		return -EINVAL;
+	}
+	if (!slave) {
+		SNDERR("slave is not defined");
+		return -EINVAL;
+	}
+	if (!tt) {
+		SNDERR("ttable is not defined");
+		return -EINVAL;
+	}
+	err = snd_pcm_slave_conf(root, slave, &sconf, 2,
+				 SND_PCM_HW_PARAM_FORMAT, 0, &sformat,
+				 SND_PCM_HW_PARAM_CHANNELS, 0, &schannels);
+	if (err < 0)
+		return err;
+	if (sformat != SND_PCM_FORMAT_UNKNOWN &&
+	    snd_pcm_format_linear(sformat) != 1) {
+	    	snd_config_delete(sconf);
+		SNDERR("slave format is not linear");
+		return -EINVAL;
+	}
+
+	err = snd_pcm_route_determine_ttable(tt, &csize, &ssize);
+	if (err < 0) {
+		snd_config_delete(sconf);
+		return err;
+	}
+	ttable = malloc(csize * ssize * sizeof(snd_pcm_route_ttable_entry_t));
+	if (ttable == NULL) {
+		snd_config_delete(sconf);
+		return -ENOMEM;
+	}
+	err = snd_pcm_route_load_ttable(tt, ttable, csize, ssize,
+					&cused, &sused, schannels);
+	if (err < 0) {
+		free(ttable);
+		snd_config_delete(sconf);
+		return err;
+	}
+
+	err = snd_pcm_open_slave(&spcm, root, sconf, stream, mode, conf);
+	snd_config_delete(sconf);
+	if (err < 0) {
+		free(ttable);
+		return err;
+	}
+	err = snd_pcm_route_open(pcmp, name, sformat, schannels,
+				 ttable, ssize,
+				 cused, sused,
+				 spcm, 1);
+	free(ttable);
+	if (err < 0)
+		snd_pcm_close(spcm);
+	return err;
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(_snd_pcm_route_open, SND_PCM_DLSYM_VERSION);
+#endif
diff --git a/src/pcm/pcm_share.c b/src/pcm/pcm_share.c
new file mode 100644
index 0000000..56a8685
--- /dev/null
+++ b/src/pcm/pcm_share.c
@@ -0,0 +1,1723 @@
+/**
+ * \file pcm/pcm_share.c
+ * \ingroup PCM_Plugins
+ * \brief PCM Share Plugin Interface
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \date 2000-2001
+ */
+/*
+ *  PCM - Share
+ *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <unistd.h>
+#include <string.h>
+#include <signal.h>
+#include <math.h>
+#include <sys/socket.h>
+#include <sys/poll.h>
+#include <sys/shm.h>
+#include <pthread.h>
+#include "pcm_local.h"
+
+#ifndef PIC
+/* entry for static linking */
+const char *_snd_module_pcm_share = "";
+#endif
+
+#ifndef DOC_HIDDEN
+
+static LIST_HEAD(snd_pcm_share_slaves);
+static pthread_mutex_t snd_pcm_share_slaves_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+#ifdef MUTEX_DEBUG
+#define Pthread_mutex_lock(mutex) \
+char *snd_pcm_share_slaves_mutex_holder;
+do { \
+	int err = pthread_mutex_trylock(mutex); \
+	if (err < 0) { \
+		fprintf(stderr, "lock " #mutex " is busy (%s): waiting in " __FUNCTION__ "\n", *(mutex##_holder)); \
+		pthread_mutex_lock(mutex); \
+		fprintf(stderr, "... got\n"); \
+	} \
+	*(mutex##_holder) = __FUNCTION__; \
+} while (0)
+
+#define Pthread_mutex_unlock(mutex) \
+do { \
+	*(mutex##_holder) = 0; \
+	pthread_mutex_unlock(mutex); \
+} while (0)
+#else
+#define Pthread_mutex_lock(mutex) pthread_mutex_lock(mutex)
+#define Pthread_mutex_unlock(mutex) pthread_mutex_unlock(mutex)
+#endif
+
+typedef struct {
+	struct list_head clients;
+	struct list_head list;
+	snd_pcm_t *pcm;
+	snd_pcm_format_t format;
+	int rate;
+	unsigned int channels;
+	snd_pcm_sframes_t period_time;
+	snd_pcm_sframes_t buffer_time;
+	unsigned int open_count;
+	unsigned int setup_count;
+	unsigned int prepared_count;
+	unsigned int running_count;
+	snd_pcm_uframes_t safety_threshold;
+	snd_pcm_uframes_t silence_frames;
+	snd_pcm_sw_params_t sw_params;
+	snd_pcm_uframes_t hw_ptr;
+	int poll[2];
+	int polling;
+	pthread_t thread;
+	pthread_mutex_t mutex;
+#ifdef MUTEX_DEBUG
+	char *mutex_holder;
+#endif
+	pthread_cond_t poll_cond;
+} snd_pcm_share_slave_t;
+
+typedef struct {
+	struct list_head list;
+	snd_pcm_t *pcm;
+	snd_pcm_share_slave_t *slave;
+	unsigned int channels;
+	unsigned int *slave_channels;
+	int drain_silenced;
+	snd_htimestamp_t trigger_tstamp;
+	snd_pcm_state_t state;
+	snd_pcm_uframes_t hw_ptr;
+	snd_pcm_uframes_t appl_ptr;
+	int ready;
+	int client_socket;
+	int slave_socket;
+} snd_pcm_share_t;
+
+#endif /* DOC_HIDDEN */
+
+static void _snd_pcm_share_stop(snd_pcm_t *pcm, snd_pcm_state_t state);
+
+static snd_pcm_uframes_t snd_pcm_share_slave_avail(snd_pcm_share_slave_t *slave)
+{
+	snd_pcm_sframes_t avail;
+	snd_pcm_t *pcm = slave->pcm;
+  	avail = slave->hw_ptr - *pcm->appl.ptr;
+	if (pcm->stream == SND_PCM_STREAM_PLAYBACK)
+		avail += pcm->buffer_size;
+	if (avail < 0)
+		avail += pcm->boundary;
+	return avail;
+}
+
+/* Warning: take the mutex before to call this */
+/* Return number of frames to mmap_commit the slave */
+static snd_pcm_uframes_t _snd_pcm_share_slave_forward(snd_pcm_share_slave_t *slave)
+{
+	struct list_head *i;
+	snd_pcm_uframes_t buffer_size, boundary;
+	snd_pcm_uframes_t slave_appl_ptr;
+	snd_pcm_sframes_t frames, safety_frames;
+	snd_pcm_sframes_t min_frames, max_frames;
+	snd_pcm_uframes_t avail, slave_avail;
+	snd_pcm_uframes_t slave_hw_avail;
+	slave_avail = snd_pcm_share_slave_avail(slave);
+	boundary = slave->pcm->boundary;
+	buffer_size = slave->pcm->buffer_size;
+	min_frames = slave_avail;
+	max_frames = 0;
+	slave_appl_ptr = *slave->pcm->appl.ptr;
+	list_for_each(i, &slave->clients) {
+		snd_pcm_share_t *share = list_entry(i, snd_pcm_share_t, list);
+		snd_pcm_t *pcm = share->pcm;
+		switch (share->state) {
+		case SND_PCM_STATE_RUNNING:
+			break;
+		case SND_PCM_STATE_DRAINING:
+			if (pcm->stream != SND_PCM_STREAM_PLAYBACK)
+				continue;
+			break;
+		default:
+			continue;
+		}
+		avail = snd_pcm_mmap_avail(pcm);
+		frames = slave_avail - avail;
+		if (frames > max_frames)
+			max_frames = frames;
+		if (share->state != SND_PCM_STATE_RUNNING)
+			continue;
+		if (frames < min_frames)
+			min_frames = frames;
+	}
+	if (max_frames == 0)
+		return 0;
+	frames = min_frames;
+	/* Slave xrun prevention */
+	slave_hw_avail = buffer_size - slave_avail;
+	safety_frames = slave->safety_threshold - slave_hw_avail;
+	if (safety_frames > 0 &&
+	    frames < safety_frames) {
+		/* Avoid to pass over the last */
+		if (max_frames < safety_frames)
+			frames = max_frames;
+		else
+			frames = safety_frames;
+	}
+	if (frames < 0)
+		return 0;
+	return frames;
+}
+
+
+/* 
+   - stop PCM on xrun
+   - update poll status
+   - draining silencing
+   - return distance in frames to next event
+*/
+static snd_pcm_uframes_t _snd_pcm_share_missing(snd_pcm_t *pcm)
+{
+	snd_pcm_share_t *share = pcm->private_data;
+	snd_pcm_share_slave_t *slave = share->slave;
+	snd_pcm_t *spcm = slave->pcm;
+	snd_pcm_uframes_t buffer_size = spcm->buffer_size;
+	int ready = 1, running = 0;
+	snd_pcm_uframes_t avail = 0, slave_avail;
+	snd_pcm_sframes_t hw_avail;
+	snd_pcm_uframes_t missing = INT_MAX;
+	snd_pcm_sframes_t ready_missing;
+	// printf("state=%s hw_ptr=%ld appl_ptr=%ld slave appl_ptr=%ld safety=%ld silence=%ld\n", snd_pcm_state_name(share->state), slave->hw_ptr, share->appl_ptr, *slave->pcm->appl_ptr, slave->safety_threshold, slave->silence_frames);
+	switch (share->state) {
+	case SND_PCM_STATE_RUNNING:
+		break;
+	case SND_PCM_STATE_DRAINING:
+		if (pcm->stream == SND_PCM_STREAM_PLAYBACK)
+			break;
+		/* Fall through */
+	default:
+		return INT_MAX;
+	}
+	share->hw_ptr = slave->hw_ptr;
+	avail = snd_pcm_mmap_avail(pcm);
+	if (avail >= pcm->stop_threshold) {
+		_snd_pcm_share_stop(pcm, share->state == SND_PCM_STATE_DRAINING ? SND_PCM_STATE_SETUP : SND_PCM_STATE_XRUN);
+		goto update_poll;
+	}
+	hw_avail = buffer_size - avail;
+	slave_avail = snd_pcm_share_slave_avail(slave);
+	if (avail < slave_avail) {
+		/* Some frames need still to be transferred */
+		snd_pcm_sframes_t slave_hw_avail = buffer_size - slave_avail;
+		snd_pcm_sframes_t safety_missing = slave_hw_avail - slave->safety_threshold;
+		if (safety_missing < 0) {
+			snd_pcm_sframes_t err;
+			snd_pcm_sframes_t frames = slave_avail - avail;
+			if (-safety_missing <= frames) {
+				frames = -safety_missing;
+				missing = 1;
+			}
+			err = snd_pcm_mmap_commit(spcm, snd_pcm_mmap_offset(spcm), frames);
+			if (err < 0) {
+				SYSMSG("snd_pcm_mmap_commit error");
+				return INT_MAX;
+			}
+			if (err != frames)
+				SYSMSG("commit returns %ld for size %ld", err, frames);
+			slave_avail -= err;
+		} else {
+			if (safety_missing == 0)
+				missing = 1;
+			else
+				missing = safety_missing;
+		}
+	}
+	switch (share->state) {
+	case SND_PCM_STATE_DRAINING:
+		if (pcm->stream == SND_PCM_STREAM_PLAYBACK) {
+			if (hw_avail <= 0) {
+				_snd_pcm_share_stop(pcm, SND_PCM_STATE_SETUP);
+				break;
+			}
+			if ((snd_pcm_uframes_t)hw_avail < missing)
+				missing = hw_avail;
+			running = 1;
+			ready = 0;
+		}
+		break;
+	case SND_PCM_STATE_RUNNING:
+		if (avail >= pcm->stop_threshold) {
+			_snd_pcm_share_stop(pcm, SND_PCM_STATE_XRUN);
+			break;
+		} else {
+			snd_pcm_uframes_t xrun_missing = pcm->stop_threshold - avail;
+			if (missing > xrun_missing)
+				missing = xrun_missing;
+		}
+		ready_missing = pcm->avail_min - avail;
+		if (ready_missing > 0) {
+			ready = 0;
+			if (missing > (snd_pcm_uframes_t)ready_missing)
+				missing = ready_missing;
+		}
+		running = 1;
+		break;
+	default:
+		SNDERR("invalid shared PCM state %d", share->state);
+		return INT_MAX;
+	}
+
+ update_poll:
+	if (ready != share->ready) {
+		char buf[1];
+		if (pcm->stream == SND_PCM_STREAM_PLAYBACK) {
+			if (ready)
+				read(share->slave_socket, buf, 1);
+			else
+				write(share->client_socket, buf, 1);
+		} else {
+			if (ready)
+				write(share->slave_socket, buf, 1);
+			else
+				read(share->client_socket, buf, 1);
+		}
+		share->ready = ready;
+	}
+	if (!running)
+		return INT_MAX;
+	if (pcm->stream == SND_PCM_STREAM_PLAYBACK &&
+	    share->state == SND_PCM_STATE_DRAINING &&
+	    !share->drain_silenced) {
+		/* drain silencing */
+		if (avail >= slave->silence_frames) {
+			snd_pcm_uframes_t offset = share->appl_ptr % buffer_size;
+			snd_pcm_uframes_t xfer = 0;
+			snd_pcm_uframes_t size = slave->silence_frames;
+			while (xfer < size) {
+				snd_pcm_uframes_t frames = size - xfer;
+				snd_pcm_uframes_t cont = buffer_size - offset;
+				if (cont < frames)
+					frames = cont;
+				snd_pcm_areas_silence(pcm->running_areas, offset, pcm->channels, frames, pcm->format);
+				offset += frames;
+				if (offset >= buffer_size)
+					offset = 0;
+				xfer += frames;
+			}
+			share->drain_silenced = 1;
+		} else {
+			snd_pcm_uframes_t silence_missing;
+			silence_missing = slave->silence_frames - avail;
+			if (silence_missing < missing)
+				missing = silence_missing;
+		}
+	}
+	// printf("missing=%d\n", missing);
+	return missing;
+}
+
+static snd_pcm_uframes_t _snd_pcm_share_slave_missing(snd_pcm_share_slave_t *slave)
+{
+	snd_pcm_uframes_t missing = INT_MAX;
+	struct list_head *i;
+	/* snd_pcm_sframes_t avail = */ snd_pcm_avail_update(slave->pcm);
+	slave->hw_ptr = *slave->pcm->hw.ptr;
+	list_for_each(i, &slave->clients) {
+		snd_pcm_share_t *share = list_entry(i, snd_pcm_share_t, list);
+		snd_pcm_t *pcm = share->pcm;
+		snd_pcm_uframes_t m = _snd_pcm_share_missing(pcm);
+		if (m < missing)
+			missing = m;
+	}
+	return missing;
+}
+
+static void *snd_pcm_share_thread(void *data)
+{
+	snd_pcm_share_slave_t *slave = data;
+	snd_pcm_t *spcm = slave->pcm;
+	struct pollfd pfd[2];
+	int err;
+
+	pfd[0].fd = slave->poll[0];
+	pfd[0].events = POLLIN;
+	err = snd_pcm_poll_descriptors(spcm, &pfd[1], 1);
+	if (err != 1) {
+		SNDERR("invalid poll descriptors %d", err);
+		return NULL;
+	}
+	Pthread_mutex_lock(&slave->mutex);
+	err = pipe(slave->poll);
+	if (err < 0) {
+		SYSERR("can't create a pipe");
+		return NULL;
+	}
+	while (slave->open_count > 0) {
+		snd_pcm_uframes_t missing;
+		// printf("begin min_missing\n");
+		missing = _snd_pcm_share_slave_missing(slave);
+		// printf("min_missing=%ld\n", missing);
+		if (missing < INT_MAX) {
+			snd_pcm_uframes_t hw_ptr;
+			snd_pcm_sframes_t avail_min;
+			hw_ptr = slave->hw_ptr + missing;
+			hw_ptr += spcm->period_size - 1;
+			if (hw_ptr >= spcm->boundary)
+				hw_ptr -= spcm->boundary;
+			hw_ptr -= hw_ptr % spcm->period_size;
+			avail_min = hw_ptr - *spcm->appl.ptr;
+			if (spcm->stream == SND_PCM_STREAM_PLAYBACK)
+				avail_min += spcm->buffer_size;
+			if (avail_min < 0)
+				avail_min += spcm->boundary;
+			// printf("avail_min=%d\n", avail_min);
+			if ((snd_pcm_uframes_t)avail_min != spcm->avail_min) {
+				snd_pcm_sw_params_set_avail_min(spcm, &slave->sw_params, avail_min);
+				err = snd_pcm_sw_params(spcm, &slave->sw_params);
+				if (err < 0) {
+					SYSERR("snd_pcm_sw_params error");
+					return NULL;
+				}
+			}
+			slave->polling = 1;
+			Pthread_mutex_unlock(&slave->mutex);
+			err = poll(pfd, 2, -1);
+			Pthread_mutex_lock(&slave->mutex);
+			if (pfd[0].revents & POLLIN) {
+				char buf[1];
+				read(pfd[0].fd, buf, 1);
+			}
+		} else {
+			slave->polling = 0;
+			pthread_cond_wait(&slave->poll_cond, &slave->mutex);
+		}
+	}
+	Pthread_mutex_unlock(&slave->mutex);
+	return NULL;
+}
+
+static void _snd_pcm_share_update(snd_pcm_t *pcm)
+{
+	snd_pcm_share_t *share = pcm->private_data;
+	snd_pcm_share_slave_t *slave = share->slave;
+	snd_pcm_t *spcm = slave->pcm;
+	snd_pcm_uframes_t missing;
+	/* snd_pcm_sframes_t avail = */ snd_pcm_avail_update(spcm);
+	slave->hw_ptr = *slave->pcm->hw.ptr;
+	missing = _snd_pcm_share_missing(pcm);
+	// printf("missing %ld\n", missing);
+	if (!slave->polling) {
+		pthread_cond_signal(&slave->poll_cond);
+		return;
+	}
+	if (missing < INT_MAX) {
+		snd_pcm_uframes_t hw_ptr;
+		snd_pcm_sframes_t avail_min;
+		hw_ptr = slave->hw_ptr + missing;
+		hw_ptr += spcm->period_size - 1;
+		if (hw_ptr >= spcm->boundary)
+			hw_ptr -= spcm->boundary;
+		hw_ptr -= hw_ptr % spcm->period_size;
+		avail_min = hw_ptr - *spcm->appl.ptr;
+		if (spcm->stream == SND_PCM_STREAM_PLAYBACK)
+			avail_min += spcm->buffer_size;
+		if (avail_min < 0)
+			avail_min += spcm->boundary;
+		if ((snd_pcm_uframes_t)avail_min < spcm->avail_min) {
+			int err;
+			snd_pcm_sw_params_set_avail_min(spcm, &slave->sw_params, avail_min);
+			err = snd_pcm_sw_params(spcm, &slave->sw_params);
+			if (err < 0) {
+				SYSERR("snd_pcm_sw_params error");
+				return;
+			}
+		}
+	}
+}
+
+static int snd_pcm_share_nonblock(snd_pcm_t *pcm ATTRIBUTE_UNUSED, int nonblock ATTRIBUTE_UNUSED)
+{
+	return 0;
+}
+
+static int snd_pcm_share_async(snd_pcm_t *pcm ATTRIBUTE_UNUSED, int sig ATTRIBUTE_UNUSED, pid_t pid ATTRIBUTE_UNUSED)
+{
+	return -ENOSYS;
+}
+
+static int snd_pcm_share_info(snd_pcm_t *pcm, snd_pcm_info_t *info)
+{
+	snd_pcm_share_t *share = pcm->private_data;
+	return snd_pcm_info(share->slave->pcm, info);
+}
+
+static int snd_pcm_share_hw_refine_cprepare(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	snd_pcm_share_t *share = pcm->private_data;
+	snd_pcm_share_slave_t *slave = share->slave;
+	snd_pcm_access_mask_t access_mask;
+	int err;
+	snd_pcm_access_mask_any(&access_mask);
+	snd_pcm_access_mask_reset(&access_mask, SND_PCM_ACCESS_MMAP_INTERLEAVED);
+	err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_ACCESS,
+					 &access_mask);
+	if (err < 0)
+		return err;
+	err = _snd_pcm_hw_param_set(params, SND_PCM_HW_PARAM_CHANNELS,
+				    share->channels, 0);
+	if (err < 0)
+		return err;
+	if (slave->format != SND_PCM_FORMAT_UNKNOWN) {
+		err = _snd_pcm_hw_params_set_format(params, slave->format);
+		if (err < 0)
+			return err;
+	}
+
+	if (slave->rate >= 0) {
+		err = _snd_pcm_hw_param_set(params, SND_PCM_HW_PARAM_RATE,
+					    slave->rate, 0);
+		if (err < 0)
+			return err;
+	}
+	if (slave->period_time >= 0) {
+		err = _snd_pcm_hw_param_set(params, SND_PCM_HW_PARAM_PERIOD_TIME,
+					    slave->period_time, 0);
+		if (err < 0)
+			return err;
+	}
+	if (slave->buffer_time >= 0) {
+		err = _snd_pcm_hw_param_set(params, SND_PCM_HW_PARAM_BUFFER_TIME,
+					    slave->buffer_time, 0);
+		if (err < 0)
+			return err;
+	}
+	params->info |= SND_PCM_INFO_DOUBLE;
+	return 0;
+}
+
+static int snd_pcm_share_hw_refine_sprepare(snd_pcm_t *pcm, snd_pcm_hw_params_t *sparams)
+{
+	snd_pcm_share_t *share = pcm->private_data;
+	snd_pcm_share_slave_t *slave = share->slave;
+	snd_pcm_access_mask_t saccess_mask = { SND_PCM_ACCBIT_MMAP };
+	_snd_pcm_hw_params_any(sparams);
+	_snd_pcm_hw_param_set_mask(sparams, SND_PCM_HW_PARAM_ACCESS,
+				   &saccess_mask);
+	_snd_pcm_hw_param_set(sparams, SND_PCM_HW_PARAM_CHANNELS,
+			      slave->channels, 0);
+	return 0;
+}
+
+static int snd_pcm_share_hw_refine_schange(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params,
+					  snd_pcm_hw_params_t *sparams)
+{
+	int err;
+	unsigned int links = (SND_PCM_HW_PARBIT_FORMAT |
+			      SND_PCM_HW_PARBIT_SUBFORMAT |
+			      SND_PCM_HW_PARBIT_RATE |
+			      SND_PCM_HW_PARBIT_PERIOD_SIZE |
+			      SND_PCM_HW_PARBIT_PERIOD_TIME |
+			      SND_PCM_HW_PARBIT_BUFFER_SIZE |
+			      SND_PCM_HW_PARBIT_BUFFER_TIME |
+			      SND_PCM_HW_PARBIT_PERIODS);
+	const snd_pcm_access_mask_t *access_mask = snd_pcm_hw_param_get_mask(params, SND_PCM_HW_PARAM_ACCESS);
+	if (!snd_pcm_access_mask_test(access_mask, SND_PCM_ACCESS_RW_INTERLEAVED) &&
+	    !snd_pcm_access_mask_test(access_mask, SND_PCM_ACCESS_RW_NONINTERLEAVED) &&
+	    !snd_pcm_access_mask_test(access_mask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED)) {
+		snd_pcm_access_mask_t saccess_mask;
+		snd_pcm_access_mask_any(&saccess_mask);
+		snd_pcm_access_mask_reset(&saccess_mask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED);
+		err = _snd_pcm_hw_param_set_mask(sparams, SND_PCM_HW_PARAM_ACCESS,
+						 &saccess_mask);
+		if (err < 0)
+			return err;
+	}
+	err = _snd_pcm_hw_params_refine(sparams, links, params);
+	if (err < 0)
+		return err;
+	return 0;
+}
+	
+static int snd_pcm_share_hw_refine_cchange(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params,
+					   snd_pcm_hw_params_t *sparams)
+{
+	int err;
+	unsigned int links = (SND_PCM_HW_PARBIT_FORMAT |
+			      SND_PCM_HW_PARBIT_SUBFORMAT |
+			      SND_PCM_HW_PARBIT_RATE |
+			      SND_PCM_HW_PARBIT_PERIOD_SIZE |
+			      SND_PCM_HW_PARBIT_PERIOD_TIME |
+			      SND_PCM_HW_PARBIT_BUFFER_SIZE |
+			      SND_PCM_HW_PARBIT_BUFFER_TIME |
+			      SND_PCM_HW_PARBIT_PERIODS);
+	snd_pcm_access_mask_t access_mask;
+	const snd_pcm_access_mask_t *saccess_mask = snd_pcm_hw_param_get_mask(sparams, SND_PCM_HW_PARAM_ACCESS);
+	snd_pcm_access_mask_any(&access_mask);
+	snd_pcm_access_mask_reset(&access_mask, SND_PCM_ACCESS_MMAP_INTERLEAVED);
+	if (!snd_pcm_access_mask_test(saccess_mask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED))
+		snd_pcm_access_mask_reset(&access_mask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED);
+	if (!snd_pcm_access_mask_test(saccess_mask, SND_PCM_ACCESS_MMAP_COMPLEX) &&
+	    !snd_pcm_access_mask_test(saccess_mask, SND_PCM_ACCESS_MMAP_INTERLEAVED))
+		snd_pcm_access_mask_reset(&access_mask, SND_PCM_ACCESS_MMAP_COMPLEX);
+	err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_ACCESS,
+					 &access_mask);
+	if (err < 0)
+		return err;
+	err = _snd_pcm_hw_params_refine(params, links, sparams);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static int snd_pcm_share_hw_refine_slave(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	snd_pcm_share_t *share = pcm->private_data;
+	return snd_pcm_hw_refine(share->slave->pcm, params);
+}
+
+static int snd_pcm_share_hw_params_slave(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	snd_pcm_share_t *share = pcm->private_data;
+	return _snd_pcm_hw_params(share->slave->pcm, params);
+}
+
+static int snd_pcm_share_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	return snd_pcm_hw_refine_slave(pcm, params,
+				       snd_pcm_share_hw_refine_cprepare,
+				       snd_pcm_share_hw_refine_cchange,
+				       snd_pcm_share_hw_refine_sprepare,
+				       snd_pcm_share_hw_refine_schange,
+				       snd_pcm_share_hw_refine_slave);
+}
+
+static int snd_pcm_share_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	snd_pcm_share_t *share = pcm->private_data;
+	snd_pcm_share_slave_t *slave = share->slave;
+	snd_pcm_t *spcm = slave->pcm;
+	int err = 0;
+	Pthread_mutex_lock(&slave->mutex);
+	if (slave->setup_count) {
+		err = _snd_pcm_hw_params_set_format(params, spcm->format);
+		if (err < 0)
+			goto _err;
+		err = _snd_pcm_hw_params_set_subformat(params, spcm->subformat);
+		if (err < 0)
+			goto _err;
+		err = _snd_pcm_hw_param_set_minmax(params, SND_PCM_HW_PARAM_RATE,
+						   spcm->rate, 0, 
+						   spcm->rate, 1);
+		if (err < 0)
+			goto _err;
+		err = _snd_pcm_hw_param_set_minmax(params, SND_PCM_HW_PARAM_PERIOD_TIME,
+						   spcm->period_time, 0,
+						   spcm->period_time, 1);
+		if (err < 0)
+			goto _err;
+		err = _snd_pcm_hw_param_set(params, SND_PCM_HW_PARAM_BUFFER_SIZE,
+					    spcm->buffer_size, 0);
+	_err:
+		if (err < 0) {
+			SNDERR("slave is already running with incompatible setup");
+			err = -EBUSY;
+			goto _end;
+		}
+	} else {
+		err = snd_pcm_hw_params_slave(pcm, params,
+					      snd_pcm_share_hw_refine_cchange,
+					      snd_pcm_share_hw_refine_sprepare,
+					      snd_pcm_share_hw_refine_schange,
+					      snd_pcm_share_hw_params_slave);
+		if (err < 0)
+			goto _end;
+		snd_pcm_sw_params_current(slave->pcm, &slave->sw_params);
+		/* >= 30 ms */
+		slave->safety_threshold = slave->pcm->rate * 30 / 1000;
+		slave->safety_threshold += slave->pcm->period_size - 1;
+		slave->safety_threshold -= slave->safety_threshold % slave->pcm->period_size;
+		slave->silence_frames = slave->safety_threshold;
+		if (slave->pcm->stream == SND_PCM_STREAM_PLAYBACK)
+			snd_pcm_areas_silence(slave->pcm->running_areas, 0, slave->pcm->channels, slave->pcm->buffer_size, slave->pcm->format);
+	}
+	share->state = SND_PCM_STATE_SETUP;
+	slave->setup_count++;
+ _end:
+	Pthread_mutex_unlock(&slave->mutex);
+	return err;
+}
+
+static int snd_pcm_share_hw_free(snd_pcm_t *pcm)
+{
+	snd_pcm_share_t *share = pcm->private_data;
+	snd_pcm_share_slave_t *slave = share->slave;
+	int err = 0;
+	Pthread_mutex_lock(&slave->mutex);
+	slave->setup_count--;
+	if (slave->setup_count == 0)
+		err = snd_pcm_hw_free(slave->pcm);
+	share->state = SND_PCM_STATE_OPEN;
+	Pthread_mutex_unlock(&slave->mutex);
+	return err;
+}
+
+static int snd_pcm_share_sw_params(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_sw_params_t *params ATTRIBUTE_UNUSED)
+{
+	return 0;
+}
+
+static int snd_pcm_share_status(snd_pcm_t *pcm, snd_pcm_status_t *status)
+{
+	snd_pcm_share_t *share = pcm->private_data;
+	snd_pcm_share_slave_t *slave = share->slave;
+	int err = 0;
+	snd_pcm_sframes_t sd = 0, d = 0;
+	Pthread_mutex_lock(&slave->mutex);
+	if (pcm->stream == SND_PCM_STREAM_PLAYBACK) {
+		status->avail = snd_pcm_mmap_playback_avail(pcm);
+		if (share->state != SND_PCM_STATE_RUNNING &&
+		    share->state != SND_PCM_STATE_DRAINING)
+			goto _notrunning;
+		d = pcm->buffer_size - status->avail;
+	} else {
+		status->avail = snd_pcm_mmap_capture_avail(pcm);
+		if (share->state != SND_PCM_STATE_RUNNING)
+			goto _notrunning;
+		d = status->avail;
+	}
+	err = snd_pcm_delay(slave->pcm, &sd);
+	if (err < 0)
+		goto _end;
+ _notrunning:
+	status->delay = sd + d;
+	status->state = share->state;
+	status->trigger_tstamp = share->trigger_tstamp;
+ _end:
+	Pthread_mutex_unlock(&slave->mutex);
+	return err;
+}
+
+static snd_pcm_state_t snd_pcm_share_state(snd_pcm_t *pcm)
+{
+	snd_pcm_share_t *share = pcm->private_data;
+	return share->state;
+}
+
+static int _snd_pcm_share_hwsync(snd_pcm_t *pcm)
+{
+	snd_pcm_share_t *share = pcm->private_data;
+	snd_pcm_share_slave_t *slave = share->slave;
+	switch (share->state) {
+	case SND_PCM_STATE_XRUN:
+		return -EPIPE;
+	default:
+		break;
+	}
+	return snd_pcm_hwsync(slave->pcm);
+}
+
+static int snd_pcm_share_hwsync(snd_pcm_t *pcm)
+{
+	snd_pcm_share_t *share = pcm->private_data;
+	snd_pcm_share_slave_t *slave = share->slave;
+	int err;
+	Pthread_mutex_lock(&slave->mutex);
+	err = _snd_pcm_share_hwsync(pcm);
+	Pthread_mutex_unlock(&slave->mutex);
+	return err;
+}
+
+static int _snd_pcm_share_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp)
+{
+	snd_pcm_share_t *share = pcm->private_data;
+	snd_pcm_share_slave_t *slave = share->slave;
+	switch (share->state) {
+	case SND_PCM_STATE_XRUN:
+		return -EPIPE;
+	case SND_PCM_STATE_RUNNING:
+		break;
+	case SND_PCM_STATE_DRAINING:
+		if (pcm->stream == SND_PCM_STREAM_PLAYBACK)
+			break;
+		/* Fall through */
+	default:
+		return -EBADFD;
+	}
+	return snd_pcm_delay(slave->pcm, delayp);
+}
+
+static int snd_pcm_share_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp)
+{
+	snd_pcm_share_t *share = pcm->private_data;
+	snd_pcm_share_slave_t *slave = share->slave;
+	int err;
+	Pthread_mutex_lock(&slave->mutex);
+	err = _snd_pcm_share_delay(pcm, delayp);
+	Pthread_mutex_unlock(&slave->mutex);
+	return err;
+}
+
+static snd_pcm_sframes_t snd_pcm_share_avail_update(snd_pcm_t *pcm)
+{
+	snd_pcm_share_t *share = pcm->private_data;
+	snd_pcm_share_slave_t *slave = share->slave;
+	snd_pcm_sframes_t avail;
+	Pthread_mutex_lock(&slave->mutex);
+	if (share->state == SND_PCM_STATE_RUNNING) {
+		avail = snd_pcm_avail_update(slave->pcm);
+		if (avail < 0) {
+			Pthread_mutex_unlock(&slave->mutex);
+			return avail;
+		}
+		share->hw_ptr = *slave->pcm->hw.ptr;
+	}
+	Pthread_mutex_unlock(&slave->mutex);
+	avail = snd_pcm_mmap_avail(pcm);
+	if ((snd_pcm_uframes_t)avail > pcm->buffer_size)
+		return -EPIPE;
+	return avail;
+}
+
+static int snd_pcm_share_htimestamp(snd_pcm_t *pcm, snd_pcm_uframes_t *avail,
+				    snd_htimestamp_t *tstamp)
+{
+	snd_pcm_share_t *share = pcm->private_data;
+	snd_pcm_share_slave_t *slave = share->slave;
+	int err;
+	Pthread_mutex_lock(&slave->mutex);
+	err = snd_pcm_htimestamp(slave->pcm, avail, tstamp);
+	Pthread_mutex_unlock(&slave->mutex);
+	return err;
+}
+
+/* Call it with mutex held */
+static snd_pcm_sframes_t _snd_pcm_share_mmap_commit(snd_pcm_t *pcm,
+						    snd_pcm_uframes_t offset ATTRIBUTE_UNUSED,
+						    snd_pcm_uframes_t size)
+{
+	snd_pcm_share_t *share = pcm->private_data;
+	snd_pcm_share_slave_t *slave = share->slave;
+	snd_pcm_t *spcm = slave->pcm;
+	snd_pcm_sframes_t ret;
+	snd_pcm_sframes_t frames;
+	if (pcm->stream == SND_PCM_STREAM_PLAYBACK &&
+	    share->state == SND_PCM_STATE_RUNNING) {
+		frames = *spcm->appl.ptr - share->appl_ptr;
+		if (frames > (snd_pcm_sframes_t)pcm->buffer_size)
+			frames -= pcm->boundary;
+		else if (frames < -(snd_pcm_sframes_t)pcm->buffer_size)
+			frames += pcm->boundary;
+		if (frames > 0) {
+			/* Latecomer PCM */
+			ret = snd_pcm_rewind(spcm, frames);
+			if (ret < 0)
+				return ret;
+		}
+	}
+	snd_pcm_mmap_appl_forward(pcm, size);
+	if (share->state == SND_PCM_STATE_RUNNING) {
+		frames = _snd_pcm_share_slave_forward(slave);
+		if (frames > 0) {
+			snd_pcm_sframes_t err;
+			err = snd_pcm_mmap_commit(spcm, snd_pcm_mmap_offset(spcm), frames);
+			if (err < 0) {
+				SYSMSG("snd_pcm_mmap_commit error");
+				return err;
+			}
+			if (err != frames) {
+				SYSMSG("commit returns %ld for size %ld", err, frames);
+				return err;
+			}
+		}
+		_snd_pcm_share_update(pcm);
+	}
+	return size;
+}
+
+static snd_pcm_sframes_t snd_pcm_share_mmap_commit(snd_pcm_t *pcm,
+						   snd_pcm_uframes_t offset,
+						   snd_pcm_uframes_t size)
+{
+	snd_pcm_share_t *share = pcm->private_data;
+	snd_pcm_share_slave_t *slave = share->slave;
+	snd_pcm_sframes_t ret;
+	Pthread_mutex_lock(&slave->mutex);
+	ret = _snd_pcm_share_mmap_commit(pcm, offset, size);
+	Pthread_mutex_unlock(&slave->mutex);
+	return ret;
+}
+
+static int snd_pcm_share_prepare(snd_pcm_t *pcm)
+{
+	snd_pcm_share_t *share = pcm->private_data;
+	snd_pcm_share_slave_t *slave = share->slave;
+	int err = 0;
+	Pthread_mutex_lock(&slave->mutex);
+	switch (share->state) {
+	case SND_PCM_STATE_OPEN:
+		err = -EBADFD;
+		goto _end;
+	case SND_PCM_STATE_RUNNING:
+		err = -EBUSY;
+		goto _end;
+	case SND_PCM_STATE_PREPARED:
+		err = 0;
+		goto _end;
+	default:	/* nothing todo */
+		break;
+	}
+	if (slave->prepared_count == 0) {
+		err = snd_pcm_prepare(slave->pcm);
+		if (err < 0)
+			goto _end;
+	}
+	slave->prepared_count++;
+	share->hw_ptr = 0;
+	share->appl_ptr = 0;
+	share->state = SND_PCM_STATE_PREPARED;
+ _end:
+	Pthread_mutex_unlock(&slave->mutex);
+	return err;
+}
+
+static int snd_pcm_share_reset(snd_pcm_t *pcm)
+{
+	snd_pcm_share_t *share = pcm->private_data;
+	snd_pcm_share_slave_t *slave = share->slave;
+	int err = 0;
+	/* FIXME? */
+	Pthread_mutex_lock(&slave->mutex);
+	snd_pcm_areas_silence(pcm->running_areas, 0, pcm->channels, pcm->buffer_size, pcm->format);
+	share->hw_ptr = *slave->pcm->hw.ptr;
+	share->appl_ptr = share->hw_ptr;
+	Pthread_mutex_unlock(&slave->mutex);
+	return err;
+}
+
+static int snd_pcm_share_start(snd_pcm_t *pcm)
+{
+	snd_pcm_share_t *share = pcm->private_data;
+	snd_pcm_share_slave_t *slave = share->slave;
+	snd_pcm_t *spcm = slave->pcm;
+	int err = 0;
+	if (share->state != SND_PCM_STATE_PREPARED)
+		return -EBADFD;
+	Pthread_mutex_lock(&slave->mutex);
+	share->state = SND_PCM_STATE_RUNNING;
+	if (pcm->stream == SND_PCM_STREAM_PLAYBACK) {
+		snd_pcm_uframes_t hw_avail = snd_pcm_mmap_playback_hw_avail(pcm);
+		snd_pcm_uframes_t xfer = 0;
+		if (hw_avail == 0) {
+			err = -EPIPE;
+			goto _end;
+		}
+		if (slave->running_count) {
+			snd_pcm_sframes_t sd;
+			err = snd_pcm_delay(spcm, &sd);
+			if (err < 0)
+				goto _end;
+			err = snd_pcm_rewind(spcm, sd);
+			if (err < 0)
+				goto _end;
+		}
+		assert(share->hw_ptr == 0);
+		share->hw_ptr = *spcm->hw.ptr;
+		share->appl_ptr = *spcm->appl.ptr;
+		while (xfer < hw_avail) {
+			snd_pcm_uframes_t frames = hw_avail - xfer;
+			snd_pcm_uframes_t offset = snd_pcm_mmap_offset(pcm);
+			snd_pcm_uframes_t cont = pcm->buffer_size - offset;
+			if (cont < frames)
+				frames = cont;
+			if (pcm->stopped_areas != NULL)
+				snd_pcm_areas_copy(pcm->running_areas, offset,
+						   pcm->stopped_areas, xfer,
+						   pcm->channels, frames,
+						   pcm->format);
+			xfer += frames;
+		}
+		snd_pcm_mmap_appl_forward(pcm, hw_avail);
+		if (slave->running_count == 0) {
+			snd_pcm_sframes_t res;
+			res = snd_pcm_mmap_commit(spcm, snd_pcm_mmap_offset(spcm), hw_avail);
+			if (res < 0) {
+				err = res;
+				goto _end;
+			}
+			assert((snd_pcm_uframes_t)res == hw_avail);
+		}
+	}
+	if (slave->running_count == 0) {
+		err = snd_pcm_start(spcm);
+		if (err < 0)
+			goto _end;
+	}
+	slave->running_count++;
+	_snd_pcm_share_update(pcm);
+	gettimestamp(&share->trigger_tstamp, pcm->monotonic);
+ _end:
+	Pthread_mutex_unlock(&slave->mutex);
+	return err;
+}
+
+static int snd_pcm_share_pause(snd_pcm_t *pcm ATTRIBUTE_UNUSED, int enable ATTRIBUTE_UNUSED)
+{
+	return -ENOSYS;
+}
+
+static int snd_pcm_share_resume(snd_pcm_t *pcm ATTRIBUTE_UNUSED)
+{
+	return -ENXIO;
+}
+
+static int snd_pcm_share_channel_info(snd_pcm_t *pcm, snd_pcm_channel_info_t *info)
+{
+	snd_pcm_share_t *share = pcm->private_data;
+	snd_pcm_share_slave_t *slave = share->slave;
+	unsigned int channel = info->channel;
+	int c = share->slave_channels[channel];
+	int err;
+	info->channel = c;
+	err = snd_pcm_channel_info(slave->pcm, info);
+	info->channel = channel;
+	return err;
+}
+
+static snd_pcm_sframes_t _snd_pcm_share_rewind(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
+{
+	snd_pcm_share_t *share = pcm->private_data;
+	snd_pcm_share_slave_t *slave = share->slave;
+	snd_pcm_sframes_t n;
+	switch (share->state) {
+	case SND_PCM_STATE_RUNNING:
+		break;
+	case SND_PCM_STATE_PREPARED:
+		if (pcm->stream != SND_PCM_STREAM_PLAYBACK)
+			return -EBADFD;
+		break;
+	case SND_PCM_STATE_DRAINING:
+		if (pcm->stream != SND_PCM_STREAM_CAPTURE)
+			return -EBADFD;
+		break;
+	case SND_PCM_STATE_XRUN:
+		return -EPIPE;
+	default:
+		return -EBADFD;
+	}
+	n = snd_pcm_mmap_hw_avail(pcm);
+	assert(n >= 0);
+	if ((snd_pcm_uframes_t)n > frames)
+		frames = n;
+	if (share->state == SND_PCM_STATE_RUNNING && frames > 0) {
+		snd_pcm_sframes_t ret = snd_pcm_rewind(slave->pcm, frames);
+		if (ret < 0)
+			return ret;
+		frames = ret;
+	}
+	snd_pcm_mmap_appl_backward(pcm, frames);
+	_snd_pcm_share_update(pcm);
+	return n;
+}
+
+static snd_pcm_sframes_t snd_pcm_share_rewindable(snd_pcm_t *pcm)
+{
+	snd_pcm_share_t *share = pcm->private_data;
+	snd_pcm_share_slave_t *slave = share->slave;
+	snd_pcm_sframes_t ret;
+	Pthread_mutex_lock(&slave->mutex);
+	ret = snd_pcm_rewindable(slave->pcm);
+	Pthread_mutex_unlock(&slave->mutex);
+	return ret;
+}
+
+static snd_pcm_sframes_t snd_pcm_share_rewind(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
+{
+	snd_pcm_share_t *share = pcm->private_data;
+	snd_pcm_share_slave_t *slave = share->slave;
+	snd_pcm_sframes_t ret;
+	Pthread_mutex_lock(&slave->mutex);
+	ret = _snd_pcm_share_rewind(pcm, frames);
+	Pthread_mutex_unlock(&slave->mutex);
+	return ret;
+}
+
+static snd_pcm_sframes_t _snd_pcm_share_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
+{
+	snd_pcm_share_t *share = pcm->private_data;
+	snd_pcm_share_slave_t *slave = share->slave;
+	snd_pcm_sframes_t n;
+	switch (share->state) {
+	case SND_PCM_STATE_RUNNING:
+		break;
+	case SND_PCM_STATE_PREPARED:
+		if (pcm->stream != SND_PCM_STREAM_PLAYBACK)
+			return -EBADFD;
+		break;
+	case SND_PCM_STATE_DRAINING:
+		if (pcm->stream != SND_PCM_STREAM_CAPTURE)
+			return -EBADFD;
+		break;
+	case SND_PCM_STATE_XRUN:
+		return -EPIPE;
+	default:
+		return -EBADFD;
+	}
+	n = snd_pcm_mmap_avail(pcm);
+	if ((snd_pcm_uframes_t)n > frames)
+		frames = n;
+	if (share->state == SND_PCM_STATE_RUNNING && frames > 0) {
+		snd_pcm_sframes_t ret = INTERNAL(snd_pcm_forward)(slave->pcm, frames);
+		if (ret < 0)
+			return ret;
+		frames = ret;
+	}
+	snd_pcm_mmap_appl_forward(pcm, frames);
+	_snd_pcm_share_update(pcm);
+	return n;
+}
+
+static snd_pcm_sframes_t snd_pcm_share_forwardable(snd_pcm_t *pcm)
+{
+	snd_pcm_share_t *share = pcm->private_data;
+	snd_pcm_share_slave_t *slave = share->slave;
+	snd_pcm_sframes_t ret;
+	Pthread_mutex_lock(&slave->mutex);
+	ret = snd_pcm_forwardable(slave->pcm);
+	Pthread_mutex_unlock(&slave->mutex);
+	return ret;
+}
+
+static snd_pcm_sframes_t snd_pcm_share_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
+{
+	snd_pcm_share_t *share = pcm->private_data;
+	snd_pcm_share_slave_t *slave = share->slave;
+	snd_pcm_sframes_t ret;
+	Pthread_mutex_lock(&slave->mutex);
+	ret = _snd_pcm_share_forward(pcm, frames);
+	Pthread_mutex_unlock(&slave->mutex);
+	return ret;
+}
+
+/* Warning: take the mutex before to call this */
+static void _snd_pcm_share_stop(snd_pcm_t *pcm, snd_pcm_state_t state)
+{
+	snd_pcm_share_t *share = pcm->private_data;
+	snd_pcm_share_slave_t *slave = share->slave;
+#if 0
+	if (!pcm->mmap_channels) {
+		/* PCM closing already begun in the main thread */
+		return;
+	}
+#endif
+	gettimestamp(&share->trigger_tstamp, pcm->monotonic);
+	if (pcm->stream == SND_PCM_STREAM_CAPTURE) {
+		snd_pcm_areas_copy(pcm->stopped_areas, 0,
+				   pcm->running_areas, 0,
+				   pcm->channels, pcm->buffer_size,
+				   pcm->format);
+	} else if (slave->running_count > 1) {
+		int err;
+		snd_pcm_sframes_t delay;
+		snd_pcm_areas_silence(pcm->running_areas, 0, pcm->channels,
+				      pcm->buffer_size, pcm->format);
+		err = snd_pcm_delay(slave->pcm, &delay);
+		if (err >= 0 && delay > 0)
+			snd_pcm_rewind(slave->pcm, delay);
+		share->drain_silenced = 0;
+	}
+	share->state = state;
+	slave->prepared_count--;
+	slave->running_count--;
+	if (slave->running_count == 0) {
+		int err = snd_pcm_drop(slave->pcm);
+		assert(err >= 0);
+	}
+}
+
+static int snd_pcm_share_drain(snd_pcm_t *pcm)
+{
+	snd_pcm_share_t *share = pcm->private_data;
+	snd_pcm_share_slave_t *slave = share->slave;
+	int err = 0;
+	Pthread_mutex_lock(&slave->mutex);
+	switch (share->state) {
+	case SND_PCM_STATE_OPEN:
+		err = -EBADFD;
+		goto _end;
+	case SND_PCM_STATE_PREPARED:
+		share->state = SND_PCM_STATE_SETUP;
+		goto _end;
+	case SND_PCM_STATE_SETUP:
+		goto _end;
+	default:
+		break;
+	}
+	if (pcm->stream == SND_PCM_STREAM_PLAYBACK) {
+		switch (share->state) {
+		case SND_PCM_STATE_XRUN:
+			share->state = SND_PCM_STATE_SETUP;
+			goto _end;
+		case SND_PCM_STATE_DRAINING:
+		case SND_PCM_STATE_RUNNING:
+			share->state = SND_PCM_STATE_DRAINING;
+			_snd_pcm_share_update(pcm);
+			Pthread_mutex_unlock(&slave->mutex);
+			if (!(pcm->mode & SND_PCM_NONBLOCK))
+				snd_pcm_wait(pcm, -1);
+			return 0;
+		default:
+			assert(0);
+			break;
+		}
+	} else {
+		switch (share->state) {
+		case SND_PCM_STATE_RUNNING:
+			_snd_pcm_share_stop(pcm, SND_PCM_STATE_DRAINING);
+			_snd_pcm_share_update(pcm);
+			/* Fall through */
+		case SND_PCM_STATE_XRUN:
+		case SND_PCM_STATE_DRAINING:
+			if (snd_pcm_mmap_capture_avail(pcm) <= 0)
+				share->state = SND_PCM_STATE_SETUP;
+			else
+				share->state = SND_PCM_STATE_DRAINING;
+			break;
+		default:
+			assert(0);
+			break;
+		}
+	}
+ _end:
+	Pthread_mutex_unlock(&slave->mutex);
+	return err;
+}
+
+static int snd_pcm_share_drop(snd_pcm_t *pcm)
+{
+	snd_pcm_share_t *share = pcm->private_data;
+	snd_pcm_share_slave_t *slave = share->slave;
+	int err = 0;
+	Pthread_mutex_lock(&slave->mutex);
+	switch (share->state) {
+	case SND_PCM_STATE_OPEN:
+		err = -EBADFD;
+		goto _end;
+	case SND_PCM_STATE_SETUP:
+		break;
+	case SND_PCM_STATE_DRAINING:
+		if (pcm->stream == SND_PCM_STREAM_CAPTURE) {
+			share->state = SND_PCM_STATE_SETUP;
+			break;
+		}
+		/* Fall through */
+	case SND_PCM_STATE_RUNNING:
+		_snd_pcm_share_stop(pcm, SND_PCM_STATE_SETUP);
+		_snd_pcm_share_update(pcm);
+		break;
+	case SND_PCM_STATE_PREPARED:
+	case SND_PCM_STATE_XRUN:
+		share->state = SND_PCM_STATE_SETUP;
+		break;
+	default:
+		assert(0);
+		break;
+	}
+	
+	share->appl_ptr = share->hw_ptr = 0;
+ _end:
+	Pthread_mutex_unlock(&slave->mutex);
+	return err;
+}
+
+static int snd_pcm_share_close(snd_pcm_t *pcm)
+{
+	snd_pcm_share_t *share = pcm->private_data;
+	snd_pcm_share_slave_t *slave = share->slave;
+	int err = 0;
+
+	Pthread_mutex_lock(&snd_pcm_share_slaves_mutex);
+	Pthread_mutex_lock(&slave->mutex);
+	slave->open_count--;
+	if (slave->open_count == 0) {
+		pthread_cond_signal(&slave->poll_cond);
+		Pthread_mutex_unlock(&slave->mutex);
+		err = pthread_join(slave->thread, 0);
+		assert(err == 0);
+		err = snd_pcm_close(slave->pcm);
+		pthread_mutex_destroy(&slave->mutex);
+		pthread_cond_destroy(&slave->poll_cond);
+		list_del(&slave->list);
+		free(slave);
+		list_del(&share->list);
+	} else {
+		list_del(&share->list);
+		Pthread_mutex_unlock(&slave->mutex);
+	}
+	Pthread_mutex_unlock(&snd_pcm_share_slaves_mutex);
+	close(share->client_socket);
+	close(share->slave_socket);
+	free(share->slave_channels);
+	free(share);
+	return err;
+}
+
+static int snd_pcm_share_mmap(snd_pcm_t *pcm ATTRIBUTE_UNUSED)
+{
+	return 0;
+}
+
+static int snd_pcm_share_munmap(snd_pcm_t *pcm ATTRIBUTE_UNUSED)
+{
+	return 0;
+}
+
+static void snd_pcm_share_dump(snd_pcm_t *pcm, snd_output_t *out)
+{
+	snd_pcm_share_t *share = pcm->private_data;
+	snd_pcm_share_slave_t *slave = share->slave;
+	unsigned int k;
+	snd_output_printf(out, "Share PCM\n");
+	snd_output_printf(out, "  Channel bindings:\n");
+	for (k = 0; k < share->channels; ++k)
+		snd_output_printf(out, "    %d: %d\n", k, share->slave_channels[k]);
+	if (pcm->setup) {
+		snd_output_printf(out, "Its setup is:\n");
+		snd_pcm_dump_setup(pcm, out);
+	}
+	snd_output_printf(out, "Slave: ");
+	snd_pcm_dump(slave->pcm, out);
+}
+
+static const snd_pcm_ops_t snd_pcm_share_ops = {
+	.close = snd_pcm_share_close,
+	.info = snd_pcm_share_info,
+	.hw_refine = snd_pcm_share_hw_refine,
+	.hw_params = snd_pcm_share_hw_params,
+	.hw_free = snd_pcm_share_hw_free,
+	.sw_params = snd_pcm_share_sw_params,
+	.channel_info = snd_pcm_share_channel_info,
+	.dump = snd_pcm_share_dump,
+	.nonblock = snd_pcm_share_nonblock,
+	.async = snd_pcm_share_async,
+	.mmap = snd_pcm_share_mmap,
+	.munmap = snd_pcm_share_munmap,
+};
+
+static const snd_pcm_fast_ops_t snd_pcm_share_fast_ops = {
+	.status = snd_pcm_share_status,
+	.state = snd_pcm_share_state,
+	.hwsync = snd_pcm_share_hwsync,
+	.delay = snd_pcm_share_delay,
+	.prepare = snd_pcm_share_prepare,
+	.reset = snd_pcm_share_reset,
+	.start = snd_pcm_share_start,
+	.drop = snd_pcm_share_drop,
+	.drain = snd_pcm_share_drain,
+	.pause = snd_pcm_share_pause,
+	.writei = snd_pcm_mmap_writei,
+	.writen = snd_pcm_mmap_writen,
+	.readi = snd_pcm_mmap_readi,
+	.readn = snd_pcm_mmap_readn,
+	.rewindable = snd_pcm_share_rewindable,
+	.rewind = snd_pcm_share_rewind,
+	.forwardable = snd_pcm_share_forwardable,
+	.forward = snd_pcm_share_forward,
+	.resume = snd_pcm_share_resume,
+	.avail_update = snd_pcm_share_avail_update,
+	.htimestamp = snd_pcm_share_htimestamp,
+	.mmap_commit = snd_pcm_share_mmap_commit,
+};
+
+/**
+ * \brief Creates a new Share PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param sname Slave name
+ * \param sformat Slave format
+ * \param srate Slave rate
+ * \param schannels Slave channels
+ * \param speriod_time Slave period time
+ * \param sbuffer_time Slave buffer time
+ * \param channels Count of channels
+ * \param channels_map Map of channels
+ * \param stream Direction
+ * \param mode PCM mode
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int snd_pcm_share_open(snd_pcm_t **pcmp, const char *name, const char *sname,
+		       snd_pcm_format_t sformat, int srate,
+		       unsigned int schannels,
+		       int speriod_time, int sbuffer_time,
+		       unsigned int channels, unsigned int *channels_map,
+		       snd_pcm_stream_t stream, int mode)
+{
+	snd_pcm_t *pcm;
+	snd_pcm_share_t *share;
+	int err;
+	struct list_head *i;
+	char slave_map[32] = { 0 };
+	unsigned int k;
+	snd_pcm_share_slave_t *slave = NULL;
+	int sd[2];
+
+	assert(pcmp);
+	assert(channels > 0 && sname && channels_map);
+
+	for (k = 0; k < channels; ++k) {
+		if (channels_map[k] >= sizeof(slave_map) / sizeof(slave_map[0])) {
+			SNDERR("Invalid slave channel (%d) in binding", channels_map[k]);
+			return -EINVAL;
+		}
+		if (slave_map[channels_map[k]]) {
+			SNDERR("Repeated slave channel (%d) in binding", channels_map[k]);
+			return -EINVAL;
+		}
+		slave_map[channels_map[k]] = 1;
+		assert((unsigned)channels_map[k] < schannels);
+	}
+
+	share = calloc(1, sizeof(snd_pcm_share_t));
+	if (!share)
+		return -ENOMEM;
+
+	share->channels = channels;
+	share->slave_channels = calloc(channels, sizeof(*share->slave_channels));
+	if (!share->slave_channels) {
+		free(share);
+		return -ENOMEM;
+	}
+	memcpy(share->slave_channels, channels_map, channels * sizeof(*share->slave_channels));
+
+	err = snd_pcm_new(&pcm, SND_PCM_TYPE_SHARE, name, stream, mode);
+	if (err < 0) {
+		free(share->slave_channels);
+		free(share);
+		return err;
+	}
+	err = socketpair(AF_LOCAL, SOCK_STREAM, 0, sd);
+	if (err < 0) {
+		snd_pcm_free(pcm);
+		free(share->slave_channels);
+		free(share);
+		return -errno;
+	}
+		
+	if (stream == SND_PCM_STREAM_PLAYBACK) {
+		int bufsize = 1;
+		err = setsockopt(sd[0], SOL_SOCKET, SO_SNDBUF, &bufsize, sizeof(bufsize));
+		if (err >= 0) {
+			struct pollfd pfd;
+			pfd.fd = sd[0];
+			pfd.events = POLLOUT;
+			while ((err = poll(&pfd, 1, 0)) == 1) {
+				char buf[1];
+				err = write(sd[0], buf, 1);
+				assert(err != 0);
+				if (err != 1)
+					break;
+			}
+		}
+	}
+	if (err < 0) {
+		err = -errno;
+		close(sd[0]);
+		close(sd[1]);
+		snd_pcm_free(pcm);
+		free(share->slave_channels);
+		free(share);
+		return err;
+	}
+
+	Pthread_mutex_lock(&snd_pcm_share_slaves_mutex);
+	list_for_each(i, &snd_pcm_share_slaves) {
+		snd_pcm_share_slave_t *s = list_entry(i, snd_pcm_share_slave_t, list);
+		if (s->pcm->name && strcmp(s->pcm->name, sname) == 0) {
+			slave = s;
+			break;
+		}
+	}
+	if (!slave) {
+		snd_pcm_t *spcm;
+		err = snd_pcm_open(&spcm, sname, stream, mode);
+		if (err < 0) {
+			Pthread_mutex_unlock(&snd_pcm_share_slaves_mutex);
+			close(sd[0]);
+			close(sd[1]);
+			snd_pcm_free(pcm);
+			free(share->slave_channels);
+			free(share);
+			return err;
+		}
+		/* FIXME: bellow is a real ugly hack to get things working */
+		/* there is a memory leak somewhere, but I'm unable to trace it --jk */
+		slave = calloc(1, sizeof(snd_pcm_share_slave_t) * 8);
+		if (!slave) {
+			Pthread_mutex_unlock(&snd_pcm_share_slaves_mutex);
+			snd_pcm_close(spcm);
+			close(sd[0]);
+			close(sd[1]);
+			snd_pcm_free(pcm);
+			free(share->slave_channels);
+			free(share);
+			return err;
+		}
+		INIT_LIST_HEAD(&slave->clients);
+		slave->pcm = spcm;
+		slave->channels = schannels;
+		slave->format = sformat;
+		slave->rate = srate;
+		slave->period_time = speriod_time;
+		slave->buffer_time = sbuffer_time;
+		pthread_mutex_init(&slave->mutex, NULL);
+		pthread_cond_init(&slave->poll_cond, NULL);
+		list_add_tail(&slave->list, &snd_pcm_share_slaves);
+		Pthread_mutex_lock(&slave->mutex);
+		err = pthread_create(&slave->thread, NULL, snd_pcm_share_thread, slave);
+		assert(err == 0);
+		Pthread_mutex_unlock(&snd_pcm_share_slaves_mutex);
+	} else {
+		Pthread_mutex_lock(&slave->mutex);
+		Pthread_mutex_unlock(&snd_pcm_share_slaves_mutex);
+		list_for_each(i, &slave->clients) {
+			snd_pcm_share_t *sh = list_entry(i, snd_pcm_share_t, list);
+			for (k = 0; k < sh->channels; ++k) {
+				if (slave_map[sh->slave_channels[k]]) {
+					SNDERR("Slave channel %d is already in use", sh->slave_channels[k]);
+					Pthread_mutex_unlock(&slave->mutex);
+					close(sd[0]);
+					close(sd[1]);
+					snd_pcm_free(pcm);
+					free(share->slave_channels);
+					free(share);
+					return -EBUSY;
+				}
+			}
+		}
+	}
+
+	share->slave = slave;
+	share->pcm = pcm;
+	share->client_socket = sd[0];
+	share->slave_socket = sd[1];
+	
+	pcm->mmap_rw = 1;
+	pcm->ops = &snd_pcm_share_ops;
+	pcm->fast_ops = &snd_pcm_share_fast_ops;
+	pcm->private_data = share;
+	pcm->poll_fd = share->client_socket;
+	pcm->poll_events = stream == SND_PCM_STREAM_PLAYBACK ? POLLOUT : POLLIN;
+	pcm->monotonic = slave->pcm->monotonic;
+	snd_pcm_set_hw_ptr(pcm, &share->hw_ptr, -1, 0);
+	snd_pcm_set_appl_ptr(pcm, &share->appl_ptr, -1, 0);
+
+	slave->open_count++;
+	list_add_tail(&share->list, &slave->clients);
+
+	Pthread_mutex_unlock(&slave->mutex);
+
+	*pcmp = pcm;
+	return 0;
+}
+
+/*! \page pcm_plugins
+
+\section pcm_plugins_share Plugin: Share
+
+This plugin allows sharing of multiple channels with more clients. The access
+to each channel is exlusive (samples are not mixed together). It means, if
+the channel zero is used with first client, the channel cannot be used with
+second one. If you are looking for a mixing plugin, use the
+\ref pcm_plugins_dmix "dmix plugin".
+
+The difference from \ref pcm_plugins_dshare "dshare plugin" is that
+share plugin requires the server program "aserver", while dshare plugin
+doesn't need the explicit server but access to the shared buffer.
+
+\code
+pcm.name {
+        type share              # Share PCM
+        slave STR               # Slave name
+        # or
+        slave {                 # Slave definition
+                pcm STR         # Slave PCM name
+                [format STR]    # Slave format
+                [channels INT]  # Slave channels
+                [rate INT]      # Slave rate
+                [period_time INT] # Slave period time in us
+                [buffer_time INT] # Slave buffer time in us
+        }
+	bindings {
+		N INT		# Slave channel INT for client channel N
+	}
+}
+\endcode
+
+\subsection pcm_plugins_share_funcref Function reference
+
+<UL>
+  <LI>snd_pcm_share_open()
+  <LI>_snd_pcm_share_open()
+</UL>
+
+*/
+
+/**
+ * \brief Creates a new Share PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param root Root configuration node
+ * \param conf Configuration node with Share PCM description
+ * \param stream Stream type
+ * \param mode Stream mode
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int _snd_pcm_share_open(snd_pcm_t **pcmp, const char *name,
+			snd_config_t *root, snd_config_t *conf,
+			snd_pcm_stream_t stream, int mode)
+{
+	snd_config_iterator_t i, next;
+	const char *sname = NULL;
+	snd_config_t *bindings = NULL;
+	int err;
+	snd_config_t *slave = NULL, *sconf;
+	unsigned int *channels_map = NULL;
+	unsigned int channels = 0;
+	snd_pcm_format_t sformat = SND_PCM_FORMAT_UNKNOWN;
+	int schannels = -1;
+	int srate = -1;
+	int speriod_time= -1, sbuffer_time = -1;
+	unsigned int schannel_max = 0;
+	
+	snd_config_for_each(i, next, conf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (snd_pcm_conf_generic_id(id))
+			continue;
+		if (strcmp(id, "slave") == 0) {
+			slave = n;
+			continue;
+		}
+		if (strcmp(id, "bindings") == 0) {
+			if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) {
+				SNDERR("Invalid type for %s", id);
+				return -EINVAL;
+			}
+			bindings = n;
+			continue;
+		}
+		SNDERR("Unknown field %s", id);
+		return -EINVAL;
+	}
+	if (!slave) {
+		SNDERR("slave is not defined");
+		return -EINVAL;
+	}
+	err = snd_pcm_slave_conf(root, slave, &sconf, 5,
+				 SND_PCM_HW_PARAM_FORMAT, 0, &sformat,
+				 SND_PCM_HW_PARAM_CHANNELS, 0, &schannels,
+				 SND_PCM_HW_PARAM_RATE, 0, &srate,
+				 SND_PCM_HW_PARAM_PERIOD_TIME, 0, &speriod_time,
+				 SND_PCM_HW_PARAM_BUFFER_TIME, 0, &sbuffer_time);
+	if (err < 0)
+		return err;
+
+	/* FIXME: nothing strictly forces to have named definition */
+	err = snd_config_get_string(sconf, &sname);
+	sname = err >= 0 && sname ? strdup(sname) : NULL;
+	snd_config_delete(sconf);
+	if (sname == NULL) {
+		SNDERR("slave.pcm is not a string");
+		return err;
+	}
+
+	if (!bindings) {
+		SNDERR("bindings is not defined");
+		err = -EINVAL;
+		goto _free;
+	}
+	snd_config_for_each(i, next, bindings) {
+		long cchannel = -1;
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		err = safe_strtol(id, &cchannel);
+		if (err < 0 || cchannel < 0) {
+			SNDERR("Invalid client channel in binding: %s", id);
+			err = -EINVAL;
+			goto _free;
+		}
+		if ((unsigned)cchannel >= channels)
+			channels = cchannel + 1;
+	}
+	if (channels == 0) {
+		SNDERR("No bindings defined");
+		err = -EINVAL;
+		goto _free;
+	}
+	channels_map = calloc(channels, sizeof(*channels_map));
+	if (! channels_map) {
+		err = -ENOMEM;
+		goto _free;
+	}
+
+	snd_config_for_each(i, next, bindings) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		long cchannel;
+		long schannel = -1;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		cchannel = atoi(id);
+		err = snd_config_get_integer(n, &schannel);
+		if (err < 0) {
+			goto _free;
+		}
+		assert(schannel >= 0);
+		assert(schannels <= 0 || schannel < schannels);
+		channels_map[cchannel] = schannel;
+		if ((unsigned)schannel > schannel_max)
+			schannel_max = schannel;
+	}
+	if (schannels <= 0)
+		schannels = schannel_max + 1;
+	err = snd_pcm_share_open(pcmp, name, sname, sformat, srate, 
+				 (unsigned int) schannels,
+				 speriod_time, sbuffer_time,
+				 channels, channels_map, stream, mode);
+_free:
+	free(channels_map);
+	free((char *)sname);
+	return err;
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(_snd_pcm_share_open, SND_PCM_DLSYM_VERSION);
+#endif
diff --git a/src/pcm/pcm_shm.c b/src/pcm/pcm_shm.c
new file mode 100644
index 0000000..69d0524
--- /dev/null
+++ b/src/pcm/pcm_shm.c
@@ -0,0 +1,956 @@
+/**
+ * \file pcm/pcm_shm.c
+ * \ingroup PCM_Plugins
+ * \brief PCM Shared Memory Plugin Interface
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \date 2000-2001
+ */
+/*
+ *  PCM - Shared Memory Client
+ *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <limits.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/shm.h>
+#include <sys/socket.h>
+#include <sys/poll.h>
+#include <sys/un.h>
+#include <sys/mman.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <netdb.h>
+#include "aserver.h"
+
+#ifndef PIC
+/* entry for static linking */
+const char *_snd_module_pcm_shm = "";
+#endif
+
+#ifndef DOC_HIDDEN
+typedef struct {
+	int socket;
+	volatile snd_pcm_shm_ctrl_t *ctrl;
+} snd_pcm_shm_t;
+#endif
+
+static long snd_pcm_shm_action_fd0(snd_pcm_t *pcm, int *fd)
+{
+	snd_pcm_shm_t *shm = pcm->private_data;
+	int err;
+	char buf[1];
+	volatile snd_pcm_shm_ctrl_t *ctrl = shm->ctrl;
+
+	err = write(shm->socket, buf, 1);
+	if (err != 1)
+		return -EBADFD;
+	err = snd_receive_fd(shm->socket, buf, 1, fd);
+	if (err != 1)
+		return -EBADFD;
+	if (ctrl->cmd) {
+		SNDERR("Server has not done the cmd");
+		return -EBADFD;
+	}
+	return ctrl->result;
+}
+
+static int snd_pcm_shm_new_rbptr(snd_pcm_t *pcm, snd_pcm_shm_t *shm,
+				 snd_pcm_rbptr_t *rbptr, volatile snd_pcm_shm_rbptr_t *shm_rbptr)
+{
+	if (!shm_rbptr->use_mmap) {
+		if (&pcm->hw == rbptr)
+			snd_pcm_set_hw_ptr(pcm, &shm_rbptr->ptr, -1, 0);
+		else
+			snd_pcm_set_appl_ptr(pcm, &shm_rbptr->ptr, -1, 0);
+	} else {
+		void *ptr;
+		size_t mmap_size, mmap_offset, offset;
+		int fd;
+		long result;
+		
+		shm->ctrl->cmd = &pcm->hw == rbptr ? SND_PCM_IOCTL_HW_PTR_FD : SND_PCM_IOCTL_APPL_PTR_FD;
+		result = snd_pcm_shm_action_fd0(pcm, &fd);
+		if (result < 0)
+			return result;
+		mmap_size = page_ptr(shm_rbptr->offset, sizeof(snd_pcm_uframes_t), &offset, &mmap_offset);
+		ptr = mmap(NULL, mmap_size, PROT_READ|PROT_WRITE, MAP_FILE|MAP_SHARED, fd, mmap_offset);
+		if (ptr == MAP_FAILED || ptr == NULL) {
+			SYSERR("shm rbptr mmap failed");
+			return -errno;
+		}
+		if (&pcm->hw == rbptr)
+			snd_pcm_set_hw_ptr(pcm, (snd_pcm_uframes_t *)((char *)ptr + offset), fd, shm_rbptr->offset);
+		else
+			snd_pcm_set_appl_ptr(pcm, (snd_pcm_uframes_t *)((char *)ptr + offset), fd, shm_rbptr->offset);
+	}
+	return 0;
+}
+
+static long snd_pcm_shm_action(snd_pcm_t *pcm)
+{
+	snd_pcm_shm_t *shm = pcm->private_data;
+	int err, result;
+	char buf[1];
+	volatile snd_pcm_shm_ctrl_t *ctrl = shm->ctrl;
+
+	if (ctrl->hw.changed || ctrl->appl.changed)
+		return -EBADFD;
+	err = write(shm->socket, buf, 1);
+	if (err != 1)
+		return -EBADFD;
+	err = read(shm->socket, buf, 1);
+	if (err != 1)
+		return -EBADFD;
+	if (ctrl->cmd) {
+		SNDERR("Server has not done the cmd");
+		return -EBADFD;
+	}
+	result = ctrl->result;
+	if (ctrl->hw.changed) {
+		err = snd_pcm_shm_new_rbptr(pcm, shm, &pcm->hw, &ctrl->hw);
+		if (err < 0)
+			return err;
+		ctrl->hw.changed = 0;
+	}
+	if (ctrl->appl.changed) {
+		err = snd_pcm_shm_new_rbptr(pcm, shm, &pcm->appl, &ctrl->appl);
+		if (err < 0)
+			return err;
+		ctrl->appl.changed = 0;
+	}
+	return result;
+}
+
+static long snd_pcm_shm_action_fd(snd_pcm_t *pcm, int *fd)
+{
+	snd_pcm_shm_t *shm = pcm->private_data;
+	int err;
+	char buf[1];
+	volatile snd_pcm_shm_ctrl_t *ctrl = shm->ctrl;
+
+	if (ctrl->hw.changed || ctrl->appl.changed)
+		return -EBADFD;
+	err = write(shm->socket, buf, 1);
+	if (err != 1)
+		return -EBADFD;
+	err = snd_receive_fd(shm->socket, buf, 1, fd);
+	if (err != 1)
+		return -EBADFD;
+	if (ctrl->cmd) {
+		SNDERR("Server has not done the cmd");
+		return -EBADFD;
+	}
+	if (ctrl->hw.changed) {
+		err = snd_pcm_shm_new_rbptr(pcm, shm, &pcm->hw, &ctrl->hw);
+		if (err < 0)
+			return err;
+		ctrl->hw.changed = 0;
+	}
+	if (ctrl->appl.changed) {
+		err = snd_pcm_shm_new_rbptr(pcm, shm, &pcm->appl, &ctrl->appl);
+		if (err < 0)
+			return err;
+		ctrl->appl.changed = 0;
+	}
+	return ctrl->result;
+}
+
+static int snd_pcm_shm_nonblock(snd_pcm_t *pcm ATTRIBUTE_UNUSED, int nonblock ATTRIBUTE_UNUSED)
+{
+	return 0;
+}
+
+static int snd_pcm_shm_async(snd_pcm_t *pcm, int sig, pid_t pid)
+{
+	snd_pcm_shm_t *shm = pcm->private_data;
+	volatile snd_pcm_shm_ctrl_t *ctrl = shm->ctrl;
+	ctrl->cmd = SND_PCM_IOCTL_ASYNC;
+	ctrl->u.async.sig = sig;
+	ctrl->u.async.pid = pid;
+	return snd_pcm_shm_action(pcm);
+}
+
+static int snd_pcm_shm_info(snd_pcm_t *pcm, snd_pcm_info_t * info)
+{
+	snd_pcm_shm_t *shm = pcm->private_data;
+	volatile snd_pcm_shm_ctrl_t *ctrl = shm->ctrl;
+	int err;
+//	ctrl->u.info = *info;
+	ctrl->cmd = SNDRV_PCM_IOCTL_INFO;
+	err = snd_pcm_shm_action(pcm);
+	if (err < 0)
+		return err;
+	*info = ctrl->u.info;
+	return err;
+}
+
+static int snd_pcm_shm_hw_refine_cprepare(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params ATTRIBUTE_UNUSED)
+{
+	return 0;
+}
+
+static int snd_pcm_shm_hw_refine_sprepare(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *sparams)
+{
+	snd_pcm_access_mask_t saccess_mask = { SND_PCM_ACCBIT_MMAP };
+	_snd_pcm_hw_params_any(sparams);
+	_snd_pcm_hw_param_set_mask(sparams, SND_PCM_HW_PARAM_ACCESS,
+				   &saccess_mask);
+	return 0;
+}
+
+static int snd_pcm_shm_hw_refine_schange(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params,
+					  snd_pcm_hw_params_t *sparams)
+{
+	int err;
+	unsigned int links = ~SND_PCM_HW_PARBIT_ACCESS;
+	const snd_pcm_access_mask_t *access_mask = snd_pcm_hw_param_get_mask(params, SND_PCM_HW_PARAM_ACCESS);
+	if (!snd_pcm_access_mask_test(access_mask, SND_PCM_ACCESS_RW_INTERLEAVED) &&
+	    !snd_pcm_access_mask_test(access_mask, SND_PCM_ACCESS_RW_NONINTERLEAVED)) {
+		err = _snd_pcm_hw_param_set_mask(sparams, SND_PCM_HW_PARAM_ACCESS,
+					     access_mask);
+		if (err < 0)
+			return err;
+	}
+	err = _snd_pcm_hw_params_refine(sparams, links, params);
+	if (err < 0)
+		return err;
+	return 0;
+}
+	
+static int snd_pcm_shm_hw_refine_cchange(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params,
+					  snd_pcm_hw_params_t *sparams)
+{
+	int err;
+	unsigned int links = ~SND_PCM_HW_PARBIT_ACCESS;
+	snd_pcm_access_mask_t access_mask;
+	snd_mask_copy(&access_mask, snd_pcm_hw_param_get_mask(sparams, SND_PCM_HW_PARAM_ACCESS));
+	snd_pcm_access_mask_set(&access_mask, SND_PCM_ACCESS_RW_INTERLEAVED);
+	snd_pcm_access_mask_set(&access_mask, SND_PCM_ACCESS_RW_NONINTERLEAVED);
+	err = _snd_pcm_hw_param_set_mask(sparams, SND_PCM_HW_PARAM_ACCESS,
+					 &access_mask);
+	if (err < 0)
+		return err;
+	err = _snd_pcm_hw_params_refine(params, links, sparams);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static int snd_pcm_shm_hw_refine_slave(snd_pcm_t *pcm,
+				       snd_pcm_hw_params_t *params)
+{
+	snd_pcm_shm_t *shm = pcm->private_data;
+	volatile snd_pcm_shm_ctrl_t *ctrl = shm->ctrl;
+	int err;
+	ctrl->u.hw_refine = *params;
+	ctrl->cmd = SNDRV_PCM_IOCTL_HW_REFINE;
+	err = snd_pcm_shm_action(pcm);
+	*params = ctrl->u.hw_refine;
+	return err;
+}
+
+static int snd_pcm_shm_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	return snd_pcm_hw_refine_slave(pcm, params,
+				       snd_pcm_shm_hw_refine_cprepare,
+				       snd_pcm_shm_hw_refine_cchange,
+				       snd_pcm_shm_hw_refine_sprepare,
+				       snd_pcm_shm_hw_refine_schange,
+				       snd_pcm_shm_hw_refine_slave);
+}
+
+static int snd_pcm_shm_hw_params_slave(snd_pcm_t *pcm, 
+				       snd_pcm_hw_params_t *params)
+{
+	snd_pcm_shm_t *shm = pcm->private_data;
+	volatile snd_pcm_shm_ctrl_t *ctrl = shm->ctrl;
+	int err;
+	params->flags |= SND_PCM_HW_PARAMS_EXPORT_BUFFER;
+	ctrl->cmd = SNDRV_PCM_IOCTL_HW_PARAMS;
+	ctrl->u.hw_params = *params;
+	err = snd_pcm_shm_action(pcm);
+	*params = ctrl->u.hw_params;
+	return err;
+}
+
+static int snd_pcm_shm_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t * params)
+{
+	return snd_pcm_hw_params_slave(pcm, params,
+				       snd_pcm_shm_hw_refine_cchange,
+				       snd_pcm_shm_hw_refine_sprepare,
+				       snd_pcm_shm_hw_refine_schange,
+				       snd_pcm_shm_hw_params_slave);
+}
+
+static int snd_pcm_shm_hw_free(snd_pcm_t *pcm)
+{
+	snd_pcm_shm_t *shm = pcm->private_data;
+	volatile snd_pcm_shm_ctrl_t *ctrl = shm->ctrl;
+	ctrl->cmd = SNDRV_PCM_IOCTL_HW_FREE;
+	return snd_pcm_shm_action(pcm);
+}
+
+static int snd_pcm_shm_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t * params)
+{
+	snd_pcm_shm_t *shm = pcm->private_data;
+	volatile snd_pcm_shm_ctrl_t *ctrl = shm->ctrl;
+	int err;
+	ctrl->cmd = SNDRV_PCM_IOCTL_SW_PARAMS;
+	ctrl->u.sw_params = *params;
+	err = snd_pcm_shm_action(pcm);
+	*params = ctrl->u.sw_params;
+	if (err < 0)
+		return err;
+	return err;
+}
+
+static int snd_pcm_shm_mmap(snd_pcm_t *pcm ATTRIBUTE_UNUSED)
+{
+	return 0;
+}
+
+static int snd_pcm_shm_munmap(snd_pcm_t *pcm)
+{
+	unsigned int c;
+	for (c = 0; c < pcm->channels; ++c) {
+		snd_pcm_channel_info_t *i = &pcm->mmap_channels[c];
+		unsigned int c1;
+		int err;
+		if (i->type != SND_PCM_AREA_MMAP)
+			continue;
+		if (i->u.mmap.fd < 0)
+			continue;
+		for (c1 = c + 1; c1 < pcm->channels; ++c1) {
+			snd_pcm_channel_info_t *i1 = &pcm->mmap_channels[c1];
+			if (i1->type != SND_PCM_AREA_MMAP)
+				continue;
+			if (i1->u.mmap.fd != i->u.mmap.fd)
+				continue;
+			i1->u.mmap.fd = -1;
+		}
+		err = close(i->u.mmap.fd);
+		if (err < 0) {
+			SYSERR("close failed");
+			return -errno;
+		}
+	}
+	return 0;
+}
+
+static int snd_pcm_shm_channel_info(snd_pcm_t *pcm, snd_pcm_channel_info_t * info)
+{
+	snd_pcm_shm_t *shm = pcm->private_data;
+	volatile snd_pcm_shm_ctrl_t *ctrl = shm->ctrl;
+	int err;
+	int fd;
+	ctrl->cmd = SNDRV_PCM_IOCTL_CHANNEL_INFO;
+	ctrl->u.channel_info = *info;
+	err = snd_pcm_shm_action_fd(pcm, &fd);
+	if (err < 0)
+		return err;
+	*info = ctrl->u.channel_info;
+	info->addr = 0;
+	switch (info->type) {
+	case SND_PCM_AREA_MMAP:
+		info->u.mmap.fd = fd;
+		break;
+	case SND_PCM_AREA_SHM:
+		break;
+	default:
+		assert(0);
+		break;
+	}
+	return err;
+}
+
+static int snd_pcm_shm_status(snd_pcm_t *pcm, snd_pcm_status_t * status)
+{
+	snd_pcm_shm_t *shm = pcm->private_data;
+	volatile snd_pcm_shm_ctrl_t *ctrl = shm->ctrl;
+	int err;
+	ctrl->cmd = SNDRV_PCM_IOCTL_STATUS;
+	// ctrl->u.status = *status;
+	err = snd_pcm_shm_action(pcm);
+	if (err < 0)
+		return err;
+	*status = ctrl->u.status;
+	return err;
+}
+
+static snd_pcm_state_t snd_pcm_shm_state(snd_pcm_t *pcm)
+{
+	snd_pcm_shm_t *shm = pcm->private_data;
+	volatile snd_pcm_shm_ctrl_t *ctrl = shm->ctrl;
+	ctrl->cmd = SND_PCM_IOCTL_STATE;
+	return snd_pcm_shm_action(pcm);
+}
+
+static int snd_pcm_shm_hwsync(snd_pcm_t *pcm)
+{
+	snd_pcm_shm_t *shm = pcm->private_data;
+	volatile snd_pcm_shm_ctrl_t *ctrl = shm->ctrl;
+	ctrl->cmd = SND_PCM_IOCTL_HWSYNC;
+	return snd_pcm_shm_action(pcm);
+}
+
+static int snd_pcm_shm_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp)
+{
+	snd_pcm_shm_t *shm = pcm->private_data;
+	volatile snd_pcm_shm_ctrl_t *ctrl = shm->ctrl;
+	int err;
+	ctrl->cmd = SNDRV_PCM_IOCTL_DELAY;
+	err = snd_pcm_shm_action(pcm);
+	if (err < 0)
+		return err;
+	*delayp = ctrl->u.delay.frames;
+	return err;
+}
+
+static snd_pcm_sframes_t snd_pcm_shm_avail_update(snd_pcm_t *pcm)
+{
+	snd_pcm_shm_t *shm = pcm->private_data;
+	volatile snd_pcm_shm_ctrl_t *ctrl = shm->ctrl;
+	int err;
+	ctrl->cmd = SND_PCM_IOCTL_AVAIL_UPDATE;
+	err = snd_pcm_shm_action(pcm);
+	if (err < 0)
+		return err;
+	return err;
+}
+
+static int snd_pcm_shm_htimestamp(snd_pcm_t *pcm ATTRIBUTE_UNUSED,
+				  snd_pcm_uframes_t *avail ATTRIBUTE_UNUSED,
+				  snd_htimestamp_t *tstamp ATTRIBUTE_UNUSED)
+{
+	return -EIO;	/* not implemented yet */
+}
+
+static int snd_pcm_shm_prepare(snd_pcm_t *pcm)
+{
+	snd_pcm_shm_t *shm = pcm->private_data;
+	volatile snd_pcm_shm_ctrl_t *ctrl = shm->ctrl;
+	ctrl->cmd = SNDRV_PCM_IOCTL_PREPARE;
+	return snd_pcm_shm_action(pcm);
+}
+
+static int snd_pcm_shm_reset(snd_pcm_t *pcm)
+{
+	snd_pcm_shm_t *shm = pcm->private_data;
+	volatile snd_pcm_shm_ctrl_t *ctrl = shm->ctrl;
+	ctrl->cmd = SNDRV_PCM_IOCTL_RESET;
+	return snd_pcm_shm_action(pcm);
+}
+
+static int snd_pcm_shm_start(snd_pcm_t *pcm)
+{
+	snd_pcm_shm_t *shm = pcm->private_data;
+	volatile snd_pcm_shm_ctrl_t *ctrl = shm->ctrl;
+	ctrl->cmd = SNDRV_PCM_IOCTL_START;
+	return snd_pcm_shm_action(pcm);
+}
+
+static int snd_pcm_shm_drop(snd_pcm_t *pcm)
+{
+	snd_pcm_shm_t *shm = pcm->private_data;
+	volatile snd_pcm_shm_ctrl_t *ctrl = shm->ctrl;
+	ctrl->cmd = SNDRV_PCM_IOCTL_DROP;
+	return snd_pcm_shm_action(pcm);
+}
+
+static int snd_pcm_shm_drain(snd_pcm_t *pcm)
+{
+	snd_pcm_shm_t *shm = pcm->private_data;
+	volatile snd_pcm_shm_ctrl_t *ctrl = shm->ctrl;
+	int err;
+	do {
+		ctrl->cmd = SNDRV_PCM_IOCTL_DRAIN;
+		err = snd_pcm_shm_action(pcm);
+		if (err != -EAGAIN)
+			break;
+		usleep(10000);
+	} while (1);
+	if (err < 0)
+		return err;
+	if (!(pcm->mode & SND_PCM_NONBLOCK))
+		snd_pcm_wait(pcm, -1);
+	return err;
+}
+
+static int snd_pcm_shm_pause(snd_pcm_t *pcm, int enable)
+{
+	snd_pcm_shm_t *shm = pcm->private_data;
+	volatile snd_pcm_shm_ctrl_t *ctrl = shm->ctrl;
+	ctrl->cmd = SNDRV_PCM_IOCTL_PAUSE;
+	ctrl->u.pause.enable = enable;
+	return snd_pcm_shm_action(pcm);
+}
+
+static snd_pcm_sframes_t snd_pcm_shm_rewindable(snd_pcm_t *pcm ATTRIBUTE_UNUSED)
+{
+	return 0;	/* FIX ME */
+}
+
+static snd_pcm_sframes_t snd_pcm_shm_rewind(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
+{
+	snd_pcm_shm_t *shm = pcm->private_data;
+	volatile snd_pcm_shm_ctrl_t *ctrl = shm->ctrl;
+	ctrl->cmd = SNDRV_PCM_IOCTL_REWIND;
+	ctrl->u.rewind.frames = frames;
+	return snd_pcm_shm_action(pcm);
+}
+
+static snd_pcm_sframes_t snd_pcm_shm_forwardable(snd_pcm_t *pcm ATTRIBUTE_UNUSED)
+{
+	return 0;	/* FIX ME */
+}
+
+static snd_pcm_sframes_t snd_pcm_shm_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
+{
+	snd_pcm_shm_t *shm = pcm->private_data;
+	volatile snd_pcm_shm_ctrl_t *ctrl = shm->ctrl;
+	ctrl->cmd = SND_PCM_IOCTL_FORWARD;
+	ctrl->u.forward.frames = frames;
+	return snd_pcm_shm_action(pcm);
+}
+
+static int snd_pcm_shm_resume(snd_pcm_t *pcm)
+{
+	snd_pcm_shm_t *shm = pcm->private_data;
+	volatile snd_pcm_shm_ctrl_t *ctrl = shm->ctrl;
+	ctrl->cmd = SNDRV_PCM_IOCTL_RESUME;
+	return snd_pcm_shm_action(pcm);
+}
+
+static snd_pcm_sframes_t snd_pcm_shm_mmap_commit(snd_pcm_t *pcm,
+						 snd_pcm_uframes_t offset ATTRIBUTE_UNUSED,
+						 snd_pcm_uframes_t size)
+{
+	snd_pcm_shm_t *shm = pcm->private_data;
+	volatile snd_pcm_shm_ctrl_t *ctrl = shm->ctrl;
+	ctrl->cmd = SND_PCM_IOCTL_MMAP_COMMIT;
+	ctrl->u.mmap_commit.offset = offset;
+	ctrl->u.mmap_commit.frames = size;
+	return snd_pcm_shm_action(pcm);
+}
+
+static int snd_pcm_shm_poll_descriptor(snd_pcm_t *pcm)
+{
+	snd_pcm_shm_t *shm = pcm->private_data;
+	volatile snd_pcm_shm_ctrl_t *ctrl = shm->ctrl;
+	int fd, err;
+	ctrl->cmd = SND_PCM_IOCTL_POLL_DESCRIPTOR;
+	err = snd_pcm_shm_action_fd(pcm, &fd);
+	if (err < 0)
+		return err;
+	return fd;
+}
+
+static int snd_pcm_shm_close(snd_pcm_t *pcm)
+{
+	snd_pcm_shm_t *shm = pcm->private_data;
+	volatile snd_pcm_shm_ctrl_t *ctrl = shm->ctrl;
+	int result;
+	ctrl->cmd = SND_PCM_IOCTL_CLOSE;
+	result = snd_pcm_shm_action(pcm);
+	shmdt((void *)ctrl);
+	close(shm->socket);
+	close(pcm->poll_fd);
+	free(shm);
+	return result;
+}
+
+static void snd_pcm_shm_dump(snd_pcm_t *pcm, snd_output_t *out)
+{
+	snd_output_printf(out, "Shm PCM\n");
+	if (pcm->setup) {
+		snd_output_printf(out, "Its setup is:\n");
+		snd_pcm_dump_setup(pcm, out);
+	}
+}
+
+static const snd_pcm_ops_t snd_pcm_shm_ops = {
+	.close = snd_pcm_shm_close,
+	.info = snd_pcm_shm_info,
+	.hw_refine = snd_pcm_shm_hw_refine,
+	.hw_params = snd_pcm_shm_hw_params,
+	.hw_free = snd_pcm_shm_hw_free,
+	.sw_params = snd_pcm_shm_sw_params,
+	.channel_info = snd_pcm_shm_channel_info,
+	.dump = snd_pcm_shm_dump,
+	.nonblock = snd_pcm_shm_nonblock,
+	.async = snd_pcm_shm_async,
+	.mmap = snd_pcm_shm_mmap,
+	.munmap = snd_pcm_shm_munmap,
+};
+
+static const snd_pcm_fast_ops_t snd_pcm_shm_fast_ops = {
+	.status = snd_pcm_shm_status,
+	.state = snd_pcm_shm_state,
+	.hwsync = snd_pcm_shm_hwsync,
+	.delay = snd_pcm_shm_delay,
+	.prepare = snd_pcm_shm_prepare,
+	.reset = snd_pcm_shm_reset,
+	.start = snd_pcm_shm_start,
+	.drop = snd_pcm_shm_drop,
+	.drain = snd_pcm_shm_drain,
+	.pause = snd_pcm_shm_pause,
+	.rewindable = snd_pcm_shm_rewindable,
+	.rewind = snd_pcm_shm_rewind,
+	.forwardable = snd_pcm_shm_forwardable,
+	.forward = snd_pcm_shm_forward,
+	.resume = snd_pcm_shm_resume,
+	.writei = snd_pcm_mmap_writei,
+	.writen = snd_pcm_mmap_writen,
+	.readi = snd_pcm_mmap_readi,
+	.readn = snd_pcm_mmap_readn,
+	.avail_update = snd_pcm_shm_avail_update,
+	.mmap_commit = snd_pcm_shm_mmap_commit,
+	.htimestamp = snd_pcm_shm_htimestamp,
+};
+
+static int make_local_socket(const char *filename)
+{
+	size_t l = strlen(filename);
+	size_t size = offsetof(struct sockaddr_un, sun_path) + l;
+	struct sockaddr_un *addr = alloca(size);
+	int sock;
+
+	sock = socket(PF_LOCAL, SOCK_STREAM, 0);
+	if (sock < 0) {
+		SYSERR("socket failed");
+		return -errno;
+	}
+	
+	addr->sun_family = AF_LOCAL;
+	memcpy(addr->sun_path, filename, l);
+
+	if (connect(sock, (struct sockaddr *) addr, size) < 0) {
+		SYSERR("connect failed");
+		return -errno;
+	}
+	return sock;
+}
+
+#if 0
+static int make_inet_socket(const char *host, int port)
+{
+	struct sockaddr_in addr;
+	int sock;
+	struct hostent *h = gethostbyname(host);
+	if (!h)
+		return -ENOENT;
+
+	sock = socket(PF_INET, SOCK_STREAM, 0);
+	if (sock < 0) {
+		SYSERR("socket failed");
+		return -errno;
+	}
+	
+	addr.sin_family = AF_INET;
+	addr.sin_port = htons(port);
+	memcpy(&addr.sin_addr, h->h_addr_list[0], sizeof(struct in_addr));
+
+	if (connect(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+		SYSERR("connect failed");
+		return -errno;
+	}
+	return sock;
+}
+#endif
+
+/**
+ * \brief Creates a new shared memory PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param sockname Unix socket name
+ * \param sname Server name
+ * \param stream PCM Stream
+ * \param mode PCM Mode
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int snd_pcm_shm_open(snd_pcm_t **pcmp, const char *name,
+		     const char *sockname, const char *sname,
+		     snd_pcm_stream_t stream, int mode)
+{
+	snd_pcm_t *pcm;
+	snd_pcm_shm_t *shm = NULL;
+	snd_client_open_request_t *req;
+	snd_client_open_answer_t ans;
+	size_t snamelen, reqlen;
+	int err;
+	int result;
+	snd_pcm_shm_ctrl_t *ctrl = NULL;
+	int sock = -1;
+	snamelen = strlen(sname);
+	if (snamelen > 255)
+		return -EINVAL;
+
+	result = make_local_socket(sockname);
+	if (result < 0) {
+		SNDERR("server for socket %s is not running", sockname);
+		goto _err;
+	}
+	sock = result;
+
+	reqlen = sizeof(*req) + snamelen;
+	req = alloca(reqlen);
+	memcpy(req->name, sname, snamelen);
+	req->dev_type = SND_DEV_TYPE_PCM;
+	req->transport_type = SND_TRANSPORT_TYPE_SHM;
+	req->stream = stream;
+	req->mode = mode;
+	req->namelen = snamelen;
+	err = write(sock, req, reqlen);
+	if (err < 0) {
+		SYSERR("write error");
+		result = -errno;
+		goto _err;
+	}
+	if ((size_t) err != reqlen) {
+		SNDERR("write size error");
+		result = -EINVAL;
+		goto _err;
+	}
+	err = read(sock, &ans, sizeof(ans));
+	if (err < 0) {
+		SYSERR("read error");
+		result = -errno;
+		goto _err;
+	}
+	if (err != sizeof(ans)) {
+		SNDERR("read size error");
+		result = -EINVAL;
+		goto _err;
+	}
+	result = ans.result;
+	if (result < 0)
+		goto _err;
+
+	ctrl = shmat(ans.cookie, 0, 0);
+	if (!ctrl) {
+		SYSERR("shmat error");
+		result = -errno;
+		goto _err;
+	}
+		
+	shm = calloc(1, sizeof(snd_pcm_shm_t));
+	if (!shm) {
+		result = -ENOMEM;
+		goto _err;
+	}
+
+	shm->socket = sock;
+	shm->ctrl = ctrl;
+
+	err = snd_pcm_new(&pcm, SND_PCM_TYPE_SHM, name, stream, mode);
+	if (err < 0) {
+		result = err;
+		goto _err;
+	}
+	pcm->mmap_rw = 1;
+	pcm->ops = &snd_pcm_shm_ops;
+	pcm->fast_ops = &snd_pcm_shm_fast_ops;
+	pcm->private_data = shm;
+	err = snd_pcm_shm_poll_descriptor(pcm);
+	if (err < 0) {
+		snd_pcm_close(pcm);
+		return err;
+	}
+	pcm->poll_fd = err;
+	pcm->poll_events = stream == SND_PCM_STREAM_PLAYBACK ? POLLOUT : POLLIN;
+	snd_pcm_set_hw_ptr(pcm, &ctrl->hw.ptr, -1, 0);
+	snd_pcm_set_appl_ptr(pcm, &ctrl->appl.ptr, -1, 0);
+	*pcmp = pcm;
+	return 0;
+
+ _err:
+	close(sock);
+	if (ctrl)
+		shmdt(ctrl);
+	free(shm);
+	return result;
+}
+
+/*! \page pcm_plugins
+
+\section pcm_plugins_shm Plugin: shm
+
+This plugin communicates with aserver via shared memory. It is a raw
+communication without any conversions, but it can be expected worse
+performance.
+
+\code
+pcm.name {
+        type shm                # Shared memory PCM
+	server STR		# Server name
+	pcm STR			# PCM name
+}
+\endcode
+
+\subsection pcm_plugins_shm_funcref Function reference
+
+<UL>
+  <LI>snd_pcm_shm_open()
+  <LI>_snd_pcm_shm_open()
+</UL>
+
+*/
+
+/**
+ * \brief Creates a new shm PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param root Root configuration node
+ * \param conf Configuration node with hw PCM description
+ * \param stream PCM Stream
+ * \param mode PCM Mode
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int _snd_pcm_shm_open(snd_pcm_t **pcmp, const char *name,
+		      snd_config_t *root, snd_config_t *conf,
+		      snd_pcm_stream_t stream, int mode)
+{
+	snd_config_iterator_t i, next;
+	const char *server = NULL;
+	const char *pcm_name = NULL;
+	snd_config_t *sconfig;
+	const char *host = NULL;
+	const char *sockname = NULL;
+	long port = -1;
+	int err;
+	int local;
+	struct hostent *h;
+	snd_config_for_each(i, next, conf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (snd_pcm_conf_generic_id(id))
+			continue;
+		if (strcmp(id, "server") == 0) {
+			err = snd_config_get_string(n, &server);
+			if (err < 0) {
+				SNDERR("Invalid type for %s", id);
+				return -EINVAL;
+			}
+			continue;
+		}
+		if (strcmp(id, "pcm") == 0) {
+			err = snd_config_get_string(n, &pcm_name);
+			if (err < 0) {
+				SNDERR("Invalid type for %s", id);
+				return -EINVAL;
+			}
+			continue;
+		}
+		SNDERR("Unknown field %s", id);
+		return -EINVAL;
+	}
+	if (!pcm_name) {
+		SNDERR("pcm is not defined");
+		return -EINVAL;
+	}
+	if (!server) {
+		SNDERR("server is not defined");
+		return -EINVAL;
+	}
+	err = snd_config_search_definition(root, "server", server, &sconfig);
+	if (err < 0) {
+		SNDERR("Unknown server %s", server);
+		return -EINVAL;
+	}
+	if (snd_config_get_type(sconfig) != SND_CONFIG_TYPE_COMPOUND) {
+		SNDERR("Invalid type for server %s definition", server);
+		goto _err;
+	}
+	snd_config_for_each(i, next, sconfig) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (strcmp(id, "comment") == 0)
+			continue;
+		if (strcmp(id, "host") == 0) {
+			err = snd_config_get_string(n, &host);
+			if (err < 0) {
+				SNDERR("Invalid type for %s", id);
+				goto _err;
+			}
+			continue;
+		}
+		if (strcmp(id, "socket") == 0) {
+			err = snd_config_get_string(n, &sockname);
+			if (err < 0) {
+				SNDERR("Invalid type for %s", id);
+				goto _err;
+			}
+			continue;
+		}
+		if (strcmp(id, "port") == 0) {
+			err = snd_config_get_integer(n, &port);
+			if (err < 0) {
+				SNDERR("Invalid type for %s", id);
+				goto _err;
+			}
+			continue;
+		}
+		SNDERR("Unknown field %s", id);
+	       _err:
+		err = -EINVAL;
+		goto __error;
+	}
+
+	if (!host) {
+		SNDERR("host is not defined");
+		goto _err;
+	}
+	if (!sockname) {
+		SNDERR("socket is not defined");
+		goto _err;
+	}
+	h = gethostbyname(host);
+	if (!h) {
+		SNDERR("Cannot resolve %s", host);
+		goto _err;
+	}
+	local = snd_is_local(h);
+	if (!local) {
+		SNDERR("%s is not the local host", host);
+		goto _err;
+	}
+	err = snd_pcm_shm_open(pcmp, name, sockname, pcm_name, stream, mode);
+      __error:
+	snd_config_delete(sconfig);
+	return err;
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(_snd_pcm_shm_open, SND_PCM_DLSYM_VERSION);
+#endif
diff --git a/src/pcm/pcm_simple.c b/src/pcm/pcm_simple.c
new file mode 100644
index 0000000..f943ec0
--- /dev/null
+++ b/src/pcm/pcm_simple.c
@@ -0,0 +1,305 @@
+/**
+ * \file pcm/pcm_simple.c
+ * \ingroup PCM_Simple
+ * \brief PCM Simple Interface
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \date 2004
+ */
+/*
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include "pcm_local.h"
+
+static int set_buffer_time(snd_spcm_latency_t latency,
+			   unsigned int *buffer_time)
+{
+	switch (latency) {
+	case SND_SPCM_LATENCY_STANDARD:
+		*buffer_time = 350000;
+		break;
+	case SND_SPCM_LATENCY_MEDIUM:
+		*buffer_time = 25000;
+		break;
+	case SND_SPCM_LATENCY_REALTIME:
+		*buffer_time = 2500;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int set_hw_params(snd_pcm_t *pcm,
+			 snd_pcm_hw_params_t *hw_params,
+			 unsigned int *rate,
+			 unsigned int channels,
+			 snd_pcm_format_t format,
+			 snd_pcm_subformat_t subformat,
+			 unsigned int *buffer_time,
+			 unsigned int *period_time,
+			 snd_pcm_access_t access)
+{
+	int err;
+
+	/*
+	 * hardware parameters
+	 */	
+	err = snd_pcm_hw_params_any(pcm, hw_params);
+	if (err < 0)
+		return err;
+	err = snd_pcm_hw_params_set_access(pcm, hw_params, access);
+	if (err < 0)
+		return err;
+	err = snd_pcm_hw_params_set_format(pcm, hw_params, format);
+	if (err < 0)
+		return err;
+	if (subformat != SND_PCM_SUBFORMAT_STD) {
+		err = snd_pcm_hw_params_set_subformat(pcm, hw_params, subformat);
+		if (err < 0)
+			return err;
+	}
+	err = snd_pcm_hw_params_set_channels(pcm, hw_params, channels);
+	if (err < 0)
+		return err;
+	err = INTERNAL(snd_pcm_hw_params_set_rate_near)(pcm, hw_params, rate, 0);
+	if (err < 0)
+		return err;
+	err = INTERNAL(snd_pcm_hw_params_set_buffer_time_near)(pcm, hw_params, buffer_time, NULL);
+	if (err < 0)
+		return err;
+	if (period_time == NULL || *period_time == 0) {
+		unsigned int periods = 3;
+		err = INTERNAL(snd_pcm_hw_params_set_periods_near)(pcm, hw_params, &periods, NULL);
+		if (err < 0)
+			return err;
+		if (periods == 1)
+			return -EINVAL;
+		if (period_time) {
+			err = INTERNAL(snd_pcm_hw_params_get_period_time)(hw_params, period_time, NULL);
+			if (err < 0)
+				return err;
+		}			
+	} else {
+		err = snd_pcm_hw_params_set_period_time(pcm, hw_params, *period_time, 0);
+		if (err < 0)
+			return err;
+		if (*buffer_time == *period_time)
+			return -EINVAL;
+	}
+	err = snd_pcm_hw_params(pcm, hw_params);
+	if (err < 0)
+		return err;
+	return 0;
+}		
+
+static int set_sw_params(snd_pcm_t *pcm,
+			 snd_pcm_sw_params_t *sw_params,
+		         snd_spcm_xrun_type_t xrun_type)
+{
+	int err;
+
+	err = snd_pcm_sw_params_current(pcm, sw_params);		
+	if (err < 0)
+		return err;
+	err = snd_pcm_sw_params_set_start_threshold(pcm, sw_params, (pcm->buffer_size / pcm->period_size) * pcm->period_size);
+	if (err < 0)
+		return err;
+	err = snd_pcm_sw_params_set_avail_min(pcm, sw_params, pcm->period_size);
+	if (err < 0)
+		return err;
+	switch (xrun_type) {
+	case SND_SPCM_XRUN_STOP:
+		err = snd_pcm_sw_params_set_stop_threshold(pcm, sw_params, pcm->buffer_size);
+		break;
+	case SND_SPCM_XRUN_IGNORE:
+		err = snd_pcm_sw_params_set_stop_threshold(pcm, sw_params, pcm->boundary);
+		break;
+	default:
+		return -EINVAL;
+	}
+	if (err < 0)
+		return err;
+	err = snd_pcm_sw_params(pcm, sw_params);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+/**
+ * \brief Set up a simple PCM
+ * \param pcm PCM handle
+ * \param rate Sample rate
+ * \param channels Number of channels
+ * \param format PCM format
+ * \param subformat PCM subformat
+ * \param latency Latency type
+ * \param access PCM acceess type
+ * \param xrun_type XRUN type
+ * \return 0 if successful, or a negative error code
+ *
+ * \warning The simple PCM API may be broken in the current release.
+ */
+int snd_spcm_init(snd_pcm_t *pcm,
+		  unsigned int rate,
+		  unsigned int channels,
+		  snd_pcm_format_t format,
+		  snd_pcm_subformat_t subformat,
+		  snd_spcm_latency_t latency,
+		  snd_pcm_access_t access,
+		  snd_spcm_xrun_type_t xrun_type)
+{
+	int err;
+	snd_pcm_hw_params_t *hw_params;
+	snd_pcm_sw_params_t *sw_params;
+	unsigned int rrate;
+	unsigned int buffer_time;
+
+	snd_pcm_hw_params_alloca(&hw_params);
+	snd_pcm_sw_params_alloca(&sw_params);
+
+	assert(pcm);
+	assert(rate > 5000 && rate < 192000);
+	assert(channels > 1 && channels < 512);
+
+	rrate = rate;
+	err = set_buffer_time(latency, &buffer_time);
+	if (err < 0)
+		return err;
+	err = set_hw_params(pcm, hw_params,
+			    &rrate, channels, format, subformat,
+			    &buffer_time, NULL, access);
+	if (err < 0)
+		return err;
+
+	err = set_sw_params(pcm, sw_params, xrun_type);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+/**
+ * \brief Initialize simple PCMs in the duplex mode
+ * \param playback_pcm PCM handle for playback
+ * \param capture_pcm PCM handle for capture
+ * \param rate Sample rate
+ * \param channels Number of channels
+ * \param format PCM format
+ * \param subformat PCM subformat
+ * \param latency Latency type
+ * \param access PCM acceess type
+ * \param xrun_type XRUN type
+ * \param duplex_type Duplex mode
+ * \return 0 if successful, or a negative error code
+ *
+ * \warning The simple PCM API may be broken in the current release.
+ */
+int snd_spcm_init_duplex(snd_pcm_t *playback_pcm,
+			 snd_pcm_t *capture_pcm,
+			 unsigned int rate,
+			 unsigned int channels,
+			 snd_pcm_format_t format,
+			 snd_pcm_subformat_t subformat,
+			 snd_spcm_latency_t latency,
+			 snd_pcm_access_t access,
+			 snd_spcm_xrun_type_t xrun_type,
+			 snd_spcm_duplex_type_t duplex_type)
+{
+	int err, i;
+	snd_pcm_hw_params_t *hw_params;
+	snd_pcm_sw_params_t *sw_params;
+	unsigned int rrate;
+	unsigned int xbuffer_time, buffer_time[2];
+	unsigned int period_time[2];
+	snd_pcm_t *pcms[2];
+
+	snd_pcm_hw_params_alloca(&hw_params);
+	snd_pcm_sw_params_alloca(&sw_params);
+
+	assert(playback_pcm);
+	assert(capture_pcm);
+	assert(rate > 5000 && rate < 192000);
+	assert(channels > 1 && channels < 512);
+
+	pcms[0] = playback_pcm;
+	pcms[1] = capture_pcm;
+
+	/*
+	 * hardware parameters
+	 */
+	err = set_buffer_time(latency, &xbuffer_time);	
+	if (err < 0)
+		return err;
+	
+	for (i = 0; i < 2; i++) {
+		buffer_time[i] = xbuffer_time;
+		period_time[i] = i > 0 ? period_time[0] : 0;
+		rrate = rate;
+		err = set_hw_params(pcms[i], hw_params,
+				    &rrate, channels, format, subformat,
+				    &buffer_time[i], &period_time[i], access);
+		if (err < 0)
+			return err;
+	}
+	if (buffer_time[0] == buffer_time[1] &&
+	    period_time[0] == period_time[1])
+		goto __sw_params;
+	if (duplex_type == SND_SPCM_DUPLEX_LIBERAL)
+		goto __sw_params;
+	/* FIXME: */
+	return -EINVAL;
+
+	/*
+	 * software parameters
+	 */
+      __sw_params:
+	for (i = 0; i < 2; i++) {
+		err = set_sw_params(pcms[i], sw_params, xrun_type);
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+
+/**
+ * \brief Get the set up of simple PCM
+ * \param pcm PCM handle
+ * \param rate Pointer to store the current sample rate
+ * \param buffer_size Pointer to store the current buffer size
+ * \param period_size Pointer to store the current period size
+ * \return 0 if successful, or a negative error code
+ *
+ * \warning The simple PCM API may be broken in the current release.
+ */
+int snd_spcm_init_get_params(snd_pcm_t *pcm,
+			     unsigned int *rate,
+			     snd_pcm_uframes_t *buffer_size,
+			     snd_pcm_uframes_t *period_size)
+{
+	assert(pcm);
+	if (!pcm->setup)
+		return -EBADFD;
+	if (rate)
+		*rate = pcm->rate;
+	if (buffer_size)
+		*buffer_size = pcm->buffer_size;
+	if (period_size)
+		*period_size = pcm->period_size;
+	return 0;
+}
diff --git a/src/pcm/pcm_softvol.c b/src/pcm/pcm_softvol.c
new file mode 100644
index 0000000..2c7c006
--- /dev/null
+++ b/src/pcm/pcm_softvol.c
@@ -0,0 +1,1100 @@
+/**
+ * \file pcm/pcm_softvol.c
+ * \ingroup PCM_Plugins
+ * \brief PCM Soft Volume Plugin Interface
+ * \author Takashi Iwai <tiwai@suse.de>
+ * \date 2004
+ */
+/*
+ *  PCM - Soft Volume Plugin
+ *  Copyright (c) 2004 by Takashi Iwai <tiwai@suse.de>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <byteswap.h>
+#include <math.h>
+#include "pcm_local.h"
+#include "pcm_plugin.h"
+
+#ifndef PIC
+/* entry for static linking */
+const char *_snd_module_pcm_softvol = "";
+#endif
+
+#ifndef DOC_HIDDEN
+
+typedef struct {
+	/* This field need to be the first */
+	snd_pcm_plugin_t plug;
+	snd_pcm_format_t sformat;
+	unsigned int cchannels;
+	snd_ctl_t *ctl;
+	snd_ctl_elem_value_t elem;
+	unsigned int cur_vol[2];
+	unsigned int max_val;     /* max index */
+	unsigned int zero_dB_val; /* index at 0 dB */
+	double min_dB;
+	double max_dB;
+	unsigned int *dB_value;
+} snd_pcm_softvol_t;
+
+#define VOL_SCALE_SHIFT		16
+#define VOL_SCALE_MASK          ((1 << VOL_SCALE_SHIFT) - 1)
+
+#define PRESET_RESOLUTION	256
+#define PRESET_MIN_DB		-51.0
+#define ZERO_DB                  0.0
+#define MAX_DB_UPPER_LIMIT      50
+
+static const unsigned int preset_dB_value[PRESET_RESOLUTION] = {
+	0x00b8, 0x00bd, 0x00c1, 0x00c5, 0x00ca, 0x00cf, 0x00d4, 0x00d9,
+	0x00de, 0x00e3, 0x00e8, 0x00ed, 0x00f3, 0x00f9, 0x00fe, 0x0104,
+	0x010a, 0x0111, 0x0117, 0x011e, 0x0124, 0x012b, 0x0132, 0x0139,
+	0x0140, 0x0148, 0x0150, 0x0157, 0x015f, 0x0168, 0x0170, 0x0179,
+	0x0181, 0x018a, 0x0194, 0x019d, 0x01a7, 0x01b0, 0x01bb, 0x01c5,
+	0x01cf, 0x01da, 0x01e5, 0x01f1, 0x01fc, 0x0208, 0x0214, 0x0221,
+	0x022d, 0x023a, 0x0248, 0x0255, 0x0263, 0x0271, 0x0280, 0x028f,
+	0x029e, 0x02ae, 0x02be, 0x02ce, 0x02df, 0x02f0, 0x0301, 0x0313,
+	0x0326, 0x0339, 0x034c, 0x035f, 0x0374, 0x0388, 0x039d, 0x03b3,
+	0x03c9, 0x03df, 0x03f7, 0x040e, 0x0426, 0x043f, 0x0458, 0x0472,
+	0x048d, 0x04a8, 0x04c4, 0x04e0, 0x04fd, 0x051b, 0x053a, 0x0559,
+	0x0579, 0x0599, 0x05bb, 0x05dd, 0x0600, 0x0624, 0x0648, 0x066e,
+	0x0694, 0x06bb, 0x06e3, 0x070c, 0x0737, 0x0762, 0x078e, 0x07bb,
+	0x07e9, 0x0818, 0x0848, 0x087a, 0x08ac, 0x08e0, 0x0915, 0x094b,
+	0x0982, 0x09bb, 0x09f5, 0x0a30, 0x0a6d, 0x0aab, 0x0aeb, 0x0b2c,
+	0x0b6f, 0x0bb3, 0x0bf9, 0x0c40, 0x0c89, 0x0cd4, 0x0d21, 0x0d6f,
+	0x0dbf, 0x0e11, 0x0e65, 0x0ebb, 0x0f12, 0x0f6c, 0x0fc8, 0x1026,
+	0x1087, 0x10e9, 0x114e, 0x11b5, 0x121f, 0x128b, 0x12fa, 0x136b,
+	0x13df, 0x1455, 0x14ce, 0x154a, 0x15c9, 0x164b, 0x16d0, 0x1758,
+	0x17e4, 0x1872, 0x1904, 0x1999, 0x1a32, 0x1ace, 0x1b6e, 0x1c11,
+	0x1cb9, 0x1d64, 0x1e13, 0x1ec7, 0x1f7e, 0x203a, 0x20fa, 0x21bf,
+	0x2288, 0x2356, 0x2429, 0x2500, 0x25dd, 0x26bf, 0x27a6, 0x2892,
+	0x2984, 0x2a7c, 0x2b79, 0x2c7c, 0x2d85, 0x2e95, 0x2fab, 0x30c7,
+	0x31ea, 0x3313, 0x3444, 0x357c, 0x36bb, 0x3801, 0x394f, 0x3aa5,
+	0x3c02, 0x3d68, 0x3ed6, 0x404d, 0x41cd, 0x4355, 0x44e6, 0x4681,
+	0x4826, 0x49d4, 0x4b8c, 0x4d4f, 0x4f1c, 0x50f3, 0x52d6, 0x54c4,
+	0x56be, 0x58c3, 0x5ad4, 0x5cf2, 0x5f1c, 0x6153, 0x6398, 0x65e9,
+	0x6849, 0x6ab7, 0x6d33, 0x6fbf, 0x7259, 0x7503, 0x77bd, 0x7a87,
+	0x7d61, 0x804d, 0x834a, 0x8659, 0x897a, 0x8cae, 0x8ff5, 0x934f,
+	0x96bd, 0x9a40, 0x9dd8, 0xa185, 0xa548, 0xa922, 0xad13, 0xb11b,
+	0xb53b, 0xb973, 0xbdc5, 0xc231, 0xc6b7, 0xcb58, 0xd014, 0xd4ed,
+	0xd9e3, 0xdef6, 0xe428, 0xe978, 0xeee8, 0xf479, 0xfa2b, 0xffff,
+};
+
+/* (32bit x 16bit) >> 16 */
+typedef union {
+	int i;
+	short s[2];
+} val_t;
+static inline int MULTI_DIV_32x16(int a, unsigned short b)
+{
+	val_t v, x, y;
+	v.i = a;
+	y.i = 0;
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+	x.i = (unsigned short)v.s[0];
+	x.i *= b;
+	y.s[0] = x.s[1];
+	y.i += (int)v.s[1] * b;
+#else
+	x.i = (unsigned int)v.s[1] * b;
+	y.s[1] = x.s[0];
+	y.i += (int)v.s[0] * b;
+#endif
+	return y.i;
+}
+
+static inline int MULTI_DIV_int(int a, unsigned int b, int swap)
+{
+	unsigned int gain = (b >> VOL_SCALE_SHIFT);
+	int fraction;
+	a = swap ? (int)bswap_32(a) : a;
+	fraction = MULTI_DIV_32x16(a, b & VOL_SCALE_MASK);
+	if (gain) {
+		long long amp = (long long)a * gain + fraction;
+		if (amp > (int)0x7fffffff)
+			amp = (int)0x7fffffff;
+		else if (amp < (int)0x80000000)
+			amp = (int)0x80000000;
+		return swap ? (int)bswap_32((int)amp) : (int)amp;
+	}
+	return swap ? (int)bswap_32(fraction) : fraction;
+}
+
+/* always little endian */
+static inline int MULTI_DIV_24(int a, unsigned int b)
+{
+	unsigned int gain = b >> VOL_SCALE_SHIFT;
+	int fraction;
+	fraction = MULTI_DIV_32x16(a, b & VOL_SCALE_MASK);
+	if (gain) {
+		long long amp = (long long)a * gain + fraction;
+		if (amp > (int)0x7fffff)
+			amp = (int)0x7fffff;
+		else if (amp < (int)0x800000)
+			amp = (int)0x800000;
+		return (int)amp;
+	}
+	return fraction;
+}
+
+static inline short MULTI_DIV_short(short a, unsigned int b, int swap)
+{
+	unsigned int gain = b >> VOL_SCALE_SHIFT;
+	int fraction;
+	a = swap ? (short)bswap_16(a) : a;
+	fraction = (int)(a * (b & VOL_SCALE_MASK)) >> VOL_SCALE_SHIFT;
+	if (gain) {
+		int amp = a * gain + fraction;
+		if (abs(amp) > 0x7fff)
+			amp = (a<0) ? (short)0x8000 : (short)0x7fff;
+		return swap ? (short)bswap_16((short)amp) : (short)amp;
+	}
+	return swap ? (short)bswap_16((short)fraction) : (short)fraction;
+}
+
+#endif /* DOC_HIDDEN */
+
+/*
+ * apply volumue attenuation
+ *
+ * TODO: use SIMD operations
+ */
+
+#ifndef DOC_HIDDEN
+#define CONVERT_AREA(TYPE, swap) do {	\
+	unsigned int ch, fr; \
+	TYPE *src, *dst; \
+	for (ch = 0; ch < channels; ch++) { \
+		src_area = &src_areas[ch]; \
+		dst_area = &dst_areas[ch]; \
+		src = snd_pcm_channel_area_addr(src_area, src_offset); \
+		dst = snd_pcm_channel_area_addr(dst_area, dst_offset); \
+		src_step = snd_pcm_channel_area_step(src_area) / sizeof(TYPE); \
+		dst_step = snd_pcm_channel_area_step(dst_area) / sizeof(TYPE); \
+		GET_VOL_SCALE; \
+		fr = frames; \
+		if (! vol_scale) { \
+			while (fr--) { \
+				*dst = 0; \
+				dst += dst_step; \
+			} \
+		} else if (vol_scale == 0xffff) { \
+			while (fr--) { \
+				*dst = *src; \
+				src += src_step; \
+				dst += dst_step; \
+			} \
+		} else { \
+			while (fr--) { \
+				*dst = (TYPE) MULTI_DIV_##TYPE(*src, vol_scale, swap); \
+				src += src_step; \
+				dst += dst_step; \
+			} \
+		} \
+	} \
+} while (0)
+
+#define CONVERT_AREA_S24_3LE() do {					\
+	unsigned int ch, fr;						\
+	unsigned char *src, *dst;					\
+	int tmp;							\
+	for (ch = 0; ch < channels; ch++) {				\
+		src_area = &src_areas[ch];				\
+		dst_area = &dst_areas[ch];				\
+		src = snd_pcm_channel_area_addr(src_area, src_offset);	\
+		dst = snd_pcm_channel_area_addr(dst_area, dst_offset);	\
+		src_step = snd_pcm_channel_area_step(src_area);		\
+		dst_step = snd_pcm_channel_area_step(dst_area);		\
+		GET_VOL_SCALE;						\
+		fr = frames;						\
+		if (! vol_scale) {					\
+			while (fr--) {					\
+				dst[0] = dst[1] = dst[2] = 0;		\
+				dst += dst_step;			\
+			}						\
+		} else if (vol_scale == 0xffff) {			\
+			while (fr--) {					\
+				dst[0] = src[0];			\
+				dst[1] = src[1];			\
+				dst[2] = src[2];			\
+				src += dst_step;			\
+				dst += src_step;			\
+			}						\
+		} else {						\
+			while (fr--) {					\
+				tmp = src[0] |				\
+				      (src[1] << 8) |			\
+				      (((signed char *) src)[2] << 16);	\
+				tmp = MULTI_DIV_24(tmp, vol_scale);	\
+				dst[0] = tmp;				\
+				dst[1] = tmp >> 8;			\
+				dst[2] = tmp >> 16;			\
+				src += dst_step;			\
+				dst += src_step;			\
+			}						\
+		}							\
+	}								\
+} while (0)
+		
+#define GET_VOL_SCALE \
+	switch (ch) { \
+	case 0: \
+	case 2: \
+		vol_scale = (channels == ch + 1) ? vol_c : vol[0]; \
+		break; \
+	case 4: \
+	case 5: \
+		vol_scale = vol_c; \
+		break; \
+	default: \
+		vol_scale = vol[ch & 1]; \
+		break; \
+	}
+
+#endif /* DOC_HIDDEN */
+
+/* 2-channel stereo control */
+static void softvol_convert_stereo_vol(snd_pcm_softvol_t *svol,
+				       const snd_pcm_channel_area_t *dst_areas,
+				       snd_pcm_uframes_t dst_offset,
+				       const snd_pcm_channel_area_t *src_areas,
+				       snd_pcm_uframes_t src_offset,
+				       unsigned int channels,
+				       snd_pcm_uframes_t frames)
+{
+	const snd_pcm_channel_area_t *dst_area, *src_area;
+	unsigned int src_step, dst_step;
+	unsigned int vol_scale, vol[2], vol_c;
+
+	if (svol->cur_vol[0] == 0 && svol->cur_vol[1] == 0) {
+		snd_pcm_areas_silence(dst_areas, dst_offset, channels, frames,
+				      svol->sformat);
+		return;
+	} else if (svol->zero_dB_val && svol->cur_vol[0] == svol->zero_dB_val &&
+		   svol->cur_vol[1] == svol->zero_dB_val) {
+		snd_pcm_areas_copy(dst_areas, dst_offset, src_areas, src_offset,
+				   channels, frames, svol->sformat);
+		return;
+	}
+
+	if (svol->max_val == 1) {
+		vol[0] = svol->cur_vol[0] ? 0xffff : 0;
+		vol[1] = svol->cur_vol[1] ? 0xffff : 0;
+		vol_c = vol[0] | vol[1];
+	} else {
+		vol[0] = svol->dB_value[svol->cur_vol[0]];
+		vol[1] = svol->dB_value[svol->cur_vol[1]];
+		vol_c = svol->dB_value[(svol->cur_vol[0] + svol->cur_vol[1]) / 2];
+	}
+	switch (svol->sformat) {
+	case SND_PCM_FORMAT_S16_LE:
+	case SND_PCM_FORMAT_S16_BE:
+		/* 16bit samples */
+		CONVERT_AREA(short, 
+			     !snd_pcm_format_cpu_endian(svol->sformat));
+		break;
+	case SND_PCM_FORMAT_S32_LE:
+	case SND_PCM_FORMAT_S32_BE:
+		/* 32bit samples */
+		CONVERT_AREA(int,
+			     !snd_pcm_format_cpu_endian(svol->sformat));
+		break;
+	case SND_PCM_FORMAT_S24_3LE:
+		CONVERT_AREA_S24_3LE();
+		break;
+	default:
+		break;
+	}
+}
+
+#undef GET_VOL_SCALE
+#define GET_VOL_SCALE
+
+/* mono control */
+static void softvol_convert_mono_vol(snd_pcm_softvol_t *svol,
+				     const snd_pcm_channel_area_t *dst_areas,
+				     snd_pcm_uframes_t dst_offset,
+				     const snd_pcm_channel_area_t *src_areas,
+				     snd_pcm_uframes_t src_offset,
+				     unsigned int channels,
+				     snd_pcm_uframes_t frames)
+{
+	const snd_pcm_channel_area_t *dst_area, *src_area;
+	unsigned int src_step, dst_step;
+	unsigned int vol_scale;
+
+	if (svol->cur_vol[0] == 0) {
+		snd_pcm_areas_silence(dst_areas, dst_offset, channels, frames,
+				      svol->sformat);
+		return;
+	} else if (svol->zero_dB_val && svol->cur_vol[0] == svol->zero_dB_val) {
+		snd_pcm_areas_copy(dst_areas, dst_offset, src_areas, src_offset,
+				   channels, frames, svol->sformat);
+		return;
+	}
+
+	if (svol->max_val == 1)
+		vol_scale = svol->cur_vol[0] ? 0xffff : 0;
+	else
+		vol_scale = svol->dB_value[svol->cur_vol[0]];
+	switch (svol->sformat) {
+	case SND_PCM_FORMAT_S16_LE:
+	case SND_PCM_FORMAT_S16_BE:
+		/* 16bit samples */
+		CONVERT_AREA(short, 
+			     !snd_pcm_format_cpu_endian(svol->sformat));
+		break;
+	case SND_PCM_FORMAT_S32_LE:
+	case SND_PCM_FORMAT_S32_BE:
+		/* 32bit samples */
+		CONVERT_AREA(int,
+			     !snd_pcm_format_cpu_endian(svol->sformat));
+		break;
+	case SND_PCM_FORMAT_S24_3LE:
+		CONVERT_AREA_S24_3LE();
+		break;
+	default:
+		break;
+	}
+}
+
+/*
+ * get the current volume value from driver
+ *
+ * TODO: mmap support?
+ */
+static void get_current_volume(snd_pcm_softvol_t *svol)
+{
+	unsigned int val;
+	unsigned int i;
+
+	if (snd_ctl_elem_read(svol->ctl, &svol->elem) < 0)
+		return;
+	for (i = 0; i < svol->cchannels; i++) {
+		val = svol->elem.value.integer.value[i];
+		if (val > svol->max_val)
+			val = svol->max_val;
+		svol->cur_vol[i] = val;
+	}
+}
+
+static void softvol_free(snd_pcm_softvol_t *svol)
+{
+	if (svol->plug.gen.close_slave)
+		snd_pcm_close(svol->plug.gen.slave);
+	if (svol->ctl)
+		snd_ctl_close(svol->ctl);
+	if (svol->dB_value && svol->dB_value != preset_dB_value)
+		free(svol->dB_value);
+	free(svol);
+}
+
+static int snd_pcm_softvol_close(snd_pcm_t *pcm)
+{
+	snd_pcm_softvol_t *svol = pcm->private_data;
+	softvol_free(svol);
+	return 0;
+}
+
+static int snd_pcm_softvol_hw_refine_cprepare(snd_pcm_t *pcm,
+					      snd_pcm_hw_params_t *params)
+{
+	int err;
+	snd_pcm_softvol_t *svol = pcm->private_data;
+	snd_pcm_access_mask_t access_mask = { SND_PCM_ACCBIT_SHM };
+	snd_pcm_format_mask_t format_mask = {
+		{
+			(1ULL << SND_PCM_FORMAT_S16_LE) |
+			(1ULL << SND_PCM_FORMAT_S16_BE) |
+			(1ULL << SND_PCM_FORMAT_S32_LE) |
+ 			(1ULL << SND_PCM_FORMAT_S32_BE),
+			(1ULL << (SND_PCM_FORMAT_S24_3LE - 32))
+		}
+	};
+	if (svol->sformat != SND_PCM_FORMAT_UNKNOWN) {
+		snd_pcm_format_mask_none(&format_mask);
+		snd_pcm_format_mask_set(&format_mask, svol->sformat);
+	}
+	err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_ACCESS,
+					 &access_mask);
+	if (err < 0)
+		return err;
+	err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_FORMAT,
+					 &format_mask);
+	if (err < 0)
+		return err;
+	err = _snd_pcm_hw_params_set_subformat(params, SND_PCM_SUBFORMAT_STD);
+	if (err < 0)
+		return err;
+	err = _snd_pcm_hw_param_set_min(params, SND_PCM_HW_PARAM_CHANNELS, 1, 0);
+	if (err < 0)
+		return err;
+	params->info &= ~(SND_PCM_INFO_MMAP | SND_PCM_INFO_MMAP_VALID);
+	return 0;
+}
+
+static int snd_pcm_softvol_hw_refine_sprepare(snd_pcm_t *pcm, snd_pcm_hw_params_t *sparams)
+{
+	snd_pcm_softvol_t *svol = pcm->private_data;
+	snd_pcm_access_mask_t saccess_mask = { SND_PCM_ACCBIT_MMAP };
+	_snd_pcm_hw_params_any(sparams);
+	_snd_pcm_hw_param_set_mask(sparams, SND_PCM_HW_PARAM_ACCESS,
+				   &saccess_mask);
+	if (svol->sformat != SND_PCM_FORMAT_UNKNOWN) {
+		_snd_pcm_hw_params_set_format(sparams, svol->sformat);
+		_snd_pcm_hw_params_set_subformat(sparams, SND_PCM_SUBFORMAT_STD);
+	}
+	return 0;
+}
+
+/*
+ * refine the access mask
+ */
+static int check_access_mask(snd_pcm_hw_params_t *src,
+			     snd_pcm_hw_params_t *dst)
+{
+	const snd_pcm_access_mask_t *mask;
+	snd_pcm_access_mask_t smask;
+
+	mask = snd_pcm_hw_param_get_mask(src, SND_PCM_HW_PARAM_ACCESS);
+	snd_mask_none(&smask);
+	if (snd_pcm_access_mask_test(mask, SND_PCM_ACCESS_RW_INTERLEAVED) ||
+	    snd_pcm_access_mask_test(mask, SND_PCM_ACCESS_MMAP_INTERLEAVED)) {
+		snd_pcm_access_mask_set(&smask,
+					SND_PCM_ACCESS_RW_INTERLEAVED);
+		snd_pcm_access_mask_set(&smask,
+					SND_PCM_ACCESS_MMAP_INTERLEAVED);
+	}
+	if (snd_pcm_access_mask_test(mask, SND_PCM_ACCESS_RW_NONINTERLEAVED) ||
+	    snd_pcm_access_mask_test(mask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED))  {
+		snd_pcm_access_mask_set(&smask,
+					SND_PCM_ACCESS_RW_NONINTERLEAVED);
+		snd_pcm_access_mask_set(&smask,
+					SND_PCM_ACCESS_MMAP_NONINTERLEAVED);
+	}
+	if (snd_pcm_access_mask_test(mask, SND_PCM_ACCESS_MMAP_COMPLEX))
+		snd_pcm_access_mask_set(&smask,
+					SND_PCM_ACCESS_MMAP_COMPLEX);
+
+	return _snd_pcm_hw_param_set_mask(dst, SND_PCM_HW_PARAM_ACCESS, &smask);
+}
+
+static int snd_pcm_softvol_hw_refine_schange(snd_pcm_t *pcm,
+					     snd_pcm_hw_params_t *params,
+					     snd_pcm_hw_params_t *sparams)
+{
+	snd_pcm_softvol_t *svol = pcm->private_data;
+	int err;
+	unsigned int links = (SND_PCM_HW_PARBIT_CHANNELS |
+			      SND_PCM_HW_PARBIT_RATE |
+			      SND_PCM_HW_PARBIT_PERIODS |
+			      SND_PCM_HW_PARBIT_PERIOD_SIZE |
+			      SND_PCM_HW_PARBIT_PERIOD_TIME |
+			      SND_PCM_HW_PARBIT_BUFFER_SIZE |
+			      SND_PCM_HW_PARBIT_BUFFER_TIME |
+			      SND_PCM_HW_PARBIT_TICK_TIME);
+	if (svol->sformat == SND_PCM_FORMAT_UNKNOWN)
+		links |= (SND_PCM_HW_PARBIT_FORMAT | 
+			  SND_PCM_HW_PARBIT_SUBFORMAT |
+			  SND_PCM_HW_PARBIT_SAMPLE_BITS);
+	err = _snd_pcm_hw_params_refine(sparams, links, params);
+	if (err < 0)
+		return err;
+
+	err = check_access_mask(params, sparams);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+	
+static int snd_pcm_softvol_hw_refine_cchange(snd_pcm_t *pcm,
+					     snd_pcm_hw_params_t *params,
+					    snd_pcm_hw_params_t *sparams)
+{
+	snd_pcm_softvol_t *svol = pcm->private_data;
+	int err;
+	unsigned int links = (SND_PCM_HW_PARBIT_CHANNELS |
+			      SND_PCM_HW_PARBIT_RATE |
+			      SND_PCM_HW_PARBIT_PERIODS |
+			      SND_PCM_HW_PARBIT_PERIOD_SIZE |
+			      SND_PCM_HW_PARBIT_PERIOD_TIME |
+			      SND_PCM_HW_PARBIT_BUFFER_SIZE |
+			      SND_PCM_HW_PARBIT_BUFFER_TIME |
+			      SND_PCM_HW_PARBIT_TICK_TIME);
+	if (svol->sformat == SND_PCM_FORMAT_UNKNOWN)
+		links |= (SND_PCM_HW_PARBIT_FORMAT | 
+			  SND_PCM_HW_PARBIT_SUBFORMAT |
+			  SND_PCM_HW_PARBIT_SAMPLE_BITS);
+	err = _snd_pcm_hw_params_refine(params, links, sparams);
+	if (err < 0)
+		return err;
+
+	err = check_access_mask(sparams, params);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static int snd_pcm_softvol_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+	return snd_pcm_hw_refine_slave(pcm, params,
+				       snd_pcm_softvol_hw_refine_cprepare,
+				       snd_pcm_softvol_hw_refine_cchange,
+				       snd_pcm_softvol_hw_refine_sprepare,
+				       snd_pcm_softvol_hw_refine_schange,
+				       snd_pcm_generic_hw_refine);
+}
+
+static int snd_pcm_softvol_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t * params)
+{
+	snd_pcm_softvol_t *svol = pcm->private_data;
+	snd_pcm_t *slave = svol->plug.gen.slave;
+	int err = snd_pcm_hw_params_slave(pcm, params,
+					  snd_pcm_softvol_hw_refine_cchange,
+					  snd_pcm_softvol_hw_refine_sprepare,
+					  snd_pcm_softvol_hw_refine_schange,
+					  snd_pcm_generic_hw_params);
+	if (err < 0)
+		return err;
+	if (slave->format != SND_PCM_FORMAT_S16_LE &&
+	    slave->format != SND_PCM_FORMAT_S16_BE &&
+	    slave->format != SND_PCM_FORMAT_S24_3LE && 
+	    slave->format != SND_PCM_FORMAT_S32_LE &&
+	    slave->format != SND_PCM_FORMAT_S32_BE) {
+		SNDERR("softvol supports only S16_LE, S16_BE, S24_3LE, S32_LE "
+		       " or S32_BE");
+		return -EINVAL;
+	}
+	svol->sformat = slave->format;
+	return 0;
+}
+
+static snd_pcm_uframes_t
+snd_pcm_softvol_write_areas(snd_pcm_t *pcm,
+			    const snd_pcm_channel_area_t *areas,
+			    snd_pcm_uframes_t offset,
+			    snd_pcm_uframes_t size,
+			    const snd_pcm_channel_area_t *slave_areas,
+			    snd_pcm_uframes_t slave_offset,
+			    snd_pcm_uframes_t *slave_sizep)
+{
+	snd_pcm_softvol_t *svol = pcm->private_data;
+	if (size > *slave_sizep)
+		size = *slave_sizep;
+	get_current_volume(svol);
+	if (svol->cchannels == 1)
+		softvol_convert_mono_vol(svol, slave_areas, slave_offset,
+					 areas, offset, pcm->channels, size);
+	else
+		softvol_convert_stereo_vol(svol, slave_areas, slave_offset,
+					   areas, offset, pcm->channels, size);
+	*slave_sizep = size;
+	return size;
+}
+
+static snd_pcm_uframes_t
+snd_pcm_softvol_read_areas(snd_pcm_t *pcm,
+			   const snd_pcm_channel_area_t *areas,
+			   snd_pcm_uframes_t offset,
+			   snd_pcm_uframes_t size,
+			   const snd_pcm_channel_area_t *slave_areas,
+			   snd_pcm_uframes_t slave_offset,
+			   snd_pcm_uframes_t *slave_sizep)
+{
+	snd_pcm_softvol_t *svol = pcm->private_data;
+	if (size > *slave_sizep)
+		size = *slave_sizep;
+	get_current_volume(svol);
+	if (svol->cchannels == 1)
+		softvol_convert_mono_vol(svol, areas, offset, slave_areas,
+					 slave_offset, pcm->channels, size);
+	else
+		softvol_convert_stereo_vol(svol, areas, offset, slave_areas,
+					   slave_offset, pcm->channels, size);
+	*slave_sizep = size;
+	return size;
+}
+
+static void snd_pcm_softvol_dump(snd_pcm_t *pcm, snd_output_t *out)
+{
+	snd_pcm_softvol_t *svol = pcm->private_data;
+	snd_output_printf(out, "Soft volume PCM\n");
+	snd_output_printf(out, "Control: %s\n", svol->elem.id.name);
+	if (svol->max_val == 1)
+		snd_output_printf(out, "boolean\n");
+	else {
+		snd_output_printf(out, "min_dB: %g\n", svol->min_dB);
+		snd_output_printf(out, "max_dB: %g\n", svol->max_dB);
+		snd_output_printf(out, "resolution: %d\n", svol->max_val + 1);
+	}
+	if (pcm->setup) {
+		snd_output_printf(out, "Its setup is:\n");
+		snd_pcm_dump_setup(pcm, out);
+	}
+	snd_output_printf(out, "Slave: ");
+	snd_pcm_dump(svol->plug.gen.slave, out);
+}
+
+static int add_tlv_info(snd_pcm_softvol_t *svol, snd_ctl_elem_info_t *cinfo)
+{
+	unsigned int tlv[4];
+	tlv[0] = SND_CTL_TLVT_DB_SCALE;
+	tlv[1] = 2 * sizeof(int);
+	tlv[2] = svol->min_dB * 100;
+	tlv[3] = (svol->max_dB - svol->min_dB) * 100 / svol->max_val;
+	return snd_ctl_elem_tlv_write(svol->ctl, &cinfo->id, tlv);
+}
+
+static int add_user_ctl(snd_pcm_softvol_t *svol, snd_ctl_elem_info_t *cinfo, int count)
+{
+	int err;
+	int i;
+	unsigned int def_val;
+	
+	if (svol->max_val == 1)
+		err = snd_ctl_elem_add_boolean(svol->ctl, &cinfo->id, count);
+	else
+		err = snd_ctl_elem_add_integer(svol->ctl, &cinfo->id, count,
+					       0, svol->max_val, 0);
+	if (err < 0)
+		return err;
+	if (svol->max_val == 1)
+		def_val = 1;
+	else {
+		add_tlv_info(svol, cinfo);
+		/* set zero dB value as default, or max_val if
+		   there is no 0 dB setting */
+		def_val = svol->zero_dB_val ? svol->zero_dB_val : svol->max_val;
+	}
+	for (i = 0; i < count; i++)
+		svol->elem.value.integer.value[i] = def_val;
+	return snd_ctl_elem_write(svol->ctl, &svol->elem);
+}
+
+/*
+ * load and set up user-control
+ * returns 0 if the user-control is found or created,
+ * returns 1 if the control is a hw control,
+ * or a negative error code
+ */
+static int softvol_load_control(snd_pcm_t *pcm, snd_pcm_softvol_t *svol,
+				int ctl_card, snd_ctl_elem_id_t *ctl_id,
+				int cchannels, double min_dB, double max_dB,
+				int resolution)
+{
+	char tmp_name[32];
+	snd_pcm_info_t *info;
+	snd_ctl_elem_info_t *cinfo;
+	int err;
+	unsigned int i;
+
+	if (ctl_card < 0) {
+		snd_pcm_info_alloca(&info);
+		err = snd_pcm_info(pcm, info);
+		if (err < 0)
+			return err;
+		ctl_card = snd_pcm_info_get_card(info);
+		if (ctl_card < 0) {
+			SNDERR("No card defined for softvol control");
+			return -EINVAL;
+		}
+	}
+	sprintf(tmp_name, "hw:%d", ctl_card);
+	err = snd_ctl_open(&svol->ctl, tmp_name, 0);
+	if (err < 0) {
+		SNDERR("Cannot open CTL %s", tmp_name);
+		return err;
+	}
+
+	svol->elem.id = *ctl_id;
+	svol->max_val = resolution - 1;
+	svol->min_dB = min_dB;
+	svol->max_dB = max_dB;
+	if (svol->max_val == 1 || svol->max_dB == ZERO_DB)
+		svol->zero_dB_val = svol->max_val;
+	else if (svol->max_dB < 0)
+		svol->zero_dB_val = 0; /* there is no 0 dB setting */
+	else
+		svol->zero_dB_val = (min_dB / (min_dB - max_dB)) * svol->max_val;
+		
+	snd_ctl_elem_info_alloca(&cinfo);
+	snd_ctl_elem_info_set_id(cinfo, ctl_id);
+	if ((err = snd_ctl_elem_info(svol->ctl, cinfo)) < 0) {
+		if (err != -ENOENT) {
+			SNDERR("Cannot get info for CTL %s", tmp_name);
+			return err;
+		}
+		err = add_user_ctl(svol, cinfo, cchannels);
+		if (err < 0) {
+			SNDERR("Cannot add a control");
+			return err;
+		}
+	} else {
+		if (! (cinfo->access & SNDRV_CTL_ELEM_ACCESS_USER)) {
+			/* hardware control exists */
+			return 1; /* notify */
+
+		} else if ((cinfo->type != SND_CTL_ELEM_TYPE_INTEGER &&
+			    cinfo->type != SND_CTL_ELEM_TYPE_BOOLEAN) ||
+			   cinfo->count != (unsigned int)cchannels ||
+			   cinfo->value.integer.min != 0 ||
+			   cinfo->value.integer.max != resolution - 1) {
+			if ((err = snd_ctl_elem_remove(svol->ctl, &cinfo->id)) < 0) {
+				SNDERR("Control %s mismatch", tmp_name);
+				return err;
+			}
+			snd_ctl_elem_info_set_id(cinfo, ctl_id); /* reset numid */
+			if ((err = add_user_ctl(svol, cinfo, cchannels)) < 0) {
+				SNDERR("Cannot add a control");
+				return err;
+			}
+		} else if (svol->max_val > 1) {
+			/* check TLV availability */
+			unsigned int tlv[4];
+			err = snd_ctl_elem_tlv_read(svol->ctl, &cinfo->id, tlv, sizeof(tlv));
+			if (err < 0)
+				add_tlv_info(svol, cinfo);
+		}
+	}
+
+	if (svol->max_val == 1)
+		return 0;
+
+	/* set up dB table */
+	if (min_dB == PRESET_MIN_DB && max_dB == ZERO_DB && resolution == PRESET_RESOLUTION)
+		svol->dB_value = (unsigned int*)preset_dB_value;
+	else {
+#ifndef HAVE_SOFT_FLOAT
+		svol->dB_value = calloc(resolution, sizeof(unsigned int));
+		if (! svol->dB_value) {
+			SNDERR("cannot allocate dB table");
+			return -ENOMEM;
+		}
+		svol->min_dB = min_dB;
+		svol->max_dB = max_dB;
+		for (i = 0; i <= svol->max_val; i++) {
+			double db = svol->min_dB + (i * (svol->max_dB - svol->min_dB)) / svol->max_val;
+			double v = (pow(10.0, db / 20.0) * (double)(1 << VOL_SCALE_SHIFT));
+			svol->dB_value[i] = (unsigned int)v;
+		}
+		if (svol->zero_dB_val)
+			svol->dB_value[svol->zero_dB_val] = 65535;
+#else
+		SNDERR("Cannot handle the given dB range and resolution");
+		return -EINVAL;
+#endif
+	}
+	return 0;
+}
+
+static const snd_pcm_ops_t snd_pcm_softvol_ops = {
+	.close = snd_pcm_softvol_close,
+	.info = snd_pcm_generic_info,
+	.hw_refine = snd_pcm_softvol_hw_refine,
+	.hw_params = snd_pcm_softvol_hw_params,
+	.hw_free = snd_pcm_generic_hw_free,
+	.sw_params = snd_pcm_generic_sw_params,
+	.channel_info = snd_pcm_generic_channel_info,
+	.dump = snd_pcm_softvol_dump,
+	.nonblock = snd_pcm_generic_nonblock,
+	.async = snd_pcm_generic_async,
+	.mmap = snd_pcm_generic_mmap,
+	.munmap = snd_pcm_generic_munmap,
+};
+
+/**
+ * \brief Creates a new SoftVolume PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param sformat Slave format
+ * \param ctl_card card index of the control
+ * \param ctl_id The control element
+ * \param cchannels PCM channels
+ * \param min_dB minimal dB value
+ * \param max_dB maximal dB value
+ * \param resolution resolution of control
+ * \param slave Slave PCM handle
+ * \param close_slave When set, the slave PCM handle is closed with copy PCM
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int snd_pcm_softvol_open(snd_pcm_t **pcmp, const char *name,
+			 snd_pcm_format_t sformat,
+			 int ctl_card, snd_ctl_elem_id_t *ctl_id,
+			 int cchannels,
+			 double min_dB, double max_dB, int resolution,
+			 snd_pcm_t *slave, int close_slave)
+{
+	snd_pcm_t *pcm;
+	snd_pcm_softvol_t *svol;
+	int err;
+	assert(pcmp && slave);
+	if (sformat != SND_PCM_FORMAT_UNKNOWN &&
+	    sformat != SND_PCM_FORMAT_S16_LE &&
+	    sformat != SND_PCM_FORMAT_S16_BE &&
+	    sformat != SND_PCM_FORMAT_S24_3LE && 
+	    sformat != SND_PCM_FORMAT_S32_LE &&
+	    sformat != SND_PCM_FORMAT_S32_BE)
+		return -EINVAL;
+	svol = calloc(1, sizeof(*svol));
+	if (! svol)
+		return -ENOMEM;
+	err = softvol_load_control(slave, svol, ctl_card, ctl_id, cchannels,
+				   min_dB, max_dB, resolution);
+	if (err < 0) {
+		softvol_free(svol);
+		return err;
+	}
+	if (err > 0) { /* hardware control - no need for softvol! */
+		softvol_free(svol);
+		*pcmp = slave; /* just pass the slave */
+		if (!slave->name && name)
+			slave->name = strdup(name);
+		return 0;
+	}
+
+	/* do softvol */
+	snd_pcm_plugin_init(&svol->plug);
+	svol->sformat = sformat;
+	svol->cchannels = cchannels;
+	svol->plug.read = snd_pcm_softvol_read_areas;
+	svol->plug.write = snd_pcm_softvol_write_areas;
+	svol->plug.undo_read = snd_pcm_plugin_undo_read_generic;
+	svol->plug.undo_write = snd_pcm_plugin_undo_write_generic;
+	svol->plug.gen.slave = slave;
+	svol->plug.gen.close_slave = close_slave;
+
+	err = snd_pcm_new(&pcm, SND_PCM_TYPE_SOFTVOL, name, slave->stream, slave->mode);
+	if (err < 0) {
+		softvol_free(svol);
+		return err;
+	}
+	pcm->ops = &snd_pcm_softvol_ops;
+	pcm->fast_ops = &snd_pcm_plugin_fast_ops;
+	pcm->private_data = svol;
+	pcm->poll_fd = slave->poll_fd;
+	pcm->poll_events = slave->poll_events;
+	/*
+	 * Since the softvol converts on the place, and the format/channels
+	 * must be identical between source and destination, we don't need
+	 * an extra buffer.
+	 */
+	pcm->mmap_shadow = 1;
+	pcm->monotonic = slave->monotonic;
+	snd_pcm_set_hw_ptr(pcm, &svol->plug.hw_ptr, -1, 0);
+	snd_pcm_set_appl_ptr(pcm, &svol->plug.appl_ptr, -1, 0);
+	*pcmp = pcm;
+
+	return 0;
+}
+
+/* in pcm_misc.c */
+int snd_pcm_parse_control_id(snd_config_t *conf, snd_ctl_elem_id_t *ctl_id, int *cardp,
+			     int *cchannelsp, int *hwctlp);
+
+/*! \page pcm_plugins
+
+\section pcm_plugins_softvol Plugin: Soft Volume
+
+This plugin applies the software volume attenuation.
+The format, rate and channels must match for both of source and destination.
+
+When the control is stereo (count=2), the channels are assumed to be either
+mono, 2.0, 2.1, 4.0, 4.1, 5.1 or 7.1.
+
+If the control already exists and it's a system control (i.e. no
+user-defined control), the plugin simply passes its slave without
+any changes.
+
+\code
+pcm.name {
+        type softvol            # Soft Volume conversion PCM
+        slave STR               # Slave name
+        # or
+        slave {                 # Slave definition
+                pcm STR         # Slave PCM name
+                # or
+                pcm { }         # Slave PCM definition
+                [format STR]    # Slave format
+        }
+        control {
+	        name STR        # control element id string
+		[card STR]      # control card index
+		[iface STR]     # interface of the element
+		[index INT]     # index of the element
+		[device INT]    # device number of the element
+		[subdevice INT] # subdevice number of the element
+		[count INT]     # control channels 1 or 2 (default: 2)
+	}
+	[min_dB REAL]           # minimal dB value (default: -51.0)
+	[max_dB REAL]           # maximal dB value (default:   0.0)
+	[resolution INT]        # resolution (default: 256)
+				# resolution = 2 means a mute switch
+}
+\endcode
+
+\subsection pcm_plugins_softvol_funcref Function reference
+
+<UL>
+  <LI>snd_pcm_softvol_open()
+  <LI>_snd_pcm_softvol_open()
+</UL>
+
+*/
+
+/**
+ * \brief Creates a new Soft Volume PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param root Root configuration node
+ * \param conf Configuration node with Soft Volume PCM description
+ * \param stream Stream type
+ * \param mode Stream mode
+ * \retval zero on success otherwise a negative error code
+ * \warning Using of this function might be dangerous in the sense
+ *          of compatibility reasons. The prototype might be freely
+ *          changed in future.
+ */
+int _snd_pcm_softvol_open(snd_pcm_t **pcmp, const char *name,
+			  snd_config_t *root, snd_config_t *conf, 
+			  snd_pcm_stream_t stream, int mode)
+{
+	snd_config_iterator_t i, next;
+	int err;
+	snd_pcm_t *spcm;
+	snd_config_t *slave = NULL, *sconf;
+	snd_config_t *control = NULL;
+	snd_pcm_format_t sformat = SND_PCM_FORMAT_UNKNOWN;
+	snd_ctl_elem_id_t *ctl_id;
+	int resolution = PRESET_RESOLUTION;
+	double min_dB = PRESET_MIN_DB;
+	double max_dB = ZERO_DB;
+	int card = -1, cchannels = 2;
+
+	snd_config_for_each(i, next, conf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (snd_pcm_conf_generic_id(id))
+			continue;
+		if (strcmp(id, "slave") == 0) {
+			slave = n;
+			continue;
+		}
+		if (strcmp(id, "control") == 0) {
+			control = n;
+			continue;
+		}
+		if (strcmp(id, "resolution") == 0) {
+			long v;
+			err = snd_config_get_integer(n, &v);
+			if (err < 0) {
+				SNDERR("Invalid resolution value");
+				return err;
+			}
+			resolution = v;
+			continue;
+		}
+		if (strcmp(id, "min_dB") == 0) {
+			err = snd_config_get_real(n, &min_dB);
+			if (err < 0) {
+				SNDERR("Invalid min_dB value");
+				return err;
+			}
+			continue;
+		}
+		if (strcmp(id, "max_dB") == 0) {
+			err = snd_config_get_real(n, &max_dB);
+			if (err < 0) {
+				SNDERR("Invalid max_dB value");
+				return err;
+			}
+			continue;
+		}
+		SNDERR("Unknown field %s", id);
+		return -EINVAL;
+	}
+	if (!slave) {
+		SNDERR("slave is not defined");
+		return -EINVAL;
+	}
+	if (!control) {
+		SNDERR("control is not defined");
+		return -EINVAL;
+	}
+	if (min_dB >= 0) {
+		SNDERR("min_dB must be a negative value");
+		return -EINVAL;
+	}
+	if (max_dB <= min_dB || max_dB > MAX_DB_UPPER_LIMIT) {
+		SNDERR("max_dB must be larger than min_dB and less than %d dB",
+		       MAX_DB_UPPER_LIMIT);
+		return -EINVAL;
+	}
+	if (resolution <= 1 || resolution > 1024) {
+		SNDERR("Invalid resolution value %d", resolution);
+		return -EINVAL;
+	}
+	if (mode & SND_PCM_NO_SOFTVOL) {
+		err = snd_pcm_slave_conf(root, slave, &sconf, 0);
+		if (err < 0)
+			return err;
+		err = snd_pcm_open_named_slave(pcmp, name, root, sconf, stream,
+					       mode, conf);
+		snd_config_delete(sconf);
+	} else {
+		snd_ctl_elem_id_alloca(&ctl_id);
+		err = snd_pcm_slave_conf(root, slave, &sconf, 1,
+					 SND_PCM_HW_PARAM_FORMAT, 0, &sformat);
+		if (err < 0)
+			return err;
+		if (sformat != SND_PCM_FORMAT_UNKNOWN &&
+		    sformat != SND_PCM_FORMAT_S16_LE &&
+		    sformat != SND_PCM_FORMAT_S16_BE &&
+		    sformat != SND_PCM_FORMAT_S24_3LE && 
+		    sformat != SND_PCM_FORMAT_S32_LE &&
+		    sformat != SND_PCM_FORMAT_S32_BE) {
+			SNDERR("only S16_LE, S16_BE, S24_3LE, S32_LE or S32_BE format "
+			       "is supported");
+			snd_config_delete(sconf);
+			return -EINVAL;
+		}
+		err = snd_pcm_open_slave(&spcm, root, sconf, stream, mode, conf);
+		snd_config_delete(sconf);
+		if (err < 0)
+			return err;
+		if ((err = snd_pcm_parse_control_id(control, ctl_id, &card, &cchannels, NULL)) < 0) {
+			snd_pcm_close(spcm);
+			return err;
+		}
+		err = snd_pcm_softvol_open(pcmp, name, sformat, card, ctl_id, cchannels,
+					   min_dB, max_dB, resolution, spcm, 1);
+		if (err < 0)
+			snd_pcm_close(spcm);
+	}
+	return err;
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(_snd_pcm_softvol_open, SND_PCM_DLSYM_VERSION);
+#endif
diff --git a/src/pcm/pcm_symbols.c b/src/pcm/pcm_symbols.c
new file mode 100644
index 0000000..91982df
--- /dev/null
+++ b/src/pcm/pcm_symbols.c
@@ -0,0 +1,64 @@
+/*
+ *  PCM Symbols
+ *  Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#ifndef PIC
+
+#include "config.h"
+
+extern const char *_snd_module_pcm_adpcm;
+extern const char *_snd_module_pcm_alaw;
+extern const char *_snd_module_pcm_copy;
+extern const char *_snd_module_pcm_file;
+extern const char *_snd_module_pcm_hooks;
+extern const char *_snd_module_pcm_hw;
+extern const char *_snd_module_pcm_linear;
+extern const char *_snd_module_pcm_meter;
+extern const char *_snd_module_pcm_mulaw;
+extern const char *_snd_module_pcm_multi;
+extern const char *_snd_module_pcm_null;
+extern const char *_snd_module_pcm_empty;
+extern const char *_snd_module_pcm_plug;
+extern const char *_snd_module_pcm_rate;
+extern const char *_snd_module_pcm_route;
+extern const char *_snd_module_pcm_share;
+extern const char *_snd_module_pcm_shm;
+extern const char *_snd_module_pcm_lfloat;
+extern const char *_snd_module_pcm_ladspa;
+extern const char *_snd_module_pcm_dmix;
+extern const char *_snd_module_pcm_dsnoop;
+extern const char *_snd_module_pcm_dshare;
+extern const char *_snd_module_pcm_asym;
+extern const char *_snd_module_pcm_iec958;
+extern const char *_snd_module_pcm_softvol;
+extern const char *_snd_module_pcm_extplug;
+extern const char *_snd_module_pcm_ioplug;
+extern const char *_snd_module_pcm_mmap_emul;
+
+static const char **snd_pcm_open_objects[] = {
+	&_snd_module_pcm_hw,
+#include "pcm_symbols_list.c"
+};
+	
+void *snd_pcm_open_symbols(void)
+{
+	return snd_pcm_open_objects;
+}
+
+#endif /* !PIC */
diff --git a/src/pcm/plugin_ops.h b/src/pcm/plugin_ops.h
new file mode 100644
index 0000000..21535c9
--- /dev/null
+++ b/src/pcm/plugin_ops.h
@@ -0,0 +1,1106 @@
+/*
+ *  Plugin sample operators with fast switch
+ *  Copyright (c) 2000 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#ifndef SX_INLINES
+#define SX_INLINES
+static inline u_int32_t sx24(u_int32_t x)
+{
+	if(x&0x00800000)
+		return x|0xFF000000;
+	return x&0x00FFFFFF;
+}
+static inline u_int32_t sx24s(u_int32_t x)
+{
+	if(x&0x00008000)
+		return x|0x000000FF;
+	return x&0xFFFFFF00;
+}
+#endif
+
+#define as_u8(ptr) (*(u_int8_t*)(ptr))
+#define as_u16(ptr) (*(u_int16_t*)(ptr))
+#define as_u32(ptr) (*(u_int32_t*)(ptr))
+#define as_u64(ptr) (*(u_int64_t*)(ptr))
+#define as_s8(ptr) (*(int8_t*)(ptr))
+#define as_s16(ptr) (*(int16_t*)(ptr))
+#define as_s32(ptr) (*(int32_t*)(ptr))
+#define as_s64(ptr) (*(int64_t*)(ptr))
+#define as_float(ptr) (*(float_t*)(ptr))
+#define as_double(ptr) (*(double_t*)(ptr))
+
+#define as_u8c(ptr) (*(const u_int8_t*)(ptr))
+#define as_u16c(ptr) (*(const u_int16_t*)(ptr))
+#define as_u32c(ptr) (*(const u_int32_t*)(ptr))
+#define as_u64c(ptr) (*(const u_int64_t*)(ptr))
+#define as_s8c(ptr) (*(const int8_t*)(ptr))
+#define as_s16c(ptr) (*(const int16_t*)(ptr))
+#define as_s32c(ptr) (*(const int32_t*)(ptr))
+#define as_s64c(ptr) (*(const int64_t*)(ptr))
+#define as_floatc(ptr) (*(const float_t*)(ptr))
+#define as_doublec(ptr) (*(const double_t*)(ptr))
+
+#define _get_triple_le(ptr) (*(u_int8_t*)(ptr) | (u_int32_t)*((u_int8_t*)(ptr) + 1) << 8 | (u_int32_t)*((u_int8_t*)(ptr) + 2) << 16)
+#define _get_triple_be(ptr) ((u_int32_t)*(u_int8_t*)(ptr) << 16 | (u_int32_t)*((u_int8_t*)(ptr) + 1) << 8 | *((u_int8_t*)(ptr) + 2))
+#define _put_triple_le(ptr,val) do { \
+	u_int8_t *_tmp = (u_int8_t *)(ptr); \
+	u_int32_t _val = (val); \
+	_tmp[0] = _val; \
+	_tmp[1] = _val >> 8; \
+	_tmp[2] = _val >> 16; \
+} while(0)
+#define _put_triple_be(ptr,val) do { \
+	u_int8_t *_tmp = (u_int8_t *)(ptr); \
+	u_int32_t _val = (val); \
+	_tmp[0] = _val >> 16; \
+	_tmp[1] = _val >> 8; \
+	_tmp[2] = _val; \
+} while(0)
+
+#ifdef SNDRV_LITTLE_ENDIAN
+#define _get_triple(ptr) _get_triple_le(ptr)
+#define _get_triple_s(ptr) _get_triple_be(ptr)
+#define _put_triple(ptr,val) _put_triple_le(ptr,val)
+#define _put_triple_s(ptr,val) _put_triple_be(ptr,val)
+#else
+#define _get_triple(ptr) _get_triple_be(ptr)
+#define _get_triple_s(ptr) _get_triple_le(ptr)
+#define _put_triple(ptr,val) _put_triple_be(ptr,val)
+#define _put_triple_s(ptr,val) _put_triple_le(ptr,val)
+#endif
+
+#ifdef COPY_LABELS
+static void *copy_labels[5] = {
+	&&copy_8,
+	&&copy_16,
+	&&copy_24
+	&&copy_32,
+	&&copy_64
+};
+#endif
+
+#ifdef COPY_END
+while(0) {
+copy_8: as_s8(dst) = as_s8c(src); goto COPY_END;
+copy_16: as_s16(dst) = as_s16c(src); goto COPY_END;
+copy_24: memcpy(dst,src,3); goto COPY_END;
+copy_32: as_s32(dst) = as_s32c(src); goto COPY_END;
+copy_64: as_s64(dst) = as_s64c(src); goto COPY_END;
+}
+#endif
+
+#ifdef CONV_LABELS
+/* src_wid src_endswap sign_toggle dst_wid dst_endswap */
+static void *const conv_labels[4 * 2 * 2 * 4 * 2] = {
+	&&conv_xxx1_xxx1,	 /*  8h ->  8h */
+	&&conv_xxx1_xxx1,	 /*  8h ->  8s */
+	&&conv_xxx1_xx10,	 /*  8h -> 16h */
+	&&conv_xxx1_xx01,	 /*  8h -> 16s */
+	&&conv_xxx1_x100,	 /*  8h -> 24h */
+	&&conv_xxx1_001x,	 /*  8h -> 24s */
+	&&conv_xxx1_1000,	 /*  8h -> 32h */
+	&&conv_xxx1_0001,	 /*  8h -> 32s */
+	&&conv_xxx1_xxx9,	 /*  8h ^>  8h */
+	&&conv_xxx1_xxx9,	 /*  8h ^>  8s */
+	&&conv_xxx1_xx90,	 /*  8h ^> 16h */
+	&&conv_xxx1_xx09,	 /*  8h ^> 16s */
+	&&conv_xxx1_x900,	 /*  8h ^> 24h */
+	&&conv_xxx1_009x,	 /*  8h ^> 24s */
+	&&conv_xxx1_9000,	 /*  8h ^> 32h */
+	&&conv_xxx1_0009,	 /*  8h ^> 32s */
+	&&conv_xxx1_xxx1,	 /*  8s ->  8h */
+	&&conv_xxx1_xxx1,	 /*  8s ->  8s */
+	&&conv_xxx1_xx10,	 /*  8s -> 16h */
+	&&conv_xxx1_xx01,	 /*  8s -> 16s */
+	&&conv_xxx1_x100,	 /*  8s -> 24h */
+	&&conv_xxx1_001x,	 /*  8s -> 24s */
+	&&conv_xxx1_1000,	 /*  8s -> 32h */
+	&&conv_xxx1_0001,	 /*  8s -> 32s */
+	&&conv_xxx1_xxx9,	 /*  8s ^>  8h */
+	&&conv_xxx1_xxx9,	 /*  8s ^>  8s */
+	&&conv_xxx1_xx90,	 /*  8s ^> 16h */
+	&&conv_xxx1_xx09,	 /*  8s ^> 16s */
+	&&conv_xxx1_x900,	 /*  8s ^> 24h */
+	&&conv_xxx1_009x,	 /*  8s ^> 24s */
+	&&conv_xxx1_9000,	 /*  8s ^> 32h */
+	&&conv_xxx1_0009,	 /*  8s ^> 32s */
+	&&conv_xx12_xxx1,	 /* 16h ->  8h */
+	&&conv_xx12_xxx1,	 /* 16h ->  8s */
+	&&conv_xx12_xx12,	 /* 16h -> 16h */
+	&&conv_xx12_xx21,	 /* 16h -> 16s */
+	&&conv_xx12_x120,	 /* 16h -> 24h */
+	&&conv_xx12_021x,	 /* 16h -> 24s */
+	&&conv_xx12_1200,	 /* 16h -> 32h */
+	&&conv_xx12_0021,	 /* 16h -> 32s */
+	&&conv_xx12_xxx9,	 /* 16h ^>  8h */
+	&&conv_xx12_xxx9,	 /* 16h ^>  8s */
+	&&conv_xx12_xx92,	 /* 16h ^> 16h */
+	&&conv_xx12_xx29,	 /* 16h ^> 16s */
+	&&conv_xx12_x920,	 /* 16h ^> 24h */
+	&&conv_xx12_029x,	 /* 16h ^> 24s */
+	&&conv_xx12_9200,	 /* 16h ^> 32h */
+	&&conv_xx12_0029,	 /* 16h ^> 32s */
+	&&conv_xx12_xxx2,	 /* 16s ->  8h */
+	&&conv_xx12_xxx2,	 /* 16s ->  8s */
+	&&conv_xx12_xx21,	 /* 16s -> 16h */
+	&&conv_xx12_xx12,	 /* 16s -> 16s */
+	&&conv_xx12_x210,	 /* 16s -> 24h */
+	&&conv_xx12_012x,	 /* 16s -> 24s */
+	&&conv_xx12_2100,	 /* 16s -> 32h */
+	&&conv_xx12_0012,	 /* 16s -> 32s */
+	&&conv_xx12_xxxA,	 /* 16s ^>  8h */
+	&&conv_xx12_xxxA,	 /* 16s ^>  8s */
+	&&conv_xx12_xxA1,	 /* 16s ^> 16h */
+	&&conv_xx12_xx1A,	 /* 16s ^> 16s */
+	&&conv_xx12_xA10,	 /* 16s ^> 24h */
+	&&conv_xx12_01Ax,	 /* 16s ^> 24s */
+	&&conv_xx12_A100,	 /* 16s ^> 32h */
+	&&conv_xx12_001A,	 /* 16s ^> 32s */
+	&&conv_x123_xxx1,	 /* 24h ->  8h */
+	&&conv_x123_xxx1,	 /* 24h ->  8s */
+	&&conv_x123_xx12,	 /* 24h -> 16h */
+	&&conv_x123_xx21,	 /* 24h -> 16s */
+	&&conv_x123_x123,	 /* 24h -> 24h */
+	&&conv_x123_321x,	 /* 24h -> 24s */
+	&&conv_x123_1230,	 /* 24h -> 32h */
+	&&conv_x123_0321,	 /* 24h -> 32s */
+	&&conv_x123_xxx9,	 /* 24h ^>  8h */
+	&&conv_x123_xxx9,	 /* 24h ^>  8s */
+	&&conv_x123_xx92,	 /* 24h ^> 16h */
+	&&conv_x123_xx29,	 /* 24h ^> 16s */
+	&&conv_x123_x923,	 /* 24h ^> 24h */
+	&&conv_x123_329x,	 /* 24h ^> 24s */
+	&&conv_x123_9230,	 /* 24h ^> 32h */
+	&&conv_x123_0329,	 /* 24h ^> 32s */
+	&&conv_123x_xxx3,	 /* 24s ->  8h */
+	&&conv_123x_xxx3,	 /* 24s ->  8s */
+	&&conv_123x_xx32,	 /* 24s -> 16h */
+	&&conv_123x_xx23,	 /* 24s -> 16s */
+	&&conv_123x_x321,	 /* 24s -> 24h */
+	&&conv_123x_123x,	 /* 24s -> 24s */
+	&&conv_123x_3210,	 /* 24s -> 32h */
+	&&conv_123x_0123,	 /* 24s -> 32s */
+	&&conv_123x_xxxB,	 /* 24s ^>  8h */
+	&&conv_123x_xxxB,	 /* 24s ^>  8s */
+	&&conv_123x_xxB2,	 /* 24s ^> 16h */
+	&&conv_123x_xx2B,	 /* 24s ^> 16s */
+	&&conv_123x_xB21,	 /* 24s ^> 24h */
+	&&conv_123x_12Bx,	 /* 24s ^> 24s */
+	&&conv_123x_B210,	 /* 24s ^> 32h */
+	&&conv_123x_012B,	 /* 24s ^> 32s */
+	&&conv_1234_xxx1,	 /* 32h ->  8h */
+	&&conv_1234_xxx1,	 /* 32h ->  8s */
+	&&conv_1234_xx12,	 /* 32h -> 16h */
+	&&conv_1234_xx21,	 /* 32h -> 16s */
+	&&conv_1234_x123,	 /* 32h -> 24h */
+	&&conv_1234_321x,	 /* 32h -> 24s */
+	&&conv_1234_1234,	 /* 32h -> 32h */
+	&&conv_1234_4321,	 /* 32h -> 32s */
+	&&conv_1234_xxx9,	 /* 32h ^>  8h */
+	&&conv_1234_xxx9,	 /* 32h ^>  8s */
+	&&conv_1234_xx92,	 /* 32h ^> 16h */
+	&&conv_1234_xx29,	 /* 32h ^> 16s */
+	&&conv_1234_x923,	 /* 32h ^> 24h */
+	&&conv_1234_329x,	 /* 32h ^> 24s */
+	&&conv_1234_9234,	 /* 32h ^> 32h */
+	&&conv_1234_4329,	 /* 32h ^> 32s */
+	&&conv_1234_xxx4,	 /* 32s ->  8h */
+	&&conv_1234_xxx4,	 /* 32s ->  8s */
+	&&conv_1234_xx43,	 /* 32s -> 16h */
+	&&conv_1234_xx34,	 /* 32s -> 16s */
+	&&conv_1234_x432,	 /* 32s -> 24h */
+	&&conv_1234_234x,	 /* 32s -> 24s */
+	&&conv_1234_4321,	 /* 32s -> 32h */
+	&&conv_1234_1234,	 /* 32s -> 32s */
+	&&conv_1234_xxxC,	 /* 32s ^>  8h */
+	&&conv_1234_xxxC,	 /* 32s ^>  8s */
+	&&conv_1234_xxC3,	 /* 32s ^> 16h */
+	&&conv_1234_xx3C,	 /* 32s ^> 16s */
+	&&conv_1234_xC32,	 /* 32s ^> 24h */
+	&&conv_1234_23Cx,	 /* 32s ^> 24s */
+	&&conv_1234_C321,	 /* 32s ^> 32h */
+	&&conv_1234_123C,	 /* 32s ^> 32s */
+};
+#endif
+
+#ifdef CONV_END
+while(0) {
+conv_xxx1_xxx1: as_u8(dst) = as_u8c(src); goto CONV_END;
+conv_xxx1_xx10: as_u16(dst) = (u_int16_t)as_u8c(src) << 8; goto CONV_END;
+conv_xxx1_xx01: as_u16(dst) = (u_int16_t)as_u8c(src); goto CONV_END;
+conv_xxx1_x100: as_u32(dst) = sx24((u_int32_t)as_u8c(src) << 16); goto CONV_END;
+conv_xxx1_001x: as_u32(dst) = sx24s((u_int32_t)as_u8c(src) << 8); goto CONV_END;
+conv_xxx1_1000: as_u32(dst) = (u_int32_t)as_u8c(src) << 24; goto CONV_END;
+conv_xxx1_0001: as_u32(dst) = (u_int32_t)as_u8c(src); goto CONV_END;
+conv_xxx1_xxx9: as_u8(dst) = as_u8c(src) ^ 0x80; goto CONV_END;
+conv_xxx1_xx90: as_u16(dst) = (u_int16_t)(as_u8c(src) ^ 0x80) << 8; goto CONV_END;
+conv_xxx1_xx09: as_u16(dst) = (u_int16_t)(as_u8c(src) ^ 0x80); goto CONV_END;
+conv_xxx1_x900: as_u32(dst) = sx24((u_int32_t)(as_u8c(src) ^ 0x80) << 16); goto CONV_END;
+conv_xxx1_009x: as_u32(dst) = sx24s((u_int32_t)(as_u8c(src) ^ 0x80) << 8); goto CONV_END;
+conv_xxx1_9000: as_u32(dst) = (u_int32_t)(as_u8c(src) ^ 0x80) << 24; goto CONV_END;
+conv_xxx1_0009: as_u32(dst) = (u_int32_t)(as_u8c(src) ^ 0x80); goto CONV_END;
+conv_xx12_xxx1: as_u8(dst) = as_u16c(src) >> 8; goto CONV_END;
+conv_xx12_xx12: as_u16(dst) = as_u16c(src); goto CONV_END;
+conv_xx12_xx21: as_u16(dst) = bswap_16(as_u16c(src)); goto CONV_END;
+conv_xx12_x120: as_u32(dst) = sx24((u_int32_t)as_u16c(src) << 8); goto CONV_END;
+conv_xx12_021x: as_u32(dst) = sx24s((u_int32_t)bswap_16(as_u16c(src)) << 8); goto CONV_END;
+conv_xx12_1200: as_u32(dst) = (u_int32_t)as_u16c(src) << 16; goto CONV_END;
+conv_xx12_0021: as_u32(dst) = (u_int32_t)bswap_16(as_u16c(src)); goto CONV_END;
+conv_xx12_xxx9: as_u8(dst) = (as_u16c(src) >> 8) ^ 0x80; goto CONV_END;
+conv_xx12_xx92: as_u16(dst) = as_u16c(src) ^ 0x8000; goto CONV_END;
+conv_xx12_xx29: as_u16(dst) = bswap_16(as_u16c(src)) ^ 0x80; goto CONV_END;
+conv_xx12_x920: as_u32(dst) = sx24((u_int32_t)(as_u16c(src) ^ 0x8000) << 8); goto CONV_END;
+conv_xx12_029x: as_u32(dst) = sx24s((u_int32_t)(bswap_16(as_u16c(src)) ^ 0x80) << 8); goto CONV_END;
+conv_xx12_9200: as_u32(dst) = (u_int32_t)(as_u16c(src) ^ 0x8000) << 16; goto CONV_END;
+conv_xx12_0029: as_u32(dst) = (u_int32_t)(bswap_16(as_u16c(src)) ^ 0x80); goto CONV_END;
+conv_xx12_xxx2: as_u8(dst) = as_u16c(src) & 0xff; goto CONV_END;
+conv_xx12_x210: as_u32(dst) = sx24((u_int32_t)bswap_16(as_u16c(src)) << 8); goto CONV_END;
+conv_xx12_012x: as_u32(dst) = sx24s((u_int32_t)as_u16c(src) << 8); goto CONV_END;
+conv_xx12_2100: as_u32(dst) = (u_int32_t)bswap_16(as_u16c(src)) << 16; goto CONV_END;
+conv_xx12_0012: as_u32(dst) = (u_int32_t)as_u16c(src); goto CONV_END; 
+conv_xx12_xxxA: as_u8(dst) = (as_u16c(src) ^ 0x80) & 0xff; goto CONV_END;
+conv_xx12_xxA1: as_u16(dst) = bswap_16(as_u16c(src) ^ 0x80); goto CONV_END;
+conv_xx12_xx1A: as_u16(dst) = as_u16c(src) ^ 0x80; goto CONV_END;
+conv_xx12_xA10: as_u32(dst) = sx24((u_int32_t)bswap_16(as_u16c(src) ^ 0x80) << 8); goto CONV_END;
+conv_xx12_01Ax: as_u32(dst) = sx24s((u_int32_t)(as_u16c(src) ^ 0x80) << 8); goto CONV_END;
+conv_xx12_A100: as_u32(dst) = (u_int32_t)bswap_16(as_u16c(src) ^ 0x80) << 16; goto CONV_END;
+conv_xx12_001A: as_u32(dst) = (u_int32_t)(as_u16c(src) ^ 0x80); goto CONV_END;
+conv_x123_xxx1: as_u8(dst) = as_u32c(src) >> 16; goto CONV_END;
+conv_x123_xx12: as_u16(dst) = as_u32c(src) >> 8; goto CONV_END;
+conv_x123_xx21: as_u16(dst) = bswap_16(as_u32c(src) >> 8); goto CONV_END;
+conv_x123_x123: as_u32(dst) = sx24(as_u32c(src)); goto CONV_END;
+conv_x123_321x: as_u32(dst) = sx24s(bswap_32(as_u32c(src))); goto CONV_END;
+conv_x123_1230: as_u32(dst) = as_u32c(src) << 8; goto CONV_END;
+conv_x123_0321: as_u32(dst) = bswap_32(as_u32c(src)) >> 8; goto CONV_END;
+conv_x123_xxx9: as_u8(dst) = (as_u32c(src) >> 16) ^ 0x80; goto CONV_END;
+conv_x123_xx92: as_u16(dst) = (as_u32c(src) >> 8) ^ 0x8000; goto CONV_END;
+conv_x123_xx29: as_u16(dst) = bswap_16(as_u32c(src) >> 8) ^ 0x80; goto CONV_END;
+conv_x123_x923: as_u32(dst) = sx24(as_u32c(src) ^ 0x800000); goto CONV_END;
+conv_x123_329x: as_u32(dst) = sx24s(bswap_32(as_u32c(src)) ^ 0x8000); goto CONV_END;
+conv_x123_9230: as_u32(dst) = (as_u32c(src) ^ 0x800000) << 8; goto CONV_END;
+conv_x123_0329: as_u32(dst) = (bswap_32(as_u32c(src)) >> 8) ^ 0x80; goto CONV_END;
+conv_123x_xxx3: as_u8(dst) = (as_u32c(src) >> 8) & 0xff; goto CONV_END;
+conv_123x_xx32: as_u16(dst) = bswap_16(as_u32c(src) >> 8); goto CONV_END;
+conv_123x_xx23: as_u16(dst) = (as_u32c(src) >> 8) & 0xffff; goto CONV_END;
+conv_123x_x321: as_u32(dst) = sx24(bswap_32(as_u32c(src))); goto CONV_END;
+conv_123x_123x: as_u32(dst) = sx24s(as_u32c(src)); goto CONV_END;
+conv_123x_3210: as_u32(dst) = bswap_32(as_u32c(src)) << 8; goto CONV_END;
+conv_123x_0123: as_u32(dst) = as_u32c(src) >> 8; goto CONV_END;
+conv_123x_xxxB: as_u8(dst) = ((as_u32c(src) >> 8) & 0xff) ^ 0x80; goto CONV_END;
+conv_123x_xxB2: as_u16(dst) = bswap_16((as_u32c(src) >> 8) ^ 0x80); goto CONV_END;
+conv_123x_xx2B: as_u16(dst) = ((as_u32c(src) >> 8) & 0xffff) ^ 0x80; goto CONV_END;
+conv_123x_xB21: as_u32(dst) = sx24(bswap_32(as_u32c(src)) ^ 0x800000); goto CONV_END;
+conv_123x_12Bx: as_u32(dst) = sx24s(as_u32c(src) ^ 0x8000); goto CONV_END;
+conv_123x_B210: as_u32(dst) = bswap_32(as_u32c(src) ^ 0x8000) << 8; goto CONV_END;
+conv_123x_012B: as_u32(dst) = (as_u32c(src) >> 8) ^ 0x80; goto CONV_END;
+conv_1234_xxx1: as_u8(dst) = as_u32c(src) >> 24; goto CONV_END;
+conv_1234_xx12: as_u16(dst) = as_u32c(src) >> 16; goto CONV_END;
+conv_1234_xx21: as_u16(dst) = bswap_16(as_u32c(src) >> 16); goto CONV_END;
+conv_1234_x123: as_u32(dst) = sx24(as_u32c(src) >> 8); goto CONV_END;
+conv_1234_321x: as_u32(dst) = sx24s(bswap_32(as_u32c(src)) << 8); goto CONV_END;
+conv_1234_1234: as_u32(dst) = as_u32c(src); goto CONV_END;
+conv_1234_4321: as_u32(dst) = bswap_32(as_u32c(src)); goto CONV_END;
+conv_1234_xxx9: as_u8(dst) = (as_u32c(src) >> 24) ^ 0x80; goto CONV_END;
+conv_1234_xx92: as_u16(dst) = (as_u32c(src) >> 16) ^ 0x8000; goto CONV_END;
+conv_1234_xx29: as_u16(dst) = bswap_16(as_u32c(src) >> 16) ^ 0x80; goto CONV_END;
+conv_1234_x923: as_u32(dst) = sx24((as_u32c(src) >> 8) ^ 0x800000); goto CONV_END;
+conv_1234_329x: as_u32(dst) = sx24s((bswap_32(as_u32c(src)) ^ 0x80) << 8); goto CONV_END;
+conv_1234_9234: as_u32(dst) = as_u32c(src) ^ 0x80000000; goto CONV_END;
+conv_1234_4329: as_u32(dst) = bswap_32(as_u32c(src)) ^ 0x80; goto CONV_END;
+conv_1234_xxx4: as_u8(dst) = as_u32c(src) & 0xff; goto CONV_END;
+conv_1234_xx43: as_u16(dst) = bswap_16(as_u32c(src)); goto CONV_END;
+conv_1234_xx34: as_u16(dst) = as_u32c(src) & 0xffff; goto CONV_END;
+conv_1234_x432: as_u32(dst) = sx24(bswap_32(as_u32c(src)) >> 8); goto CONV_END;
+conv_1234_234x: as_u32(dst) = sx24s(as_u32c(src) << 8); goto CONV_END;
+conv_1234_xxxC: as_u8(dst) = (as_u32c(src) & 0xff) ^ 0x80; goto CONV_END;
+conv_1234_xxC3: as_u16(dst) = bswap_16(as_u32c(src) ^ 0x80); goto CONV_END;
+conv_1234_xx3C: as_u16(dst) = (as_u32c(src) & 0xffff) ^ 0x80; goto CONV_END;
+conv_1234_xC32: as_u32(dst) = sx24((bswap_32(as_u32c(src)) >> 8) ^ 0x800000); goto CONV_END;
+conv_1234_23Cx: as_u32(dst) = sx24s((as_u32c(src) ^ 0x80) << 8); goto CONV_END;
+conv_1234_C321: as_u32(dst) = bswap_32(as_u32c(src) ^ 0x80); goto CONV_END;
+conv_1234_123C: as_u32(dst) = as_u32c(src) ^ 0x80; goto CONV_END;
+}
+#endif
+
+#ifdef GET16_LABELS
+/* src_wid src_endswap sign_toggle */
+static void *const get16_labels[4 * 2 * 2 + 4 * 3] = {
+	&&get16_1_10,	 /*  8h -> 16h */
+	&&get16_1_90,	 /*  8h ^> 16h */
+	&&get16_1_10,	 /*  8s -> 16h */
+	&&get16_1_90,	 /*  8s ^> 16h */
+	&&get16_12_12,	 /* 16h -> 16h */
+	&&get16_12_92,	 /* 16h ^> 16h */
+	&&get16_12_21,	 /* 16s -> 16h */
+	&&get16_12_A1,	 /* 16s ^> 16h */
+	&&get16_0123_12, /* 24h -> 16h */
+	&&get16_0123_92, /* 24h ^> 16h */
+	&&get16_1230_32, /* 24s -> 16h */
+	&&get16_1230_B2, /* 24s ^> 16h */
+	&&get16_1234_12, /* 32h -> 16h */
+	&&get16_1234_92, /* 32h ^> 16h */
+	&&get16_1234_43, /* 32s -> 16h */
+	&&get16_1234_C3, /* 32s ^> 16h */
+	/* 3bytes format */
+	&&get16_123_12,	 /* 24h -> 16h */
+	&&get16_123_92,	 /* 24h ^> 16h */
+	&&get16_123_32,	 /* 24s -> 16h */
+	&&get16_123_B2,	 /* 24s ^> 16h */
+	&&get16_123_12_20,	 /* 20h -> 16h */
+	&&get16_123_92_20,	 /* 20h ^> 16h */
+	&&get16_123_32_20,	 /* 20s -> 16h */
+	&&get16_123_B2_20,	 /* 20s ^> 16h */
+	&&get16_123_12_18,	 /* 18h -> 16h */
+	&&get16_123_92_18,	 /* 18h ^> 16h */
+	&&get16_123_32_18,	 /* 18s -> 16h */
+	&&get16_123_B2_18,	 /* 18s ^> 16h */
+};
+#endif
+
+#ifdef GET16_END
+while(0) {
+get16_1_10: sample = (u_int16_t)as_u8c(src) << 8; goto GET16_END;
+get16_1_90: sample = (u_int16_t)(as_u8c(src) ^ 0x80) << 8; goto GET16_END;
+get16_12_12: sample = as_u16c(src); goto GET16_END;
+get16_12_92: sample = as_u16c(src) ^ 0x8000; goto GET16_END;
+get16_12_21: sample = bswap_16(as_u16c(src)); goto GET16_END;
+get16_12_A1: sample = bswap_16(as_u16c(src) ^ 0x80); goto GET16_END;
+get16_0123_12: sample = as_u32c(src) >> 8; goto GET16_END;
+get16_0123_92: sample = (as_u32c(src) >> 8) ^ 0x8000; goto GET16_END;
+get16_1230_32: sample = bswap_16(as_u32c(src) >> 8); goto GET16_END;
+get16_1230_B2: sample = bswap_16((as_u32c(src) >> 8) ^ 0x80); goto GET16_END;
+get16_1234_12: sample = as_u32c(src) >> 16; goto GET16_END;
+get16_1234_92: sample = (as_u32c(src) >> 16) ^ 0x8000; goto GET16_END;
+get16_1234_43: sample = bswap_16(as_u32c(src)); goto GET16_END;
+get16_1234_C3: sample = bswap_16(as_u32c(src) ^ 0x80); goto GET16_END;
+get16_123_12: sample = _get_triple(src) >> 8; goto GET16_END;
+get16_123_92: sample = (_get_triple(src) >> 8) ^ 0x8000; goto GET16_END;
+get16_123_32: sample = _get_triple_s(src) >> 8; goto GET16_END;
+get16_123_B2: sample = (_get_triple_s(src) >> 8) ^ 0x8000; goto GET16_END;
+get16_123_12_20: sample = _get_triple(src) >> 4; goto GET16_END;
+get16_123_92_20: sample = (_get_triple(src) >> 4) ^ 0x8000; goto GET16_END;
+get16_123_32_20: sample = _get_triple_s(src) >> 4; goto GET16_END;
+get16_123_B2_20: sample = (_get_triple_s(src) >> 4) ^ 0x8000; goto GET16_END;
+get16_123_12_18: sample = _get_triple(src) >> 2; goto GET16_END;
+get16_123_92_18: sample = (_get_triple(src) >> 2) ^ 0x8000; goto GET16_END;
+get16_123_32_18: sample = _get_triple_s(src) >> 2; goto GET16_END;
+get16_123_B2_18: sample = (_get_triple_s(src) >> 2) ^ 0x8000; goto GET16_END;
+}
+#endif
+
+#ifdef PUT16_LABELS
+/* dst_wid dst_endswap sign_toggle */
+static void *const put16_labels[4 * 2 * 2 + 4 * 3] = {
+	&&put16_12_1,		 /* 16h ->  8h */
+	&&put16_12_9,		 /* 16h ^>  8h */
+	&&put16_12_1,		 /* 16h ->  8s */
+	&&put16_12_9,		 /* 16h ^>  8s */
+	&&put16_12_12,		 /* 16h -> 16h */
+	&&put16_12_92,		 /* 16h ^> 16h */
+	&&put16_12_21,		 /* 16h -> 16s */
+	&&put16_12_29,		 /* 16h ^> 16s */
+	&&put16_12_0120,	 /* 16h -> 24h */
+	&&put16_12_0920,	 /* 16h ^> 24h */
+	&&put16_12_0210,	 /* 16h -> 24s */
+	&&put16_12_0290,	 /* 16h ^> 24s */
+	&&put16_12_1200,	 /* 16h -> 32h */
+	&&put16_12_9200,	 /* 16h ^> 32h */
+	&&put16_12_0021,	 /* 16h -> 32s */
+	&&put16_12_0029,	 /* 16h ^> 32s */
+	/* 3bytes format */
+	&&put16_12_120,		 /* 16h -> 24h */
+	&&put16_12_920,		 /* 16h ^> 24h */
+	&&put16_12_021,		 /* 16h -> 24s */
+	&&put16_12_029,		 /* 16h ^> 24s */
+	&&put16_12_120_20,	 /* 16h -> 20h */
+	&&put16_12_920_20,	 /* 16h ^> 20h */
+	&&put16_12_021_20,	 /* 16h -> 20s */
+	&&put16_12_029_20,	 /* 16h ^> 20s */
+	&&put16_12_120_18,	 /* 16h -> 18h */
+	&&put16_12_920_18,	 /* 16h ^> 18h */
+	&&put16_12_021_18,	 /* 16h -> 18s */
+	&&put16_12_029_18,	 /* 16h ^> 18s */
+};
+#endif
+
+#ifdef PUT16_END
+while (0) {
+put16_12_1: as_u8(dst) = sample >> 8; goto PUT16_END;
+put16_12_9: as_u8(dst) = (sample >> 8) ^ 0x80; goto PUT16_END;
+put16_12_12: as_u16(dst) = sample; goto PUT16_END;
+put16_12_92: as_u16(dst) = sample ^ 0x8000; goto PUT16_END;
+put16_12_21: as_u16(dst) = bswap_16(sample); goto PUT16_END;
+put16_12_29: as_u16(dst) = bswap_16(sample) ^ 0x80; goto PUT16_END;
+put16_12_0120: as_u32(dst) = sx24((u_int32_t)sample << 8); goto PUT16_END;
+put16_12_0920: as_u32(dst) = sx24((u_int32_t)(sample ^ 0x8000) << 8); goto PUT16_END;
+put16_12_0210: as_u32(dst) = sx24s((u_int32_t)bswap_16(sample) << 8); goto PUT16_END;
+put16_12_0290: as_u32(dst) = sx24s((u_int32_t)(bswap_16(sample) ^ 0x80) << 8); goto PUT16_END;
+put16_12_1200: as_u32(dst) = (u_int32_t)sample << 16; goto PUT16_END;
+put16_12_9200: as_u32(dst) = (u_int32_t)(sample ^ 0x8000) << 16; goto PUT16_END;
+put16_12_0021: as_u32(dst) = (u_int32_t)bswap_16(sample); goto PUT16_END;
+put16_12_0029: as_u32(dst) = (u_int32_t)bswap_16(sample) ^ 0x80; goto PUT16_END;
+put16_12_120: _put_triple(dst, (u_int32_t)sample << 8); goto PUT16_END;
+put16_12_920: _put_triple(dst, (u_int32_t)(sample ^ 0x8000) << 8); goto PUT16_END;
+put16_12_021: _put_triple_s(dst, (u_int32_t)sample << 8); goto PUT16_END;
+put16_12_029: _put_triple_s(dst, (u_int32_t)(sample ^ 0x8000) << 8); goto PUT16_END;
+put16_12_120_20: _put_triple(dst, (u_int32_t)sample << 4); goto PUT16_END;
+put16_12_920_20: _put_triple(dst, (u_int32_t)(sample ^ 0x8000) << 4); goto PUT16_END;
+put16_12_021_20: _put_triple_s(dst, (u_int32_t)sample << 4); goto PUT16_END;
+put16_12_029_20: _put_triple_s(dst, (u_int32_t)(sample ^ 0x8000) << 4); goto PUT16_END;
+put16_12_120_18: _put_triple(dst, (u_int32_t)sample << 2); goto PUT16_END;
+put16_12_920_18: _put_triple(dst, (u_int32_t)(sample ^ 0x8000) << 2); goto PUT16_END;
+put16_12_021_18: _put_triple_s(dst, (u_int32_t)sample << 2); goto PUT16_END;
+put16_12_029_18: _put_triple_s(dst, (u_int32_t)(sample ^ 0x8000) << 2); goto PUT16_END;
+}
+#endif
+
+#ifdef CONV24_LABELS
+#define GET32_LABELS
+#define PUT32_LABELS
+#endif
+
+#ifdef GET32_LABELS
+/* src_wid src_endswap sign_toggle */
+static void *const get32_labels[4 * 2 * 2 + 4 * 3] = {
+	&&get32_1_1000,	 	 /*  8h -> 32h */
+	&&get32_1_9000,	 	 /*  8h ^> 32h */
+	&&get32_1_1000,		 /*  8s -> 32h */
+	&&get32_1_9000,		 /*  8s ^> 32h */
+	&&get32_12_1200,	 /* 16h -> 32h */
+	&&get32_12_9200,	 /* 16h ^> 32h */
+	&&get32_12_2100,	 /* 16s -> 32h */
+	&&get32_12_A100,	 /* 16s ^> 32h */
+	&&get32_0123_1230,	 /* 24h -> 32h */
+	&&get32_0123_9230,	 /* 24h ^> 32h */
+	&&get32_1230_3210,	 /* 24s -> 32h */
+	&&get32_1230_B210,	 /* 24s ^> 32h */
+	&&get32_1234_1234,	 /* 32h -> 32h */
+	&&get32_1234_9234,	 /* 32h ^> 32h */
+	&&get32_1234_4321,	 /* 32s -> 32h */
+	&&get32_1234_C321,	 /* 32s ^> 32h */
+	/* 3bytes format */
+	&&get32_123_1230,	 /* 24h -> 32h */
+	&&get32_123_9230,	 /* 24h ^> 32h */
+	&&get32_123_3210,	 /* 24s -> 32h */
+	&&get32_123_B210,	 /* 24s ^> 32h */
+	&&get32_123_1230_20,	 /* 20h -> 32h */
+	&&get32_123_9230_20,	 /* 20h ^> 32h */
+	&&get32_123_3210_20,	 /* 20s -> 32h */
+	&&get32_123_B210_20,	 /* 20s ^> 32h */
+	&&get32_123_1230_18,	 /* 18h -> 32h */
+	&&get32_123_9230_18,	 /* 18h ^> 32h */
+	&&get32_123_3210_18,	 /* 18s -> 32h */
+	&&get32_123_B210_18,	 /* 18s ^> 32h */
+};
+#endif
+
+#ifdef CONV24_END
+#define GET32_END __conv24_get
+#endif
+
+#ifdef GET32_END
+while (0) {
+get32_1_1000: sample = (u_int32_t)as_u8c(src) << 24; goto GET32_END;
+get32_1_9000: sample = (u_int32_t)(as_u8c(src) ^ 0x80) << 24; goto GET32_END;
+get32_12_1200: sample = (u_int32_t)as_u16c(src) << 16; goto GET32_END;
+get32_12_9200: sample = (u_int32_t)(as_u16c(src) ^ 0x8000) << 16; goto GET32_END;
+get32_12_2100: sample = (u_int32_t)bswap_16(as_u16c(src)) << 16; goto GET32_END;
+get32_12_A100: sample = (u_int32_t)bswap_16(as_u16c(src) ^ 0x80) << 16; goto GET32_END;
+get32_0123_1230: sample = as_u32c(src) << 8; goto GET32_END;
+get32_0123_9230: sample = (as_u32c(src) << 8) ^ 0x80000000; goto GET32_END;
+get32_1230_3210: sample = bswap_32(as_u32c(src) >> 8); goto GET32_END;
+get32_1230_B210: sample = bswap_32((as_u32c(src) >> 8) ^ 0x80); goto GET32_END;
+get32_1234_1234: sample = as_u32c(src); goto GET32_END;
+get32_1234_9234: sample = as_u32c(src) ^ 0x80000000; goto GET32_END;
+get32_1234_4321: sample = bswap_32(as_u32c(src)); goto GET32_END;
+get32_1234_C321: sample = bswap_32(as_u32c(src) ^ 0x80); goto GET32_END;
+get32_123_1230: sample = _get_triple(src) << 8; goto GET32_END;
+get32_123_9230: sample = (_get_triple(src) << 8) ^ 0x80000000; goto GET32_END;
+get32_123_3210: sample = _get_triple_s(src) << 8; goto GET32_END;
+get32_123_B210: sample = (_get_triple_s(src) << 8) ^ 0x80000000; goto GET32_END;
+get32_123_1230_20: sample = _get_triple(src) << 12; goto GET32_END;
+get32_123_9230_20: sample = (_get_triple(src) << 12) ^ 0x80000000; goto GET32_END;
+get32_123_3210_20: sample = _get_triple_s(src) << 12; goto GET32_END;
+get32_123_B210_20: sample = (_get_triple_s(src) << 12) ^ 0x80000000; goto GET32_END;
+get32_123_1230_18: sample = _get_triple(src) << 14; goto GET32_END;
+get32_123_9230_18: sample = (_get_triple(src) << 14) ^ 0x80000000; goto GET32_END;
+get32_123_3210_18: sample = _get_triple_s(src) << 14; goto GET32_END;
+get32_123_B210_18: sample = (_get_triple_s(src) << 14) ^ 0x80000000; goto GET32_END;
+}
+#endif
+
+#ifdef CONV24_END
+__conv24_get: goto *put;
+#define PUT32_END CONV24_END
+#endif
+
+#ifdef PUT32_LABELS
+/* dst_wid dst_endswap sign_toggle */
+static void *const put32_labels[4 * 2 * 2 + 4 * 3] = {
+	&&put32_1234_1,	 	 /* 32h ->  8h */
+	&&put32_1234_9,	 	 /* 32h ^>  8h */
+	&&put32_1234_1,	 	 /* 32h ->  8s */
+	&&put32_1234_9,	 	 /* 32h ^>  8s */
+	&&put32_1234_12,	 /* 32h -> 16h */
+	&&put32_1234_92,	 /* 32h ^> 16h */
+	&&put32_1234_21,	 /* 32h -> 16s */
+	&&put32_1234_29,	 /* 32h ^> 16s */
+	&&put32_1234_0123,	 /* 32h -> 24h */
+	&&put32_1234_0923,	 /* 32h ^> 24h */
+	&&put32_1234_3210,	 /* 32h -> 24s */
+	&&put32_1234_3290,	 /* 32h ^> 24s */
+	&&put32_1234_1234,	 /* 32h -> 32h */
+	&&put32_1234_9234,	 /* 32h ^> 32h */
+	&&put32_1234_4321,	 /* 32h -> 32s */
+	&&put32_1234_4329,	 /* 32h ^> 32s */
+	/* 3bytes format */
+	&&put32_1234_123,	 /* 32h -> 24h */
+	&&put32_1234_923,	 /* 32h ^> 24h */
+	&&put32_1234_321,	 /* 32h -> 24s */
+	&&put32_1234_329,	 /* 32h ^> 24s */
+	&&put32_1234_123_20,	 /* 32h -> 24h */
+	&&put32_1234_923_20,	 /* 32h ^> 24h */
+	&&put32_1234_321_20,	 /* 32h -> 24s */
+	&&put32_1234_329_20,	 /* 32h ^> 24s */
+	&&put32_1234_123_18,	 /* 32h -> 24h */
+	&&put32_1234_923_18,	 /* 32h ^> 24h */
+	&&put32_1234_321_18,	 /* 32h -> 24s */
+	&&put32_1234_329_18,	 /* 32h ^> 24s */
+};
+#endif
+
+#ifdef CONV24_LABELS
+#undef GET32_LABELS
+#undef PUT32_LABELS
+#endif
+
+#ifdef PUT32_END
+while (0) {
+put32_1234_1: as_u8(dst) = sample >> 24; goto PUT32_END;
+put32_1234_9: as_u8(dst) = (sample >> 24) ^ 0x80; goto PUT32_END;
+put32_1234_12: as_u16(dst) = sample >> 16; goto PUT32_END;
+put32_1234_92: as_u16(dst) = (sample >> 16) ^ 0x8000; goto PUT32_END;
+put32_1234_21: as_u16(dst) = bswap_16(sample >> 16); goto PUT32_END;
+put32_1234_29: as_u16(dst) = bswap_16(sample >> 16) ^ 0x80; goto PUT32_END;
+put32_1234_0123: as_u32(dst) = sx24(sample >> 8); goto PUT32_END;
+put32_1234_0923: as_u32(dst) = sx24((sample >> 8) ^ 0x800000); goto PUT32_END;
+put32_1234_3210: as_u32(dst) = sx24s(bswap_32(sample) << 8); goto PUT32_END;
+put32_1234_3290: as_u32(dst) = sx24s((bswap_32(sample) ^ 0x80) << 8); goto PUT32_END;
+put32_1234_1234: as_u32(dst) = sample; goto PUT32_END;
+put32_1234_9234: as_u32(dst) = sample ^ 0x80000000; goto PUT32_END;
+put32_1234_4321: as_u32(dst) = bswap_32(sample); goto PUT32_END;
+put32_1234_4329: as_u32(dst) = bswap_32(sample) ^ 0x80; goto PUT32_END;
+put32_1234_123: _put_triple(dst, sample >> 8); goto PUT32_END;
+put32_1234_923: _put_triple(dst, (sample ^ 0x80000000) >> 8); goto PUT32_END;
+put32_1234_321: _put_triple_s(dst, sample >> 8); goto PUT32_END;
+put32_1234_329: _put_triple_s(dst, (sample ^ 0x80000000) >> 8); goto PUT32_END;
+put32_1234_123_20: _put_triple(dst, sample >> 12); goto PUT32_END;
+put32_1234_923_20: _put_triple(dst, (sample ^ 0x80000000) >> 12); goto PUT32_END;
+put32_1234_321_20: _put_triple_s(dst, sample >> 12); goto PUT32_END;
+put32_1234_329_20: _put_triple_s(dst, (sample ^ 0x80000000) >> 12); goto PUT32_END;
+put32_1234_123_18: _put_triple(dst, sample >> 14); goto PUT32_END;
+put32_1234_923_18: _put_triple(dst, (sample ^ 0x80000000) >> 14); goto PUT32_END;
+put32_1234_321_18: _put_triple_s(dst, sample >> 14); goto PUT32_END;
+put32_1234_329_18: _put_triple_s(dst, (sample ^ 0x80000000) >> 14); goto PUT32_END;
+}
+#endif
+
+#ifdef CONV24_END
+#undef GET32_END
+#undef PUT32_END
+#endif
+
+#ifdef GETU_LABELS
+/* width endswap sign_toggle */
+static void *const getu_labels[4 * 2 * 2] = {
+	&&getu_1_1,		/*  8h ->  8h */
+	&&getu_1_9,		/*  8h ^>  8h */
+	&&getu_1_1,		/*  8s ->  8h */
+	&&getu_1_9,		/*  8s ^>  8h */
+	&&getu_12_12,		/* 16h -> 16h */
+	&&getu_12_92,		/* 16h ^> 16h */
+	&&getu_12_21,		/* 16s -> 16h */
+	&&getu_12_A1,		/* 16s ^> 16h */
+	&&getu_0123_0123,	/* 24h -> 24h */
+	&&getu_0123_0923,	/* 24h ^> 24h */
+	&&getu_1230_0321,	/* 24s -> 24h */
+	&&getu_1230_0B21,	/* 24s ^> 24h */
+	&&getu_1234_1234,	/* 32h -> 32h */
+	&&getu_1234_9234,	/* 32h ^> 32h */
+	&&getu_1234_4321,	/* 32s -> 32h */
+	&&getu_1234_C321,	/* 32s ^> 32h */
+};
+#endif
+
+#ifdef GETU_END
+while (0) {
+getu_1_1: sample = as_u8c(src); goto GETU_END;
+getu_1_9: sample = as_u8c(src) ^ 0x80; goto GETU_END;
+getu_12_12: sample = as_u16c(src); goto GETU_END;
+getu_12_92: sample = as_u16c(src) ^ 0x8000; goto GETU_END;
+getu_12_21: sample = bswap_16(as_u16c(src)); goto GETU_END;
+getu_12_A1: sample = bswap_16(as_u16c(src) ^ 0x80); goto GETU_END;
+getu_0123_0123: sample = sx24(as_u32c(src)); goto GETU_END;
+getu_0123_0923: sample = sx24(as_u32c(src) ^ 0x800000); goto GETU_END;
+getu_1230_0321: sample = sx24(bswap_32(as_u32c(src))); goto GETU_END;
+getu_1230_0B21: sample = sx24(bswap_32(as_u32c(src) ^ 0x8000)); goto GETU_END;
+getu_1234_1234: sample = as_u32c(src); goto GETU_END;
+getu_1234_9234: sample = as_u32c(src) ^ 0x80000000; goto GETU_END;
+getu_1234_4321: sample = bswap_32(as_u32c(src)); goto GETU_END;
+getu_1234_C321: sample = bswap_32(as_u32c(src) ^ 0x80); goto GETU_END;
+}
+#endif
+
+#ifdef GETS_LABELS
+/* width endswap sign_toggle */
+static void *const gets_labels[4 * 2 * 2] = {
+	&&gets_1_1,		/*  8h ->  8h */
+	&&gets_1_9,		/*  8h ^>  8h */
+	&&gets_1_1,		/*  8s ->  8h */
+	&&gets_1_9,		/*  8s ^>  8h */
+	&&gets_12_12,		/* 16h -> 16h */
+	&&gets_12_92,		/* 16h ^> 16h */
+	&&gets_12_21,		/* 16s -> 16h */
+	&&gets_12_A1,		/* 16s ^> 16h */
+	&&gets_0123_0123,	/* 24h -> 24h */
+	&&gets_0123_0923,	/* 24h ^> 24h */
+	&&gets_1230_0321,	/* 24s -> 24h */
+	&&gets_1230_0B21,	/* 24s ^> 24h */
+	&&gets_1234_1234,	/* 32h -> 32h */
+	&&gets_1234_9234,	/* 32h ^> 32h */
+	&&gets_1234_4321,	/* 32s -> 32h */
+	&&gets_1234_C321,	/* 32s ^> 32h */
+};
+#endif
+
+#ifdef GETS_END
+while (0) {
+gets_1_1: sample = as_s8c(src); goto GETS_END;
+gets_1_9: sample = (int8_t)(as_s8c(src) ^ 0x80); goto GETS_END;
+gets_12_12: sample = as_s16c(src); goto GETS_END;
+gets_12_92: sample = (int16_t)(as_s16c(src) ^ 0x8000); goto GETS_END;
+gets_12_21: sample = (int16_t)bswap_16(as_s16c(src)); goto GETS_END;
+gets_12_A1: sample = (int16_t)bswap_16(as_s16c(src) ^ 0x80); goto GETS_END;
+gets_0123_0123: sample = sx24((int32_t)(as_s32c(src) << 8) >> 8); goto GETS_END;
+gets_0123_0923: sample = sx24((int32_t)((as_s32c(src) ^ 0x800000) << 8) >> 8); goto GETS_END;
+gets_1230_0321: sample = sx24((int32_t)(bswap_32(as_s32c(src)) << 8) >> 8); goto GETS_END;
+gets_1230_0B21: sample = sx24((int32_t)(bswap_32(as_s32c(src) ^ 0x8000) << 8) >> 8); goto GETS_END;
+gets_1234_1234: sample = as_s32c(src); goto GETS_END;
+gets_1234_9234: sample = (int32_t)(as_s32c(src) ^ 0x80000000); goto GETS_END;
+gets_1234_4321: sample = (int32_t)bswap_32(as_s32c(src)); goto GETS_END;
+gets_1234_C321: sample = (int32_t)bswap_32(as_s32c(src) ^ 0x80); goto GETS_END;
+}
+#endif
+
+#ifdef PUT_LABELS
+/* width endswap sign_toggle */
+static void *const put_labels[4 * 2 * 2] = {
+	&&put_1_1,		/*  8h ->  8h */
+	&&put_1_9,		/*  8h ^>  8h */
+	&&put_1_1,		/*  8h ->  8s */
+	&&put_1_9,		/*  8h ^>  8s */
+	&&put_12_12,		/* 16h -> 16h */
+	&&put_12_92,		/* 16h ^> 16h */
+	&&put_12_21,		/* 16h -> 16s */
+	&&put_12_29,		/* 16h ^> 16s */
+	&&put_0123_0123,	/* 24h -> 24h */
+	&&put_0123_0923,	/* 24h ^> 24h */
+	&&put_0123_3210,	/* 24h -> 24s */
+	&&put_0123_3290,	/* 24h ^> 24s */
+	&&put_1234_1234,	/* 32h -> 32h */
+	&&put_1234_9234,	/* 32h ^> 32h */
+	&&put_1234_4321,	/* 32h -> 32s */
+	&&put_1234_4329,	/* 32h ^> 32s */
+};
+#endif
+
+#ifdef PUT_END
+put_1_1: as_s8(dst) = sample; goto PUT_END;
+put_1_9: as_u8(dst) = sample ^ 0x80; goto PUT_END;
+put_12_12: as_s16(dst) = sample; goto PUT_END;
+put_12_92: as_u16(dst) = sample ^ 0x8000; goto PUT_END;
+put_12_21: as_s16(dst) = bswap_16(sample); goto PUT_END;
+put_12_29: as_u16(dst) = bswap_16(sample) ^ 0x80; goto PUT_END;
+/* this always writes the unused byte in 24-bit formats as 0x00 */
+put_0123_0123: as_s32(dst) = sx24(sample & 0x00ffffff); goto PUT_END;
+put_0123_0923: as_u32(dst) = sx24((sample & 0x00ffffff) ^ 0x800000); goto PUT_END;
+put_0123_3210: as_s32(dst) = sx24s(bswap_32(sample) & 0xffffff00); goto PUT_END;
+put_0123_3290: as_u32(dst) = sx24s((bswap_32(sample) & 0xffffff00) ^ 0x8000); goto PUT_END;
+put_1234_1234: as_s32(dst) = sample; goto PUT_END;
+put_1234_9234: as_u32(dst) = sample ^ 0x80000000; goto PUT_END;
+put_1234_4321: as_s32(dst) = bswap_32(sample); goto PUT_END;
+put_1234_4329: as_u32(dst) = bswap_32(sample) ^ 0x80; goto PUT_END;
+#endif
+
+#ifdef PUT32F_LABELS
+/* type (0 = float, 1 = float64), endswap */
+static void *const put32float_labels[2 * 2] = {
+	&&put32f_1234_1234F,	/* 32h -> (float)h */
+	&&put32f_1234_4321F,	/* 32h -> (float)s */
+	&&put32f_1234_1234D,	/* 32h -> (float64)h */
+	&&put32f_1234_4321D,	/* 32h -> (float64)s */
+};
+#endif
+
+#ifdef PUT32F_END
+put32f_1234_1234F: as_float(dst) = (float_t)((int32_t)sample) / (float_t)0x80000000UL; goto PUT32F_END;
+put32f_1234_4321F: tmp_float.f = (float_t)((int32_t)sample) / (float_t)0x80000000UL;
+		   as_u32(dst) = bswap_32(tmp_float.i); goto PUT32F_END;
+put32f_1234_1234D: as_double(dst) = (double_t)((int32_t)sample) / (double_t)0x80000000UL; goto PUT32F_END;
+put32f_1234_4321D: tmp_double.d = (double_t)((int32_t)sample) / (double_t)0x80000000UL;
+		   as_u64(dst) = bswap_64(tmp_double.l); goto PUT32F_END;
+#endif
+
+#ifdef GET32F_LABELS
+/* type (0 = float, 1 = float64), endswap */
+static void *const get32float_labels[2 * 2] = {
+	&&get32f_1234F_1234,	/* (float)h -> 32h */
+	&&get32f_4321F_1234,	/* (float)s -> 32h */
+	&&get32f_1234D_1234,	/* (float64)h -> 32h */
+	&&get32f_4321D_1234,	/* (float64)s -> 32h */
+};
+#endif
+
+#ifdef GET32F_END
+get32f_1234F_1234: tmp_float.f = as_floatc(src);
+		   if (tmp_float.f >= 1.0)
+		   	sample = 0x7fffffff;
+		   else if (tmp_float.f <= -1.0)
+		   	sample = 0x80000000;
+		   else
+		   	sample = (int32_t)(tmp_float.f * (float_t)0x80000000UL);
+		   goto GET32F_END;
+get32f_4321F_1234: tmp_float.i = bswap_32(as_u32c(src));
+		   if (tmp_float.f >= 1.0)
+		   	sample = 0x7fffffff;
+		   else if (tmp_float.f <= -1.0)
+		   	sample = 0x80000000;
+		   else
+		   	sample = (int32_t)(tmp_float.f * (float_t)0x80000000UL);
+		   goto GET32F_END;
+get32f_1234D_1234: tmp_double.d = as_doublec(src);
+		   if (tmp_double.d >= 1.0)
+		   	sample = 0x7fffffff;
+		   else if (tmp_double.d <= -1.0)
+		   	sample = 0x80000000;
+		   else
+		   	sample = (int32_t)(tmp_double.d * (double_t)0x80000000UL);
+		   goto GET32F_END;
+get32f_4321D_1234: tmp_double.l = bswap_64(as_u64c(src));
+		   if (tmp_double.d >= 1.0)
+		   	sample = 0x7fffffff;
+		   else if (tmp_double.d <= -1.0)
+		   	sample = 0x80000000;
+		   else
+		   	sample = (int32_t)(tmp_double.d * (double_t)0x80000000UL);
+		   goto GET32F_END;
+#endif
+
+#ifdef NORMS_LABELS
+static inline void _norms(const void *src, void *dst,
+			  int src_wid,
+			  int dst_sign, int dst_wid, int dst_end)
+{
+	int32_t s;
+	switch (src_wid) {
+	case 8:
+		s = *(int32_t*)src;
+		if (s >= 0x7f)
+			goto _max;
+		else if (s <= -0x80)
+			goto _min;
+		break;
+	case 16:
+		s = *(int32_t*)src;
+		if (s >= 0x7fff)
+			goto _max;
+		else if (s <= -0x8000)
+			goto _min;
+		break;
+	case 24:
+		s = *(int32_t*)src;
+		if (s >= 0x7fffff)
+			goto _max;
+		else if (s <= -0x800000)
+			goto _min;
+		break;
+	case 32:
+	{
+		int64_t s64;
+		s64 = *(int64_t*)src;
+		if (s64 >= 0x7fffffff)
+			goto _max;
+		else if (s64 <= -0x80000000)
+			goto _min;
+		s = s64;
+		break;
+	}
+	default:
+		assert(0);
+		return;
+	}
+	if (src_wid < dst_wid) {
+		unsigned int bits = dst_wid - src_wid;
+		s *= 1 << bits;
+	} else if (src_wid > dst_wid) {
+		unsigned int bits = src_wid - dst_wid;
+		s = (s + (1 << (bits - 1))) / (1 << bits);
+	}
+	if (!dst_sign)
+		s += (1U << (dst_wid - 1));
+	switch (dst_wid) {
+	case 8:
+		*(u_int8_t*)dst = s;
+		break;
+	case 16:
+		if (dst_end)
+			s = bswap_16(s);
+		*(u_int16_t*)dst = s;
+		break;
+	case 24:
+	case 32:
+		if (dst_end)
+			s = bswap_32(s);
+		*(u_int32_t*)dst = s;
+		break;
+	}
+	return;
+
+ _min:
+	switch (dst_wid) {
+	case 8:
+		if (dst_sign)
+			*(u_int8_t*)dst = 0x80;
+		else
+			*(u_int8_t*)dst = 0;
+		break;
+	case 16:
+		if (dst_sign)
+			*(u_int16_t*)dst = dst_end ? 0x0080 : 0x8000;
+		else
+			*(u_int16_t*)dst = 0;
+		break;
+	case 24:
+		if (dst_sign)
+			*(u_int32_t*)dst = dst_end ? 0x00008000 : 0x00800000;
+		else
+			*(u_int32_t*)dst = 0;
+		break;
+	case 32:
+		if (dst_sign)
+			*(u_int32_t*)dst = dst_end ? 0x00000080 : 0x80000000;
+		else
+			*(u_int32_t*)dst = 0;
+		break;
+	default:
+		assert(0);
+		break;
+	}
+	return;
+
+ _max:
+	switch (dst_wid) {
+	case 8:
+		if (dst_sign)
+			*(u_int8_t*)dst = 0x7f;
+		else
+			*(u_int8_t*)dst = 0xff;
+		break;
+	case 16:
+		if (dst_sign)
+			*(u_int16_t*)dst = dst_end ? 0xff7f : 0x7fff;
+		else
+			*(u_int16_t*)dst = 0;
+		break;
+	case 24:
+		if (dst_sign)
+			*(u_int32_t*)dst = dst_end ? 0xffff7f00 : 0x007fffff;
+		else
+			*(u_int32_t*)dst = 0;
+		break;
+	case 32:
+		if (dst_sign)
+			*(u_int32_t*)dst = dst_end ? 0xffffff7f : 0x7fffffff;
+		else
+			*(u_int32_t*)dst = 0;
+		break;
+	default:
+		assert(0);
+		break;
+	}
+	return;
+}
+
+/* src_wid dst_sign dst_wid dst_end */
+static void *const norms_labels[4 * 2 * 4 * 2] = {
+	&&norms_8_u8,	        /*  s8 -> u8 */
+	&&norms_8_u8,	        /*  s8 -> u8 */
+	&&norms_8_u16h,	        /*  s8 -> u16h */
+	&&norms_8_u16s,	        /*  s8 -> u16s */
+	&&norms_8_u24h,	        /*  s8 -> u24h */
+	&&norms_8_u24s,	        /*  s8 -> u24s */
+	&&norms_8_u32h,	        /*  s8 -> u32h */
+	&&norms_8_u32s,	        /*  s8 -> u32s */
+	&&norms_8_s8,	        /*  s8 -> s8 */
+	&&norms_8_s8,	        /*  s8 -> s8 */
+	&&norms_8_s16h,	        /*  s8 -> s16h */
+	&&norms_8_s16s,	        /*  s8 -> s16s */
+	&&norms_8_s24h,	        /*  s8 -> s24h */
+	&&norms_8_s24s,	        /*  s8 -> s24s */
+	&&norms_8_s32h,	        /*  s8 -> s32h */
+	&&norms_8_s32s,	        /*  s8 -> s32s */
+	&&norms_16_u8,	        /* s16 -> u8 */
+	&&norms_16_u8,	        /* s16 -> u8 */
+	&&norms_16_u16h,	/* s16 -> u16h */
+	&&norms_16_u16s,	/* s16 -> u16s */
+	&&norms_16_u24h,	/* s16 -> u24h */
+	&&norms_16_u24s,	/* s16 -> u24s */
+	&&norms_16_u32h,	/* s16 -> u32h */
+	&&norms_16_u32s,	/* s16 -> u32s */
+	&&norms_16_s8,		/* s16 -> s8 h*/
+	&&norms_16_s8,		/* s16 -> s8 */
+	&&norms_16_s16h,	/* s16 -> s16h */
+	&&norms_16_s16s,	/* s16 -> s16s */
+	&&norms_16_s24h,	/* s16 -> s24h */
+	&&norms_16_s24s,	/* s16 -> s24s */
+	&&norms_16_s32h,	/* s16 -> s32h */
+	&&norms_16_s32s,	/* s16 -> s32s */
+	&&norms_24_u8,		/* s24 -> u8 */
+	&&norms_24_u8,		/* s24 -> u8 */
+	&&norms_24_u16h,	/* s24 -> u16h */
+	&&norms_24_u16s,	/* s24 -> u16s */
+	&&norms_24_u24h,	/* s24 -> u24h */
+	&&norms_24_u24s,	/* s24 -> u24s */
+	&&norms_24_u32h,	/* s24 -> u32h */
+	&&norms_24_u32s,	/* s24 -> u32s */
+	&&norms_24_s8,		/* s24 -> s8 */
+	&&norms_24_s8,		/* s24 -> s8 */
+	&&norms_24_s16h,	/* s24 -> s16h */
+	&&norms_24_s16s,	/* s24 -> s16s */
+	&&norms_24_s24h,	/* s24 -> s24h */
+	&&norms_24_s24s,	/* s24 -> s24s */
+	&&norms_24_s32h,	/* s24 -> s32h */
+	&&norms_24_s32s,	/* s24 -> s32s */
+	&&norms_32_u8,		/* s32 -> u8 */
+	&&norms_32_u8,		/* s32 -> u8 */
+	&&norms_32_u16h,	/* s32 -> u16h */
+	&&norms_32_u16s,	/* s32 -> u16s */
+	&&norms_32_u24h,	/* s32 -> u24h */
+	&&norms_32_u24s,	/* s32 -> u24s */
+	&&norms_32_u32h,	/* s32 -> u32h */
+	&&norms_32_u32s,	/* s32 -> u32s */
+	&&norms_32_s8,		/* s32 -> s8 */
+	&&norms_32_s8,		/* s32 -> s8 */
+	&&norms_32_s16h,	/* s32 -> s16h */
+	&&norms_32_s16s,	/* s32 -> s16s */
+	&&norms_32_s24h,	/* s32 -> s24h */
+	&&norms_32_s24s,	/* s32 -> s24s */
+	&&norms_32_s32h,	/* s32 -> s32h */
+	&&norms_32_s32s,	/* s32 -> s32s */
+};
+#endif
+
+#ifdef NORMS_END
+norms_8_u8:	_norms(src, dst,  8, 0,  8, 0); goto NORMS_END;
+norms_8_u16h:	_norms(src, dst,  8, 0,  16, 0); goto NORMS_END;
+norms_8_u16s:	_norms(src, dst,  8, 0,  16, 1); goto NORMS_END;
+norms_8_u24h:	_norms(src, dst,  8, 0,  24, 0); goto NORMS_END;
+norms_8_u24s:	_norms(src, dst,  8, 0,  24, 1); goto NORMS_END;
+norms_8_u32h:	_norms(src, dst,  8, 0,  32, 0); goto NORMS_END;
+norms_8_u32s:	_norms(src, dst,  8, 0,  32, 1); goto NORMS_END;
+norms_8_s8:	_norms(src, dst,  8, 1,  8, 0); goto NORMS_END;
+norms_8_s16h:	_norms(src, dst,  8, 1,  16, 0); goto NORMS_END;
+norms_8_s16s:	_norms(src, dst,  8, 1,  16, 1); goto NORMS_END;
+norms_8_s24h:	_norms(src, dst,  8, 1,  24, 0); goto NORMS_END;
+norms_8_s24s:	_norms(src, dst,  8, 1,  24, 1); goto NORMS_END;
+norms_8_s32h:	_norms(src, dst,  8, 1,  32, 0); goto NORMS_END;
+norms_8_s32s:	_norms(src, dst,  8, 1,  32, 1); goto NORMS_END;
+norms_16_u8:	_norms(src, dst, 16, 0,  8, 0); goto NORMS_END;
+norms_16_u16h:	_norms(src, dst, 16, 0,  16, 0); goto NORMS_END;
+norms_16_u16s:	_norms(src, dst, 16, 0,  16, 1); goto NORMS_END;
+norms_16_u24h:	_norms(src, dst, 16, 0,  24, 0); goto NORMS_END;
+norms_16_u24s:	_norms(src, dst, 16, 0,  24, 1); goto NORMS_END;
+norms_16_u32h:	_norms(src, dst, 16, 0,  32, 0); goto NORMS_END;
+norms_16_u32s:	_norms(src, dst, 16, 0,  32, 1); goto NORMS_END;
+norms_16_s8:	_norms(src, dst, 16, 1,  8, 0); goto NORMS_END;
+norms_16_s16h:	_norms(src, dst, 16, 1,  16, 0); goto NORMS_END;
+norms_16_s16s:	_norms(src, dst, 16, 1,  16, 1); goto NORMS_END;
+norms_16_s24h:	_norms(src, dst, 16, 1,  24, 0); goto NORMS_END;
+norms_16_s24s:	_norms(src, dst, 16, 1,  24, 1); goto NORMS_END;
+norms_16_s32h:	_norms(src, dst, 16, 1,  32, 0); goto NORMS_END;
+norms_16_s32s:	_norms(src, dst, 16, 1,  32, 1); goto NORMS_END;
+norms_24_u8:	_norms(src, dst, 24, 0,  8, 0); goto NORMS_END;
+norms_24_u16h:	_norms(src, dst, 24, 0,  16, 0); goto NORMS_END;
+norms_24_u16s:	_norms(src, dst, 24, 0,  16, 1); goto NORMS_END;
+norms_24_u24h:	_norms(src, dst, 24, 0,  24, 0); goto NORMS_END;
+norms_24_u24s:	_norms(src, dst, 24, 0,  24, 1); goto NORMS_END;
+norms_24_u32h:	_norms(src, dst, 24, 0,  32, 0); goto NORMS_END;
+norms_24_u32s:	_norms(src, dst, 24, 0,  32, 1); goto NORMS_END;
+norms_24_s8:	_norms(src, dst, 24, 1,  8, 0); goto NORMS_END;
+norms_24_s16h:	_norms(src, dst, 24, 1,  16, 0); goto NORMS_END;
+norms_24_s16s:	_norms(src, dst, 24, 1,  16, 1); goto NORMS_END;
+norms_24_s24h:	_norms(src, dst, 24, 1,  24, 0); goto NORMS_END;
+norms_24_s24s:	_norms(src, dst, 24, 1,  24, 1); goto NORMS_END;
+norms_24_s32h:	_norms(src, dst, 24, 1,  32, 0); goto NORMS_END;
+norms_24_s32s:	_norms(src, dst, 24, 1,  32, 1); goto NORMS_END;
+norms_32_u8:	_norms(src, dst, 32, 0,  8, 0); goto NORMS_END;
+norms_32_u16h:	_norms(src, dst, 32, 0,  16, 0); goto NORMS_END;
+norms_32_u16s:	_norms(src, dst, 32, 0,  16, 1); goto NORMS_END;
+norms_32_u24h:	_norms(src, dst, 32, 0,  24, 0); goto NORMS_END;
+norms_32_u24s:	_norms(src, dst, 32, 0,  24, 1); goto NORMS_END;
+norms_32_u32h:	_norms(src, dst, 32, 0,  32, 0); goto NORMS_END;
+norms_32_u32s:	_norms(src, dst, 32, 0,  32, 1); goto NORMS_END;
+norms_32_s8:	_norms(src, dst, 32, 1,  8, 0); goto NORMS_END;
+norms_32_s16h:	_norms(src, dst, 32, 1,  16, 0); goto NORMS_END;
+norms_32_s16s:	_norms(src, dst, 32, 1,  16, 1); goto NORMS_END;
+norms_32_s24h:	_norms(src, dst, 32, 1,  24, 0); goto NORMS_END;
+norms_32_s24s:	_norms(src, dst, 32, 1,  24, 1); goto NORMS_END;
+norms_32_s32h:	_norms(src, dst, 32, 1,  32, 0); goto NORMS_END;
+norms_32_s32s:	_norms(src, dst, 32, 1,  32, 1); goto NORMS_END;
+#endif
+
+
+#undef as_u8
+#undef as_u16
+#undef as_u32
+#undef as_s8
+#undef as_s16
+#undef as_s32
+#undef as_float
+#undef as_double
+
+#undef as_u8c
+#undef as_u16c
+#undef as_u32c
+#undef as_s8c
+#undef as_s16c
+#undef as_s32c
+#undef as_floatc
+#undef as_doublec
+
+#undef _get_triple
+#undef _get_triple_s
+#undef _get_triple_le
+#undef _get_triple_be
+#undef _put_triple
+#undef _put_triple_s
+#undef _put_triple_le
+#undef _put_triple_be
+
diff --git a/src/pcm/scopes/Makefile.am b/src/pcm/scopes/Makefile.am
new file mode 100644
index 0000000..0ce845d
--- /dev/null
+++ b/src/pcm/scopes/Makefile.am
@@ -0,0 +1,9 @@
+pkglibdir = $(libdir)/@PACKAGE@/scopes
+
+AM_CFLAGS = -g -O2 -W -Wall
+
+pkglib_LTLIBRARIES = scope-level.la
+
+scope_level_la_SOURCES = level.c
+scope_level_la_LDFLAGS = -module
+scope_level_la_LIBADD = -lncurses
diff --git a/src/pcm/scopes/level.c b/src/pcm/scopes/level.c
new file mode 100644
index 0000000..eea9eac
--- /dev/null
+++ b/src/pcm/scopes/level.c
@@ -0,0 +1,271 @@
+/*
+ *  PCM - Meter level plugin (ncurses)
+ *  Copyright (c) 2001 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <curses.h>
+#include <errno.h>
+#include <alsa/asoundlib.h>
+
+#define BAR_WIDTH 70
+/* milliseconds to go from 32767 to 0 */
+#define DECAY_MS 400
+/* milliseconds for peak to disappear */
+#define PEAK_MS 800
+
+typedef struct _snd_pcm_scope_level_channel {
+	int16_t level;
+	int16_t peak;
+	unsigned int peak_age;
+} snd_pcm_scope_level_channel_t;
+
+typedef struct _snd_pcm_scope_level {
+	snd_pcm_t *pcm;
+	snd_pcm_scope_t *s16;
+	snd_pcm_scope_level_channel_t *channels;
+	snd_pcm_uframes_t old;
+	int top;
+	WINDOW *win;
+	unsigned int bar_width;
+	unsigned int decay_ms;
+	unsigned int peak_ms;
+} snd_pcm_scope_level_t;
+
+static int level_enable(snd_pcm_scope_t *scope)
+{
+	snd_pcm_scope_level_t *level = snd_pcm_scope_get_callback_private(scope);
+	int y, x;
+	level->channels = calloc(snd_pcm_meter_get_channels(level->pcm), sizeof(*level->channels));
+	if (!level->channels) {
+		free(level);
+		return -ENOMEM;
+	}
+	snd_pcm_scope_set_callback_private(scope, level);
+	level->win = initscr();
+	winsdelln(level->win, snd_pcm_meter_get_channels(level->pcm));
+        getyx(level->win, y, x);
+	level->top = y;
+	return 0;
+}
+
+static void level_disable(snd_pcm_scope_t *scope)
+{
+	snd_pcm_scope_level_t *level = snd_pcm_scope_get_callback_private(scope);
+	endwin();
+	free(level->channels);
+}
+
+static void level_close(snd_pcm_scope_t *scope)
+{
+	snd_pcm_scope_level_t *level = snd_pcm_scope_get_callback_private(scope);
+	free(level);
+}
+
+static void level_start(snd_pcm_scope_t *scope ATTRIBUTE_UNUSED)
+{
+}
+
+static void level_stop(snd_pcm_scope_t *scope)
+{
+	snd_pcm_scope_level_t *level = snd_pcm_scope_get_callback_private(scope);
+	unsigned int c;
+	for (c = 0; c < snd_pcm_meter_get_channels(level->pcm); c++) {
+		move(level->top + c, 0);
+		clrtoeol();
+	}
+	move(level->top, 0);
+	refresh();
+}
+
+static void level_update(snd_pcm_scope_t *scope)
+{
+	snd_pcm_scope_level_t *level = snd_pcm_scope_get_callback_private(scope);
+	snd_pcm_t *pcm = level->pcm;
+	snd_pcm_sframes_t size;
+	snd_pcm_uframes_t size1, size2;
+	snd_pcm_uframes_t offset, cont;
+	unsigned int c, channels;
+	unsigned int ms;
+	static char bar[256] = { [0 ... 255] = '#' };
+	int max_decay;
+	size = snd_pcm_meter_get_now(pcm) - level->old;
+	if (size < 0)
+		size += snd_pcm_meter_get_boundary(pcm);
+	offset = level->old % snd_pcm_meter_get_bufsize(pcm);
+	cont = snd_pcm_meter_get_bufsize(pcm) - offset;
+	size1 = size;
+	if (size1 > cont)
+		size1 = cont;
+	size2 = size - size1;
+	ms = size * 1000 / snd_pcm_meter_get_rate(pcm);
+	max_decay = 32768 * ms / level->decay_ms;
+	channels = snd_pcm_meter_get_channels(pcm);
+	for (c = 0; c < channels; c++) {
+		int16_t *ptr;
+		int s, lev = 0;
+		snd_pcm_uframes_t n;
+		snd_pcm_scope_level_channel_t *l;
+		unsigned int lev_pos, peak_pos;
+		l = &level->channels[c];
+		ptr = snd_pcm_scope_s16_get_channel_buffer(level->s16, c) + offset;
+		for (n = size1; n > 0; n--) {
+			s = *ptr;
+			if (s < 0)
+				s = -s;
+			if (s > lev)
+				lev = s;
+			ptr++;
+		}
+		ptr = snd_pcm_scope_s16_get_channel_buffer(level->s16, c);
+		for (n = size2; n > 0; n--) {
+			s = *ptr;
+			if (s < 0)
+				s = -s;
+			if (s > lev)
+				lev = s;
+			ptr++;
+		}
+		l->level = lev;
+		l->peak_age += ms;
+		if (l->peak_age >= level->peak_ms ||
+		    lev >= l->peak) {
+			l->peak = lev;
+			l->peak_age = 0;
+		}
+		if (lev < l->level - max_decay)
+			lev = l->level - max_decay;
+		move(level->top + c, 0);
+		lev_pos = lev * level->bar_width / 32768;
+		peak_pos = l->peak * level->bar_width / 32768;
+		addnstr(bar, lev_pos);
+		clrtoeol();
+		mvaddch(level->top + c, peak_pos - 1, '#');
+	}
+	move(level->top, 0);
+	refresh();
+	level->old = snd_pcm_meter_get_now(pcm);
+}
+
+static void level_reset(snd_pcm_scope_t *scope)
+{
+	snd_pcm_scope_level_t *level = snd_pcm_scope_get_callback_private(scope);
+	snd_pcm_t *pcm = level->pcm;
+	memset(level->channels, 0, snd_pcm_meter_get_channels(pcm) * sizeof(*level->channels));
+	level->old = snd_pcm_meter_get_now(pcm);
+}
+
+snd_pcm_scope_ops_t level_ops = {
+	.enable = level_enable,
+	.disable = level_disable,
+	.close = level_close,
+	.start = level_start,
+	.stop = level_stop,
+	.update = level_update,
+	.reset = level_reset,
+};
+
+int snd_pcm_scope_level_open(snd_pcm_t *pcm, const char *name,
+			     unsigned int bar_width, unsigned int decay_ms,
+			     unsigned int peak_ms,
+			     snd_pcm_scope_t **scopep)
+{
+	snd_pcm_scope_t *scope, *s16;
+	snd_pcm_scope_level_t *level;
+	int err = snd_pcm_scope_malloc(&scope);
+	if (err < 0)
+		return err;
+	level = calloc(1, sizeof(*level));
+	if (!level) {
+		free(scope);
+		return -ENOMEM;
+	}
+	level->pcm = pcm;
+	level->bar_width = bar_width;
+	level->decay_ms = decay_ms;
+	level->peak_ms = peak_ms;
+	s16 = snd_pcm_meter_search_scope(pcm, "s16");
+	if (!s16) {
+		err = snd_pcm_scope_s16_open(pcm, "s16", &s16);
+		if (err < 0) {
+			free(scope);
+			free(level);
+			return err;
+		}
+	}
+	level->s16 = s16;
+	snd_pcm_scope_set_ops(scope, &level_ops);
+	snd_pcm_scope_set_callback_private(scope, level);
+	if (name)
+		snd_pcm_scope_set_name(scope, strdup(name));
+	snd_pcm_meter_add_scope(pcm, scope);
+	*scopep = scope;
+	return 0;
+}
+
+int _snd_pcm_scope_level_open(snd_pcm_t *pcm, const char *name,
+			      snd_config_t *root, snd_config_t *conf)
+{
+	snd_config_iterator_t i, next;
+	snd_pcm_scope_t *scope;
+	long bar_width = -1, decay_ms = -1, peak_ms = -1;
+	int err;
+	snd_config_for_each(i, next, conf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (strcmp(id, "comment") == 0)
+			continue;
+		if (strcmp(id, "type") == 0)
+			continue;
+		if (strcmp(id, "bar_width") == 0) {
+			err = snd_config_get_integer(n, &bar_width);
+			if (err < 0) {
+				SNDERR("Invalid type for %s", id);
+				return -EINVAL;
+			}
+			continue;
+		}
+		if (strcmp(id, "decay_ms") == 0) {
+			err = snd_config_get_integer(n, &decay_ms);
+			if (err < 0) {
+				SNDERR("Invalid type for %s", id);
+				return -EINVAL;
+			}
+			continue;
+		}
+		if (strcmp(id, "peak_ms") == 0) {
+			err = snd_config_get_integer(n, &peak_ms);
+			if (err < 0) {
+				SNDERR("Invalid type for %s", id);
+				return -EINVAL;
+			}
+			continue;
+		}
+		SNDERR("Unknown field %s", id);
+		return -EINVAL;
+	}
+	if (bar_width < 0)
+		bar_width = BAR_WIDTH;
+	if (decay_ms < 0)
+		decay_ms = DECAY_MS;
+	if (peak_ms < 0)
+		peak_ms = PEAK_MS;
+	return snd_pcm_scope_level_open(pcm, name, bar_width, decay_ms, peak_ms,
+					&scope);
+}
diff --git a/src/rawmidi/Makefile.am b/src/rawmidi/Makefile.am
new file mode 100644
index 0000000..2470c7a
--- /dev/null
+++ b/src/rawmidi/Makefile.am
@@ -0,0 +1,12 @@
+EXTRA_LTLIBRARIES=librawmidi.la
+
+librawmidi_la_SOURCES = rawmidi.c rawmidi_hw.c rawmidi_symbols.c
+if BUILD_SEQ
+librawmidi_la_SOURCES += rawmidi_virt.c
+endif
+noinst_HEADERS = rawmidi_local.h
+
+all: librawmidi.la
+
+
+INCLUDES=-I$(top_srcdir)/include
diff --git a/src/rawmidi/rawmidi.c b/src/rawmidi/rawmidi.c
new file mode 100644
index 0000000..0bd6b96
--- /dev/null
+++ b/src/rawmidi/rawmidi.c
@@ -0,0 +1,1007 @@
+/**
+ * \file rawmidi/rawmidi.c
+ * \brief RawMidi Interface
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \date 2000-2001
+ *
+ * See the \ref rawmidi page for more details.
+ */
+/*
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+/*! \page rawmidi RawMidi interface
+
+<P>RawMidi Interface is designed to write or read raw (unchanged) MIDI
+data over the MIDI line without any timestamps defined in interface. MIDI
+stands Musical Instrument Digital Interface and more information about
+this standard can be found at http://www.midi.org.
+
+\section rawmidi_general_overview General overview
+
+The rawmidi implementation uses ring buffers to store outgoing and incoming
+MIDI stream. The buffer size is tunable and drivers report underruns for incoming
+stream as well.
+
+\section rawmidi_open Open handling
+
+RawMidi devices are opened exclusively for a selected direction.
+While more than one process may not open a given MIDI device in the same
+direction simultaneously, separate processes may open a single MIDI device
+in different directions (i.e. process one opens a MIDI device in write
+direction and process two opens the same device in read direction).
+
+\subsection rawmidi_open_nonblock Nonblocking open (flag)
+
+Using #SND_RAWMIDI_NONBLOCK flag for snd_rawmidi_open() or snd_rawmidi_open_lconf()
+instruct device driver to return the -EBUSY error when device is already occupied
+with another application. This flag also changes behaviour of snd_rawmidi_write()
+and snd_rawmidi_read() returning -EAGAIN when no more bytes can be processed.
+
+Note: In opposite (default) behaviour, application is blocked until device resources
+are free.
+
+\subsection rawmidi_open_append Append open (flag)
+
+Using #SND_RAWMIDI_APPEND flag (output only) instruct device driver to append
+contents of written buffer - passed by snd_rawmidi_write() - atomically
+to output ring buffer in the kernel space. This flag also means that device
+is not opened exclusively, so more applications can share given rawmidi device.
+Note that applications must send the whole MIDI message including the running status,
+because another writting application might break the MIDI message in the output
+buffer.
+
+\subsection rawmidi_open_sync Sync open (flag)
+
+Using #SND_RAWMIDI_SYNC flag (output only) assures that the contents of output
+buffer specified using snd_rawmidi_write() is always drained before the function
+exits. This behaviour is same like 'snd_rawmidi_write() followed by
+snd_rawmidi_drain() immediately'.
+
+\subsection rawmidi_io I/O handling
+
+There is only standard read/write access to device internal ring buffer. Use
+snd_rawmidi_read() and snd_rawmidi_write() functions to obtain / write MIDI bytes.
+
+\subsection rawmidi_dev_names RawMidi naming conventions
+
+The ALSA library uses a generic string representation for names of devices.
+The devices might be virtual, physical or a mix of both. The generic string
+is passed to \link ::snd_rawmidi_open() \endlink or \link ::snd_rawmidi_open_lconf() \endlink.
+It contains two parts: device name and arguments. Devices and arguments are described
+in configuration files. The usual place for default definitions is at /usr/share/alsa/alsa.conf.
+
+\subsection rawmidi_dev_names_default 
+
+The default device is equal to hw device. The defaults are used:
+
+defaults.rawmidi.card 0
+defaults.rawmidi.device 0
+defaults.rawmidi.subdevice -1
+
+These defaults can be freely overwritten in local configuration files.
+
+Example:
+
+\code
+default
+\endcode
+
+\subsection rawmidi_dev_names_hw HW device
+
+The hw device description uses the hw plugin. The three arguments (in order: CARD,DEV,SUBDEV)
+specify card number or identifier, device number and subdevice number (-1 means any).
+
+Example:
+
+\code
+hw
+hw:0
+hw:0,0
+hw:supersonic,1
+hw:soundwave,1,2
+hw:DEV=1,CARD=soundwave,SUBDEV=2
+\endcode
+
+\section rawmidi_examples Examples
+
+The full featured examples with cross-links:
+
+\par Simple input/output test program
+\ref example_test_rawmidi "example code"
+\par
+This example shows open and read/write rawmidi operations.
+
+*/
+
+/**
+ * \example ../test/rawmidi.c
+ * \anchor example_test_rawmidi
+ */
+ 
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <string.h>
+#include "rawmidi_local.h"
+
+/**
+ * \brief setup the default parameters
+ * \param rawmidi RawMidi handle
+ * \param params pointer to a snd_rawmidi_params_t structure
+ * \return 0 on success otherwise a negative error code
+ */
+static int snd_rawmidi_params_default(snd_rawmidi_t *rawmidi, snd_rawmidi_params_t *params)
+{
+	assert(rawmidi);
+	assert(params);
+	params->buffer_size = page_size();
+	params->avail_min = 1;
+	params->no_active_sensing = 1;
+	return 0;
+}
+
+static int snd_rawmidi_open_conf(snd_rawmidi_t **inputp, snd_rawmidi_t **outputp,
+				 const char *name, snd_config_t *rawmidi_root,
+				 snd_config_t *rawmidi_conf, int mode)
+{
+	const char *str;
+	char buf[256];
+	int err;
+	snd_config_t *conf, *type_conf = NULL;
+	snd_config_iterator_t i, next;
+	snd_rawmidi_params_t params;
+	const char *id;
+	const char *lib = NULL, *open_name = NULL;
+	int (*open_func)(snd_rawmidi_t **, snd_rawmidi_t **,
+			 const char *, snd_config_t *, snd_config_t *, int) = NULL;
+#ifndef PIC
+	extern void *snd_rawmidi_open_symbols(void);
+#endif
+	void *h = NULL;
+	if (snd_config_get_type(rawmidi_conf) != SND_CONFIG_TYPE_COMPOUND) {
+		if (name)
+			SNDERR("Invalid type for RAWMIDI %s definition", name);
+		else
+			SNDERR("Invalid type for RAWMIDI definition");
+		return -EINVAL;
+	}
+	err = snd_config_search(rawmidi_conf, "type", &conf);
+	if (err < 0) {
+		SNDERR("type is not defined");
+		return err;
+	}
+	err = snd_config_get_id(conf, &id);
+	if (err < 0) {
+		SNDERR("unable to get id");
+		return err;
+	}
+	err = snd_config_get_string(conf, &str);
+	if (err < 0) {
+		SNDERR("Invalid type for %s", id);
+		return err;
+	}
+	err = snd_config_search_definition(rawmidi_root, "rawmidi_type", str, &type_conf);
+	if (err >= 0) {
+		if (snd_config_get_type(type_conf) != SND_CONFIG_TYPE_COMPOUND) {
+			SNDERR("Invalid type for RAWMIDI type %s definition", str);
+			err = -EINVAL;
+			goto _err;
+		}
+		snd_config_for_each(i, next, type_conf) {
+			snd_config_t *n = snd_config_iterator_entry(i);
+			const char *id;
+			if (snd_config_get_id(n, &id) < 0)
+				continue;
+			if (strcmp(id, "comment") == 0)
+				continue;
+			if (strcmp(id, "lib") == 0) {
+				err = snd_config_get_string(n, &lib);
+				if (err < 0) {
+					SNDERR("Invalid type for %s", id);
+					goto _err;
+				}
+				continue;
+			}
+			if (strcmp(id, "open") == 0) {
+				err = snd_config_get_string(n, &open_name);
+				if (err < 0) {
+					SNDERR("Invalid type for %s", id);
+					goto _err;
+				}
+				continue;
+			}
+			SNDERR("Unknown field %s", id);
+			err = -EINVAL;
+			goto _err;
+		}
+	}
+	if (!open_name) {
+		open_name = buf;
+		snprintf(buf, sizeof(buf), "_snd_rawmidi_%s_open", str);
+	}
+#ifndef PIC
+	snd_rawmidi_open_symbols();
+#endif
+	h = snd_dlopen(lib, RTLD_NOW);
+	if (h)
+		open_func = snd_dlsym(h, open_name, SND_DLSYM_VERSION(SND_RAWMIDI_DLSYM_VERSION));
+	err = 0;
+	if (!h) {
+		SNDERR("Cannot open shared library %s", lib);
+		err = -ENOENT;
+	} else if (!open_func) {
+		SNDERR("symbol %s is not defined inside %s", open_name, lib);
+		snd_dlclose(h);
+		err = -ENXIO;
+	}
+       _err:
+	if (type_conf)
+		snd_config_delete(type_conf);
+	if (err >= 0)
+		err = open_func(inputp, outputp, name, rawmidi_root, rawmidi_conf, mode);
+	if (err < 0)
+		return err;
+	if (inputp) {
+		(*inputp)->dl_handle = h; h = NULL;
+		snd_rawmidi_params_default(*inputp, &params);
+		err = snd_rawmidi_params(*inputp, &params);
+		assert(err >= 0);
+	}
+	if (outputp) {
+		(*outputp)->dl_handle = h;
+		snd_rawmidi_params_default(*outputp, &params);
+		err = snd_rawmidi_params(*outputp, &params);
+		assert(err >= 0);
+	}
+	return 0;
+}
+
+static int snd_rawmidi_open_noupdate(snd_rawmidi_t **inputp, snd_rawmidi_t **outputp,
+				     snd_config_t *root, const char *name, int mode)
+{
+	int err;
+	snd_config_t *rawmidi_conf;
+	err = snd_config_search_definition(root, "rawmidi", name, &rawmidi_conf);
+	if (err < 0) {
+		SNDERR("Unknown RawMidi %s", name);
+		return err;
+	}
+	err = snd_rawmidi_open_conf(inputp, outputp, name, root, rawmidi_conf, mode);
+	snd_config_delete(rawmidi_conf);
+	return err;
+}
+
+/**
+ * \brief Opens a new connection to the RawMidi interface.
+ * \param inputp Returned input handle (NULL if not wanted)
+ * \param outputp Returned output handle (NULL if not wanted)
+ * \param name ASCII identifier of the RawMidi handle
+ * \param mode Open mode
+ * \return 0 on success otherwise a negative error code
+ *
+ * Opens a new connection to the RawMidi interface specified with
+ * an ASCII identifier and mode.
+ */
+int snd_rawmidi_open(snd_rawmidi_t **inputp, snd_rawmidi_t **outputp,
+		     const char *name, int mode)
+{
+	int err;
+	assert((inputp || outputp) && name);
+	err = snd_config_update();
+	if (err < 0)
+		return err;
+	return snd_rawmidi_open_noupdate(inputp, outputp, snd_config, name, mode);
+}
+
+/**
+ * \brief Opens a new connection to the RawMidi interface using local configuration
+ * \param inputp Returned input handle (NULL if not wanted)
+ * \param outputp Returned output handle (NULL if not wanted)
+ * \param name ASCII identifier of the RawMidi handle
+ * \param mode Open mode
+ * \param lconf Local configuration
+ * \return 0 on success otherwise a negative error code
+ *
+ * Opens a new connection to the RawMidi interface specified with
+ * an ASCII identifier and mode.
+ */
+int snd_rawmidi_open_lconf(snd_rawmidi_t **inputp, snd_rawmidi_t **outputp,
+			   const char *name, int mode, snd_config_t *lconf)
+{
+	assert((inputp || outputp) && name && lconf);
+	return snd_rawmidi_open_noupdate(inputp, outputp, lconf, name, mode);
+}
+
+/**
+ * \brief close RawMidi handle
+ * \param rawmidi RawMidi handle
+ * \return 0 on success otherwise a negative error code
+ *
+ * Closes the specified RawMidi handle and frees all associated
+ * resources.
+ */
+int snd_rawmidi_close(snd_rawmidi_t *rawmidi)
+{
+	int err;
+  	assert(rawmidi);
+	err = rawmidi->ops->close(rawmidi);
+	free(rawmidi->name);
+	if (rawmidi->dl_handle)
+		snd_dlclose(rawmidi->dl_handle);
+	free(rawmidi);
+	return err;
+}
+
+/**
+ * \brief get identifier of RawMidi handle
+ * \param rawmidi a RawMidi handle
+ * \return ascii identifier of RawMidi handle
+ *
+ * Returns the ASCII identifier of given RawMidi handle. It's the same
+ * identifier specified in snd_rawmidi_open().
+ */
+const char *snd_rawmidi_name(snd_rawmidi_t *rawmidi)
+{
+	assert(rawmidi);
+	return rawmidi->name;
+}
+
+/**
+ * \brief get type of RawMidi handle
+ * \param rawmidi a RawMidi handle
+ * \return type of RawMidi handle
+ *
+ * Returns the type #snd_rawmidi_type_t of given RawMidi handle.
+ */
+snd_rawmidi_type_t snd_rawmidi_type(snd_rawmidi_t *rawmidi)
+{
+	assert(rawmidi);
+	return rawmidi->type;
+}
+
+/**
+ * \brief get stream (direction) of RawMidi handle
+ * \param rawmidi a RawMidi handle
+ * \return stream of RawMidi handle
+ *
+ * Returns the stream #snd_rawmidi_stream_t of given RawMidi handle.
+ */
+snd_rawmidi_stream_t snd_rawmidi_stream(snd_rawmidi_t *rawmidi)
+{
+	assert(rawmidi);
+	return rawmidi->stream;
+}
+
+/**
+ * \brief get count of poll descriptors for RawMidi handle
+ * \param rawmidi RawMidi handle
+ * \return count of poll descriptors
+ */
+int snd_rawmidi_poll_descriptors_count(snd_rawmidi_t *rawmidi)
+{
+	assert(rawmidi);
+	return 1;
+}
+
+/**
+ * \brief get poll descriptors
+ * \param rawmidi RawMidi handle
+ * \param pfds array of poll descriptors
+ * \param space space in the poll descriptor array
+ * \return count of filled descriptors
+ */
+int snd_rawmidi_poll_descriptors(snd_rawmidi_t *rawmidi, struct pollfd *pfds, unsigned int space)
+{
+	assert(rawmidi);
+	if (space >= 1) {
+		pfds->fd = rawmidi->poll_fd;
+		pfds->events = rawmidi->stream == SND_RAWMIDI_STREAM_OUTPUT ? (POLLOUT|POLLERR|POLLNVAL) : (POLLIN|POLLERR|POLLNVAL);
+		return 1;
+	}
+	return 0;
+}
+
+/**
+ * \brief get returned events from poll descriptors
+ * \param rawmidi rawmidi RawMidi handle
+ * \param pfds array of poll descriptors
+ * \param nfds count of poll descriptors
+ * \param revents returned events
+ * \return zero if success, otherwise a negative error code
+ */
+int snd_rawmidi_poll_descriptors_revents(snd_rawmidi_t *rawmidi, struct pollfd *pfds, unsigned int nfds, unsigned short *revents)
+{
+        assert(rawmidi && pfds && revents);
+        if (nfds == 1) {
+                *revents = pfds->revents;
+                return 0;
+        }
+        return -EINVAL;
+}
+
+/**
+ * \brief set nonblock mode
+ * \param rawmidi RawMidi handle
+ * \param nonblock 0 = block, 1 = nonblock mode
+ * \return 0 on success otherwise a negative error code
+ *
+ * The nonblock mode cannot be used when the stream is in
+ * #SND_RAWMIDI_APPEND state.
+ */
+int snd_rawmidi_nonblock(snd_rawmidi_t *rawmidi, int nonblock)
+{
+	int err;
+	assert(rawmidi);
+	assert(!(rawmidi->mode & SND_RAWMIDI_APPEND));
+	if ((err = rawmidi->ops->nonblock(rawmidi, nonblock)) < 0)
+		return err;
+	if (nonblock)
+		rawmidi->mode |= SND_RAWMIDI_NONBLOCK;
+	else
+		rawmidi->mode &= ~SND_RAWMIDI_NONBLOCK;
+	return 0;
+}
+
+/**
+ * \brief get size of the snd_rawmidi_info_t structure in bytes
+ * \return size of the snd_rawmidi_info_t structure in bytes
+ */
+size_t snd_rawmidi_info_sizeof()
+{
+	return sizeof(snd_rawmidi_info_t);
+}
+
+/**
+ * \brief allocate a new snd_rawmidi_info_t structure
+ * \param info returned pointer
+ * \return 0 on success otherwise a negative error code if fails
+ *
+ * Allocates a new snd_rawmidi_params_t structure using the standard
+ * malloc C library function.
+ */
+int snd_rawmidi_info_malloc(snd_rawmidi_info_t **info)
+{
+	assert(info);
+	*info = calloc(1, sizeof(snd_rawmidi_info_t));
+	if (!*info)
+		return -ENOMEM;
+	return 0;
+}
+
+/**
+ * \brief frees the snd_rawmidi_info_t structure
+ * \param info pointer to the snd_rawmidi_info_t structure to free
+ *
+ * Frees the given snd_rawmidi_params_t structure using the standard
+ * free C library function.
+ */
+void snd_rawmidi_info_free(snd_rawmidi_info_t *info)
+{
+	assert(info);
+	free(info);
+}
+
+/**
+ * \brief copy one snd_rawmidi_info_t structure to another
+ * \param dst destination snd_rawmidi_info_t structure
+ * \param src source snd_rawmidi_info_t structure
+ */
+void snd_rawmidi_info_copy(snd_rawmidi_info_t *dst, const snd_rawmidi_info_t *src)
+{
+	assert(dst && src);
+	*dst = *src;
+}
+
+/**
+ * \brief get rawmidi device number
+ * \param info pointer to a snd_rawmidi_info_t structure
+ * \return rawmidi device number
+ */
+unsigned int snd_rawmidi_info_get_device(const snd_rawmidi_info_t *info)
+{
+	assert(info);
+	return info->device;
+}
+
+/**
+ * \brief get rawmidi subdevice number
+ * \param info pointer to a snd_rawmidi_info_t structure
+ * \return rawmidi subdevice number
+ */
+unsigned int snd_rawmidi_info_get_subdevice(const snd_rawmidi_info_t *info)
+{
+	assert(info);
+	return info->subdevice;
+}
+
+/**
+ * \brief get rawmidi stream identification
+ * \param info pointer to a snd_rawmidi_info_t structure
+ * \return rawmidi stream identification
+ */
+snd_rawmidi_stream_t snd_rawmidi_info_get_stream(const snd_rawmidi_info_t *info)
+{
+	assert(info);
+	return info->stream;
+}
+
+/**
+ * \brief get rawmidi card number
+ * \param info pointer to a snd_rawmidi_info_t structure
+ * \return rawmidi card number
+ */
+int snd_rawmidi_info_get_card(const snd_rawmidi_info_t *info)
+{
+	assert(info);
+	return info->card;
+}
+
+/**
+ * \brief get rawmidi flags
+ * \param info pointer to a snd_rawmidi_info_t structure
+ * \return rawmidi flags
+ */
+unsigned int snd_rawmidi_info_get_flags(const snd_rawmidi_info_t *info)
+{
+	assert(info);
+	return info->flags;
+}
+
+/**
+ * \brief get rawmidi hardware driver identifier
+ * \param info pointer to a snd_rawmidi_info_t structure
+ * \return rawmidi hardware driver identifier
+ */
+const char *snd_rawmidi_info_get_id(const snd_rawmidi_info_t *info)
+{
+	assert(info);
+	return (const char *)info->id;
+}
+
+/**
+ * \brief get rawmidi hardware driver name
+ * \param info pointer to a snd_rawmidi_info_t structure
+ * \return rawmidi hardware driver name
+ */
+const char *snd_rawmidi_info_get_name(const snd_rawmidi_info_t *info)
+{
+	assert(info);
+	return (const char *)info->name;
+}
+
+/**
+ * \brief get rawmidi subdevice name
+ * \param info pointer to a snd_rawmidi_info_t structure
+ * \return rawmidi subdevice name
+ */
+const char *snd_rawmidi_info_get_subdevice_name(const snd_rawmidi_info_t *info)
+{
+	assert(info);
+	return (const char *)info->subname;
+}
+
+/**
+ * \brief get rawmidi count of subdevices
+ * \param info pointer to a snd_rawmidi_info_t structure
+ * \return rawmidi count of subdevices
+ */
+unsigned int snd_rawmidi_info_get_subdevices_count(const snd_rawmidi_info_t *info)
+{
+	assert(info);
+	return info->subdevices_count;
+}
+
+/**
+ * \brief get rawmidi available count of subdevices
+ * \param info pointer to a snd_rawmidi_info_t structure
+ * \return rawmidi available count of subdevices
+ */
+unsigned int snd_rawmidi_info_get_subdevices_avail(const snd_rawmidi_info_t *info)
+{
+	assert(info);
+	return info->subdevices_avail;
+}
+
+/**
+ * \brief set rawmidi device number
+ * \param info pointer to a snd_rawmidi_info_t structure
+ * \param val device number
+ */
+void snd_rawmidi_info_set_device(snd_rawmidi_info_t *info, unsigned int val)
+{
+	assert(info);
+	info->device = val;
+}
+
+/**
+ * \brief set rawmidi subdevice number
+ * \param info pointer to a snd_rawmidi_info_t structure
+ * \param val subdevice number
+ */
+void snd_rawmidi_info_set_subdevice(snd_rawmidi_info_t *info, unsigned int val)
+{
+	assert(info);
+	info->subdevice = val;
+}
+
+/**
+ * \brief set rawmidi stream identifier
+ * \param info pointer to a snd_rawmidi_info_t structure
+ * \param val rawmidi stream identifier
+ */
+void snd_rawmidi_info_set_stream(snd_rawmidi_info_t *info, snd_rawmidi_stream_t val)
+{
+	assert(info);
+	info->stream = val;
+}
+
+/**
+ * \brief get information about RawMidi handle
+ * \param rawmidi RawMidi handle
+ * \param info pointer to a snd_rawmidi_info_t structure to be filled
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_rawmidi_info(snd_rawmidi_t *rawmidi, snd_rawmidi_info_t * info)
+{
+	assert(rawmidi);
+	assert(info);
+	return rawmidi->ops->info(rawmidi, info);
+}
+
+/**
+ * \brief get size of the snd_rawmidi_params_t structure in bytes
+ * \return size of the snd_rawmidi_params_t structure in bytes
+ */
+size_t snd_rawmidi_params_sizeof()
+{
+	return sizeof(snd_rawmidi_params_t);
+}
+
+/**
+ * \brief allocate the snd_rawmidi_params_t structure
+ * \param params returned pointer
+ * \return 0 on success otherwise a negative error code if fails
+ *
+ * Allocates a new snd_rawmidi_params_t structure using the standard
+ * malloc C library function.
+ */
+int snd_rawmidi_params_malloc(snd_rawmidi_params_t **params)
+{
+	assert(params);
+	*params = calloc(1, sizeof(snd_rawmidi_params_t));
+	if (!*params)
+		return -ENOMEM;
+	return 0;
+}
+
+/**
+ * \brief frees the snd_rawmidi_params_t structure
+ * \param params pointer to the #snd_rawmidi_params_t structure to free
+ *
+ * Frees the given snd_rawmidi_params_t structure using the standard
+ * free C library function.
+ */
+void snd_rawmidi_params_free(snd_rawmidi_params_t *params)
+{
+	assert(params);
+	free(params);
+}
+
+/**
+ * \brief copy one snd_rawmidi_params_t structure to another
+ * \param dst destination snd_rawmidi_params_t structure
+ * \param src source snd_rawmidi_params_t structure
+ */
+void snd_rawmidi_params_copy(snd_rawmidi_params_t *dst, const snd_rawmidi_params_t *src)
+{
+	assert(dst && src);
+	*dst = *src;
+}
+
+/**
+ * \brief set rawmidi I/O ring buffer size
+ * \param rawmidi RawMidi handle
+ * \param params pointer to a snd_rawmidi_params_t structure
+ * \param val size in bytes
+ * \return 0 on success otherwise a negative error code
+ */
+#ifndef DOXYGEN
+int snd_rawmidi_params_set_buffer_size(snd_rawmidi_t *rawmidi ATTRIBUTE_UNUSED, snd_rawmidi_params_t *params, size_t val)
+#else
+int snd_rawmidi_params_set_buffer_size(snd_rawmidi_t *rawmidi, snd_rawmidi_params_t *params, size_t val)
+#endif
+{
+	assert(rawmidi && params);
+	assert(val > params->avail_min);
+	params->buffer_size = val;
+	return 0;
+}
+
+/**
+ * \brief get rawmidi I/O ring buffer size
+ * \param params pointer to a snd_rawmidi_params_t structure
+ * \return size of rawmidi I/O ring buffer in bytes
+ */
+size_t snd_rawmidi_params_get_buffer_size(const snd_rawmidi_params_t *params)
+{
+	assert(params);
+	return params->buffer_size;
+}
+
+/**
+ * \brief set minimum available bytes in rawmidi I/O ring buffer for wakeup
+ * \param rawmidi RawMidi handle
+ * \param params pointer to a snd_rawmidi_params_t structure
+ * \param val desired value
+ */
+#ifndef DOXYGEN
+int snd_rawmidi_params_set_avail_min(snd_rawmidi_t *rawmidi ATTRIBUTE_UNUSED, snd_rawmidi_params_t *params, size_t val)
+#else
+int snd_rawmidi_params_set_avail_min(snd_rawmidi_t *rawmidi, snd_rawmidi_params_t *params, size_t val)
+#endif
+{
+	assert(rawmidi && params);
+	assert(val < params->buffer_size);
+	params->avail_min = val;
+	return 0;
+}
+
+/**
+ * \brief get minimum available bytes in rawmidi I/O ring buffer for wakeup
+ * \param params pointer to snd_rawmidi_params_t structure
+ * \return minimum available bytes
+ */
+size_t snd_rawmidi_params_get_avail_min(const snd_rawmidi_params_t *params)
+{
+	assert(params);
+	return params->avail_min;
+}
+
+/**
+ * \brief set no-active-sensing action on snd_rawmidi_close()
+ * \param rawmidi RawMidi handle
+ * \param params pointer to snd_rawmidi_params_t structure
+ * \param val value: 0 = enable to send the active sensing message, 1 = disable
+ * \return 0 on success otherwise a negative error code
+ */
+#ifndef DOXYGEN
+int snd_rawmidi_params_set_no_active_sensing(snd_rawmidi_t *rawmidi ATTRIBUTE_UNUSED, snd_rawmidi_params_t *params, int val)
+#else
+int snd_rawmidi_params_set_no_active_sensing(snd_rawmidi_t *rawmidi, snd_rawmidi_params_t *params, int val)
+#endif
+{
+	assert(rawmidi && params);
+	params->no_active_sensing = val;
+	return 0;
+}
+
+/**
+ * \brief get no-active-sensing action status
+ * \param params pointer to snd_rawmidi_params_t structure
+ * \return the current status (0 = enable, 1 = disable the active sensing message)
+ */
+int snd_rawmidi_params_get_no_active_sensing(const snd_rawmidi_params_t *params)
+{
+	assert(params);
+	return params->no_active_sensing;
+}
+
+/**
+ * \brief set parameters about rawmidi stream
+ * \param rawmidi RawMidi handle
+ * \param params pointer to a snd_rawmidi_params_t structure to be filled
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_rawmidi_params(snd_rawmidi_t *rawmidi, snd_rawmidi_params_t * params)
+{
+	int err;
+	assert(rawmidi);
+	assert(params);
+	err = rawmidi->ops->params(rawmidi, params);
+	if (err < 0)
+		return err;
+	rawmidi->buffer_size = params->buffer_size;
+	rawmidi->avail_min = params->avail_min;
+	rawmidi->no_active_sensing = params->no_active_sensing;
+	return 0;
+}
+
+/**
+ * \brief get current parameters about rawmidi stream
+ * \param rawmidi RawMidi handle
+ * \param params pointer to a snd_rawmidi_params_t structure to be filled
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_rawmidi_params_current(snd_rawmidi_t *rawmidi, snd_rawmidi_params_t *params)
+{
+	assert(rawmidi);
+	assert(params);
+	params->buffer_size = rawmidi->buffer_size;
+	params->avail_min = rawmidi->avail_min;
+	params->no_active_sensing = rawmidi->no_active_sensing;
+	return 0;
+}
+
+/**
+ * \brief get size of the snd_rawmidi_status_t structure in bytes
+ * \return size of the snd_rawmidi_status_t structure in bytes
+ */
+size_t snd_rawmidi_status_sizeof()
+{
+	return sizeof(snd_rawmidi_status_t);
+}
+
+/**
+ * \brief allocate the snd_rawmidi_status_t structure
+ * \param ptr returned pointer
+ * \return 0 on success otherwise a negative error code if fails
+ *
+ * Allocates a new snd_rawmidi_status_t structure using the standard
+ * malloc C library function.
+ */
+int snd_rawmidi_status_malloc(snd_rawmidi_status_t **ptr)
+{
+	assert(ptr);
+	*ptr = calloc(1, sizeof(snd_rawmidi_status_t));
+	if (!*ptr)
+		return -ENOMEM;
+	return 0;
+}
+
+/**
+ * \brief frees the snd_rawmidi_status_t structure
+ * \param status pointer to the snd_rawmidi_status_t structure to free
+ *
+ * Frees the given snd_rawmidi_status_t structure using the standard
+ * free C library function.
+ */
+void snd_rawmidi_status_free(snd_rawmidi_status_t *status)
+{
+	assert(status);
+	free(status);
+}
+
+/**
+ * \brief copy one snd_rawmidi_status_t structure to another
+ * \param dst destination snd_rawmidi_status_t structure
+ * \param src source snd_rawmidi_status_t structure
+ */
+void snd_rawmidi_status_copy(snd_rawmidi_status_t *dst, const snd_rawmidi_status_t *src)
+{
+	assert(dst && src);
+	*dst = *src;
+}
+
+/**
+ * \brief get the start timestamp
+ * \param status pointer to a snd_rawmidi_status_t structure
+ * \param tstamp returned timestamp value
+ */
+void snd_rawmidi_status_get_tstamp(const snd_rawmidi_status_t *status, snd_htimestamp_t *tstamp)
+{
+	assert(status && tstamp);
+	*tstamp = status->tstamp;
+}
+
+/**
+ * \brief get current available bytes in the rawmidi I/O ring buffer
+ * \param status pointer to a snd_rawmidi_status_t structure
+ * \return current available bytes in the rawmidi I/O ring buffer
+ */
+size_t snd_rawmidi_status_get_avail(const snd_rawmidi_status_t *status)
+{
+	assert(status);
+	return status->avail;
+}
+
+/**
+ * \brief get count of xruns
+ * \param status pointer to a snd_rawmidi_status_t structure
+ * \return count of xruns
+ */
+size_t snd_rawmidi_status_get_xruns(const snd_rawmidi_status_t *status)
+{
+	assert(status);
+	return status->xruns;
+}
+
+/**
+ * \brief get status of rawmidi stream
+ * \param rawmidi RawMidi handle
+ * \param status pointer to a snd_rawmidi_status_t structure to be filled
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_rawmidi_status(snd_rawmidi_t *rawmidi, snd_rawmidi_status_t * status)
+{
+	assert(rawmidi);
+	assert(status);
+	return rawmidi->ops->status(rawmidi, status);
+}
+
+/**
+ * \brief drop all bytes in the rawmidi I/O ring buffer immediately
+ * \param rawmidi RawMidi handle
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_rawmidi_drop(snd_rawmidi_t *rawmidi)
+{
+	assert(rawmidi);
+	return rawmidi->ops->drop(rawmidi);
+}
+
+/**
+ * \brief drain all bytes in the rawmidi I/O ring buffer
+ * \param rawmidi RawMidi handle
+ * \return 0 on success otherwise a negative error code
+ *
+ * Waits until all MIDI bytes are not drained (sent) to the
+ * hardware device.
+ */
+int snd_rawmidi_drain(snd_rawmidi_t *rawmidi)
+{
+	assert(rawmidi);
+	return rawmidi->ops->drain(rawmidi);
+}
+
+/**
+ * \brief write MIDI bytes to MIDI stream
+ * \param rawmidi RawMidi handle
+ * \param buffer buffer containing MIDI bytes
+ * \param size output buffer size in bytes
+ */
+ssize_t snd_rawmidi_write(snd_rawmidi_t *rawmidi, const void *buffer, size_t size)
+{
+	assert(rawmidi);
+	assert(rawmidi->stream == SND_RAWMIDI_STREAM_OUTPUT);
+	assert(buffer || size == 0);
+	return rawmidi->ops->write(rawmidi, buffer, size);
+}
+
+/**
+ * \brief read MIDI bytes from MIDI stream
+ * \param rawmidi RawMidi handle
+ * \param buffer buffer to store the input MIDI bytes
+ * \param size input buffer size in bytes
+ */
+ssize_t snd_rawmidi_read(snd_rawmidi_t *rawmidi, void *buffer, size_t size)
+{
+	assert(rawmidi);
+	assert(rawmidi->stream == SND_RAWMIDI_STREAM_INPUT);
+	assert(buffer || size == 0);
+	return (rawmidi->ops->read)(rawmidi, buffer, size);
+}
+
+#ifndef DOC_HIDDEN
+int snd_rawmidi_conf_generic_id(const char *id)
+{
+	static const char ids[][8] = {
+		"comment",
+		"type",
+		"hint",
+	};
+	unsigned int k;
+
+	for (k = 0; k < sizeof ids / sizeof *ids; ++k) {
+		if (strcmp(id, ids[k]) == 0)
+			return 1;
+	}
+	return 0;
+}
+#endif
diff --git a/src/rawmidi/rawmidi_hw.c b/src/rawmidi/rawmidi_hw.c
new file mode 100644
index 0000000..e08dca7
--- /dev/null
+++ b/src/rawmidi/rawmidi_hw.c
@@ -0,0 +1,361 @@
+/*
+ *  RawMIDI - Hardware
+ *  Copyright (c) 2000 by Jaroslav Kysela <perex@perex.cz>
+ *                        Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include "../control/control_local.h"
+#include "rawmidi_local.h"
+
+#ifndef PIC
+/* entry for static linking */
+const char *_snd_module_rawmidi_hw = "";
+#endif
+
+#define SNDRV_FILE_RAWMIDI		ALSA_DEVICE_DIRECTORY "midiC%iD%i"
+#define SNDRV_RAWMIDI_VERSION_MAX	SNDRV_PROTOCOL_VERSION(2, 0, 0)
+
+#ifndef DOC_HIDDEN
+typedef struct {
+	int open;
+	int fd;
+	int card, device, subdevice;
+} snd_rawmidi_hw_t;
+#endif
+
+static int snd_rawmidi_hw_close(snd_rawmidi_t *rmidi)
+{
+	snd_rawmidi_hw_t *hw = rmidi->private_data;
+	int err = 0;
+
+	hw->open--;
+	if (hw->open)
+		return 0;
+	if (close(hw->fd)) {
+		err = -errno;
+		SYSERR("close failed\n");
+	}
+	free(hw);
+	return err;
+}
+
+static int snd_rawmidi_hw_nonblock(snd_rawmidi_t *rmidi, int nonblock)
+{
+	snd_rawmidi_hw_t *hw = rmidi->private_data;
+	long flags;
+
+	if ((flags = fcntl(hw->fd, F_GETFL)) < 0) {
+		SYSERR("F_GETFL failed");
+		return -errno;
+	}
+	if (nonblock)
+		flags |= O_NONBLOCK;
+	else
+		flags &= ~O_NONBLOCK;
+	if (fcntl(hw->fd, F_SETFL, flags) < 0) {
+		SYSERR("F_SETFL for O_NONBLOCK failed");
+		return -errno;
+	}
+	return 0;
+}
+
+static int snd_rawmidi_hw_info(snd_rawmidi_t *rmidi, snd_rawmidi_info_t * info)
+{
+	snd_rawmidi_hw_t *hw = rmidi->private_data;
+	info->stream = rmidi->stream;
+	if (ioctl(hw->fd, SNDRV_RAWMIDI_IOCTL_INFO, info) < 0) {
+		SYSERR("SNDRV_RAWMIDI_IOCTL_INFO failed");
+		return -errno;
+	}
+	return 0;
+}
+
+static int snd_rawmidi_hw_params(snd_rawmidi_t *rmidi, snd_rawmidi_params_t * params)
+{
+	snd_rawmidi_hw_t *hw = rmidi->private_data;
+	params->stream = rmidi->stream;
+	if (ioctl(hw->fd, SNDRV_RAWMIDI_IOCTL_PARAMS, params) < 0) {
+		SYSERR("SNDRV_RAWMIDI_IOCTL_PARAMS failed");
+		return -errno;
+	}
+	return 0;
+}
+
+static int snd_rawmidi_hw_status(snd_rawmidi_t *rmidi, snd_rawmidi_status_t * status)
+{
+	snd_rawmidi_hw_t *hw = rmidi->private_data;
+	status->stream = rmidi->stream;
+	if (ioctl(hw->fd, SNDRV_RAWMIDI_IOCTL_STATUS, status) < 0) {
+		SYSERR("SNDRV_RAWMIDI_IOCTL_STATUS failed");
+		return -errno;
+	}
+	return 0;
+}
+
+static int snd_rawmidi_hw_drop(snd_rawmidi_t *rmidi)
+{
+	snd_rawmidi_hw_t *hw = rmidi->private_data;
+	int str = rmidi->stream;
+	if (ioctl(hw->fd, SNDRV_RAWMIDI_IOCTL_DROP, &str) < 0) {
+		SYSERR("SNDRV_RAWMIDI_IOCTL_DROP failed");
+		return -errno;
+	}
+	return 0;
+}
+
+static int snd_rawmidi_hw_drain(snd_rawmidi_t *rmidi)
+{
+	snd_rawmidi_hw_t *hw = rmidi->private_data;
+	int str = rmidi->stream;
+	if (ioctl(hw->fd, SNDRV_RAWMIDI_IOCTL_DRAIN, &str) < 0) {
+		SYSERR("SNDRV_RAWMIDI_IOCTL_DRAIN failed");
+		return -errno;
+	}
+	return 0;
+}
+
+static ssize_t snd_rawmidi_hw_write(snd_rawmidi_t *rmidi, const void *buffer, size_t size)
+{
+	snd_rawmidi_hw_t *hw = rmidi->private_data;
+	ssize_t result;
+	result = write(hw->fd, buffer, size);
+	if (result < 0)
+		return -errno;
+	return result;
+}
+
+static ssize_t snd_rawmidi_hw_read(snd_rawmidi_t *rmidi, void *buffer, size_t size)
+{
+	snd_rawmidi_hw_t *hw = rmidi->private_data;
+	ssize_t result;
+	result = read(hw->fd, buffer, size);
+	if (result < 0)
+		return -errno;
+	return result;
+}
+
+static const snd_rawmidi_ops_t snd_rawmidi_hw_ops = {
+	.close = snd_rawmidi_hw_close,
+	.nonblock = snd_rawmidi_hw_nonblock,
+	.info = snd_rawmidi_hw_info,
+	.params = snd_rawmidi_hw_params,
+	.status = snd_rawmidi_hw_status,
+	.drop = snd_rawmidi_hw_drop,
+	.drain = snd_rawmidi_hw_drain,
+	.write = snd_rawmidi_hw_write,
+	.read = snd_rawmidi_hw_read,
+};
+
+
+int snd_rawmidi_hw_open(snd_rawmidi_t **inputp, snd_rawmidi_t **outputp,
+			const char *name, int card, int device, int subdevice,
+			int mode)
+{
+	int fd, ver, ret;
+	int attempt = 0;
+	char filename[sizeof(SNDRV_FILE_RAWMIDI) + 20];
+	snd_ctl_t *ctl;
+	snd_rawmidi_t *rmidi;
+	snd_rawmidi_hw_t *hw = NULL;
+	snd_rawmidi_info_t info;
+	int fmode;
+
+	if (inputp)
+		*inputp = NULL;
+	if (outputp)
+		*outputp = NULL;
+	
+	if ((ret = snd_ctl_hw_open(&ctl, NULL, card, 0)) < 0)
+		return ret;
+	sprintf(filename, SNDRV_FILE_RAWMIDI, card, device);
+
+      __again:
+      	if (attempt++ > 3) {
+      		snd_ctl_close(ctl);
+      		return -EBUSY;
+      	}
+      	ret = snd_ctl_rawmidi_prefer_subdevice(ctl, subdevice);
+	if (ret < 0) {
+		snd_ctl_close(ctl);
+		return ret;
+	}
+
+	if (!inputp)
+		fmode = O_WRONLY;
+	else if (!outputp)
+		fmode = O_RDONLY;
+	else
+		fmode = O_RDWR;
+
+	if (mode & SND_RAWMIDI_APPEND) {
+		assert(outputp);
+		fmode |= O_APPEND;
+	}
+
+	if (mode & SND_RAWMIDI_NONBLOCK) {
+		fmode |= O_NONBLOCK;
+	}
+	
+	if (mode & SND_RAWMIDI_SYNC) {
+		fmode |= O_SYNC;
+	}
+
+	assert(!(mode & ~(SND_RAWMIDI_APPEND|SND_RAWMIDI_NONBLOCK|SND_RAWMIDI_SYNC)));
+
+	fd = snd_open_device(filename, fmode);
+	if (fd < 0) {
+		snd_card_load(card);
+		fd = snd_open_device(filename, fmode);
+		if (fd < 0) {
+			snd_ctl_close(ctl);
+			SYSERR("open %s failed", filename);
+			return -errno;
+		}
+	}
+	if (ioctl(fd, SNDRV_RAWMIDI_IOCTL_PVERSION, &ver) < 0) {
+		ret = -errno;
+		SYSERR("SNDRV_RAWMIDI_IOCTL_PVERSION failed");
+		close(fd);
+		snd_ctl_close(ctl);
+		return ret;
+	}
+	if (SNDRV_PROTOCOL_INCOMPATIBLE(ver, SNDRV_RAWMIDI_VERSION_MAX)) {
+		close(fd);
+		snd_ctl_close(ctl);
+		return -SND_ERROR_INCOMPATIBLE_VERSION;
+	}
+	if (subdevice >= 0) {
+		memset(&info, 0, sizeof(info));
+		info.stream = outputp ? SNDRV_RAWMIDI_STREAM_OUTPUT : SNDRV_RAWMIDI_STREAM_INPUT;
+		if (ioctl(fd, SNDRV_RAWMIDI_IOCTL_INFO, &info) < 0) {
+			SYSERR("SNDRV_RAWMIDI_IOCTL_INFO failed");
+			ret = -errno;
+			close(fd);
+			snd_ctl_close(ctl);
+			return ret;
+		}
+		if (info.subdevice != (unsigned int) subdevice) {
+			close(fd);
+			goto __again;
+		}
+	}
+	snd_ctl_close(ctl);
+
+	hw = calloc(1, sizeof(snd_rawmidi_hw_t));
+	if (hw == NULL)
+		goto _nomem;
+	hw->card = card;
+	hw->device = device;
+	hw->subdevice = subdevice;
+	hw->fd = fd;
+
+	if (inputp) {
+		rmidi = calloc(1, sizeof(snd_rawmidi_t));
+		if (rmidi == NULL)
+			goto _nomem;
+		if (name)
+			rmidi->name = strdup(name);
+		rmidi->type = SND_RAWMIDI_TYPE_HW;
+		rmidi->stream = SND_RAWMIDI_STREAM_INPUT;
+		rmidi->mode = mode;
+		rmidi->poll_fd = fd;
+		rmidi->ops = &snd_rawmidi_hw_ops;
+		rmidi->private_data = hw;
+		hw->open++;
+		*inputp = rmidi;
+	}
+	if (outputp) {
+		rmidi = calloc(1, sizeof(snd_rawmidi_t));
+		if (rmidi == NULL)
+			goto _nomem;
+		if (name)
+			rmidi->name = strdup(name);
+		rmidi->type = SND_RAWMIDI_TYPE_HW;
+		rmidi->stream = SND_RAWMIDI_STREAM_OUTPUT;
+		rmidi->mode = mode;
+		rmidi->poll_fd = fd;
+		rmidi->ops = &snd_rawmidi_hw_ops;
+		rmidi->private_data = hw;
+		hw->open++;
+		*outputp = rmidi;
+	}
+	return 0;
+
+ _nomem:
+	close(fd);
+	free(hw);
+	if (inputp)
+		free(*inputp);
+	if (outputp)
+		free(*outputp);
+	return -ENOMEM;
+}
+
+int _snd_rawmidi_hw_open(snd_rawmidi_t **inputp, snd_rawmidi_t **outputp,
+			 char *name, snd_config_t *root ATTRIBUTE_UNUSED,
+			 snd_config_t *conf, int mode)
+{
+	snd_config_iterator_t i, next;
+	long card = -1, device = 0, subdevice = -1;
+	const char *str;
+	int err;
+	snd_config_for_each(i, next, conf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (snd_rawmidi_conf_generic_id(id))
+			continue;
+		if (strcmp(id, "card") == 0) {
+			err = snd_config_get_integer(n, &card);
+			if (err < 0) {
+				err = snd_config_get_string(n, &str);
+				if (err < 0)
+					return -EINVAL;
+				card = snd_card_get_index(str);
+				if (card < 0)
+					return card;
+			}
+			continue;
+		}
+		if (strcmp(id, "device") == 0) {
+			err = snd_config_get_integer(n, &device);
+			if (err < 0)
+				return err;
+			continue;
+		}
+		if (strcmp(id, "subdevice") == 0) {
+			err = snd_config_get_integer(n, &subdevice);
+			if (err < 0)
+				return err;
+			continue;
+		}
+		return -EINVAL;
+	}
+	if (card < 0)
+		return -EINVAL;
+	return snd_rawmidi_hw_open(inputp, outputp, name, card, device, subdevice, mode);
+}
+SND_DLSYM_BUILD_VERSION(_snd_rawmidi_hw_open, SND_RAWMIDI_DLSYM_VERSION);
diff --git a/src/rawmidi/rawmidi_local.h b/src/rawmidi/rawmidi_local.h
new file mode 100644
index 0000000..3388502
--- /dev/null
+++ b/src/rawmidi/rawmidi_local.h
@@ -0,0 +1,61 @@
+/*
+ *  Rawmidi interface - local header file
+ *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include "local.h"
+
+typedef struct {
+	int (*close)(snd_rawmidi_t *rawmidi);
+	int (*nonblock)(snd_rawmidi_t *rawmidi, int nonblock);
+	int (*info)(snd_rawmidi_t *rawmidi, snd_rawmidi_info_t *info);
+	int (*params)(snd_rawmidi_t *rawmidi, snd_rawmidi_params_t *params);
+	int (*status)(snd_rawmidi_t *rawmidi, snd_rawmidi_status_t *status);
+	int (*drop)(snd_rawmidi_t *rawmidi);
+	int (*drain)(snd_rawmidi_t *rawmidi);
+	ssize_t (*write)(snd_rawmidi_t *rawmidi, const void *buffer, size_t size);
+	ssize_t (*read)(snd_rawmidi_t *rawmidi, void *buffer, size_t size);
+} snd_rawmidi_ops_t;
+
+struct _snd_rawmidi {
+	void *dl_handle;
+	char *name;
+	snd_rawmidi_type_t type;
+	snd_rawmidi_stream_t stream;
+	int mode;
+	int poll_fd;
+	const snd_rawmidi_ops_t *ops;
+	void *private_data;
+	size_t buffer_size;
+	size_t avail_min;
+	unsigned int no_active_sensing: 1;
+};
+
+int snd_rawmidi_hw_open(snd_rawmidi_t **input, snd_rawmidi_t **output,
+			const char *name, int card, int device, int subdevice,
+			int mode);
+
+int snd_rawmidi_virtual_open(snd_rawmidi_t **inputp, snd_rawmidi_t **outputp,
+			     const char *name, snd_seq_t *seq_handle, int port,
+			     int merge, int mode);
+
+int snd_rawmidi_conf_generic_id(const char *id);
diff --git a/src/rawmidi/rawmidi_symbols.c b/src/rawmidi/rawmidi_symbols.c
new file mode 100644
index 0000000..cdc06d7
--- /dev/null
+++ b/src/rawmidi/rawmidi_symbols.c
@@ -0,0 +1,36 @@
+/*
+ *  RawMidi Symbols
+ *  Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#ifndef PIC
+
+extern const char *_snd_module_rawmidi_hw;
+extern const char *_snd_module_rawmidi_virt;
+
+static const char **snd_rawmidi_open_objects[] = {
+	&_snd_module_rawmidi_hw,
+	&_snd_module_rawmidi_virt
+};
+	
+void *snd_rawmidi_open_symbols(void)
+{
+	return snd_rawmidi_open_objects;
+}
+
+#endif /* !PIC */
diff --git a/src/rawmidi/rawmidi_virt.c b/src/rawmidi/rawmidi_virt.c
new file mode 100644
index 0000000..e5b17e4
--- /dev/null
+++ b/src/rawmidi/rawmidi_virt.c
@@ -0,0 +1,471 @@
+/*
+ *  RawMIDI - Virtual (sequencer mode)
+ *  Copyright (c) 2003 by Takashi Iwai <tiwai@suse.de>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include "rawmidi_local.h"
+#include "seq.h"
+#include "seq_midi_event.h"
+
+#ifndef PIC
+/* entry for static linking */
+const char *_snd_module_rawmidi_virt = "";
+#endif
+
+
+#ifndef DOC_HIDDEN
+typedef struct {
+	int open;
+
+	snd_seq_t *handle;
+	int port;
+
+	snd_midi_event_t *midi_event;
+
+	snd_seq_event_t *in_event;
+	int in_buf_size;
+	int in_buf_ofs;
+	char *in_buf_ptr;
+	char in_tmp_buf[16];
+
+	snd_seq_event_t out_event;
+	int pending;
+} snd_rawmidi_virtual_t;
+
+int _snd_seq_open_lconf(snd_seq_t **seqp, const char *name, 
+			int streams, int mode, snd_config_t *lconf,
+			snd_config_t *parent_conf);
+#endif
+
+static int snd_rawmidi_virtual_close(snd_rawmidi_t *rmidi)
+{
+	snd_rawmidi_virtual_t *virt = rmidi->private_data;
+	virt->open--;
+	if (virt->open)
+		return 0;
+	snd_seq_close(virt->handle);
+	if (virt->midi_event)
+		snd_midi_event_free(virt->midi_event);
+	free(virt);
+	return 0;
+}
+
+static int snd_rawmidi_virtual_nonblock(snd_rawmidi_t *rmidi, int nonblock)
+{
+	snd_rawmidi_virtual_t *virt = rmidi->private_data;
+
+	return snd_seq_nonblock(virt->handle, nonblock);
+}
+
+static int snd_rawmidi_virtual_info(snd_rawmidi_t *rmidi, snd_rawmidi_info_t * info)
+{
+	// snd_rawmidi_virtual_t *virt = rmidi->private_data;
+
+	info->stream = rmidi->stream;
+	/* FIXME: what values should be there? */
+	info->card = 0;
+	info->device = 0;
+	info->subdevice = 0;
+	info->flags = 0;
+	strcpy((char *)info->id, "Virtual");
+	strcpy((char *)info->name, "Virtual RawMIDI");
+	strcpy((char *)info->subname, "Virtual RawMIDI");
+	info->subdevices_count = 1;
+	info->subdevices_avail = 0;
+	return 0;
+}
+
+static int snd_rawmidi_virtual_input_params(snd_rawmidi_virtual_t *virt, snd_rawmidi_params_t *params)
+{
+	int err;
+
+	// snd_rawmidi_drain_input(substream);
+	if (params->buffer_size < sizeof(snd_seq_event_t) ||
+	    params->buffer_size > 1024L * 1024L) {
+		return -EINVAL;
+	}
+	if (params->buffer_size != snd_seq_get_input_buffer_size(virt->handle)) {
+		err = snd_seq_set_input_buffer_size(virt->handle, params->buffer_size);
+		if (err < 0)
+			return err;
+		params->buffer_size = snd_seq_get_input_buffer_size(virt->handle);
+		/* FIXME: input pool size? */
+	}
+	return 0;
+}
+
+
+static int snd_rawmidi_virtual_output_params(snd_rawmidi_virtual_t *virt, snd_rawmidi_params_t *params)
+{
+	int err;
+
+	// snd_rawmidi_drain_output(substream);
+	if (params->buffer_size < sizeof(snd_seq_event_t) ||
+	    params->buffer_size > 1024L * 1024L) {
+		return -EINVAL;
+	}
+	if (params->buffer_size != snd_seq_get_output_buffer_size(virt->handle)) {
+		err = snd_seq_set_output_buffer_size(virt->handle, params->buffer_size);
+		if (err < 0)
+			return err;
+		params->buffer_size = snd_seq_get_output_buffer_size(virt->handle);
+	}
+	return 0;
+}
+
+
+static int snd_rawmidi_virtual_params(snd_rawmidi_t *rmidi, snd_rawmidi_params_t * params)
+{
+	snd_rawmidi_virtual_t *virt = rmidi->private_data;
+	params->stream = rmidi->stream;
+
+	if (rmidi->stream == SND_RAWMIDI_STREAM_INPUT)
+		return snd_rawmidi_virtual_input_params(virt, params);
+	else
+		return snd_rawmidi_virtual_output_params(virt, params);
+}
+
+static int snd_rawmidi_virtual_status(snd_rawmidi_t *rmidi, snd_rawmidi_status_t * status)
+{
+	// snd_rawmidi_virtual_t *virt = rmidi->private_data;
+	memset(status, 0, sizeof(*status));
+	status->stream = rmidi->stream;
+	return 0;
+}
+
+static int snd_rawmidi_virtual_drop(snd_rawmidi_t *rmidi)
+{
+	snd_rawmidi_virtual_t *virt = rmidi->private_data;
+	if (rmidi->stream == SND_RAWMIDI_STREAM_OUTPUT) {
+		snd_seq_drop_output(virt->handle);
+		snd_midi_event_reset_encode(virt->midi_event);
+		virt->pending = 0;
+	} else {
+		snd_seq_drop_input(virt->handle);
+		snd_midi_event_reset_decode(virt->midi_event);
+		virt->in_buf_ofs = 0;
+	}
+	return 0;
+}
+
+static int snd_rawmidi_virtual_drain(snd_rawmidi_t *rmidi)
+{
+	snd_rawmidi_virtual_t *virt = rmidi->private_data;
+	int err;
+
+	if (rmidi->stream == SND_RAWMIDI_STREAM_OUTPUT) {
+		if (virt->pending) {
+			err = snd_seq_event_output(virt->handle, &virt->out_event);
+			if (err < 0)
+				return err;
+			virt->pending = 0;
+		}
+		snd_seq_drain_output(virt->handle);
+		snd_seq_sync_output_queue(virt->handle);
+	}
+	return snd_rawmidi_virtual_drop(rmidi);
+}
+
+static ssize_t snd_rawmidi_virtual_write(snd_rawmidi_t *rmidi, const void *buffer, size_t size)
+{
+	snd_rawmidi_virtual_t *virt = rmidi->private_data;
+	ssize_t result = 0;
+	ssize_t size1;
+	int err;
+
+	if (virt->pending) {
+		err = snd_seq_event_output(virt->handle, &virt->out_event);
+		if (err < 0) {
+			if (err != -EAGAIN)
+				/* we got some fatal error. removing this event
+				 * at the next time
+				 */
+				virt->pending = 0;
+			return err;
+		}
+		virt->pending = 0;
+	}
+
+	while (size > 0) {
+		size1 = snd_midi_event_encode(virt->midi_event, buffer, size, &virt->out_event);
+		if (size1 <= 0)
+			break;
+		size -= size1;
+		result += size1;
+		buffer += size1;
+		if (virt->out_event.type == SND_SEQ_EVENT_NONE)
+			continue;
+		snd_seq_ev_set_subs(&virt->out_event);
+		snd_seq_ev_set_source(&virt->out_event, virt->port);
+		snd_seq_ev_set_direct(&virt->out_event);
+		err = snd_seq_event_output(virt->handle, &virt->out_event);
+		if (err < 0) {
+			virt->pending = 1;
+			return result > 0 ? result : err;
+		}
+	}
+
+	if (result > 0)
+		snd_seq_drain_output(virt->handle);
+
+	return result;
+}
+
+static ssize_t snd_rawmidi_virtual_read(snd_rawmidi_t *rmidi, void *buffer, size_t size)
+{
+	snd_rawmidi_virtual_t *virt = rmidi->private_data;
+	ssize_t result = 0;
+	int size1, err;
+
+	while (size > 0) {
+		if (! virt->in_buf_ofs) {
+			err = snd_seq_event_input_pending(virt->handle, 1);
+			if (err <= 0 && result > 0)
+				return result;
+			err = snd_seq_event_input(virt->handle, &virt->in_event);
+			if (err < 0)
+				return result > 0 ? result : err;
+
+			if (virt->in_event->type == SND_SEQ_EVENT_SYSEX) {
+				virt->in_buf_ptr = virt->in_event->data.ext.ptr;
+				virt->in_buf_size = virt->in_event->data.ext.len;
+			} else {
+				virt->in_buf_ptr = virt->in_tmp_buf;
+				virt->in_buf_size = snd_midi_event_decode(virt->midi_event,
+									  (unsigned char *)virt->in_tmp_buf,
+									  sizeof(virt->in_tmp_buf),
+									  virt->in_event);
+			}
+			if (virt->in_buf_size <= 0)
+				continue;
+		}
+		size1 = virt->in_buf_size - virt->in_buf_ofs;
+		if ((size_t)size1 > size) {
+			virt->in_buf_ofs += size1 - size;
+			memcpy(buffer, virt->in_buf_ptr, size);
+			result += size;
+			break;
+		}
+		memcpy(buffer, virt->in_buf_ptr + virt->in_buf_ofs, size1);
+		size -= size1;
+		result += size1;
+		buffer += size1;
+		virt->in_buf_ofs = 0;
+	}
+
+	return result;
+}
+
+static const snd_rawmidi_ops_t snd_rawmidi_virtual_ops = {
+	.close = snd_rawmidi_virtual_close,
+	.nonblock = snd_rawmidi_virtual_nonblock,
+	.info = snd_rawmidi_virtual_info,
+	.params = snd_rawmidi_virtual_params,
+	.status = snd_rawmidi_virtual_status,
+	.drop = snd_rawmidi_virtual_drop,
+	.drain = snd_rawmidi_virtual_drain,
+	.write = snd_rawmidi_virtual_write,
+	.read = snd_rawmidi_virtual_read,
+};
+
+
+/*! \page rawmidi RawMidi interface
+
+\section rawmidi_virt Virtual RawMidi interface
+
+The "virtual" plugin creates a virtual RawMidi instance on the ALSA
+sequencer, which can be accessed through the connection of the sequencer
+ports.
+There is no connection established as default.
+
+For creating a virtual RawMidi instance, pass "virtual" as its name at
+creation.
+
+Example:
+\code
+snd_rawmidi_open(&read_handle, &write_handle, "virtual", 0);
+\endcode
+
+*/
+
+int snd_rawmidi_virtual_open(snd_rawmidi_t **inputp, snd_rawmidi_t **outputp,
+			     const char *name, snd_seq_t *seq_handle, int port,
+			     int merge, int mode)
+{
+	int err;
+	snd_rawmidi_t *rmidi;
+	snd_rawmidi_virtual_t *virt = NULL;
+	struct pollfd pfd;
+
+	if (inputp)
+		*inputp = 0;
+	if (outputp)
+		*outputp = 0;
+
+	virt = calloc(1, sizeof(*virt));
+	if (virt == NULL) {
+		err = -ENOMEM;
+		goto _err;
+	}
+	virt->handle = seq_handle;
+	virt->port = port;
+	err = snd_midi_event_new(256, &virt->midi_event);
+	if (err < 0)
+		goto _err;
+	snd_midi_event_init(virt->midi_event);
+	snd_midi_event_no_status(virt->midi_event, !merge);
+
+	if (inputp) {
+		rmidi = calloc(1, sizeof(*rmidi));
+		if (rmidi == NULL) {
+			err = -ENOMEM;
+			goto _err;
+		}
+		if (name)
+			rmidi->name = strdup(name);
+		rmidi->type = SND_RAWMIDI_TYPE_VIRTUAL;
+		rmidi->stream = SND_RAWMIDI_STREAM_INPUT;
+		rmidi->mode = mode;
+		err = snd_seq_poll_descriptors(seq_handle, &pfd, 1, POLLIN);
+		if (err < 0)
+			goto _err;
+		rmidi->poll_fd = pfd.fd;
+		rmidi->ops = &snd_rawmidi_virtual_ops;
+		rmidi->private_data = virt;
+		virt->open++;
+		*inputp = rmidi;
+	}
+	if (outputp) {
+		rmidi = calloc(1, sizeof(*rmidi));
+		if (rmidi == NULL) {
+			err = -ENOMEM;
+			goto _err;
+		}
+		if (name)
+			rmidi->name = strdup(name);
+		rmidi->type = SND_RAWMIDI_TYPE_VIRTUAL;
+		rmidi->stream = SND_RAWMIDI_STREAM_OUTPUT;
+		rmidi->mode = mode;
+		err = snd_seq_poll_descriptors(seq_handle, &pfd, 1, POLLOUT);
+		if (err < 0)
+			goto _err;
+		rmidi->poll_fd = pfd.fd;
+		rmidi->ops = &snd_rawmidi_virtual_ops;
+		rmidi->private_data = virt;
+		virt->open++;
+		*outputp = rmidi;
+	}
+
+	return 0;
+
+ _err:
+	if (seq_handle)
+		snd_seq_close(seq_handle);
+	if (virt) {
+		if (virt->midi_event)
+			snd_midi_event_free(virt->midi_event);
+		free(virt);
+	}
+	if (inputp)
+		free(*inputp);
+	if (outputp)
+		free(*outputp);
+	return err;
+}
+
+int _snd_rawmidi_virtual_open(snd_rawmidi_t **inputp, snd_rawmidi_t **outputp,
+			   char *name, snd_config_t *root ATTRIBUTE_UNUSED,
+			   snd_config_t *conf, int mode)
+{
+	snd_config_iterator_t i, next;
+	const char *slave_str = NULL;
+	int err;
+	int streams, seq_mode;
+	int merge = 1;
+	int port;
+	unsigned int caps;
+	snd_seq_t *seq_handle;
+
+	snd_config_for_each(i, next, conf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (snd_rawmidi_conf_generic_id(id))
+			continue;
+		if (strcmp(id, "slave") == 0) {
+			err = snd_config_get_string(n, &slave_str);
+			if (err < 0)
+				return err;
+			continue;
+		}
+		if (strcmp(id, "merge") == 0) {
+			merge = snd_config_get_bool(n);
+			continue;
+		}
+		return -EINVAL;
+	}
+
+	streams = 0;
+	if (inputp)
+		streams |= SND_SEQ_OPEN_INPUT;
+	if (outputp)
+		streams |= SND_SEQ_OPEN_OUTPUT;
+	if (! streams)
+		return -EINVAL;
+
+	seq_mode = 0;
+	if (mode & SND_RAWMIDI_NONBLOCK)
+		seq_mode |= SND_SEQ_NONBLOCK;
+
+	if (! slave_str)
+		slave_str = "default";
+	err = _snd_seq_open_lconf(&seq_handle, slave_str, streams, seq_mode,
+				  root, conf);
+	if (err < 0)
+		return err;
+
+	caps = 0;
+	if (inputp)
+		caps |= SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SYNC_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE;
+	if (outputp)
+		caps |= SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SYNC_READ | SND_SEQ_PORT_CAP_SUBS_READ;
+	if (inputp && outputp)
+		caps |= SNDRV_SEQ_PORT_CAP_DUPLEX;
+
+	port = snd_seq_create_simple_port(seq_handle, "Virtual RawMIDI",
+					  caps, SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC);
+	if (port < 0) {
+		snd_seq_close(seq_handle);
+		return port;
+	}
+
+	return snd_rawmidi_virtual_open(inputp, outputp, name, seq_handle, port,
+				     merge, mode);
+}
+
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(_snd_rawmidi_virtual_open, SND_RAWMIDI_DLSYM_VERSION);
+#endif
diff --git a/src/seq/Makefile.am b/src/seq/Makefile.am
new file mode 100644
index 0000000..1ea92f0
--- /dev/null
+++ b/src/seq/Makefile.am
@@ -0,0 +1,13 @@
+EXTRA_LTLIBRARIES=libseq.la
+
+libseq_la_SOURCES = seq_hw.c seq.c seq_event.c seqmid.c seq_midi_event.c \
+		    seq_symbols.c
+if KEEP_OLD_SYMBOLS
+libseq_la_SOURCES += seq_old.c
+endif
+noinst_HEADERS = seq_local.h
+
+all: libseq.la
+
+
+INCLUDES=-I$(top_srcdir)/include
diff --git a/src/seq/seq.c b/src/seq/seq.c
new file mode 100644
index 0000000..f5dfe9c
--- /dev/null
+++ b/src/seq/seq.c
@@ -0,0 +1,4783 @@
+/**
+ * \file seq/seq.c
+ * \brief Sequencer Interface
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \author Takashi Iwai <tiwai@suse.de>
+ * \date 2000-2001
+ *
+ * See \ref seq page for more details.
+ */
+
+/* 
+ *  Sequencer Interface - main file
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+/*! \page seq Sequencer interface
+
+\section seq_general General
+
+The ALSA sequencer interface is designed to deliver the MIDI-like
+events between clients/ports.
+A typical usage is the MIDI patch-bay.  A MIDI application can be
+connected arbitrarily from/to the other MIDI clients.
+The routing between clients can be changed dynamically, so the
+application can handle incoming or outgoing MIDI events regardless of
+the devices or the application connections.
+
+The sequencer core stuff only takes care of two things:
+scheduling events and dispatching them to the destination at the
+right time.  All processing of MIDI events has to be done within the clients.
+The event can be dispatched immediately without queueing, too.
+The event scheduling can be done either on a MIDI tempo queue or
+on a wallclock-time queue.
+
+\section seq_client Client and Port
+
+A <i>client</i> is created at each time #snd_seq_open() is called.
+Later on, the attributes of client such as its name string can be changed
+via #snd_seq_set_client_info().  There are helper functions for ease of use,
+e.g. #snd_seq_set_client_name() and #snd_seq_set_client_event_filter().
+A typical code would be like below:
+\code
+// create a new client
+snd_seq_t *open_client()
+{
+        snd_seq_t *handle;
+        int err;
+        err = snd_seq_open(&handle, "default", SND_SEQ_OPEN_INPUT, 0);
+        if (err < 0)
+                return NULL;
+        snd_seq_set_client_name(handle, "My Client");
+	return handle;
+}
+\endcode
+
+You'll need to know the id number of the client eventually, for example,
+when accessing to a certain port (see the section \ref seq_subs).
+The client id can be obtained by #snd_seq_client_id() function.
+
+A client can have one or more <i>ports</i> to communicate between other
+clients.  A port is corresponding to the MIDI port in the case of MIDI device,
+but in general it is nothing but the access point between other clients.
+Each port may have capability flags, which specify the read/write
+accessibility and subscription permissions of the port.
+For creation of a port, call #snd_seq_create_port()
+with the appropriate port attribute specified in #snd_seq_port_info_t
+record.
+
+For creating a port for the normal use, there is a helper function
+#snd_seq_create_simple_port().  An example with this function is like below.
+\code
+// create a new port; return the port id
+// port will be writable and accept the write-subscription.
+int my_new_port(snd_seq_t *handle)
+{
+	return snd_seq_create_simple_port(handle, "my port",
+			SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE,
+			SND_SEQ_PORT_TYPE_MIDI_GENERIC);
+}
+\endcode
+
+\section seq_memory Memory Pool
+
+Each client owns memory pools on kernel space
+for each input and output events.
+Here, input and output mean
+input (read) from other clients and output (write) to others, respectively.
+Since memory pool of each client is independent from others,
+it avoids such a situation that a client eats the whole events pool
+and interfere other clients' response.
+
+The all scheduled output events or input events from dispatcher are stored
+on these pools until delivered to other clients or extracted to user space.
+The size of input/output pools can be changed independently.
+The output pool has also a room size, which is used to wake up the
+thread when it falls into sleep in blocking write mode.
+
+Note that ports on the same client share the same memory pool.
+If a port fills the memory pool, another can't use it any more.
+For avoiding this, multiple clients can be used.
+
+For chancing the pool size and the condition, access to #snd_seq_client_pool_t
+record.  There are helper functions, #snd_seq_set_client_pool_output(),
+#snd_seq_set_client_pool_output_room() and #snd_seq_set_client_pool_input(),
+for setting the total output-pool size, the output-room size and the input-pool
+size, respectively.
+
+\section seq_subs Subscription
+
+One of the new features in ALSA sequencer system is <i>subscription</i> of ports.
+In general, subscription is a connection between two sequencer ports.
+Even though an event can be delivered to a port without subscription
+using an explicit destination address,
+the subscription mechanism provides us more abstraction.
+
+Suppose a MIDI input device which sends events from a keyboard.
+The port associated with this device has READ capability - which means
+this port is readable from other ports.
+If a user program wants to capture events from keyboard and store them
+as MIDI stream, this program must subscribe itself to the MIDI port
+for read.
+Then, a connection from MIDI input port to this program is established.
+From this time, events from keyboard are automatically sent to this program.
+Timestamps will be updated according to the subscribed queue.
+\code
+        MIDI input port (keyboard)
+            |
+            V
+        ALSA sequencer - update timestamp
+            |
+            V
+        application port
+\endcode
+
+There is another subscription type for opposite direction:
+Suppose a MIDI sequencer program which sends events to a MIDI output device.
+In ALSA system, MIDI device is not opened until the associated MIDI port
+is accessed.  Thus, in order to activate MIDI device, we have to subscribe
+to MIDI port for write.
+After this connection is established, events will be properly sent
+to MIDI output device.
+\code
+        application port
+            |
+            V
+        ALSA sequencer - events are scheduled
+            |
+            V
+        MIDI output port (WaveTable etc.)
+\endcode
+
+From the viewpoint of subscription, the examples above are special cases.
+Basically, subscription means the connection between two arbitrary ports.
+For example, imagine a filter application which modifies
+the MIDI events like program, velocity or chorus effects.
+This application can accept arbitrary MIDI input
+and send to arbitrary port, just like a Unix pipe application using
+stdin and stdout files.
+We can even connect several filter applications which work individually
+in order to process the MIDI events.
+Subscription can be used for this purpose.
+The connection between ports can be done also by the "third" client.
+Thus, filter applications have to manage
+only input and output events regardless of receiver/sender addresses.
+\code
+        sequencer port #1
+            |
+            V
+        ALSA sequencer (scheduled or real-time)
+            |
+            V
+        sequencer port #2
+\endcode
+
+For the detail about subscription, see the section \ref seq_subs_more.
+
+\section seq_events Sequencer Events
+
+Messaging between clients is performed by sending events from one client to
+another. These events contain high-level MIDI oriented messages or sequencer
+specific messages.
+
+All the sequencer events are stored in a sequencer event record,
+#snd_seq_event_t type.
+Application can send and receive these event records to/from other
+clients via sequencer.
+An event has several storage types according to its usage.
+For example, a SYSEX message is stored on the variable length event,
+and a large synth sample data is delivered using a user-space data pointer.
+
+
+\subsection seq_ev_struct Structure of an event
+
+An event consists of the following items:
+<ul>
+<li>The type of the event
+<li>Event flags.  It describes various conditions:
+  <ul>
+  <li>time stamp; "real time" / "song ticks"
+  <li>time mode; "absolute" / "relative to current time"
+  </ul>
+<li>Timestamp of the event.
+<li>Scheduling queue id.
+<li>Source address of the event, given by the combination
+  of client id and port id numbers.
+<li>Destination address of the event.
+<li>The actual event data. (up to 12 bytes)
+</ul>
+
+The actual record is shown in #snd_seq_event_t.
+The type field contains the type of the event
+(1 byte).
+The flags field consists of bit flags which
+describe several conditions of the event (1 byte).
+It includes the time-stamp mode, data storage type, and scheduling priority.
+The tag field is an arbitrary tag.
+This tag can used for removing a distinct event from the event queue
+via #snd_seq_remove_events().
+The queue field is the queue id for scheduling.
+The source and dest fields are source and destination addresses.
+The data field is a union of event data.
+
+\subsection seq_ev_queue Scheduling queue
+
+An event can be delivered either on scheduled or direct dispatch mode.
+On the scheduling mode, an event is once stored on the priority queue
+and delivered later (or even immediately) to the destination,
+whereas on the direct dispatch mode, an event is passed to the destination
+without any queue.
+
+For a scheduled delivery, a queue to process the event must exist.
+Usually, a client creates its own queue by
+#snd_seq_alloc_queue() function.
+Alternatively, a queue may be shared among several clients.
+For scheduling an event on the specified queue,
+a client needs to fill queue field
+with the preferred queue id.
+
+Meanwhile, for dispatching an event directly, just
+use #SND_SEQ_QUEUE_DIRECT as the target queue id.
+A macro #snd_seq_ev_set_direct() is provided for ease
+and compatibility.
+
+Note that scheduling at the current or earlier time is different
+from the direct dispatch mode even though the event is delivered immediately.
+On the former scheme, an event is once stored on priority queue, then
+delivered actually.  Thus, it acquires a space from memory pool.
+On the other hand, the latter is passed without using memory pool.
+Although the direct dispatched event needs less memory, it means also
+that the event cannot be resent if the destination is unable to receive it
+momentarily.
+
+\subsection seq_ev_time Time stamp
+
+The timestamp of the event can either specified in
+<i>real time</i> or in <i>song ticks</i>.
+The former means the wallclock time while the latter corresponds to
+the MIDI ticks.
+Which format is used is determined by the event flags.
+
+The resolution of real-time value is in nano second.
+Since 64 bit length is required for the actual time calculation,
+it is represented by
+a structure of pair of second and nano second
+defined as #snd_seq_real_time_t type.
+The song tick is defined simply as a 32 bit integer,
+defined as #snd_seq_tick_time_t type.
+The time stored in an event record is a union of these two different
+time values.
+
+Note that the time format used for real time events is very similar to
+timeval struct used for Unix system time.
+The absurd resolution of the timestamps allows us to perform very accurate
+conversions between songposition and real time. Round-off errors can be
+neglected.
+
+If a timestamp with a
+<i>relative</i> timestamp is delivered to ALSA, the
+specified timestamp will be used as an offset to the current time of the
+queue the event is sent into.
+An <i>absolute</i> timestamp is on the contrary the time
+counted from the moment when the queue started.
+
+An client that relies on these relative timestamps is the MIDI input port.
+As each sequencer queue has it's own clock the only way to deliver events at
+the right time is by using the relative timestamp format. When the event
+arrives at the queue it is normalized to absolute format.
+
+The timestamp format is specified in the flag bitfield masked by
+#SND_SEQ_TIME_STAMP_MASK.
+To schedule the event in a real-time queue or in a tick queue,
+macros #snd_seq_ev_schedule_real() and
+#snd_seq_ev_schedule_tick() are provided, respectively.
+
+\subsection seq_ev_addr Source and destination addresses
+
+To identify the source and destination of an event, the addressing field
+contains a combination of client id and port id numbers, defined as
+#snd_seq_addr_t type.
+When an event is passed to sequencer from a client, sequencer fills
+source.client field
+with the sender's id automatically.
+It is the responsibility of sender client to 
+fill the port id of source.port and
+both client and port of dest field.
+
+If an existing address is set to the destination,
+the event is simply delivered to it.
+When #SND_SEQ_ADDRESS_SUBSCRIBERS is set to the destination client id,
+the event is delivered to all the clients connected to the source port.
+
+
+A sequencer core has two pre-defined system ports on the system client
+#SND_SEQ_CLIENT_SYSTEM: #SND_SEQ_PORT_SYSTEM_TIMER and #SND_SEQ_PORT_SYSTEM_ANNOUNCE.
+The #SND_SEQ_PORT_SYSTEM_TIMER is the system timer port,
+and #SND_SEQ_PORT_SYSTEM_ANNOUNCE is the system
+announce port.
+In order to control a queue from a client, client should send a
+queue-control event
+like start, stop and continue queue, change tempo, etc.
+to the system timer port.
+Then the sequencer system handles the queue according to the received event.
+This port supports subscription. The received timer events are 
+broadcasted to all subscribed clients.
+
+The latter port does not receive messages but supports subscription.
+When each client or port is attached, detached or modified,
+an announcement is sent to subscribers from this port.
+
+\subsection seq_ev_data Data storage type
+
+Some events like SYSEX message, however, need larger data space
+than the standard data.
+For such events, ALSA sequencer provides several different data storage types.
+The data type is specified in the flag bits masked by #SND_SEQ_EVENT_LENGTH_MASK.
+The following data types are available:
+
+\par Fixed size data
+Normal events stores their parameters on
+data field (12 byte).
+The flag-bit type is  #SND_SEQ_EVENT_LENGTH_FIXED.
+A macro #snd_seq_ev_set_fixed() is provided to set this type.
+
+\par Variable length data
+SYSEX or a returned error use this type.
+The actual data is stored on an extra allocated space.
+On sequencer kernel, the whole extra-data is duplicated, so that the event
+can be scheduled on queue.
+The data contains only the length and the
+pointer of extra-data.
+The flag-bit type is  #SND_SEQ_EVENT_LENGTH_VARIABLE.
+A macro #snd_seq_ev_set_variable() is provided to set this type.
+
+\par User-space data
+This type refers also an extra data space like variable length data,
+but the extra-data is not duplicated but
+but referred as a user-space data on kernel,
+so that it reduces the time and resource for transferring
+large bulk of data like synth sample wave.
+This data type, however, can be used only for direct dispatch mode,
+and supposed to be used only for a special purpose like a bulk data
+transfer.
+The data length and pointer are stored also in
+data.ext field as well as variable length data.
+The flag-bit type is  #SND_SEQ_EVENT_LENGTH_VARUSR.
+A macro #snd_seq_ev_set_varusr() is provided to set this type.
+
+\subsection seq_ev_sched Scheduling priority
+
+There are two priorities for scheduling:
+\par Normal priority
+If an event with the same scheduling time is already present on the queue,
+the new event is appended to the older.
+\par High priority
+If an event with the same scheduling time is already present on the queue,
+the new event is inserted before others.
+
+The scheduling priority is set in the flag bitfeld masked by #SND_SEQ_PRIORITY_MASK.
+A macro #snd_seq_ev_set_priority() is provided to set the mode type.
+
+\section seq_queue Event Queues
+\subsection seq_ev_control Creation of a queue
+
+Creating a queue is done usually by calling #snd_seq_alloc_queue.
+You can create a queue with a certain name by #snd_seq_alloc_named_queue(), too.
+\code
+// create a queue and return its id
+int my_queue(snd_seq_t *handle)
+{
+	return snd_seq_alloc_named_queue(handle, "my queue");
+}
+\endcode
+These functions are the wrapper to the function #snd_seq_create_queue().
+For releasing the allocated queue, call #snd_seq_free_queue() with the
+obtained queue id.
+
+Once when a queue is created, the two queues are associated to that
+queue record in fact: one is the realtime queue and another is the
+tick queue.  These two queues are bound together to work
+synchronously.  Hence, when you schedule an event, you have to choose
+which queue type is used as described in the section \ref
+seq_ev_time.
+
+\subsection seq_ev_tempo Setting queue tempo
+
+The tempo (or the speed) of the scheduling queue is variable.
+In the case of <i>tick</i> queue, the tempo is controlled
+in the manner of MIDI.  There are two parameters to define the
+actual tempo, PPQ (pulse per quarter note) and MIDI tempo.
+The former defines the base resolution of the ticks, while
+the latter defines the beat tempo in microseconds.
+As default, 96 PPQ and 120 BPM are used, respectively.
+That is, the tempo is set to 500000 (= 60 * 1000000 / 120).
+Note that PPQ cannot be changed while the queue is running.
+It must be set before the queue is started.
+
+On the other hand, in the case of <i>realtime</i> queue, the
+time resolution is fixed to nanoseconds.  There is, however,
+a parameter to change the speed of this queue, called <i>skew</i>.
+You can make the queue faster or slower by setting the skew value
+bigger or smaller.  In the API, the skew is defined by two values,
+the skew base and the skew value.  The actual skew is the fraction
+of them, <i>value/base</i>.  As default, the skew base is set to 16bit
+(0x10000) and the skew value is the identical, so that the queue is
+processed as well as in the real world.
+
+When the tempo of realtime queue is changed, the tempo of
+the associated tick queue is changed together, too.
+That's the reason why two queues are created always.
+This feature can be used to synchronize the event queue with
+the external synchronization source like SMPTE.  In such a case,
+the realtime queue is skewed to match with the external source,
+so that both the realtime timestamp and the MIDI timestamp are
+synchronized.
+
+For setting these tempo parameters, use #snd_seq_queue_tempo_t record.
+For example, to set the tempo of the queue <code>q</code> to
+48 PPQ, 60 BPM,
+\code
+void set_tempo(snd_seq_t *handle)
+{
+        snd_seq_queue_tempo_t *tempo;
+        snd_seq_queue_tempo_alloca(&tempo);
+        snd_seq_queue_tempo_set_tempo(tempo, 1000000); // 60 BPM
+        snd_seq_queue_tempo_set_ppq(tempo, 48); // 48 PPQ
+        snd_seq_set_queue_tempo(handle, tempo);
+}
+\endcode
+
+For changing the (running) queue's tempo on the fly, you can either
+set the tempo via #snd_seq_set_queue_tempo() or send a MIDI tempo event
+to the system timer port.  For example,
+\code
+int change_tempo(snd_seq_t *handle, int q, unsigned int tempo)
+{
+	snd_seq_event_t ev;
+	snd_seq_ev_clear(&ev);
+	ev.dest.client = SND_SEQ_CLIENT_SYSTEM;
+	ev.dest.port = SND_SEQ_PORT_SYSTEM_TIMER;
+	ev.source.client = my_client_id;
+	ev.source.port = my_port_id;
+	ev.queue = SND_SEQ_QUEUE_DIRECT; // no scheduling
+	ev.data.queue.queue = q;	// affected queue id
+	ev.data.queue.value = tempo;	// new tempo in microsec.
+	return snd_seq_event_output(handle, &ev);
+}
+\endcode
+There is a helper function to do this easily,
+#snd_seq_change_queue_tempo().
+Set NULL to the last argument, if you don't need any
+special settings.
+
+In the above example, the tempo is changed immediately after
+the buffer is flushed by #snd_seq_drain_output() call.
+You can schedule the event in a certain queue so that the tempo
+change happens at the scheduled time, too.
+
+\subsection seq_ev_start Starting and stopping a queue
+
+To start, stop, or continue a queue, you need to send a queue-control
+event to the system timer port as well.  There are helper functions,
+#snd_seq_start_queue(), #snd_seq_stop_queue() and
+#snd_seq_continue_queue().
+Note that if the last argument of these functions is NULL, the
+event is sent (i.e. operated) immediately after the buffer flush.
+If you want to schedule the event at the certain time, set up
+the event record and provide the pointer of that event record as the
+argument.
+
+Only calling these functions doesn't deliver the event to the
+sequencer core but only put to the output buffer.  You'll need to
+call #snd_seq_drain_output() eventually.
+
+
+\section seq_subs_more More inside the subscription
+
+\subsection seq_subs_perm Permissions
+
+Each ALSA port can have capability flags.
+The most basic capability flags are
+#SND_SEQ_PORT_CAP_READ and #SND_SEQ_PORT_CAP_WRITE.
+The former means that the port allows to send events to other ports,
+whereas the latter capability means
+that the port allows to receive events from other ports.
+You may have noticed that meanings of \c READ and \c WRITE
+are permissions of the port from the viewpoint of other ports.
+
+For allowing subscription from/to other clients, another capability
+flags must be set together with read/write capabilities above.
+For allowing read and write subscriptions,
+#SND_SEQ_PORT_CAP_SUBS_READ and
+#SND_SEQ_PORT_CAP_SUBS_WRITE are used,
+respectively.
+For example, the port with MIDI input device always has
+#SND_SEQ_PORT_CAP_SUBS_READ capability,
+and the port with MIDI output device always has
+#SND_SEQ_PORT_CAP_SUBS_WRITE capability together with
+#SND_SEQ_PORT_CAP_READ and #SND_SEQ_PORT_CAP_WRITE capabilities,
+respectively.
+Obviously, these flags have no influence
+if \c READ or \c WRITE> capability is not set.
+
+Note that these flags are not necessary if the client subscribes itself
+to the specified port.
+For example, when a port makes READ subscription
+to MIDI input port, this port must have #SND_SEQ_PORT_CAP_WRITE capability,
+but no #SND_SEQ_PORT_CAP_SUBS_WRITE capability is required.
+Only MIDI input port must have #SND_SEQ_PORT_CAP_SUBS_READ capability.
+
+As default, the connection of ports via the third client is always allowed
+if proper read and write (subscription) capabilities are set both to the
+source and destination ports.
+For prohibiting this behavior, set a capability
+#SND_SEQ_PORT_CAP_NO_EXPORT to the port.
+If this flag is set, subscription must be done by sender or receiver
+client itself.
+It is useful to avoid unexpected disconnection.
+The ports which won't accept subscription should have this capability
+for better security.
+
+\subsection seq_subs_handle Subscription handlers
+
+In ALSA library, subscription is done via
+#snd_seq_subscribe_port() function.
+It takes the argument of #snd_seq_port_subscribe_t record pointer.
+Suppose that you have a client which will receive data from
+a MIDI input device.  The source and destination addresses
+are like the below;
+\code
+snd_seq_addr_t sender, dest;
+sender.client = MIDI_input_client;
+sender.port = MIDI_input_port;
+dest.client = my_client;
+dest.port = my_port;
+\endcode
+To set these values as the connection call like this.
+\code
+snd_seq_port_subscribe_t *subs;
+snd_seq_port_subscribe_alloca(&subs);
+snd_seq_port_subscribe_set_sender(subs, &sender);
+snd_seq_port_subscribe_set_dest(subs, &dest);
+snd_seq_subscribe_port(handle, subs);
+\endcode
+
+When the connection should be exclusively done only between
+a certain pair, set <i>exclusive</i> attribute to the subscription
+record before calling #snd_seq_subscribe_port.
+\code
+snd_seq_port_subscribe_set_exclusive(subs, 1);
+\endcode
+The succeeding subscriptions will be refused.
+
+The timestamp can be updated independently on each connection.
+When set up, the timestamp of incoming queue to the destination port
+is updated automatically to the time of the specified queue.
+\code
+snd_seq_port_subscribe_set_time_update(subs, 1);
+snd_seq_port_subscribe_set_queue(subs, q);
+\endcode
+For getting the wallclock time (sec/nsec pair), set <i>real</i> attribute:
+\code
+snd_seq_port_subscribe_set_time_real(subs, 1);
+\endcode
+Otherwise, the timestamp is stored in tick unit.
+This feature is useful when receiving events from MIDI input device.
+The event time is automatically set in the event record.
+
+Note that an outsider client may connect other ports.
+In this case, however, the subscription may be refused
+if #SND_SEQ_PORT_CAP_NO_EXPORT capability is set in either sender or receiver port.
+
+\section seq_subs_ex Examples of subscription
+
+\subsection seq_subs_ex_capt Capture from keyboard
+
+Assume MIDI input port = 64:0, application port = 128:0, and
+queue for timestamp = 1 with real-time stamp.
+The application port must have capability #SND_SEQ_PORT_CAP_WRITE.
+\code
+void capture_keyboard(snd_seq_t *seq)
+{
+        snd_seq_addr_t sender, dest;
+        snd_seq_port_subscribe_t *subs;
+        sender.client = 64;
+        sender.port = 0;
+        dest.client = 128;
+        dest.port = 0;
+        snd_seq_port_subscribe_alloca(&subs);
+        snd_seq_port_subscribe_set_sender(subs, &sender);
+        snd_seq_port_subscribe_set_dest(subs, &dest);
+        snd_seq_port_subscribe_set_queue(subs, 1);
+        snd_seq_port_subscribe_set_time_update(subs, 1);
+        snd_seq_port_subscribe_set_time_real(subs, 1);
+        snd_seq_subscribe_port(seq, subs);
+}
+\endcode
+
+\subsection seq_subs_ex_out Output to MIDI device
+
+Assume MIDI output port = 65:1 and application port = 128:0.
+The application port must have capability #SND_SEQ_PORT_CAP_READ.
+\code
+void subscribe_output(snd_seq_t *seq)
+{
+        snd_seq_addr_t sender, dest;
+        snd_seq_port_subscribe_t *subs;
+        sender.client = 128;
+        sender.port = 0;
+        dest.client = 65;
+        dest.port = 1;
+        snd_seq_port_subscribe_alloca(&subs);
+        snd_seq_port_subscribe_set_sender(subs, &sender);
+        snd_seq_port_subscribe_set_dest(subs, &dest);
+        snd_seq_subscribe_port(seq, subs);
+}
+\endcode
+This example can be simplified by using #snd_seq_connect_to() function.
+\code
+void subscribe_output(snd_seq_t *seq)
+{
+        snd_seq_connect_to(seq, 0, 65, 1);
+}
+\endcode
+
+\subsection seq_subs_ex_arbit Arbitrary connection
+
+Assume connection from application 128:0 to 129:0,
+and that subscription is done by the third application (130:0).
+The sender must have capabilities both
+#SND_SEQ_PORT_CAP_READ and
+#SND_SEQ_PORT_CAP_SUBS_READ,
+and the receiver
+#SND_SEQ_PORT_CAP_WRITE and
+#SND_SEQ_PORT_CAP_SUBS_WRITE, respectively.
+\code
+// ..in the third application (130:0) ..
+void coupling(snd_seq_t *seq)
+{
+        snd_seq_addr_t sender, dest;
+        snd_seq_port_subscribe_t *subs;
+        sender.client = 128;
+        sender.port = 0;
+        dest.client = 129;
+        dest.port = 0;
+        snd_seq_port_subscribe_alloca(&subs);
+        snd_seq_port_subscribe_set_sender(subs, &sender);
+        snd_seq_port_subscribe_set_dest(subs, &dest);
+        snd_seq_subscribe_port(seq, subs);
+}
+\endcode
+
+\section seq_ex_event Event Processing
+
+\subsection seq_ex_address Addressing
+
+Now, two ports are connected by subscription.  Then how to send events?
+
+The subscribed port doesn't have to know the exact sender address.
+Instead, there is a special address for subscribers,
+#SND_SEQ_ADDRESS_SUBSCRIBERS.
+The sender must set this value as the destination client.
+Destination port is ignored.
+
+The other values in source and destination addresses are identical with
+the normal event record.
+If the event is scheduled, proper queue and timestamp values must be set.
+
+There is a convenient function to set the address in an event record.
+In order to set destination as subscribers, use
+#snd_seq_ev_set_subs().
+
+\subsection Scheduled Delivery
+
+If we send an event at the scheduled time <code>t</code> (tick)
+on the queue <code>Q</code>,
+the sender must set both schedule queue and time in the
+event record.
+The program appears like this:
+\code
+void schedule_event(snd_seq_t *seq)
+{
+        snd_seq_event_t ev;
+
+        snd_seq_ev_clear(&ev);
+        snd_seq_ev_set_source(&ev, my_port);
+        snd_seq_ev_set_subs(&ev);
+        snd_seq_ev_schedule_tick(&ev, Q, 0, t);
+        ... // set event type, data, so on..
+
+        snd_seq_event_output(seq, &ev);
+        ...
+        snd_seq_drain_output(seq);  // if necessary
+}
+\endcode
+Of course, you can use realtime stamp, too.
+
+\subsection seq_ex_direct Direct Delivery
+
+If the event is sent immediately without enqueued, the sender doesn't take
+care of queue and timestamp.
+As well as the case above, there is a function to set the direct delivery,
+#snd_seq_ev_set_direct().
+The program can be more simplified as follows:
+\code
+void direct_delivery(snd_seq_t *seq)
+{
+        snd_seq_event_t ev;
+
+        snd_seq_ev_clear(&ev);
+        snd_seq_ev_set_source(&ev, port);
+        snd_seq_ev_set_subs(&ev);
+        snd_seq_ev_set_direct(&ev);
+        ... // set event type, data, so on..
+
+        snd_seq_event_output(seq, &ev);
+        snd_seq_drain_output(seq);
+}
+\endcode
+You should flush event soon after output event.
+Otherwise, the event is enqueued on output queue of ALSA library
+(not in the kernel!), and will be never processed until
+this queue becomes full.
+
+\subsection seq_ex_filter Filter Application
+
+A typical filter program, which receives an event and sends it immediately
+after some modification, will appear as following:
+\code
+void event_filter(snd_seq_t *seq, snd_seq_event_t *ev)
+{
+        while (snd_seq_event_input(seq, &ev) >= 0) {
+                //.. modify input event ..
+
+                snd_seq_ev_set_source(ev, my_port);
+                snd_seq_ev_set_subs(ev);
+                snd_seq_ev_set_direct(ev);
+                snd_seq_event_output(seq, ev);
+                snd_seq_drain_output(seq);
+        }
+}
+\endcode
+
+*/
+
+#include <sys/poll.h>
+#include "seq_local.h"
+
+/****************************************************************************
+ *                                                                          *
+ *                                seq.h                                     *
+ *                              Sequencer                                   *
+ *                                                                          *
+ ****************************************************************************/
+
+/**
+ * \brief get identifier of sequencer handle
+ * \param seq sequencer handle
+ * \return ASCII identifier of sequencer handle
+ *
+ * Returns the ASCII identifier of the given sequencer handle. It's the same
+ * identifier specified in snd_seq_open().
+ *
+ * \sa snd_seq_open()
+ */
+const char *snd_seq_name(snd_seq_t *seq)
+{
+	assert(seq);
+	return seq->name;
+}
+
+/**
+ * \brief get type of sequencer handle
+ * \param seq sequencer handle
+ * \return type of sequencer handle
+ *
+ * Returns the type #snd_seq_type_t of the given sequencer handle.
+ *
+ * \sa snd_seq_open()
+ */
+snd_seq_type_t snd_seq_type(snd_seq_t *seq)
+{
+	assert(seq);
+	return seq->type;
+}
+
+static int snd_seq_open_conf(snd_seq_t **seqp, const char *name,
+			     snd_config_t *seq_root, snd_config_t *seq_conf,
+			     int streams, int mode)
+{
+	const char *str;
+	char buf[256];
+	int err;
+	snd_config_t *conf, *type_conf = NULL;
+	snd_config_iterator_t i, next;
+	const char *id;
+	const char *lib = NULL, *open_name = NULL;
+	int (*open_func)(snd_seq_t **, const char *,
+			 snd_config_t *, snd_config_t *, 
+			 int, int) = NULL;
+#ifndef PIC
+	extern void *snd_seq_open_symbols(void);
+#endif
+	void *h = NULL;
+	if (snd_config_get_type(seq_conf) != SND_CONFIG_TYPE_COMPOUND) {
+		if (name)
+			SNDERR("Invalid type for SEQ %s definition", name);
+		else
+			SNDERR("Invalid type for SEQ definition");
+		return -EINVAL;
+	}
+	err = snd_config_search(seq_conf, "type", &conf);
+	if (err < 0) {
+		SNDERR("type is not defined");
+		return err;
+	}
+	err = snd_config_get_id(conf, &id);
+	if (err < 0) {
+		SNDERR("unable to get id");
+		return err;
+	}
+	err = snd_config_get_string(conf, &str);
+	if (err < 0) {
+		SNDERR("Invalid type for %s", id);
+		return err;
+	}
+	err = snd_config_search_definition(seq_root, "seq_type", str, &type_conf);
+	if (err >= 0) {
+		if (snd_config_get_type(type_conf) != SND_CONFIG_TYPE_COMPOUND) {
+			SNDERR("Invalid type for SEQ type %s definition", str);
+			goto _err;
+		}
+		snd_config_for_each(i, next, type_conf) {
+			snd_config_t *n = snd_config_iterator_entry(i);
+			const char *id;
+			if (snd_config_get_id(n, &id) < 0)
+				continue;
+			if (strcmp(id, "comment") == 0)
+				continue;
+			if (strcmp(id, "lib") == 0) {
+				err = snd_config_get_string(n, &lib);
+				if (err < 0) {
+					SNDERR("Invalid type for %s", id);
+					goto _err;
+				}
+				continue;
+			}
+			if (strcmp(id, "open") == 0) {
+				err = snd_config_get_string(n, &open_name);
+				if (err < 0) {
+					SNDERR("Invalid type for %s", id);
+					goto _err;
+				}
+				continue;
+			}
+			SNDERR("Unknown field %s", id);
+			err = -EINVAL;
+			goto _err;
+		}
+	}
+	if (!open_name) {
+		open_name = buf;
+		snprintf(buf, sizeof(buf), "_snd_seq_%s_open", str);
+	}
+#ifndef PIC
+	snd_seq_open_symbols();
+#endif
+	h = snd_dlopen(lib, RTLD_NOW);
+	if (h)
+		open_func = snd_dlsym(h, open_name, SND_DLSYM_VERSION(SND_SEQ_DLSYM_VERSION));
+	err = 0;
+	if (!h) {
+		SNDERR("Cannot open shared library %s", lib);
+		err = -ENOENT;
+	} else if (!open_func) {
+		SNDERR("symbol %s is not defined inside %s", open_name, lib);
+		snd_dlclose(h);
+		err = -ENXIO;
+	}
+       _err:
+	if (type_conf)
+		snd_config_delete(type_conf);
+	if (! err) {
+		err = open_func(seqp, name, seq_root, seq_conf, streams, mode);
+		if (err < 0)
+			snd_dlclose(h);
+		else
+			(*seqp)->dl_handle = h;
+	}
+	return err;
+}
+
+static int snd_seq_open_noupdate(snd_seq_t **seqp, snd_config_t *root,
+				 const char *name, int streams, int mode,
+				 int hop)
+{
+	int err;
+	snd_config_t *seq_conf;
+	err = snd_config_search_definition(root, "seq", name, &seq_conf);
+	if (err < 0) {
+		SNDERR("Unknown SEQ %s", name);
+		return err;
+	}
+	snd_config_set_hop(seq_conf, hop);
+	err = snd_seq_open_conf(seqp, name, root, seq_conf, streams, mode);
+	snd_config_delete(seq_conf);
+	return err;
+}
+
+
+/**
+ * \brief Open the ALSA sequencer
+ *
+ * \param seqp Pointer to a snd_seq_t pointer.  This pointer must be
+ * kept and passed to most of the other sequencer functions.
+ * \param name The sequencer's "name".  This is \em not a name you make
+ * up for your own purposes; it has special significance to the ALSA
+ * library.  Usually you need to pass \c "default" here.
+ * \param streams The read/write mode of the sequencer.  Can be one of
+ * three values:
+ * - #SND_SEQ_OPEN_OUTPUT - open the sequencer for output only
+ * - #SND_SEQ_OPEN_INPUT - open the sequencer for input only
+ * - #SND_SEQ_OPEN_DUPLEX - open the sequencer for output and input
+ * \note Internally, these are translated to \c O_WRONLY, \c O_RDONLY and
+ * \c O_RDWR respectively and used as the second argument to the C library
+ * open() call.
+ * \param mode Optional modifier.  Can be either 0, or
+ * #SND_SEQ_NONBLOCK, which will make read/write operations
+ * non-blocking.  This can also be set later using #snd_seq_nonblock().
+ * \return 0 on success otherwise a negative error code
+ *
+ * Creates a new handle and opens a connection to the kernel
+ * sequencer interface.
+ * After a client is created successfully, an event
+ * with #SND_SEQ_EVENT_CLIENT_START is broadcast to announce port.
+ *
+ * \sa snd_seq_open_lconf(), snd_seq_close(), snd_seq_type(), snd_seq_name(),
+ *     snd_seq_nonblock(), snd_seq_client_id()
+ */
+int snd_seq_open(snd_seq_t **seqp, const char *name, 
+		 int streams, int mode)
+{
+	int err;
+	assert(seqp && name);
+	err = snd_config_update();
+	if (err < 0)
+		return err;
+	return snd_seq_open_noupdate(seqp, snd_config, name, streams, mode, 0);
+}
+
+/**
+ * \brief Open the ALSA sequencer using local configuration
+ *
+ * \param seqp Pointer to a snd_seq_t pointer.
+ * \param name The name to open
+ * \param streams The read/write mode of the sequencer.
+ * \param mode Optional modifier
+ * \param lconf Local configuration
+ * \return 0 on success otherwise a negative error code
+ *
+ * See the snd_seq_open() function for further details. The extension
+ * is that the given configuration is used to resolve abstract name.
+ *
+ * \sa snd_seq_open()
+ */
+int snd_seq_open_lconf(snd_seq_t **seqp, const char *name, 
+		       int streams, int mode, snd_config_t *lconf)
+{
+	assert(seqp && name && lconf);
+	return snd_seq_open_noupdate(seqp, lconf, name, streams, mode, 0);
+}
+
+#ifndef DOC_HIDDEN
+int _snd_seq_open_lconf(snd_seq_t **seqp, const char *name, 
+			int streams, int mode, snd_config_t *lconf,
+			snd_config_t *parent_conf)
+{
+	int hop;
+	assert(seqp && name && lconf);
+	if ((hop = snd_config_check_hop(parent_conf)) < 0)
+		return hop;
+	return snd_seq_open_noupdate(seqp, lconf, name, streams, mode, hop + 1);
+}
+#endif
+
+/**
+ * \brief Close the sequencer
+ * \param seq Handle returned from #snd_seq_open()
+ * \return 0 on success otherwise a negative error code
+ *
+ * Closes the sequencer client and releases its resources.
+ * After a client is closed, an event with
+ * #SND_SEQ_EVENT_CLIENT_EXIT is broadcast to announce port.
+ * The connection between other clients are disconnected.
+ * Call this just before exiting your program.
+ *
+ * \sa snd_seq_close()
+ */
+int snd_seq_close(snd_seq_t *seq)
+{
+	int err;
+	assert(seq);
+	err = seq->ops->close(seq);
+	if (seq->dl_handle)
+		snd_dlclose(seq->dl_handle);
+	free(seq->obuf);
+	free(seq->ibuf);
+	free(seq->tmpbuf);
+	free(seq->name);
+	free(seq);
+	return err;
+}
+
+/**
+ * \brief Returns the number of poll descriptors
+ * \param seq sequencer handle
+ * \param events the poll events to be checked (\c POLLIN and \c POLLOUT)
+ * \return the number of poll descriptors.
+ *
+ * Get the number of poll descriptors.  The polling events to be checked
+ * can be specified by the second argument.  When both input and output
+ * are checked, pass \c POLLIN|POLLOUT
+ *
+ * \sa snd_seq_poll_descriptors()
+ */
+int snd_seq_poll_descriptors_count(snd_seq_t *seq, short events)
+{
+	int result = 0;
+	assert(seq);
+	if (events & POLLIN) {
+		assert(seq->streams & SND_SEQ_OPEN_INPUT);
+		result++;
+	}
+	if (events & POLLOUT) {
+		assert(seq->streams & SND_SEQ_OPEN_OUTPUT);
+		result++;
+	}
+	return result ? 1 : 0;
+}
+
+/**
+ * \brief Get poll descriptors
+ * \param seq sequencer handle
+ * \param pfds array of poll descriptors
+ * \param space space in the poll descriptor array
+ * \param events polling events to be checked (\c POLLIN and \c POLLOUT)
+ * \return count of filled descriptors
+ *
+ * Get poll descriptors assigned to the sequencer handle.
+ * Since a sequencer handle can duplex streams, you need to set which direction(s)
+ * is/are polled in \a events argument.  When \c POLLIN bit is specified,
+ * the incoming events to the ports are checked.
+ *
+ * To check the returned poll-events, call #snd_seq_poll_descriptors_revents()
+ * instead of reading the pollfd structs directly.
+ *
+ * \sa snd_seq_poll_descriptors_count(), snd_seq_poll_descriptors_revents()
+ */
+int snd_seq_poll_descriptors(snd_seq_t *seq, struct pollfd *pfds, unsigned int space, short events)
+{
+	short revents = 0;
+
+	assert(seq);
+	if ((events & POLLIN) && space >= 1) {
+		assert(seq->streams & SND_SEQ_OPEN_INPUT);
+		revents |= POLLIN|POLLERR|POLLNVAL;
+	}
+	if ((events & POLLOUT) && space >= 1) {
+		assert(seq->streams & SND_SEQ_OPEN_OUTPUT);
+		revents |= POLLOUT|POLLERR|POLLNVAL;
+	}
+	if (!revents)
+		return 0;
+	pfds->fd = seq->poll_fd;
+	pfds->events = revents;
+	return 1;
+}
+
+/**
+ * \brief get returned events from poll descriptors
+ * \param seq sequencer handle
+ * \param pfds array of poll descriptors
+ * \param nfds count of poll descriptors
+ * \param revents returned events
+ * \return zero if success, otherwise a negative error code
+ *
+ * \sa snd_seq_poll_descriptors()
+ */
+int snd_seq_poll_descriptors_revents(snd_seq_t *seq, struct pollfd *pfds, unsigned int nfds, unsigned short *revents)
+{
+        assert(seq && pfds && revents);
+        if (nfds == 1) {
+                *revents = pfds->revents;
+                return 0;
+        }
+        return -EINVAL;
+}
+
+/**
+ * \brief Set nonblock mode
+ * \param seq sequencer handle
+ * \param nonblock 0 = block, 1 = nonblock mode
+ * \return 0 on success otherwise a negative error code
+ *
+ * Change the blocking mode of the given client.
+ * In block mode, the client falls into sleep when it fills the
+ * output memory pool with full events.  The client will be woken up
+ * after a certain amount of free space becomes available.
+ *
+ * \sa snd_seq_open()
+ */
+int snd_seq_nonblock(snd_seq_t *seq, int nonblock)
+{
+	int err;
+	assert(seq);
+	err = seq->ops->nonblock(seq, nonblock);
+	if (err < 0)
+		return err;
+	if (nonblock)
+		seq->mode |= SND_SEQ_NONBLOCK;
+	else
+		seq->mode &= ~SND_SEQ_NONBLOCK;
+	return 0;
+}
+
+/**
+ * \brief Get the client id
+ * \param seq sequencer handle
+ * \return the client id
+ *
+ * Returns the id of the specified client.
+ * If an error occurs, function returns the negative error code.
+ * A client id is necessary to inquiry or to set the client information.
+ * A user client is assigned from 128 to 191.
+ *
+ * \sa snd_seq_open()
+ */
+int snd_seq_client_id(snd_seq_t *seq)
+{
+	assert(seq);
+	return seq->client;
+}
+
+/**
+ * \brief Return the size of output buffer
+ * \param seq sequencer handle
+ * \return the size of output buffer in bytes
+ *
+ * Obtains the size of output buffer.
+ * This buffer is used to store decoded byte-stream of output events
+ * before transferring to sequencer.
+ *
+ * \sa snd_seq_set_output_buffer_size()
+ */
+size_t snd_seq_get_output_buffer_size(snd_seq_t *seq)
+{
+	assert(seq);
+	if (!seq->obuf)
+		return 0;
+	return seq->obufsize;
+}
+
+/**
+ * \brief Return the size of input buffer
+ * \param seq sequencer handle
+ * \return the size of input buffer in bytes
+ *
+ * Obtains the size of input buffer.
+ * This buffer is used to read byte-stream of input events from sequencer.
+ *
+ * \sa snd_seq_set_input_buffer_size()
+ */
+size_t snd_seq_get_input_buffer_size(snd_seq_t *seq)
+{
+	assert(seq);
+	if (!seq->ibuf)
+		return 0;
+	return seq->ibufsize * sizeof(snd_seq_event_t);
+}
+
+/**
+ * \brief Change the size of output buffer
+ * \param seq sequencer handle
+ * \param size the size of output buffer to be changed in bytes
+ * \return 0 on success otherwise a negative error code
+ *
+ * Changes the size of output buffer.
+ *
+ * \sa snd_seq_get_output_buffer_size()
+ */
+int snd_seq_set_output_buffer_size(snd_seq_t *seq, size_t size)
+{
+	assert(seq && seq->obuf);
+	assert(size >= sizeof(snd_seq_event_t));
+	snd_seq_drop_output(seq);
+	if (size != seq->obufsize) {
+		char *newbuf;
+		newbuf = calloc(1, size);
+		if (newbuf == NULL)
+			return -ENOMEM;
+		free(seq->obuf);
+		seq->obuf = newbuf;
+		seq->obufsize = size;
+	}
+	return 0;
+}
+
+/**
+ * \brief Resize the input buffer
+ * \param seq sequencer handle
+ * \param size the size of input buffer to be changed in bytes
+ * \return 0 on success otherwise a negative error code
+ *
+ * Changes the size of input buffer.
+ *
+ * \sa snd_seq_get_input_buffer_size()
+ */
+int snd_seq_set_input_buffer_size(snd_seq_t *seq, size_t size)
+{
+	assert(seq && seq->ibuf);
+	assert(size >= sizeof(snd_seq_event_t));
+	snd_seq_drop_input(seq);
+	size = (size + sizeof(snd_seq_event_t) - 1) / sizeof(snd_seq_event_t);
+	if (size != seq->ibufsize) {
+		snd_seq_event_t *newbuf;
+		newbuf = calloc(sizeof(snd_seq_event_t), size);
+		if (newbuf == NULL)
+			return -ENOMEM;
+		free(seq->ibuf);
+		seq->ibuf = newbuf;
+		seq->ibufsize = size;
+	}
+	return 0;
+}
+
+
+/**
+ * \brief Get size of #snd_seq_system_info_t
+ * \return size in bytes
+ */
+size_t snd_seq_system_info_sizeof()
+{
+	return sizeof(snd_seq_system_info_t);
+}
+
+/**
+ * \brief Allocate an empty #snd_seq_system_info_t using standard malloc
+ * \param ptr returned pointer
+ * \return 0 on success otherwise negative error code
+ */
+int snd_seq_system_info_malloc(snd_seq_system_info_t **ptr)
+{
+	assert(ptr);
+	*ptr = calloc(1, sizeof(snd_seq_system_info_t));
+	if (!*ptr)
+		return -ENOMEM;
+	return 0;
+}
+
+/**
+ * \brief Frees a previously allocated #snd_seq_system_info_t
+ * \param obj pointer to object to free
+ */
+void snd_seq_system_info_free(snd_seq_system_info_t *obj)
+{
+	free(obj);
+}
+
+/**
+ * \brief Copy one #snd_seq_system_info_t to another
+ * \param dst pointer to destination
+ * \param src pointer to source
+ */
+void snd_seq_system_info_copy(snd_seq_system_info_t *dst, const snd_seq_system_info_t *src)
+{
+	assert(dst && src);
+	*dst = *src;
+}
+
+
+/**
+ * \brief Get maximum number of queues
+ * \param info #snd_seq_system_info_t container
+ * \return maximum number of queues
+ *
+ * \sa snd_seq_system_info()
+ */
+int snd_seq_system_info_get_queues(const snd_seq_system_info_t *info)
+{
+	assert(info);
+	return info->queues;
+}
+
+/**
+ * \brief Get maximum number of clients
+ * \param info #snd_seq_system_info_t container
+ * \return maximum number of clients
+ *
+ * \sa snd_seq_system_info()
+ */
+int snd_seq_system_info_get_clients(const snd_seq_system_info_t *info)
+{
+	assert(info);
+	return info->clients;
+}
+
+/**
+ * \brief Get maximum number of ports
+ * \param info #snd_seq_system_info_t container
+ * \return maximum number of ports
+ *
+ * \sa snd_seq_system_info()
+ */
+int snd_seq_system_info_get_ports(const snd_seq_system_info_t *info)
+{
+	assert(info);
+	return info->ports;
+}
+
+/**
+ * \brief Get maximum number of channels
+ * \param info #snd_seq_system_info_t container
+ * \return maximum number of channels
+ *
+ * \sa snd_seq_system_info()
+ */
+int snd_seq_system_info_get_channels(const snd_seq_system_info_t *info)
+{
+	assert(info);
+	return info->channels;
+}
+
+/**
+ * \brief Get the current number of clients
+ * \param info #snd_seq_system_info_t container
+ * \return current number of clients
+ *
+ * \sa snd_seq_system_info()
+ */
+int snd_seq_system_info_get_cur_clients(const snd_seq_system_info_t *info)
+{
+	assert(info);
+	return info->cur_clients;
+}
+
+/**
+ * \brief Get the current number of queues
+ * \param info #snd_seq_system_info_t container
+ * \return current number of queues
+ *
+ * \sa snd_seq_system_info()
+ */
+int snd_seq_system_info_get_cur_queues(const snd_seq_system_info_t *info)
+{
+	assert(info);
+	return info->cur_queues;
+}
+
+/**
+ * \brief obtain the sequencer system information
+ * \param seq sequencer handle
+ * \param info the pointer to be stored
+ * \return 0 on success otherwise a negative error code
+ *
+ * Stores the global system information of ALSA sequencer system.
+ * The returned data contains
+ * the maximum available numbers of queues, clients, ports and channels.
+ */
+int snd_seq_system_info(snd_seq_t *seq, snd_seq_system_info_t * info)
+{
+	assert(seq && info);
+	return seq->ops->system_info(seq, info);
+}
+
+
+/*----------------------------------------------------------------*/
+
+/**
+ * \brief get size of #snd_seq_client_info_t
+ * \return size in bytes
+ */
+size_t snd_seq_client_info_sizeof()
+{
+	return sizeof(snd_seq_client_info_t);
+}
+
+/**
+ * \brief allocate an empty #snd_seq_client_info_t using standard malloc
+ * \param ptr returned pointer
+ * \return 0 on success otherwise negative error code
+ */
+int snd_seq_client_info_malloc(snd_seq_client_info_t **ptr)
+{
+	assert(ptr);
+	*ptr = calloc(1, sizeof(snd_seq_client_info_t));
+	if (!*ptr)
+		return -ENOMEM;
+	return 0;
+}
+
+/**
+ * \brief frees a previously allocated #snd_seq_client_info_t
+ * \param obj pointer to object to free
+ */
+void snd_seq_client_info_free(snd_seq_client_info_t *obj)
+{
+	free(obj);
+}
+
+/**
+ * \brief copy one #snd_seq_client_info_t to another
+ * \param dst pointer to destination
+ * \param src pointer to source
+ */
+void snd_seq_client_info_copy(snd_seq_client_info_t *dst, const snd_seq_client_info_t *src)
+{
+	assert(dst && src);
+	*dst = *src;
+}
+
+
+/**
+ * \brief Get client id of a client_info container
+ * \param info client_info container
+ * \return client id
+ *
+ * \sa snd_seq_get_client_info(), snd_seq_client_info_set_client(), snd_seq_client_id()
+ */
+int snd_seq_client_info_get_client(const snd_seq_client_info_t *info)
+{
+	assert(info);
+	return info->client;
+}
+
+/**
+ * \brief Get client type of a client_info container
+ * \param info client_info container
+ * \return client type
+ *
+ * The client type is either #SND_SEQ_KERNEL_CLIENT or #SND_SEQ_USER_CLIENT
+ * for kernel or user client respectively.
+ *
+ * \sa snd_seq_get_client_info()
+ */
+snd_seq_client_type_t snd_seq_client_info_get_type(const snd_seq_client_info_t *info)
+{
+	assert(info);
+	return info->type;
+}
+
+/**
+ * \brief Get the name of a client_info container
+ * \param info client_info container
+ * \return name string
+ *
+ * \sa snd_seq_get_client_info(), snd_seq_client_info_set_name()
+ */
+const char *snd_seq_client_info_get_name(snd_seq_client_info_t *info)
+{
+	assert(info);
+	return info->name;
+}
+
+/**
+ * \brief Get the broadcast filter usage of a client_info container
+ * \param info client_info container
+ * \return 1 if broadcast is accepted
+ *
+ * \sa snd_seq_get_client_info(), snd_seq_client_info_set_broadcast_filter()
+ */
+int snd_seq_client_info_get_broadcast_filter(const snd_seq_client_info_t *info)
+{
+	assert(info);
+	return (info->filter & SNDRV_SEQ_FILTER_BROADCAST) ? 1 : 0;
+}
+
+/**
+ * \brief Get the error-bounce usage of a client_info container
+ * \param info client_info container
+ * \return 1 if error-bounce is enabled
+ *
+ * \sa snd_seq_get_client_info(), snd_seq_client_info_set_error_bounce()
+ */
+int snd_seq_client_info_get_error_bounce(const snd_seq_client_info_t *info)
+{
+	assert(info);
+	return (info->filter & SNDRV_SEQ_FILTER_BOUNCE) ? 1 : 0;
+}
+
+/**
+ * \brief (DEPRECATED) Get the event filter bitmap of a client_info container
+ * \param info client_info container
+ * \return NULL if no event filter, or pointer to event filter bitmap
+ *
+ * Use #snd_seq_client_info_event_filter_check() instead.
+ *
+ * \sa snd_seq_client_info_event_filter_add(),
+ *     snd_seq_client_info_event_filter_del(),
+ *     snd_seq_client_info_event_filter_check(),
+ *     snd_seq_client_info_event_filter_clear(),
+ *     snd_seq_get_client_info()
+ */
+const unsigned char *snd_seq_client_info_get_event_filter(const snd_seq_client_info_t *info)
+{
+	assert(info);
+	if (info->filter & SNDRV_SEQ_FILTER_USE_EVENT)
+		return info->event_filter;
+	else
+		return NULL;
+}
+
+/**
+ * \brief Disable event filtering of a client_info container
+ * \param info client_info container
+ *
+ * Remove all event types added with #snd_seq_client_info_event_filter_add and clear
+ * the event filtering flag of this client_info container.
+ * 
+ * \sa snd_seq_client_info_event_filter_add(),
+ *     snd_seq_client_info_event_filter_del(),
+ *     snd_seq_client_info_event_filter_check(),
+ *     snd_seq_get_client_info(),
+ *     snd_seq_set_client_info()
+ */
+void snd_seq_client_info_event_filter_clear(snd_seq_client_info_t *info)
+{
+       assert(info);
+       info->filter &= ~SNDRV_SEQ_FILTER_USE_EVENT;
+       memset(info->event_filter, 0, sizeof(info->event_filter));
+}
+
+/**
+ * \brief Add an event type to the event filtering of a client_info container
+ * \param info client_info container
+ * \param event_type event type to be added
+ * 
+ * Set the event filtering flag of this client_info and add the specified event type to the 
+ * filter bitmap of this client_info container.
+ *
+ * \sa snd_seq_get_client_info(),
+ *     snd_seq_set_client_info(),
+ *     snd_seq_client_info_event_filter_del(),
+ *     snd_seq_client_info_event_filter_check(),
+ *     snd_seq_client_info_event_filter_clear()
+ */
+void snd_seq_client_info_event_filter_add(snd_seq_client_info_t *info, int event_type)
+{
+       assert(info);
+       info->filter |= SNDRV_SEQ_FILTER_USE_EVENT;
+       snd_seq_set_bit(event_type, info->event_filter);
+}
+
+/**
+ * \brief Remove an event type from the event filtering of a client_info container
+ * \param info client_info container
+ * \param event_type event type to be removed
+ *
+ * Removes the specified event from the filter bitmap of this client_info container. It will
+ * not clear the event filtering flag, use #snd_seq_client_info_event_filter_clear instead.
+ *
+ * \sa snd_seq_get_client_info(),
+ *     snd_seq_set_client_info(),
+ *     snd_seq_client_info_event_filter_add(),
+ *     snd_seq_client_info_event_filter_check(),
+ *     snd_seq_client_info_event_filter_clear()
+ */
+void snd_seq_client_info_event_filter_del(snd_seq_client_info_t *info, int event_type)
+{
+       assert(info);
+       snd_seq_unset_bit(event_type, info->event_filter);
+}
+
+/**
+ * \brief Check if an event type is present in the event filtering of a client_info container
+ * \param info client_info container
+ * \param event_type event type to be checked
+ * \return 1 if the event type is present, 0 otherwise
+ *
+ * Test if the event type is in the filter bitmap of this client_info container.
+ *
+ * \sa snd_seq_get_client_info(),
+ *     snd_seq_set_client_info(),
+ *     snd_seq_client_info_event_filter_add(),
+ *     snd_seq_client_info_event_filter_del(),
+ *     snd_seq_client_info_event_filter_clear()
+ */
+int snd_seq_client_info_event_filter_check(snd_seq_client_info_t *info, int event_type)
+{
+       assert(info);
+       return snd_seq_get_bit(event_type, info->event_filter);
+} 
+
+/**
+ * \brief Get the number of opened ports of a client_info container
+ * \param info client_info container
+ * \return number of opened ports
+ *
+ * \sa snd_seq_get_client_info()
+ */
+int snd_seq_client_info_get_num_ports(const snd_seq_client_info_t *info)
+{
+	assert(info);
+	return info->num_ports;
+}
+
+/**
+ * \brief Get the number of lost events of a client_info container
+ * \param info client_info container
+ * \return number of lost events
+ *
+ * \sa snd_seq_get_client_info()
+ */
+int snd_seq_client_info_get_event_lost(const snd_seq_client_info_t *info)
+{
+	assert(info);
+	return info->event_lost;
+}
+
+/**
+ * \brief Set the client id of a client_info container
+ * \param info client_info container
+ * \param client client id
+ *
+ * \sa snd_seq_get_client_info(), snd_seq_client_info_get_client()
+ */
+void snd_seq_client_info_set_client(snd_seq_client_info_t *info, int client)
+{
+	assert(info);
+	info->client = client;
+}
+
+/**
+ * \brief Set the name of a client_info container
+ * \param info client_info container
+ * \param name name string
+ *
+ * \sa snd_seq_get_client_info(), snd_seq_client_info_get_name(),
+ *     snd_seq_set_client_name()
+ */
+void snd_seq_client_info_set_name(snd_seq_client_info_t *info, const char *name)
+{
+	assert(info && name);
+	strncpy(info->name, name, sizeof(info->name));
+}
+
+/**
+ * \brief Set the broadcast filter usage of a client_info container
+ * \param info client_info container
+ * \param val non-zero if broadcast is accepted
+ *
+ * \sa snd_seq_get_client_info(), snd_seq_client_info_get_broadcast_filter()
+ */
+void snd_seq_client_info_set_broadcast_filter(snd_seq_client_info_t *info, int val)
+{
+	assert(info);
+	if (val)
+		info->filter |= SNDRV_SEQ_FILTER_BROADCAST;
+	else
+		info->filter &= ~SNDRV_SEQ_FILTER_BROADCAST;
+}
+
+/**
+ * \brief Set the error-bounce usage of a client_info container
+ * \param info client_info container
+ * \param val non-zero if error is bounced
+ *
+ * \sa snd_seq_get_client_info(), snd_seq_client_info_get_error_bounce()
+ */
+void snd_seq_client_info_set_error_bounce(snd_seq_client_info_t *info, int val)
+{
+	assert(info);
+	if (val)
+		info->filter |= SNDRV_SEQ_FILTER_BOUNCE;
+	else
+		info->filter &= ~SNDRV_SEQ_FILTER_BOUNCE;
+}
+
+/**
+ * \brief (DEPRECATED) Set the event filter bitmap of a client_info container
+ * \param info client_info container
+ * \param filter event filter bitmap, pass NULL for no event filtering
+ *
+ * Use #snd_seq_client_info_event_filter_add instead.
+ *
+ * \sa snd_seq_client_info_event_filter_add(),
+ *     snd_seq_client_info_event_filter_del(),
+ *     snd_seq_client_info_event_filter_check(),
+ *     snd_seq_client_info_event_filter_clear(),
+ *     snd_seq_set_client_info()
+ */
+void snd_seq_client_info_set_event_filter(snd_seq_client_info_t *info, unsigned char *filter)
+{
+	assert(info);
+	if (! filter)
+		info->filter &= ~SNDRV_SEQ_FILTER_USE_EVENT;
+	else {
+		info->filter |= SNDRV_SEQ_FILTER_USE_EVENT;
+		memcpy(info->event_filter, filter, sizeof(info->event_filter));
+	}
+}
+
+
+/**
+ * \brief obtain the information of the given client
+ * \param seq sequencer handle
+ * \param client client id
+ * \param info the pointer to be stored
+ * \return 0 on success otherwise a negative error code
+ * 
+ * Obtains the information of the client with a client id specified by
+ * info argument.
+ * The obtained information is written on info parameter.
+ *
+ * \sa snd_seq_get_client_info()
+ */
+int snd_seq_get_any_client_info(snd_seq_t *seq, int client, snd_seq_client_info_t *info)
+{
+	assert(seq && info && client >= 0);
+	memset(info, 0, sizeof(snd_seq_client_info_t));
+	info->client = client;
+	return seq->ops->get_client_info(seq, info);
+}
+
+/**
+ * \brief obtain the current client information
+ * \param seq sequencer handle
+ * \param info the pointer to be stored
+ * \return 0 on success otherwise a negative error code
+ *
+ * Obtains the information of the current client stored on info.
+ * client and type fields are ignored.
+ *
+ * \sa snd_seq_get_any_client_info(), snd_seq_set_client_info(),
+ *     snd_seq_query_next_client()
+ */
+int snd_seq_get_client_info(snd_seq_t *seq, snd_seq_client_info_t *info)
+{
+	return snd_seq_get_any_client_info(seq, seq->client, info);
+}
+
+/**
+ * \brief set the current client information
+ * \param seq sequencer handle
+ * \param info the client info data to set
+ * \return 0 on success otherwise a negative error code
+ *
+ * Obtains the information of the current client stored on info.
+ * client and type fields are ignored.
+ *
+ * \sa snd_seq_get_client_info()
+ */
+int snd_seq_set_client_info(snd_seq_t *seq, snd_seq_client_info_t *info)
+{
+	assert(seq && info);
+	info->client = seq->client;
+	info->type = USER_CLIENT;
+	return seq->ops->set_client_info(seq, info);
+}
+
+/**
+ * \brief query the next client
+ * \param seq sequencer handle
+ * \param info query pattern and result
+ *
+ * Queries the next client.
+ * The search begins at the client with an id one greater than
+ * client field in info.
+ * If a client is found, its attributes are stored in info,
+ * and zero is returned.
+ * Otherwise returns a negative error code.
+ *
+ * \sa snd_seq_get_any_client_info()
+ */
+int snd_seq_query_next_client(snd_seq_t *seq, snd_seq_client_info_t *info)
+{
+	assert(seq && info);
+	return seq->ops->query_next_client(seq, info);
+}
+
+
+/*----------------------------------------------------------------*/
+
+
+/*
+ * Port
+ */
+
+/**
+ * \brief get size of #snd_seq_port_info_t
+ * \return size in bytes
+ */
+size_t snd_seq_port_info_sizeof()
+{
+	return sizeof(snd_seq_port_info_t);
+}
+
+/**
+ * \brief allocate an empty #snd_seq_port_info_t using standard malloc
+ * \param ptr returned pointer
+ * \return 0 on success otherwise negative error code
+ */
+int snd_seq_port_info_malloc(snd_seq_port_info_t **ptr)
+{
+	assert(ptr);
+	*ptr = calloc(1, sizeof(snd_seq_port_info_t));
+	if (!*ptr)
+		return -ENOMEM;
+	return 0;
+}
+
+/**
+ * \brief frees a previously allocated #snd_seq_port_info_t
+ * \param obj pointer to object to free
+ */
+void snd_seq_port_info_free(snd_seq_port_info_t *obj)
+{
+	free(obj);
+}
+
+/**
+ * \brief copy one #snd_seq_port_info_t to another
+ * \param dst pointer to destination
+ * \param src pointer to source
+ */
+void snd_seq_port_info_copy(snd_seq_port_info_t *dst, const snd_seq_port_info_t *src)
+{
+	assert(dst && src);
+	*dst = *src;
+}
+
+
+/**
+ * \brief Get client id of a port_info container
+ * \param info port_info container
+ * \return client id
+ *
+ * \sa snd_seq_get_port_info(), snd_seq_port_info_set_client()
+ */
+int snd_seq_port_info_get_client(const snd_seq_port_info_t *info)
+{
+	assert(info);
+	return info->addr.client;
+}
+
+/**
+ * \brief Get port id of a port_info container
+ * \param info port_info container
+ * \return port id
+ *
+ * \sa snd_seq_get_port_info(), snd_seq_port_info_set_port()
+ */
+int snd_seq_port_info_get_port(const snd_seq_port_info_t *info)
+{
+	assert(info);
+	return info->addr.port;
+}
+
+/**
+ * \brief Get client/port address of a port_info container
+ * \param info port_info container
+ * \return client/port address pointer
+ *
+ * \sa snd_seq_get_port_info(), snd_seq_port_info_set_addr()
+ */
+const snd_seq_addr_t *snd_seq_port_info_get_addr(const snd_seq_port_info_t *info)
+{
+	assert(info);
+	return &info->addr;
+}
+
+/**
+ * \brief Get the name of a port_info container
+ * \param info port_info container
+ * \return name string
+ *
+ * \sa snd_seq_get_port_info(), snd_seq_port_info_set_name()
+ */
+const char *snd_seq_port_info_get_name(const snd_seq_port_info_t *info)
+{
+	assert(info);
+	return info->name;
+}
+
+/**
+ * \brief Get the capability bits of a port_info container
+ * \param info port_info container
+ * \return capability bits
+ *
+ * \sa snd_seq_get_port_info(), snd_seq_port_info_set_capability()
+ */
+unsigned int snd_seq_port_info_get_capability(const snd_seq_port_info_t *info)
+{
+	assert(info);
+	return info->capability;
+}
+
+/**
+ * \brief Get the type bits of a port_info container
+ * \param info port_info container
+ * \return port type bits
+ *
+ * \sa snd_seq_get_port_info(), snd_seq_port_info_set_type()
+ */
+unsigned int snd_seq_port_info_get_type(const snd_seq_port_info_t *info)
+{
+	assert(info);
+	return info->type;
+}
+
+/**
+ * \brief Get the number of read subscriptions of a port_info container
+ * \param info port_info container
+ * \return number of read subscriptions
+ *
+ * \sa snd_seq_get_port_info()
+ */
+int snd_seq_port_info_get_read_use(const snd_seq_port_info_t *info)
+{
+	assert(info);
+	return info->read_use;
+}
+
+/**
+ * \brief Get the number of write subscriptions of a port_info container
+ * \param info port_info container
+ * \return number of write subscriptions
+ *
+ * \sa snd_seq_get_port_info()
+ */
+int snd_seq_port_info_get_write_use(const snd_seq_port_info_t *info)
+{
+	assert(info);
+	return info->write_use;
+}
+
+/**
+ * \brief Get the midi channels of a port_info container
+ * \param info port_info container
+ * \return number of midi channels (default 0)
+ *
+ * \sa snd_seq_get_port_info(), snd_seq_port_info_set_midi_channels()
+ */
+int snd_seq_port_info_get_midi_channels(const snd_seq_port_info_t *info)
+{
+	assert(info);
+	return info->midi_channels;
+}
+
+/**
+ * \brief Get the midi voices of a port_info container
+ * \param info port_info container
+ * \return number of midi voices (default 0)
+ *
+ * \sa snd_seq_get_port_info(), snd_seq_port_info_set_midi_voices()
+ */
+int snd_seq_port_info_get_midi_voices(const snd_seq_port_info_t *info)
+{
+	assert(info);
+	return info->midi_voices;
+}
+
+/**
+ * \brief Get the synth voices of a port_info container
+ * \param info port_info container
+ * \return number of synth voices (default 0)
+ *
+ * \sa snd_seq_get_port_info(), snd_seq_port_info_set_synth_voices()
+ */
+int snd_seq_port_info_get_synth_voices(const snd_seq_port_info_t *info)
+{
+	assert(info);
+	return info->synth_voices;
+}
+
+/**
+ * \brief Get the port-specified mode of a port_info container
+ * \param info port_info container
+ * \return 1 if port id is specified at creation
+ *
+ * \sa snd_seq_get_port_info(), snd_seq_port_info_set_port_specified()
+ */
+int snd_seq_port_info_get_port_specified(const snd_seq_port_info_t *info)
+{
+	assert(info);
+	return (info->flags & SNDRV_SEQ_PORT_FLG_GIVEN_PORT) ? 1 : 0;
+}
+
+/**
+ * \brief Get the time-stamping mode of the given port in a port_info container
+ * \param info port_info container
+ * \return 1 if the port updates timestamps of incoming events
+ *
+ * \sa snd_seq_get_port_info(), snd_seq_port_info_set_timestamping()
+ */
+int snd_seq_port_info_get_timestamping(const snd_seq_port_info_t *info)
+{
+	assert(info);
+	return (info->flags & SNDRV_SEQ_PORT_FLG_TIMESTAMP) ? 1 : 0;
+}
+
+/**
+ * \brief Get whether the time-stamping of the given port is real-time mode
+ * \param info port_info container
+ * \return 1 if the time-stamping is in the real-time mode
+ *
+ * \sa snd_seq_get_port_info(), snd_seq_port_info_set_timestamp_real()
+ */
+int snd_seq_port_info_get_timestamp_real(const snd_seq_port_info_t *info)
+{
+	assert(info);
+	return (info->flags & SNDRV_SEQ_PORT_FLG_TIME_REAL) ? 1 : 0;
+}
+
+/**
+ * \brief Get the queue id to update timestamps
+ * \param info port_info container
+ * \return the queue id to get the timestamps
+ *
+ * \sa snd_seq_get_port_info(), snd_seq_port_info_set_timestamp_queue()
+ */
+int snd_seq_port_info_get_timestamp_queue(const snd_seq_port_info_t *info)
+{
+	assert(info);
+	return info->time_queue;
+}
+
+/**
+ * \brief Set the client id of a port_info container
+ * \param info port_info container
+ * \param client client id
+ *
+ * \sa snd_seq_get_port_info(), snd_seq_port_info_get_client()
+ */
+void snd_seq_port_info_set_client(snd_seq_port_info_t *info, int client)
+{
+	assert(info);
+	info->addr.client = client;
+}
+
+/**
+ * \brief Set the port id of a port_info container
+ * \param info port_info container
+ * \param port port id
+ *
+ * \sa snd_seq_get_port_info(), snd_seq_port_info_get_port()
+ */
+void snd_seq_port_info_set_port(snd_seq_port_info_t *info, int port)
+{
+	assert(info);
+	info->addr.port = port;
+}
+
+/**
+ * \brief Set the client/port address of a port_info container
+ * \param info port_info container
+ * \param addr client/port address
+ *
+ * \sa snd_seq_get_port_info(), snd_seq_port_info_get_addr()
+ */
+void snd_seq_port_info_set_addr(snd_seq_port_info_t *info, const snd_seq_addr_t *addr)
+{
+	assert(info);
+	info->addr = *addr;
+}
+
+/**
+ * \brief Set the name of a port_info container
+ * \param info port_info container
+ * \param name name string
+ *
+ * \sa snd_seq_get_port_info(), snd_seq_port_info_get_name()
+ */
+void snd_seq_port_info_set_name(snd_seq_port_info_t *info, const char *name)
+{
+	assert(info && name);
+	strncpy(info->name, name, sizeof(info->name));
+}
+
+/**
+ * \brief set the capability bits of a port_info container
+ * \param info port_info container
+ * \param capability capability bits
+ *
+ * \sa snd_seq_get_port_info(), snd_seq_port_info_get_capability()
+ */
+void snd_seq_port_info_set_capability(snd_seq_port_info_t *info, unsigned int capability)
+{
+	assert(info);
+	info->capability = capability;
+}
+
+/**
+ * \brief Get the type bits of a port_info container
+ * \param info port_info container
+ * \param type port type bits
+ *
+ * \sa snd_seq_get_port_info(), snd_seq_port_info_get_type()
+ */
+void snd_seq_port_info_set_type(snd_seq_port_info_t *info, unsigned int type)
+{
+	assert(info);
+	info->type = type;
+}
+
+/**
+ * \brief set the midi channels of a port_info container
+ * \param info port_info container
+ * \param channels midi channels (default 0)
+ *
+ * \sa snd_seq_get_port_info(), snd_seq_port_info_get_midi_channels()
+ */
+void snd_seq_port_info_set_midi_channels(snd_seq_port_info_t *info, int channels)
+{
+	assert(info);
+	info->midi_channels = channels;
+}
+
+/**
+ * \brief set the midi voices of a port_info container
+ * \param info port_info container
+ * \param voices midi voices (default 0)
+ *
+ * \sa snd_seq_get_port_info(), snd_seq_port_info_get_midi_voices()
+ */
+void snd_seq_port_info_set_midi_voices(snd_seq_port_info_t *info, int voices)
+{
+	assert(info);
+	info->midi_voices = voices;
+}
+
+/**
+ * \brief set the synth voices of a port_info container
+ * \param info port_info container
+ * \param voices synth voices (default 0)
+ *
+ * \sa snd_seq_get_port_info(), snd_seq_port_info_get_synth_voice()
+ */
+void snd_seq_port_info_set_synth_voices(snd_seq_port_info_t *info, int voices)
+{
+	assert(info);
+	info->synth_voices = voices;
+}
+
+/**
+ * \brief Set the port-specified mode of a port_info container
+ * \param info port_info container
+ * \param val non-zero if specifying the port id at creation
+ *
+ * \sa snd_seq_get_port_info(), snd_seq_port_info_get_port_specified()
+ */
+void snd_seq_port_info_set_port_specified(snd_seq_port_info_t *info, int val)
+{
+	assert(info);
+	if (val)
+		info->flags |= SNDRV_SEQ_PORT_FLG_GIVEN_PORT;
+	else
+		info->flags &= ~SNDRV_SEQ_PORT_FLG_GIVEN_PORT;
+}
+
+/**
+ * \brief Set the time-stamping mode of the given port
+ * \param info port_info container
+ * \param enable non-zero if updating the timestamps of incoming events
+ *
+ * \sa snd_seq_get_port_info(), snd_seq_port_info_get_timestamping()
+ */
+void snd_seq_port_info_set_timestamping(snd_seq_port_info_t *info, int enable)
+{
+	assert(info);
+	if (enable)
+		info->flags |= SNDRV_SEQ_PORT_FLG_TIMESTAMP;
+	else
+		info->flags &= ~SNDRV_SEQ_PORT_FLG_TIMESTAMP;
+}
+
+/**
+ * \brief Set whether the timestime is updated in the real-time mode
+ * \param info port_info container
+ * \param enable non-zero if updating the timestamps in real-time mode
+ *
+ * \sa snd_seq_get_port_info(), snd_seq_port_info_get_timestamp_real()
+ */
+void snd_seq_port_info_set_timestamp_real(snd_seq_port_info_t *info, int enable)
+{
+	assert(info);
+	if (enable)
+		info->flags |= SNDRV_SEQ_PORT_FLG_TIME_REAL;
+	else
+		info->flags &= ~SNDRV_SEQ_PORT_FLG_TIME_REAL;
+}
+
+/**
+ * \brief Set the queue id for timestamping
+ * \param info port_info container
+ * \param queue the queue id to get timestamps
+ *
+ * \sa snd_seq_get_port_info(), snd_seq_port_info_get_timestamp_queue()
+ */
+void snd_seq_port_info_set_timestamp_queue(snd_seq_port_info_t *info, int queue)
+{
+	assert(info);
+	info->time_queue = queue;
+}
+
+
+/**
+ * \brief create a sequencer port on the current client
+ * \param seq sequencer handle
+ * \param port port information for the new port
+ * \return 0 on success otherwise a negative error code
+ *
+ * Creates a sequencer port on the current client.
+ * The attributes of created port is specified in \a info argument.
+ *
+ * The client field in \a info argument is overwritten with the current client id.
+ * The port id to be created can be specified via #snd_seq_port_info_set_port_specified.
+ * You can get the created port id by reading the port pointer via #snd_seq_port_info_get_port.
+ *
+ * Each port has the capability bit-masks to specify the access capability
+ * of the port from other clients.
+ * The capability bit flags are defined as follows:
+ * - #SND_SEQ_PORT_CAP_READ Readable from this port
+ * - #SND_SEQ_PORT_CAP_WRITE Writable to this port.
+ * - #SND_SEQ_PORT_CAP_SYNC_READ For synchronization (not implemented)
+ * - #SND_SEQ_PORT_CAP_SYNC_WRITE For synchronization (not implemented)
+ * - #SND_SEQ_PORT_CAP_DUPLEX Read/write duplex access is supported
+ * - #SND_SEQ_PORT_CAP_SUBS_READ Read subscription is allowed
+ * - #SND_SEQ_PORT_CAP_SUBS_WRITE Write subscription is allowed
+ * - #SND_SEQ_PORT_CAP_NO_EXPORT Subscription management from 3rd client is disallowed
+ *
+ * Each port has also the type bitmasks defined as follows:
+ * - #SND_SEQ_PORT_TYPE_SPECIFIC Hardware specific port
+ * - #SND_SEQ_PORT_TYPE_MIDI_GENERIC Generic MIDI device
+ * - #SND_SEQ_PORT_TYPE_MIDI_GM General MIDI compatible device
+ * - #SND_SEQ_PORT_TYPE_MIDI_GM2 General MIDI 2 compatible device
+ * - #SND_SEQ_PORT_TYPE_MIDI_GS GS compatible device
+ * - #SND_SEQ_PORT_TYPE_MIDI_XG XG compatible device
+ * - #SND_SEQ_PORT_TYPE_MIDI_MT32 MT-32 compatible device
+ * - #SND_SEQ_PORT_TYPE_HARDWARE Implemented in hardware
+ * - #SND_SEQ_PORT_TYPE_SOFTWARE Implemented in software
+ * - #SND_SEQ_PORT_TYPE_SYNTHESIZER Generates sound
+ * - #SND_SEQ_PORT_TYPE_PORT Connects to other device(s)
+ * - #SND_SEQ_PORT_TYPE_APPLICATION Application (sequencer/editor)
+ *
+ * A port may contain specific midi channels, midi voices and synth voices.
+ * These values could be zero as default.
+ *
+ * \sa snd_seq_delete_port(), snd_seq_get_port_info(),
+ *     snd_seq_create_simple_port()
+ */
+int snd_seq_create_port(snd_seq_t *seq, snd_seq_port_info_t * port)
+{
+	assert(seq && port);
+	port->addr.client = seq->client;
+	return seq->ops->create_port(seq, port);
+}
+
+/**
+ * \brief delete a sequencer port on the current client
+ * \param seq sequencer handle
+ * \param port port to be deleted
+ * \return 0 on success otherwise a negative error code
+ *
+ * Deletes the existing sequencer port on the current client.
+ *
+ * \sa snd_seq_create_port(), snd_seq_delete_simple_port()
+ */
+int snd_seq_delete_port(snd_seq_t *seq, int port)
+{
+	snd_seq_port_info_t pinfo;
+	assert(seq);
+	memset(&pinfo, 0, sizeof(pinfo));
+	pinfo.addr.client = seq->client;
+	pinfo.addr.port = port;
+	return seq->ops->delete_port(seq, &pinfo);
+}
+
+/**
+ * \brief obtain the information of a port on an arbitrary client
+ * \param seq sequencer handle
+ * \param client client id to get
+ * \param port port id to get
+ * \param info pointer information returns
+ * \return 0 on success otherwise a negative error code
+ *
+ * \sa snd_seq_get_port_info()
+ */
+int snd_seq_get_any_port_info(snd_seq_t *seq, int client, int port, snd_seq_port_info_t * info)
+{
+	assert(seq && info && client >= 0 && port >= 0);
+	memset(info, 0, sizeof(snd_seq_port_info_t));
+	info->addr.client = client;
+	info->addr.port = port;
+	return seq->ops->get_port_info(seq, info);
+}
+
+/**
+ * \brief obtain the information of a port on the current client
+ * \param seq sequencer handle
+ * \param port port id to get
+ * \param info pointer information returns
+ * \return 0 on success otherwise a negative error code
+ *
+ * \sa snd_seq_create_port(), snd_seq_get_any_port_info(), snd_seq_set_port_info(),
+ *     snd_seq_query_next_port()
+ */
+int snd_seq_get_port_info(snd_seq_t *seq, int port, snd_seq_port_info_t * info)
+{
+	return snd_seq_get_any_port_info(seq, seq->client, port, info);
+}
+
+/**
+ * \brief set the information of a port on the current client
+ * \param seq sequencer handle
+ * \param port port to be set
+ * \param info port information to be set
+ * \return 0 on success otherwise a negative error code
+ *
+ * \sa snd_seq_set_port_info()
+ */
+int snd_seq_set_port_info(snd_seq_t *seq, int port, snd_seq_port_info_t * info)
+{
+	assert(seq && info && port >= 0);
+	info->addr.client = seq->client;
+	info->addr.port = port;
+	return seq->ops->set_port_info(seq, info);
+}
+
+/**
+ * \brief query the next matching port
+ * \param seq sequencer handle
+ * \param info query pattern and result
+
+ * Queries the next matching port on the client specified in
+ * \a info argument.
+ * The search begins at the next port specified in
+ * port field of \a info argument.
+ * For finding the first port at a certain client, give -1.
+ *
+ * If a matching port is found, its attributes are stored on
+ * \a info and function returns zero.
+ * Otherwise, a negative error code is returned.
+ *
+ * \sa snd_seq_get_port_info()
+ */
+int snd_seq_query_next_port(snd_seq_t *seq, snd_seq_port_info_t *info)
+{
+	assert(seq && info);
+	return seq->ops->query_next_port(seq, info);
+}
+
+
+/*----------------------------------------------------------------*/
+
+/*
+ * subscription
+ */
+
+
+/**
+ * \brief get size of #snd_seq_port_subscribe_t
+ * \return size in bytes
+ */
+size_t snd_seq_port_subscribe_sizeof()
+{
+	return sizeof(snd_seq_port_subscribe_t);
+}
+
+/**
+ * \brief allocate an empty #snd_seq_port_subscribe_t using standard malloc
+ * \param ptr returned pointer
+ * \return 0 on success otherwise negative error code
+ */
+int snd_seq_port_subscribe_malloc(snd_seq_port_subscribe_t **ptr)
+{
+	assert(ptr);
+	*ptr = calloc(1, sizeof(snd_seq_port_subscribe_t));
+	if (!*ptr)
+		return -ENOMEM;
+	return 0;
+}
+
+/**
+ * \brief frees a previously allocated #snd_seq_port_subscribe_t
+ * \param obj pointer to object to free
+ */
+void snd_seq_port_subscribe_free(snd_seq_port_subscribe_t *obj)
+{
+	free(obj);
+}
+
+/**
+ * \brief copy one #snd_seq_port_subscribe_t to another
+ * \param dst pointer to destination
+ * \param src pointer to source
+ */
+void snd_seq_port_subscribe_copy(snd_seq_port_subscribe_t *dst, const snd_seq_port_subscribe_t *src)
+{
+	assert(dst && src);
+	*dst = *src;
+}
+
+
+/**
+ * \brief Get sender address of a port_subscribe container
+ * \param info port_subscribe container
+ *
+ * \sa snd_seq_subscribe_port(), snd_seq_port_subscribe_set_sender()
+ */
+const snd_seq_addr_t *snd_seq_port_subscribe_get_sender(const snd_seq_port_subscribe_t *info)
+{
+	assert(info);
+	return &info->sender;
+}
+
+/**
+ * \brief Get destination address of a port_subscribe container
+ * \param info port_subscribe container
+ *
+ * \sa snd_seq_subscribe_port(), snd_seq_port_subscribe_set_dest()
+ */
+const snd_seq_addr_t *snd_seq_port_subscribe_get_dest(const snd_seq_port_subscribe_t *info)
+{
+	assert(info);
+	return &info->dest;
+}
+
+/**
+ * \brief Get the queue id of a port_subscribe container
+ * \param info port_subscribe container
+ * \return queue id
+ *
+ * \sa snd_seq_subscribe_port(), snd_seq_port_subscribe_set_queue()
+ */
+int snd_seq_port_subscribe_get_queue(const snd_seq_port_subscribe_t *info)
+{
+	assert(info);
+	return info->queue;
+}
+
+/**
+ * \brief Get the exclusive mode of a port_subscribe container
+ * \param info port_subscribe container
+ * \return 1 if exclusive mode
+ *
+ * \sa snd_seq_subscribe_port(), snd_seq_port_subscribe_set_exclusive()
+ */
+int snd_seq_port_subscribe_get_exclusive(const snd_seq_port_subscribe_t *info)
+{
+	assert(info);
+	return (info->flags & SNDRV_SEQ_PORT_SUBS_EXCLUSIVE) ? 1 : 0;
+}
+
+/**
+ * \brief Get the time-update mode of a port_subscribe container
+ * \param info port_subscribe container
+ * \return 1 if update timestamp
+ *
+ * \sa snd_seq_subscribe_port(), snd_seq_port_subscribe_set_time_update()
+ */
+int snd_seq_port_subscribe_get_time_update(const snd_seq_port_subscribe_t *info)
+{
+	assert(info);
+	return (info->flags & SNDRV_SEQ_PORT_SUBS_TIMESTAMP) ? 1 : 0;
+}
+
+/**
+ * \brief Get the real-time update mode of a port_subscribe container
+ * \param info port_subscribe container
+ * \return 1 if real-time update mode
+ *
+ * \sa snd_seq_subscribe_port(), snd_seq_port_subscribe_set_time_real()
+ */
+int snd_seq_port_subscribe_get_time_real(const snd_seq_port_subscribe_t *info)
+{
+	assert(info);
+	return (info->flags & SNDRV_SEQ_PORT_SUBS_TIME_REAL) ? 1 : 0;
+}
+
+/**
+ * \brief Set sender address of a port_subscribe container
+ * \param info port_subscribe container
+ * \param addr sender address
+ *
+ * \sa snd_seq_subscribe_port(), snd_seq_port_subscribe_get_sender()
+ */
+void snd_seq_port_subscribe_set_sender(snd_seq_port_subscribe_t *info, const snd_seq_addr_t *addr)
+{
+	assert(info);
+	memcpy(&info->sender, addr, sizeof(*addr));
+}
+      
+/**
+ * \brief Set destination address of a port_subscribe container
+ * \param info port_subscribe container
+ * \param addr destination address
+ *
+ * \sa snd_seq_subscribe_port(), snd_seq_port_subscribe_get_dest()
+ */
+void snd_seq_port_subscribe_set_dest(snd_seq_port_subscribe_t *info, const snd_seq_addr_t *addr)
+{
+	assert(info);
+	memcpy(&info->dest, addr, sizeof(*addr));
+}
+
+/**
+ * \brief Set the queue id of a port_subscribe container
+ * \param info port_subscribe container
+ * \param q queue id
+ *
+ * \sa snd_seq_subscribe_port(), snd_seq_port_subscribe_get_queue()
+ */
+void snd_seq_port_subscribe_set_queue(snd_seq_port_subscribe_t *info, int q)
+{
+	assert(info);
+	info->queue = q;
+}
+
+/**
+ * \brief Set the exclusive mode of a port_subscribe container
+ * \param info port_subscribe container
+ * \param val non-zero to enable
+ *
+ * \sa snd_seq_subscribe_port(), snd_seq_port_subscribe_get_exclusive()
+ */
+void snd_seq_port_subscribe_set_exclusive(snd_seq_port_subscribe_t *info, int val)
+{
+	assert(info);
+	if (val)
+		info->flags |= SNDRV_SEQ_PORT_SUBS_EXCLUSIVE;
+	else
+		info->flags &= ~SNDRV_SEQ_PORT_SUBS_EXCLUSIVE;
+}
+
+/**
+ * \brief Set the time-update mode of a port_subscribe container
+ * \param info port_subscribe container
+ * \param val non-zero to enable
+ *
+ * \sa snd_seq_subscribe_port(), snd_seq_port_subscribe_get_time_update()
+ */
+void snd_seq_port_subscribe_set_time_update(snd_seq_port_subscribe_t *info, int val)
+{
+	assert(info);
+	if (val)
+		info->flags |= SNDRV_SEQ_PORT_SUBS_TIMESTAMP;
+	else
+		info->flags &= ~SNDRV_SEQ_PORT_SUBS_TIMESTAMP;
+}
+
+/**
+ * \brief Set the real-time mode of a port_subscribe container
+ * \param info port_subscribe container
+ * \param val non-zero to enable
+ *
+ * \sa snd_seq_subscribe_port(), snd_seq_port_subscribe_get_time_real()
+ */
+void snd_seq_port_subscribe_set_time_real(snd_seq_port_subscribe_t *info, int val)
+{
+	assert(info);
+	if (val)
+		info->flags |= SNDRV_SEQ_PORT_SUBS_TIME_REAL;
+	else
+		info->flags &= ~SNDRV_SEQ_PORT_SUBS_TIME_REAL;
+}
+
+
+/**
+ * \brief obtain subscription information
+ * \param seq sequencer handle
+ * \param sub pointer to return the subscription information
+ * \return 0 on success otherwise a negative error code
+ *
+ * \sa snd_seq_subscribe_port(), snd_seq_query_port_subscribers()
+ */
+int snd_seq_get_port_subscription(snd_seq_t *seq, snd_seq_port_subscribe_t * sub)
+{
+	assert(seq && sub);
+	return seq->ops->get_port_subscription(seq, sub);
+}
+
+/**
+ * \brief subscribe a port connection
+ * \param seq sequencer handle
+ * \param sub subscription information
+ * \return 0 on success otherwise a negative error code
+ *
+ * Subscribes a connection between two ports.
+ * The subscription information is stored in sub argument.
+ *
+ * \sa snd_seq_get_port_subscription(), snd_seq_unsubscribe_port(),
+ *     snd_seq_connect_from(), snd_seq_connect_to()
+ */
+int snd_seq_subscribe_port(snd_seq_t *seq, snd_seq_port_subscribe_t * sub)
+{
+	assert(seq && sub);
+	return seq->ops->subscribe_port(seq, sub);
+}
+
+/**
+ * \brief unsubscribe a connection between ports
+ * \param seq sequencer handle
+ * \param sub subscription information to disconnect
+ * \return 0 on success otherwise a negative error code
+ *
+ * Unsubscribes a connection between two ports,
+ * described in sender and dest fields in sub argument.
+ *
+ * \sa snd_seq_subscribe_port(), snd_seq_disconnect_from(), snd_seq_disconnect_to()
+ */
+int snd_seq_unsubscribe_port(snd_seq_t *seq, snd_seq_port_subscribe_t * sub)
+{
+	assert(seq && sub);
+	return seq->ops->unsubscribe_port(seq, sub);
+}
+
+
+/**
+ * \brief get size of #snd_seq_query_subscribe_t
+ * \return size in bytes
+ */
+size_t snd_seq_query_subscribe_sizeof()
+{
+	return sizeof(snd_seq_query_subscribe_t);
+}
+
+/**
+ * \brief allocate an empty #snd_seq_query_subscribe_t using standard malloc
+ * \param ptr returned pointer
+ * \return 0 on success otherwise negative error code
+ */
+int snd_seq_query_subscribe_malloc(snd_seq_query_subscribe_t **ptr)
+{
+	assert(ptr);
+	*ptr = calloc(1, sizeof(snd_seq_query_subscribe_t));
+	if (!*ptr)
+		return -ENOMEM;
+	return 0;
+}
+
+/**
+ * \brief frees a previously allocated #snd_seq_query_subscribe_t
+ * \param obj pointer to object to free
+ */
+void snd_seq_query_subscribe_free(snd_seq_query_subscribe_t *obj)
+{
+	free(obj);
+}
+
+/**
+ * \brief copy one #snd_seq_query_subscribe_t to another
+ * \param dst pointer to destination
+ * \param src pointer to source
+ */
+void snd_seq_query_subscribe_copy(snd_seq_query_subscribe_t *dst, const snd_seq_query_subscribe_t *src)
+{
+	assert(dst && src);
+	*dst = *src;
+}
+
+
+/**
+ * \brief Get the client id of a query_subscribe container
+ * \param info query_subscribe container
+ * \return client id
+ *
+ * \sa snd_seq_query_port_subscribers(), snd_seq_query_subscribe_set_client()
+ */
+int snd_seq_query_subscribe_get_client(const snd_seq_query_subscribe_t *info)
+{
+	assert(info);
+	return info->root.client;
+}
+
+/**
+ * \brief Get the port id of a query_subscribe container
+ * \param info query_subscribe container
+ * \return port id
+ *
+ * \sa snd_seq_query_port_subscribers(), snd_seq_query_subscribe_set_port()
+ */
+int snd_seq_query_subscribe_get_port(const snd_seq_query_subscribe_t *info)
+{
+	assert(info);
+	return info->root.port;
+}
+
+/**
+ * \brief Get the client/port address of a query_subscribe container
+ * \param info query_subscribe container
+ * \return client/port address pointer
+ *
+ * \sa snd_seq_query_port_subscribers(), snd_seq_query_subscribe_set_root()
+ */
+const snd_seq_addr_t *snd_seq_query_subscribe_get_root(const snd_seq_query_subscribe_t *info)
+{
+	assert(info);
+	return &info->root;
+}
+
+/**
+ * \brief Get the query type of a query_subscribe container
+ * \param info query_subscribe container
+ * \return query type
+ *
+ * \sa snd_seq_query_port_subscribers(), snd_seq_query_subscribe_set_type()
+ */
+snd_seq_query_subs_type_t snd_seq_query_subscribe_get_type(const snd_seq_query_subscribe_t *info)
+{
+	assert(info);
+	return info->type;
+}
+
+/**
+ * \brief Get the index of subscriber of a query_subscribe container
+ * \param info query_subscribe container
+ * \return subscriber's index
+ *
+ * \sa snd_seq_query_port_subscribers(), snd_seq_query_subscribe_set_index()
+ */
+int snd_seq_query_subscribe_get_index(const snd_seq_query_subscribe_t *info)
+{
+	assert(info);
+	return info->index;
+}
+
+/**
+ * \brief Get the number of subscriptions of a query_subscribe container
+ * \param info query_subscribe container
+ * \return number of subscriptions
+ *
+ * \sa snd_seq_query_port_subscribers()
+ */
+int snd_seq_query_subscribe_get_num_subs(const snd_seq_query_subscribe_t *info)
+{
+	assert(info);
+	return info->num_subs;
+}	
+
+/**
+ * \brief Get the address of subscriber of a query_subscribe container
+ * \param info query_subscribe container
+ * \return subscriber's address pointer
+ *
+ * \sa snd_seq_query_port_subscribers()
+ */
+const snd_seq_addr_t *snd_seq_query_subscribe_get_addr(const snd_seq_query_subscribe_t *info)
+{
+	assert(info);
+	return &info->addr;
+}
+
+/**
+ * \brief Get the queue id of subscriber of a query_subscribe container
+ * \param info query_subscribe container
+ * \return subscriber's queue id
+ *
+ * \sa snd_seq_query_port_subscribers()
+ */
+int snd_seq_query_subscribe_get_queue(const snd_seq_query_subscribe_t *info)
+{
+	assert(info);
+	return info->queue;
+}
+
+/**
+ * \brief Get the exclusive mode of a query_subscribe container
+ * \param info query_subscribe container
+ * \return 1 if exclusive mode
+ *
+ * \sa snd_seq_query_port_subscribers()
+ */
+int snd_seq_query_subscribe_get_exclusive(const snd_seq_query_subscribe_t *info)
+{
+	assert(info);
+	return (info->flags & SNDRV_SEQ_PORT_SUBS_EXCLUSIVE) ? 1 : 0;
+}
+
+/**
+ * \brief Get the time-update mode of a query_subscribe container
+ * \param info query_subscribe container
+ * \return 1 if update timestamp
+ *
+ * \sa snd_seq_query_port_subscribers()
+ */
+int snd_seq_query_subscribe_get_time_update(const snd_seq_query_subscribe_t *info)
+{
+	assert(info);
+	return (info->flags & SNDRV_SEQ_PORT_SUBS_TIMESTAMP) ? 1 : 0;
+}
+
+/**
+ * \brief Get the real-time update mode of a query_subscribe container
+ * \param info query_subscribe container
+ * \return 1 if real-time update mode
+ *
+ * \sa snd_seq_query_port_subscribers()
+ */
+int snd_seq_query_subscribe_get_time_real(const snd_seq_query_subscribe_t *info)
+{
+	assert(info);
+	return (info->flags & SNDRV_SEQ_PORT_SUBS_TIMESTAMP) ? 1 : 0;
+}
+
+/**
+ * \brief Set the client id of a query_subscribe container
+ * \param info query_subscribe container
+ * \param client client id
+ *
+ * \sa snd_seq_query_port_subscribers(), snd_seq_query_subscribe_get_client()
+ */
+void snd_seq_query_subscribe_set_client(snd_seq_query_subscribe_t *info, int client)
+{
+	assert(info);
+	info->root.client = client;
+}
+
+/**
+ * \brief Set the port id of a query_subscribe container
+ * \param info query_subscribe container
+ * \param port port id
+ *
+ * \sa snd_seq_query_port_subscribers(), snd_seq_query_subscribe_get_port()
+ */
+void snd_seq_query_subscribe_set_port(snd_seq_query_subscribe_t *info, int port)
+{
+	assert(info);
+	info->root.port = port;
+}
+
+/**
+ * \brief Set the client/port address of a query_subscribe container
+ * \param info query_subscribe container
+ * \param addr client/port address pointer
+ *
+ * \sa snd_seq_query_port_subscribers(), snd_seq_query_subscribe_get_root()
+ */
+void snd_seq_query_subscribe_set_root(snd_seq_query_subscribe_t *info, const snd_seq_addr_t *addr)
+{
+	assert(info);
+	info->root = *addr;
+}
+
+/**
+ * \brief Set the query type of a query_subscribe container
+ * \param info query_subscribe container
+ * \param type query type
+ *
+ * \sa snd_seq_query_port_subscribers(), snd_seq_query_subscribe_get_type()
+ */
+void snd_seq_query_subscribe_set_type(snd_seq_query_subscribe_t *info, snd_seq_query_subs_type_t type)
+{
+	assert(info);
+	info->type = type;
+}
+
+/**
+ * \brief Set the subscriber's index to be queried
+ * \param info query_subscribe container
+ * \param index index to be queried
+ *
+ * \sa snd_seq_query_port_subscribers(), snd_seq_query_subscribe_get_index()
+ */
+void snd_seq_query_subscribe_set_index(snd_seq_query_subscribe_t *info, int index)
+{
+	assert(info);
+	info->index = index;
+}
+
+
+/**
+ * \brief query port subscriber list
+ * \param seq sequencer handle
+ * \param subs subscription to query
+ * \return 0 on success otherwise a negative error code
+ *
+ * Queries the subscribers accessing to a port.
+ * The query information is specified in subs argument.
+ *
+ * At least, the client id, the port id, the index number and
+ * the query type must be set to perform a proper query.
+ * As the query type, #SND_SEQ_QUERY_SUBS_READ or #SND_SEQ_QUERY_SUBS_WRITE
+ * can be specified to check whether the readers or the writers to the port.
+ * To query the first subscription, set 0 to the index number.  To list up
+ * all the subscriptions, call this function with the index numbers from 0
+ * until this returns a negative value.
+ *
+ * \sa snd_seq_get_port_subscription()
+ */
+int snd_seq_query_port_subscribers(snd_seq_t *seq, snd_seq_query_subscribe_t * subs)
+{
+	assert(seq && subs);
+	return seq->ops->query_port_subscribers(seq, subs);
+}
+
+/*----------------------------------------------------------------*/
+
+/*
+ * queue handlers
+ */
+
+/**
+ * \brief get size of #snd_seq_queue_info_t
+ * \return size in bytes
+ */
+size_t snd_seq_queue_info_sizeof()
+{
+	return sizeof(snd_seq_queue_info_t);
+}
+
+/**
+ * \brief allocate an empty #snd_seq_queue_info_t using standard malloc
+ * \param ptr returned pointer
+ * \return 0 on success otherwise negative error code
+ */
+int snd_seq_queue_info_malloc(snd_seq_queue_info_t **ptr)
+{
+	assert(ptr);
+	*ptr = calloc(1, sizeof(snd_seq_queue_info_t));
+	if (!*ptr)
+		return -ENOMEM;
+	return 0;
+}
+
+/**
+ * \brief frees a previously allocated #snd_seq_queue_info_t
+ * \param obj pointer to object to free
+ */
+void snd_seq_queue_info_free(snd_seq_queue_info_t *obj)
+{
+	free(obj);
+}
+
+/**
+ * \brief copy one #snd_seq_queue_info_t to another
+ * \param dst pointer to destination
+ * \param src pointer to source
+ */
+void snd_seq_queue_info_copy(snd_seq_queue_info_t *dst, const snd_seq_queue_info_t *src)
+{
+	assert(dst && src);
+	*dst = *src;
+}
+
+
+/**
+ * \brief Get the queue id of a queue_info container
+ * \param info queue_info container
+ * \return queue id
+ *
+ * \sa snd_seq_get_queue_info(), snd_seq_queue_info_set_queue()
+ */
+int snd_seq_queue_info_get_queue(const snd_seq_queue_info_t *info)
+{
+	assert(info);
+	return info->queue;
+}
+
+/**
+ * \brief Get the name of a queue_info container
+ * \param info queue_info container
+ * \return name string
+ *
+ * \sa snd_seq_get_queue_info(), snd_seq_queue_info_set_name()
+ */
+const char *snd_seq_queue_info_get_name(const snd_seq_queue_info_t *info)
+{
+	assert(info);
+	return info->name;
+}
+
+/**
+ * \brief Get the owner client id of a queue_info container
+ * \param info queue_info container
+ * \return owner client id
+ *
+ * \sa snd_seq_get_queue_info(), snd_seq_queue_info_set_owner()
+ */
+int snd_seq_queue_info_get_owner(const snd_seq_queue_info_t *info)
+{
+	assert(info);
+	return info->owner;
+}
+
+/**
+ * \brief Get the lock status of a queue_info container
+ * \param info queue_info container
+ * \return lock status --- non-zero = locked
+ *
+ * \sa snd_seq_get_queue_info(), snd_seq_queue_info_set_locked()
+ */
+int snd_seq_queue_info_get_locked(const snd_seq_queue_info_t *info)
+{
+	assert(info);
+	return info->locked;
+}
+
+/**
+ * \brief Get the conditional bit flags of a queue_info container
+ * \param info queue_info container
+ * \return conditional bit flags
+ *
+ * \sa snd_seq_get_queue_info(), snd_seq_queue_info_set_flags()
+ */
+unsigned int snd_seq_queue_info_get_flags(const snd_seq_queue_info_t *info)
+{
+	assert(info);
+	return info->flags;
+}
+
+/**
+ * \brief Set the name of a queue_info container
+ * \param info queue_info container
+ * \param name name string
+ *
+ * \sa snd_seq_get_queue_info(), snd_seq_queue_info_get_name()
+ */
+void snd_seq_queue_info_set_name(snd_seq_queue_info_t *info, const char *name)
+{
+	assert(info && name);
+	strncpy(info->name, name, sizeof(info->name));
+}
+
+/**
+ * \brief Set the owner client id of a queue_info container
+ * \param info queue_info container
+ * \param owner client id
+ *
+ * \sa snd_seq_get_queue_info(), snd_seq_queue_info_get_owner()
+ */
+void snd_seq_queue_info_set_owner(snd_seq_queue_info_t *info, int owner)
+{
+	assert(info);
+	info->owner = owner;
+}
+
+/**
+ * \brief Set the lock status of a queue_info container
+ * \param info queue_info container
+ * \param locked lock status
+ *
+ * \sa snd_seq_get_queue_info(), snd_seq_queue_info_get_locked()
+ */
+void snd_seq_queue_info_set_locked(snd_seq_queue_info_t *info, int locked)
+{
+	assert(info);
+	info->locked = locked;
+}
+
+/**
+ * \brief Set the conditional bit flags of a queue_info container
+ * \param info queue_info container
+ * \param flags conditional bit flags
+ *
+ * \sa snd_seq_get_queue_info(), snd_seq_queue_info_get_flags()
+ */
+void snd_seq_queue_info_set_flags(snd_seq_queue_info_t *info, unsigned int flags)
+{
+	assert(info);
+	info->flags = flags;
+}
+
+
+/**
+ * \brief create a queue
+ * \param seq sequencer handle
+ * \param info queue information to initialize
+ * \return the queue id (zero or positive) on success otherwise a negative error code
+ *
+ * \sa snd_seq_alloc_queue()
+ */
+int snd_seq_create_queue(snd_seq_t *seq, snd_seq_queue_info_t *info)
+{
+	int err;
+	assert(seq && info);
+	info->owner = seq->client;
+	err = seq->ops->create_queue(seq, info);
+	if (err < 0)
+		return err;
+	return info->queue;
+}
+
+/**
+ * \brief allocate a queue with the specified name
+ * \param seq sequencer handle
+ * \param name the name of the new queue
+ * \return the queue id (zero or positive) on success otherwise a negative error code
+ *
+ * \sa snd_seq_alloc_queue()
+ */ 
+int snd_seq_alloc_named_queue(snd_seq_t *seq, const char *name)
+{
+	snd_seq_queue_info_t info;
+	memset(&info, 0, sizeof(info));
+	info.locked = 1;
+	if (name)
+		strncpy(info.name, name, sizeof(info.name) - 1);
+	return snd_seq_create_queue(seq, &info);
+}
+
+/**
+ * \brief allocate a queue
+ * \param seq sequencer handle
+ * \return the queue id (zero or positive) on success otherwise a negative error code
+ *
+ * \sa snd_seq_alloc_named_queue(), snd_seq_create_queue(), snd_seq_free_queue(),
+ *     snd_seq_get_queue_info()
+ */ 
+int snd_seq_alloc_queue(snd_seq_t *seq)
+{
+	return snd_seq_alloc_named_queue(seq, NULL);
+}
+
+/**
+ * \brief delete the specified queue
+ * \param seq sequencer handle
+ * \param q queue id to delete
+ * \return 0 on success otherwise a negative error code
+ *
+ * \sa snd_seq_alloc_queue()
+ */
+int snd_seq_free_queue(snd_seq_t *seq, int q)
+{
+	snd_seq_queue_info_t info;
+	assert(seq);
+	memset(&info, 0, sizeof(info));
+	info.queue = q;
+	return seq->ops->delete_queue(seq, &info);
+}
+
+/**
+ * \brief obtain queue attributes
+ * \param seq sequencer handle
+ * \param q queue id to query
+ * \param info information returned
+ * \return 0 on success otherwise a negative error code
+ *
+ * \sa snd_seq_alloc_queue(), snd_seq_set_queue_info(), snd_seq_query_named_queue()
+ */
+int snd_seq_get_queue_info(snd_seq_t *seq, int q, snd_seq_queue_info_t *info)
+{
+	assert(seq && info);
+	info->queue = q;
+	return seq->ops->get_queue_info(seq, info);
+}
+
+/**
+ * \brief change the queue attributes
+ * \param seq sequencer handle
+ * \param q queue id to change
+ * \param info information changed
+ * \return 0 on success otherwise a negative error code
+ *
+ * \sa snd_seq_get_queue_info()
+ */
+int snd_seq_set_queue_info(snd_seq_t *seq, int q, snd_seq_queue_info_t *info)
+{
+	assert(seq && info);
+	info->queue = q;
+	return seq->ops->set_queue_info(seq, info);
+}
+
+/**
+ * \brief query the matching queue with the specified name
+ * \param seq sequencer handle
+ * \param name the name string to query
+ * \return the queue id if found or negative error code
+ *
+ * Searches the matching queue with the specified name string.
+ *
+ * \sa snd_seq_get_queue_info()
+ */
+int snd_seq_query_named_queue(snd_seq_t *seq, const char *name)
+{
+	int err;
+	snd_seq_queue_info_t info;
+	assert(seq && name);
+	strncpy(info.name, name, sizeof(info.name));
+	err = seq->ops->get_named_queue(seq, &info);
+	if (err < 0)
+		return err;
+	return info.queue;
+}
+
+/**
+ * \brief Get the queue usage flag to the client
+ * \param seq sequencer handle
+ * \param q queue id
+ * \return 1 = client is allowed to access the queue, 0 = not allowed, 
+ *     otherwise a negative error code
+ *
+ * \sa snd_seq_get_queue_info(), snd_seq_set_queue_usage()
+ */
+int snd_seq_get_queue_usage(snd_seq_t *seq, int q)
+{
+	struct sndrv_seq_queue_client info;
+	int err;
+	assert(seq);
+	memset(&info, 0, sizeof(info));
+	info.queue = q;
+	info.client = seq->client;
+	if ((err = seq->ops->get_queue_client(seq, &info)) < 0)
+		return err;
+	return info.used;
+}
+
+/**
+ * \brief Set the queue usage flag to the client
+ * \param seq sequencer handle
+ * \param q queue id
+ * \param used non-zero if the client is allowed
+ * \return 0 on success otherwise a negative error code
+ *
+ * \sa snd_seq_get_queue_info(), snd_seq_set_queue_usage()
+ */
+int snd_seq_set_queue_usage(snd_seq_t *seq, int q, int used)
+{
+	struct sndrv_seq_queue_client info;
+	assert(seq);
+	memset(&info, 0, sizeof(info));
+	info.queue = q;
+	info.client = seq->client;
+	info.used = used ? 1 : 0;
+	return seq->ops->set_queue_client(seq, &info);
+}
+
+
+/**
+ * \brief get size of #snd_seq_queue_status_t
+ * \return size in bytes
+ */
+size_t snd_seq_queue_status_sizeof()
+{
+	return sizeof(snd_seq_queue_status_t);
+}
+
+/**
+ * \brief allocate an empty #snd_seq_queue_status_t using standard malloc
+ * \param ptr returned pointer
+ * \return 0 on success otherwise negative error code
+ */
+int snd_seq_queue_status_malloc(snd_seq_queue_status_t **ptr)
+{
+	assert(ptr);
+	*ptr = calloc(1, sizeof(snd_seq_queue_status_t));
+	if (!*ptr)
+		return -ENOMEM;
+	return 0;
+}
+
+/**
+ * \brief frees a previously allocated #snd_seq_queue_status_t
+ * \param obj pointer to object to free
+ */
+void snd_seq_queue_status_free(snd_seq_queue_status_t *obj)
+{
+	free(obj);
+}
+
+/**
+ * \brief copy one #snd_seq_queue_status_t to another
+ * \param dst pointer to destination
+ * \param src pointer to source
+ */
+void snd_seq_queue_status_copy(snd_seq_queue_status_t *dst, const snd_seq_queue_status_t *src)
+{
+	assert(dst && src);
+	*dst = *src;
+}
+
+
+/**
+ * \brief Get the queue id of a queue_status container
+ * \param info queue_status container
+ * \return queue id
+ *
+ * \sa snd_seq_get_queue_status()
+ */
+int snd_seq_queue_status_get_queue(const snd_seq_queue_status_t *info)
+{
+	assert(info);
+	return info->queue;
+}
+
+/**
+ * \brief Get the number of events of a queue_status container
+ * \param info queue_status container
+ * \return number of events
+ *
+ * \sa snd_seq_get_queue_status()
+ */
+int snd_seq_queue_status_get_events(const snd_seq_queue_status_t *info)
+{
+	assert(info);
+	return info->events;
+}
+
+/**
+ * \brief Get the tick time of a queue_status container
+ * \param info queue_status container
+ * \return tick time
+ *
+ * \sa snd_seq_get_queue_status()
+ */
+snd_seq_tick_time_t snd_seq_queue_status_get_tick_time(const snd_seq_queue_status_t *info)
+{
+	assert(info);
+	return info->tick;
+}
+
+/**
+ * \brief Get the real time of a queue_status container
+ * \param info queue_status container
+ *
+ * \sa snd_seq_get_queue_status()
+ */
+const snd_seq_real_time_t *snd_seq_queue_status_get_real_time(const snd_seq_queue_status_t *info)
+{
+	assert(info);
+	return &info->time;
+}
+
+/**
+ * \brief Get the running status bits of a queue_status container
+ * \param info queue_status container
+ * \return running status bits
+ *
+ * \sa snd_seq_get_queue_status()
+ */
+unsigned int snd_seq_queue_status_get_status(const snd_seq_queue_status_t *info)
+{
+	assert(info);
+	return info->running;
+}
+
+
+/**
+ * \brief obtain the running state of the queue
+ * \param seq sequencer handle
+ * \param q queue id to query
+ * \param status pointer to store the current status
+ * \return 0 on success otherwise a negative error code
+ *
+ * Obtains the running state of the specified queue q.
+ */
+int snd_seq_get_queue_status(snd_seq_t *seq, int q, snd_seq_queue_status_t * status)
+{
+	assert(seq && status);
+	memset(status, 0, sizeof(snd_seq_queue_status_t));
+	status->queue = q;
+	return seq->ops->get_queue_status(seq, status);
+}
+
+
+/**
+ * \brief get size of #snd_seq_queue_tempo_t
+ * \return size in bytes
+ */
+size_t snd_seq_queue_tempo_sizeof()
+{
+	return sizeof(snd_seq_queue_tempo_t);
+}
+
+/**
+ * \brief allocate an empty #snd_seq_queue_tempo_t using standard malloc
+ * \param ptr returned pointer
+ * \return 0 on success otherwise negative error code
+ */
+int snd_seq_queue_tempo_malloc(snd_seq_queue_tempo_t **ptr)
+{
+	assert(ptr);
+	*ptr = calloc(1, sizeof(snd_seq_queue_tempo_t));
+	if (!*ptr)
+		return -ENOMEM;
+	return 0;
+}
+
+/**
+ * \brief frees a previously allocated #snd_seq_queue_tempo_t
+ * \param obj pointer to object to free
+ */
+void snd_seq_queue_tempo_free(snd_seq_queue_tempo_t *obj)
+{
+	free(obj);
+}
+
+/**
+ * \brief copy one #snd_seq_queue_tempo_t to another
+ * \param dst pointer to destination
+ * \param src pointer to source
+ */
+void snd_seq_queue_tempo_copy(snd_seq_queue_tempo_t *dst, const snd_seq_queue_tempo_t *src)
+{
+	assert(dst && src);
+	*dst = *src;
+}
+
+
+/**
+ * \brief Get the queue id of a queue_status container
+ * \param info queue_status container
+ * \return queue id
+ *
+ * \sa snd_seq_get_queue_tempo()
+ */
+int snd_seq_queue_tempo_get_queue(const snd_seq_queue_tempo_t *info)
+{
+	assert(info);
+	return info->queue;
+}
+
+/**
+ * \brief Get the tempo of a queue_status container
+ * \param info queue_status container
+ * \return tempo value
+ *
+ * \sa snd_seq_get_queue_tempo()
+ */
+unsigned int snd_seq_queue_tempo_get_tempo(const snd_seq_queue_tempo_t *info)
+{
+	assert(info);
+	return info->tempo;
+}
+
+/**
+ * \brief Get the ppq of a queue_status container
+ * \param info queue_status container
+ * \return ppq value
+ *
+ * \sa snd_seq_get_queue_tempo()
+ */
+int snd_seq_queue_tempo_get_ppq(const snd_seq_queue_tempo_t *info)
+{
+	assert(info);
+	return info->ppq;
+}
+
+/**
+ * \brief Get the timer skew value of a queue_status container
+ * \param info queue_status container
+ * \return timer skew value
+ *
+ * \sa snd_seq_get_queue_tempo()
+ */
+unsigned int snd_seq_queue_tempo_get_skew(const snd_seq_queue_tempo_t *info)
+{
+	assert(info);
+	return info->skew_value;
+}
+
+/**
+ * \brief Get the timer skew base value of a queue_status container
+ * \param info queue_status container
+ * \return timer skew base value
+ *
+ * \sa snd_seq_get_queue_tempo()
+ */
+unsigned int snd_seq_queue_tempo_get_skew_base(const snd_seq_queue_tempo_t *info)
+{
+	assert(info);
+	return info->skew_base;
+}
+
+/**
+ * \brief Set the tempo of a queue_status container
+ * \param info queue_status container
+ * \param tempo tempo value
+ *
+ * \sa snd_seq_get_queue_tempo()
+ */
+void snd_seq_queue_tempo_set_tempo(snd_seq_queue_tempo_t *info, unsigned int tempo)
+{
+	assert(info);
+	info->tempo = tempo;
+}
+
+/**
+ * \brief Set the ppq of a queue_status container
+ * \param info queue_status container
+ * \param ppq ppq value
+ *
+ * \sa snd_seq_get_queue_tempo()
+ */
+void snd_seq_queue_tempo_set_ppq(snd_seq_queue_tempo_t *info, int ppq)
+{
+	assert(info);
+	info->ppq = ppq;
+}
+
+/**
+ * \brief Set the timer skew value of a queue_status container
+ * \param info queue_status container
+ * \param skew timer skew value
+ *
+ * The skew of timer is calculated as skew / base.
+ * For example, to play with double speed, pass base * 2 as the skew value.
+ *
+ * \sa snd_seq_get_queue_tempo()
+ */
+void snd_seq_queue_tempo_set_skew(snd_seq_queue_tempo_t *info, unsigned int skew)
+{
+	assert(info);
+	info->skew_value = skew;
+}
+
+/**
+ * \brief Set the timer skew base value of a queue_status container
+ * \param info queue_status container
+ * \param base timer skew base value
+ *
+ * \sa snd_seq_get_queue_tempo()
+ */
+void snd_seq_queue_tempo_set_skew_base(snd_seq_queue_tempo_t *info, unsigned int base)
+{
+	assert(info);
+	info->skew_base = base;
+}
+
+/**
+ * \brief obtain the current tempo of the queue
+ * \param seq sequencer handle
+ * \param q queue id to be queried
+ * \param tempo pointer to store the current tempo
+ * \return 0 on success otherwise a negative error code
+ *
+ * \sa snd_seq_set_queue_tempo()
+ */
+int snd_seq_get_queue_tempo(snd_seq_t *seq, int q, snd_seq_queue_tempo_t * tempo)
+{
+	assert(seq && tempo);
+	memset(tempo, 0, sizeof(snd_seq_queue_tempo_t));
+	tempo->queue = q;
+	return seq->ops->get_queue_tempo(seq, tempo);
+}
+
+/**
+ * \brief set the tempo of the queue
+ * \param seq sequencer handle
+ * \param q queue id to change the tempo
+ * \param tempo tempo information
+ * \return 0 on success otherwise a negative error code
+ *
+ * \sa snd_seq_get_queue_tempo()
+ */
+int snd_seq_set_queue_tempo(snd_seq_t *seq, int q, snd_seq_queue_tempo_t * tempo)
+{
+	assert(seq && tempo);
+	tempo->queue = q;
+	return seq->ops->set_queue_tempo(seq, tempo);
+}
+
+
+/*----------------------------------------------------------------*/
+
+/**
+ * \brief get size of #snd_seq_queue_timer_t
+ * \return size in bytes
+ */
+size_t snd_seq_queue_timer_sizeof()
+{
+	return sizeof(snd_seq_queue_timer_t);
+}
+
+/**
+ * \brief allocate an empty #snd_seq_queue_timer_t using standard malloc
+ * \param ptr returned pointer
+ * \return 0 on success otherwise negative error code
+ */
+int snd_seq_queue_timer_malloc(snd_seq_queue_timer_t **ptr)
+{
+	assert(ptr);
+	*ptr = calloc(1, sizeof(snd_seq_queue_timer_t));
+	if (!*ptr)
+		return -ENOMEM;
+	return 0;
+}
+
+/**
+ * \brief frees a previously allocated #snd_seq_queue_timer_t
+ * \param obj pointer to object to free
+ */
+void snd_seq_queue_timer_free(snd_seq_queue_timer_t *obj)
+{
+	free(obj);
+}
+
+/**
+ * \brief copy one #snd_seq_queue_timer_t to another
+ * \param dst pointer to destination
+ * \param src pointer to source
+ */
+void snd_seq_queue_timer_copy(snd_seq_queue_timer_t *dst, const snd_seq_queue_timer_t *src)
+{
+	assert(dst && src);
+	*dst = *src;
+}
+
+
+/**
+ * \brief Get the queue id of a queue_timer container
+ * \param info queue_timer container
+ * \return queue id
+ *
+ * \sa snd_seq_get_queue_timer()
+ */
+int snd_seq_queue_timer_get_queue(const snd_seq_queue_timer_t *info)
+{
+	assert(info);
+	return info->queue;
+}
+
+/**
+ * \brief Get the timer type of a queue_timer container
+ * \param info queue_timer container
+ * \return timer type
+ *
+ * \sa snd_seq_get_queue_timer()
+ */
+snd_seq_queue_timer_type_t snd_seq_queue_timer_get_type(const snd_seq_queue_timer_t *info)
+{
+	assert(info);
+	return (snd_seq_queue_timer_type_t)info->type;
+}
+
+/**
+ * \brief Get the timer id of a queue_timer container
+ * \param info queue_timer container
+ * \return timer id pointer
+ *
+ * \sa snd_seq_get_queue_timer()
+ */
+const snd_timer_id_t *snd_seq_queue_timer_get_id(const snd_seq_queue_timer_t *info)
+{
+	assert(info);
+	return &info->u.alsa.id;
+}
+
+/**
+ * \brief Get the timer resolution of a queue_timer container
+ * \param info queue_timer container
+ * \return timer resolution
+ *
+ * \sa snd_seq_get_queue_timer()
+ */
+unsigned int snd_seq_queue_timer_get_resolution(const snd_seq_queue_timer_t *info)
+{
+	assert(info);
+	return info->u.alsa.resolution;
+}
+
+/**
+ * \brief Set the timer type of a queue_timer container
+ * \param info queue_timer container
+ * \param type timer type
+ *
+ * \sa snd_seq_get_queue_timer()
+ */
+void snd_seq_queue_timer_set_type(snd_seq_queue_timer_t *info, snd_seq_queue_timer_type_t type)
+{
+	assert(info);
+	info->type = (int)type;
+}
+	
+/**
+ * \brief Set the timer id of a queue_timer container
+ * \param info queue_timer container
+ * \param id timer id pointer
+ *
+ * \sa snd_seq_get_queue_timer()
+ */
+void snd_seq_queue_timer_set_id(snd_seq_queue_timer_t *info, const snd_timer_id_t *id)
+{
+	assert(info && id);
+	info->u.alsa.id = *id;
+}
+
+/**
+ * \brief Set the timer resolution of a queue_timer container
+ * \param info queue_timer container
+ * \param resolution timer resolution
+ *
+ * \sa snd_seq_get_queue_timer()
+ */
+void snd_seq_queue_timer_set_resolution(snd_seq_queue_timer_t *info, unsigned int resolution)
+{
+	assert(info);
+	info->u.alsa.resolution = resolution;
+}
+
+
+/**
+ * \brief obtain the queue timer information
+ * \param seq sequencer handle
+ * \param q queue id to query
+ * \param timer pointer to store the timer information
+ * \return 0 on success otherwise a negative error code
+ *
+ * \sa snd_seq_set_queue_timer()
+ */
+int snd_seq_get_queue_timer(snd_seq_t *seq, int q, snd_seq_queue_timer_t * timer)
+{
+	assert(seq && timer);
+	memset(timer, 0, sizeof(snd_seq_queue_timer_t));
+	timer->queue = q;
+	return seq->ops->get_queue_timer(seq, timer);
+}
+
+/**
+ * \brief set the queue timer information
+ * \param seq sequencer handle
+ * \param q queue id to change the timer
+ * \param timer timer information
+ * \return 0 on success otherwise a negative error code
+ *
+ * \sa snd_seq_get_queue_timer()
+ */
+int snd_seq_set_queue_timer(snd_seq_t *seq, int q, snd_seq_queue_timer_t * timer)
+{
+	assert(seq && timer);
+	timer->queue = q;
+	return seq->ops->set_queue_timer(seq, timer);
+}
+
+/*----------------------------------------------------------------*/
+
+#ifndef DOC_HIDDEN
+/**
+ * \brief (DEPRECATED) create an event cell
+ * \return the cell pointer allocated
+ *
+ * create an event cell via malloc.  the returned pointer must be released
+ * by the application itself via normal free() call,
+ * not via snd_seq_free_event().
+ */
+snd_seq_event_t *snd_seq_create_event(void)
+{
+	return (snd_seq_event_t *) calloc(1, sizeof(snd_seq_event_t));
+}
+#endif
+
+/**
+ * \brief (DEPRECATED) free an event
+ *
+ * In the former version, this function was used to
+ * release the event pointer which was allocated by snd_seq_event_input().
+ * In the current version, the event record is not allocated, so
+ * you don't have to call this function any more.
+ */
+#ifndef DOXYGEN
+int snd_seq_free_event(snd_seq_event_t *ev ATTRIBUTE_UNUSED)
+#else
+int snd_seq_free_event(snd_seq_event_t *ev)
+#endif
+{
+	return 0;
+}
+
+/**
+ * \brief calculates the (encoded) byte-stream size of the event
+ * \param ev the event
+ * \return the size of decoded bytes
+ */
+ssize_t snd_seq_event_length(snd_seq_event_t *ev)
+{
+	ssize_t len = sizeof(snd_seq_event_t);
+	assert(ev);
+	if (snd_seq_ev_is_variable(ev))
+		len += ev->data.ext.len;
+	return len;
+}
+
+/*----------------------------------------------------------------*/
+
+/*
+ * output to sequencer
+ */
+
+/**
+ * \brief output an event
+ * \param seq sequencer handle
+ * \param ev event to be output
+ * \return the number of remaining events or a negative error code
+ *
+ * An event is once expanded on the output buffer.
+ * The output buffer will be drained automatically if it becomes full.
+ *
+ * If events remain unprocessed on output buffer before drained,
+ * the size of total byte data on output buffer is returned.
+ * If the output buffer is empty, this returns zero.
+ *
+ * \sa snd_seq_event_output_direct(), snd_seq_event_output_buffer(),
+ *    snd_seq_event_output_pending(), snd_seq_drain_output(),
+ *    snd_seq_drop_output(), snd_seq_extract_output(),
+ *    snd_seq_remove_events()
+ */
+int snd_seq_event_output(snd_seq_t *seq, snd_seq_event_t *ev)
+{
+	int result;
+
+	result = snd_seq_event_output_buffer(seq, ev);
+	if (result == -EAGAIN) {
+		result = snd_seq_drain_output(seq);
+		if (result < 0)
+			return result;
+		return snd_seq_event_output_buffer(seq, ev);
+	}
+	return result;
+}
+
+/**
+ * \brief output an event onto the lib buffer without draining buffer
+ * \param seq sequencer handle
+ * \param ev event to be output
+ * \return the byte size of remaining events. \c -EAGAIN if the buffer becomes full.
+ *
+ * This function doesn't drain buffer unlike snd_seq_event_output().
+ *
+ * \sa snd_seq_event_output()
+ */
+int snd_seq_event_output_buffer(snd_seq_t *seq, snd_seq_event_t *ev)
+{
+	int len;
+	assert(seq && ev);
+	len = snd_seq_event_length(ev);
+	if (len < 0)
+		return -EINVAL;
+	if ((size_t) len >= seq->obufsize)
+		return -EINVAL;
+	if ((seq->obufsize - seq->obufused) < (size_t) len)
+		return -EAGAIN;
+	memcpy(seq->obuf + seq->obufused, ev, sizeof(snd_seq_event_t));
+	seq->obufused += sizeof(snd_seq_event_t);
+	if (snd_seq_ev_is_variable(ev)) {
+		memcpy(seq->obuf + seq->obufused, ev->data.ext.ptr, ev->data.ext.len);
+		seq->obufused += ev->data.ext.len;
+	}
+	return seq->obufused;
+}
+
+/*
+ * allocate the temporary buffer
+ */
+static int alloc_tmpbuf(snd_seq_t *seq, size_t len)
+{
+	size_t size = ((len + sizeof(snd_seq_event_t) - 1) / sizeof(snd_seq_event_t));
+	if (seq->tmpbuf == NULL) {
+		if (size > DEFAULT_TMPBUF_SIZE)
+			seq->tmpbufsize = size;
+		else
+			seq->tmpbufsize = DEFAULT_TMPBUF_SIZE;
+		seq->tmpbuf = malloc(seq->tmpbufsize * sizeof(snd_seq_event_t));
+		if (seq->tmpbuf == NULL)
+			return -ENOMEM;
+	}  else if (len > seq->tmpbufsize) {
+		seq->tmpbuf = realloc(seq->tmpbuf, size * sizeof(snd_seq_event_t));
+		if (seq->tmpbuf == NULL)
+			return -ENOMEM;
+		seq->tmpbufsize = size;
+	}
+	return 0;
+}
+
+/**
+ * \brief output an event directly to the sequencer NOT through output buffer
+ * \param seq sequencer handle
+ * \param ev event to be output
+ * \return the byte size sent to sequencer or a negative error code
+ *
+ * This function sends an event to the sequencer directly not through the
+ * output buffer.  When the event is a variable length event, a temporary
+ * buffer is allocated inside alsa-lib and the data is copied there before
+ * actually sent.
+ *
+ * \sa snd_seq_event_output()
+ */
+int snd_seq_event_output_direct(snd_seq_t *seq, snd_seq_event_t *ev)
+{
+	ssize_t len;
+	void *buf;
+
+	len = snd_seq_event_length(ev);
+	if (len < 0)
+		return len;
+	else if (len == sizeof(*ev)) {
+		buf = ev;
+	} else {
+		if (alloc_tmpbuf(seq, (size_t)len) < 0)
+			return -ENOMEM;
+		*seq->tmpbuf = *ev;
+		memcpy(seq->tmpbuf + 1, ev->data.ext.ptr, ev->data.ext.len);
+		buf = seq->tmpbuf;
+	}
+	return seq->ops->write(seq, buf, (size_t) len);
+}
+
+/**
+ * \brief return the size of pending events on output buffer
+ * \param seq sequencer handle
+ * \return the byte size of total of pending events
+ *
+ * \sa snd_seq_event_output()
+ */
+int snd_seq_event_output_pending(snd_seq_t *seq)
+{
+	assert(seq);
+	return seq->obufused;
+}
+
+/**
+ * \brief drain output buffer to sequencer
+ * \param seq sequencer handle
+ * \return 0 when all events are drained and sent to sequencer.
+ *         When events still remain on the buffer, the byte size of remaining
+ *         events are returned.  On error a negative error code is returned.
+ *
+ * This function drains all pending events on the output buffer.
+ * The function returns immediately after the events are sent to the queues
+ * regardless whether the events are processed or not.
+ * To get synchronization with the all event processes, use
+ * #snd_seq_sync_output_queue() after calling this function.
+ *
+ * \sa snd_seq_event_output(), snd_seq_sync_output_queue()
+ */
+int snd_seq_drain_output(snd_seq_t *seq)
+{
+	ssize_t result, processed = 0;
+	assert(seq);
+	while (seq->obufused > 0) {
+		result = seq->ops->write(seq, seq->obuf, seq->obufused);
+		if (result < 0) {
+			if (result == -EAGAIN && processed)
+				return seq->obufused;
+			return result;
+		}
+		if ((size_t)result < seq->obufused)
+			memmove(seq->obuf, seq->obuf + result, seq->obufused - result);
+		seq->obufused -= result;
+	}
+	return 0;
+}
+
+/**
+ * \brief extract the first event in output buffer
+ * \param seq sequencer handle
+ * \param ev_res event pointer to be extracted
+ * \return 0 on success otherwise a negative error code
+ *
+ * Extracts the first event in output buffer.
+ * If ev_res is NULL, just remove the event.
+ *
+ * \sa snd_seq_event_output()
+ */
+int snd_seq_extract_output(snd_seq_t *seq, snd_seq_event_t **ev_res)
+{
+	size_t len, olen;
+	snd_seq_event_t ev;
+	assert(seq);
+	if (ev_res)
+		*ev_res = NULL;
+	if ((olen = seq->obufused) < sizeof(snd_seq_event_t))
+		return -ENOENT;
+	memcpy(&ev, seq->obuf, sizeof(snd_seq_event_t));
+	len = snd_seq_event_length(&ev);
+	if (ev_res) {
+		/* extract the event */
+		if (alloc_tmpbuf(seq, len) < 0)
+			return -ENOMEM;
+		memcpy(seq->tmpbuf, seq->obuf, len);
+		*ev_res = seq->tmpbuf;
+	}
+	seq->obufused = olen - len;
+	memmove(seq->obuf, seq->obuf + len, seq->obufused);
+	return 0;
+}
+
+/*----------------------------------------------------------------*/
+
+/*
+ * input from sequencer
+ */
+
+/*
+ * read from sequencer to input buffer
+ */
+static ssize_t snd_seq_event_read_buffer(snd_seq_t *seq)
+{
+	ssize_t len;
+	len = (seq->ops->read)(seq, seq->ibuf, seq->ibufsize * sizeof(snd_seq_event_t));
+	if (len < 0)
+		return len;
+	seq->ibuflen = len / sizeof(snd_seq_event_t);
+	seq->ibufptr = 0;
+	return seq->ibuflen;
+}
+
+static int snd_seq_event_retrieve_buffer(snd_seq_t *seq, snd_seq_event_t **retp)
+{
+	size_t ncells;
+	snd_seq_event_t *ev;
+
+	*retp = ev = &seq->ibuf[seq->ibufptr];
+	seq->ibufptr++;
+	seq->ibuflen--;
+	if (! snd_seq_ev_is_variable(ev))
+		return 1;
+	ncells = (ev->data.ext.len + sizeof(snd_seq_event_t) - 1) / sizeof(snd_seq_event_t);
+	if (seq->ibuflen < ncells) {
+		seq->ibuflen = 0; /* clear buffer */
+		*retp = NULL;
+		return -EINVAL;
+	}
+	ev->data.ext.ptr = ev + 1;
+	seq->ibuflen -= ncells;
+	seq->ibufptr += ncells;
+	return 1;
+}
+
+/**
+ * \brief retrieve an event from sequencer
+ * \param seq sequencer handle
+ * \param ev event pointer to be stored
+ * \return 
+ *
+ * Obtains an input event from sequencer.
+ * The event is created via snd_seq_create_event(), and its pointer is stored on
+ * ev argument.
+ *
+ * This function firstly receives the event byte-stream data from sequencer
+ * as much as possible at once.  Then it retrieves the first event record
+ * and store the pointer on ev.
+ * By calling this function sequentially, events are extracted from the input buffer.
+ *
+ * If there is no input from sequencer, function falls into sleep
+ * in blocking mode until an event is received,
+ * or returns \c -EAGAIN error in non-blocking mode.
+ * Occasionally, this function may return \c -ENOSPC error.
+ * This means that the input FIFO of sequencer overran, and some events are
+ * lost.
+ * Once this error is returned, the input FIFO is cleared automatically.
+ *
+ * Function returns the byte size of remaining events on the input buffer
+ * if an event is successfully received.
+ * Application can determine from the returned value whether to call
+ * input once more or not.
+ *
+ * \sa snd_seq_event_input_pending(), snd_seq_drop_input()
+ */
+int snd_seq_event_input(snd_seq_t *seq, snd_seq_event_t **ev)
+{
+	int err;
+	assert(seq);
+	*ev = NULL;
+	if (seq->ibuflen <= 0) {
+		if ((err = snd_seq_event_read_buffer(seq)) < 0)
+			return err;
+	}
+
+	return snd_seq_event_retrieve_buffer(seq, ev);
+}
+
+/*
+ * read input data from sequencer if available
+ */
+static int snd_seq_event_input_feed(snd_seq_t *seq, int timeout)
+{
+	struct pollfd pfd;
+	int err;
+	pfd.fd = seq->poll_fd;
+	pfd.events = POLLIN;
+	err = poll(&pfd, 1, timeout);
+	if (err < 0) {
+		SYSERR("poll");
+		return -errno;
+	}
+	if (pfd.revents & POLLIN) 
+		return snd_seq_event_read_buffer(seq);
+	return seq->ibuflen;
+}
+
+/**
+ * \brief check events in input buffer
+ * \return the byte size of remaining input events on input buffer.
+ *
+ * If events remain on the input buffer of user-space, function returns
+ * the total byte size of events on it.
+ * If fetch_sequencer argument is non-zero,
+ * this function checks the presence of events on sequencer FIFO
+ * When events exist, they are transferred to the input buffer,
+ * and the number of received events are returned.
+ * If fetch_sequencer argument is zero and
+ * no events remain on the input buffer, function simply returns zero.
+ *
+ * \sa snd_seq_event_input()
+ */
+int snd_seq_event_input_pending(snd_seq_t *seq, int fetch_sequencer)
+{
+	if (seq->ibuflen == 0 && fetch_sequencer) {
+		return snd_seq_event_input_feed(seq, 0);
+	}
+	return seq->ibuflen;
+}
+
+/*----------------------------------------------------------------*/
+
+/*
+ * clear event buffers
+ */
+
+/**
+ * \brief remove all events on user-space output buffer
+ * \param seq sequencer handle
+ *
+ * Removes all events on user-space output buffer.
+ * Unlike snd_seq_drain_output(), this function doesn't remove
+ * events on output memory pool of sequencer.
+ *
+ * \sa snd_seq_drop_output()
+ */
+int snd_seq_drop_output_buffer(snd_seq_t *seq)
+{
+	assert(seq);
+	seq->obufused = 0;
+	return 0;
+}
+
+/**
+ * \brief remove all events on user-space input FIFO
+ * \param seq sequencer handle
+ *
+ * \sa snd_seq_drop_input()
+ */
+int snd_seq_drop_input_buffer(snd_seq_t *seq)
+{
+	assert(seq);
+	seq->ibufptr = 0;
+	seq->ibuflen = 0;
+	return 0;
+}
+
+/**
+ * \brief remove all events on output buffer
+ * \param seq sequencer handle
+ *
+ * Removes all events on both user-space output buffer and
+ * output memory pool on kernel.
+ *
+ * \sa snd_seq_drain_output(), snd_seq_drop_output_buffer(), snd_seq_remove_events()
+ */
+int snd_seq_drop_output(snd_seq_t *seq)
+{
+	snd_seq_remove_events_t rminfo;
+	assert(seq);
+
+	memset(&rminfo, 0, sizeof(rminfo));
+	rminfo.remove_mode = SNDRV_SEQ_REMOVE_OUTPUT;
+
+	return snd_seq_remove_events(seq, &rminfo);
+}
+
+/**
+ * \brief clear input buffer and and remove events in sequencer queue
+ * \param seq sequencer handle
+ *
+ * \sa snd_seq_drop_input_buffer(), snd_seq_remove_events()
+ */
+int snd_seq_drop_input(snd_seq_t *seq)
+{
+	snd_seq_remove_events_t rminfo;
+	assert(seq);
+
+	memset(&rminfo, 0, sizeof(rminfo));
+	rminfo.remove_mode = SNDRV_SEQ_REMOVE_INPUT;
+
+	return snd_seq_remove_events(seq, &rminfo);
+}
+
+
+/**
+ * \brief get size of #snd_seq_remove_events_t
+ * \return size in bytes
+ */
+size_t snd_seq_remove_events_sizeof()
+{
+	return sizeof(snd_seq_remove_events_t);
+}
+
+/**
+ * \brief allocate an empty #snd_seq_remove_events_t using standard malloc
+ * \param ptr returned pointer
+ * \return 0 on success otherwise negative error code
+ */
+int snd_seq_remove_events_malloc(snd_seq_remove_events_t **ptr)
+{
+	assert(ptr);
+	*ptr = calloc(1, sizeof(snd_seq_remove_events_t));
+	if (!*ptr)
+		return -ENOMEM;
+	return 0;
+}
+
+/**
+ * \brief frees a previously allocated #snd_seq_remove_events_t
+ * \param obj pointer to object to free
+ */
+void snd_seq_remove_events_free(snd_seq_remove_events_t *obj)
+{
+	free(obj);
+}
+
+/**
+ * \brief copy one #snd_seq_remove_events_t to another
+ * \param dst pointer to destination
+ * \param src pointer to source
+ */
+void snd_seq_remove_events_copy(snd_seq_remove_events_t *dst, const snd_seq_remove_events_t *src)
+{
+	assert(dst && src);
+	*dst = *src;
+}
+
+
+/**
+ * \brief Get the removal condition bits
+ * \param info remove_events container
+ * \return removal condition bits
+ *
+ * \sa snd_seq_remove_events()
+ */
+unsigned int snd_seq_remove_events_get_condition(const snd_seq_remove_events_t *info)
+{
+	assert(info);
+	return info->remove_mode;
+}
+
+/**
+ * \brief Get the queue as removal condition
+ * \param info remove_events container
+ * \return queue id
+ *
+ * \sa snd_seq_remove_events()
+ */
+int snd_seq_remove_events_get_queue(const snd_seq_remove_events_t *info)
+{
+	assert(info);
+	return info->queue;
+}
+
+/**
+ * \brief Get the event timestamp as removal condition
+ * \param info remove_events container
+ * \return time stamp
+ *
+ * \sa snd_seq_remove_events()
+ */
+const snd_seq_timestamp_t *snd_seq_remove_events_get_time(const snd_seq_remove_events_t *info)
+{
+	assert(info);
+	return &info->time;
+}
+
+/**
+ * \brief Get the event destination address as removal condition
+ * \param info remove_events container
+ * \return destination address
+ *
+ * \sa snd_seq_remove_events()
+ */
+const snd_seq_addr_t *snd_seq_remove_events_get_dest(const snd_seq_remove_events_t *info)
+{
+	assert(info);
+	return &info->dest;
+}
+
+/**
+ * \brief Get the event channel as removal condition
+ * \param info remove_events container
+ * \return channel number
+ *
+ * \sa snd_seq_remove_events()
+ */
+int snd_seq_remove_events_get_channel(const snd_seq_remove_events_t *info)
+{
+	assert(info);
+	return info->channel;
+}
+
+/**
+ * \brief Get the event type as removal condition
+ * \param info remove_events container
+ * \return event type
+ *
+ * \sa snd_seq_remove_events()
+ */
+int snd_seq_remove_events_get_event_type(const snd_seq_remove_events_t *info)
+{
+	assert(info);
+	return info->type;
+}
+
+/**
+ * \brief Get the event tag id as removal condition
+ * \param info remove_events container
+ * \return tag id
+ *
+ * \sa snd_seq_remove_events()
+ */
+int snd_seq_remove_events_get_tag(const snd_seq_remove_events_t *info)
+{
+	assert(info);
+	return info->tag;
+}
+
+/**
+ * \brief Set the removal condition bits
+ * \param info remove_events container
+ * \param flags removal condition bits
+ *
+ * \sa snd_seq_remove_events()
+ */
+void snd_seq_remove_events_set_condition(snd_seq_remove_events_t *info, unsigned int flags)
+{
+	assert(info);
+	info->remove_mode = flags;
+}
+
+/**
+ * \brief Set the queue as removal condition
+ * \param info remove_events container
+ * \param queue queue id
+ *
+ * \sa snd_seq_remove_events()
+ */
+void snd_seq_remove_events_set_queue(snd_seq_remove_events_t *info, int queue)
+{
+	assert(info);
+	info->queue = queue;
+}
+
+/**
+ * \brief Set the timestamp as removal condition
+ * \param info remove_events container
+ * \param time timestamp pointer
+ *
+ * \sa snd_seq_remove_events()
+ */
+void snd_seq_remove_events_set_time(snd_seq_remove_events_t *info, const snd_seq_timestamp_t *time)
+{
+	assert(info);
+	info->time = *time;
+}
+
+/**
+ * \brief Set the destination address as removal condition
+ * \param info remove_events container
+ * \param addr destination address
+ *
+ * \sa snd_seq_remove_events()
+ */
+void snd_seq_remove_events_set_dest(snd_seq_remove_events_t *info, const snd_seq_addr_t *addr)
+{
+	assert(info);
+	info->dest = *addr;
+}
+
+/**
+ * \brief Set the channel as removal condition
+ * \param info remove_events container
+ * \param channel channel number
+ *
+ * \sa snd_seq_remove_events()
+ */
+void snd_seq_remove_events_set_channel(snd_seq_remove_events_t *info, int channel)
+{
+	assert(info);
+	info->channel = channel;
+}
+
+/**
+ * \brief Set the event type as removal condition
+ * \param info remove_events container
+ * \param type event type
+ *
+ * \sa snd_seq_remove_events()
+ */
+void snd_seq_remove_events_set_event_type(snd_seq_remove_events_t *info, int type)
+{
+	assert(info);
+	info->type = type;
+}
+
+/**
+ * \brief Set the event tag as removal condition
+ * \param info remove_events container
+ * \param tag tag id
+ *
+ * \sa snd_seq_remove_events()
+ */
+void snd_seq_remove_events_set_tag(snd_seq_remove_events_t *info, int tag)
+{
+	assert(info);
+	info->tag = tag;
+}
+
+
+/* compare timestamp between events */
+/* return 1 if a >= b; otherwise return 0 */
+static inline int snd_seq_compare_tick_time(snd_seq_tick_time_t *a, snd_seq_tick_time_t *b)
+{
+	/* compare ticks */
+	return (*a >= *b);
+}
+
+static inline int snd_seq_compare_real_time(snd_seq_real_time_t *a, snd_seq_real_time_t *b)
+{
+	/* compare real time */
+	if (a->tv_sec > b->tv_sec)
+		return 1;
+	if ((a->tv_sec == b->tv_sec) && (a->tv_nsec >= b->tv_nsec))
+		return 1;
+	return 0;
+}
+
+/* Routine to match events to be removed */
+static int remove_match(snd_seq_remove_events_t *info, snd_seq_event_t *ev)
+{
+	int res;
+
+	if (info->remove_mode & SNDRV_SEQ_REMOVE_DEST) {
+		if (ev->dest.client != info->dest.client ||
+				ev->dest.port != info->dest.port)
+			return 0;
+	}
+	if (info->remove_mode & SNDRV_SEQ_REMOVE_DEST_CHANNEL) {
+		if (! snd_seq_ev_is_channel_type(ev))
+			return 0;
+		/* data.note.channel and data.control.channel are identical */
+		if (ev->data.note.channel != info->channel)
+			return 0;
+	}
+	if (info->remove_mode & SNDRV_SEQ_REMOVE_TIME_AFTER) {
+		if (info->remove_mode & SNDRV_SEQ_REMOVE_TIME_TICK)
+			res = snd_seq_compare_tick_time(&ev->time.tick, &info->time.tick);
+		else
+			res = snd_seq_compare_real_time(&ev->time.time, &info->time.time);
+		if (!res)
+			return 0;
+	}
+	if (info->remove_mode & SNDRV_SEQ_REMOVE_TIME_BEFORE) {
+		if (info->remove_mode & SNDRV_SEQ_REMOVE_TIME_TICK)
+			res = snd_seq_compare_tick_time(&ev->time.tick, &info->time.tick);
+		else
+			res = snd_seq_compare_real_time(&ev->time.time, &info->time.time);
+		if (res)
+			return 0;
+	}
+	if (info->remove_mode & SNDRV_SEQ_REMOVE_EVENT_TYPE) {
+		if (ev->type != info->type)
+			return 0;
+	}
+	if (info->remove_mode & SNDRV_SEQ_REMOVE_IGNORE_OFF) {
+		/* Do not remove off events */
+		switch (ev->type) {
+		case SND_SEQ_EVENT_NOTEOFF:
+		/* case SND_SEQ_EVENT_SAMPLE_STOP: */
+			return 0;
+		default:
+			break;
+		}
+	}
+	if (info->remove_mode & SNDRV_SEQ_REMOVE_TAG_MATCH) {
+		if (info->tag != ev->tag)
+			return 0;
+	}
+
+	return 1;
+}
+
+/**
+ * \brief remove events on input/output buffers and pools
+ * \param seq sequencer handle
+ * \param rmp remove event container
+ *
+ * Removes matching events with the given condition from input/output buffers
+ * and pools.
+ * The removal condition is specified in \a rmp argument.
+ *
+ * \sa snd_seq_event_output(), snd_seq_drop_output(), snd_seq_reset_pool_output()
+ */
+int snd_seq_remove_events(snd_seq_t *seq, snd_seq_remove_events_t *rmp)
+{
+	if (rmp->remove_mode & SNDRV_SEQ_REMOVE_INPUT) {
+		/*
+		 * First deal with any events that are still buffered
+		 * in the library.
+		 */
+		snd_seq_drop_input_buffer(seq);
+	}
+
+	if (rmp->remove_mode & SNDRV_SEQ_REMOVE_OUTPUT) {
+		/*
+		 * First deal with any events that are still buffered
+		 * in the library.
+		 */
+		 if (! (rmp->remove_mode & ~(SNDRV_SEQ_REMOVE_INPUT|SNDRV_SEQ_REMOVE_OUTPUT))) {
+			 /* The simple case - remove all */
+			 snd_seq_drop_output_buffer(seq);
+		} else {
+			char *ep;
+			size_t len;
+			snd_seq_event_t *ev;
+
+			ep = seq->obuf;
+			while (ep - seq->obuf < (ssize_t)seq->obufused) {
+
+				ev = (snd_seq_event_t *)ep;
+				len = snd_seq_event_length(ev);
+
+				if (remove_match(rmp, ev)) {
+					/* Remove event */
+					seq->obufused -= len;
+					memmove(ep, ep + len, seq->obufused - (ep - seq->obuf));
+				} else {
+					ep += len;
+				}
+			}
+		}
+	}
+
+	return seq->ops->remove_events(seq, rmp);
+}
+
+/*----------------------------------------------------------------*/
+
+/*
+ * client memory pool
+ */
+
+/**
+ * \brief get size of #snd_seq_client_pool_t
+ * \return size in bytes
+ */
+size_t snd_seq_client_pool_sizeof()
+{
+	return sizeof(snd_seq_client_pool_t);
+}
+
+/**
+ * \brief allocate an empty #snd_seq_client_pool_t using standard malloc
+ * \param ptr returned pointer
+ * \return 0 on success otherwise negative error code
+ */
+int snd_seq_client_pool_malloc(snd_seq_client_pool_t **ptr)
+{
+	assert(ptr);
+	*ptr = calloc(1, sizeof(snd_seq_client_pool_t));
+	if (!*ptr)
+		return -ENOMEM;
+	return 0;
+}
+
+/**
+ * \brief frees a previously allocated #snd_seq_client_pool_t
+ * \param obj pointer to object to free
+ */
+void snd_seq_client_pool_free(snd_seq_client_pool_t *obj)
+{
+	free(obj);
+}
+
+/**
+ * \brief copy one #snd_seq_client_pool_t to another
+ * \param dst pointer to destination
+ * \param src pointer to source
+ */
+void snd_seq_client_pool_copy(snd_seq_client_pool_t *dst, const snd_seq_client_pool_t *src)
+{
+	assert(dst && src);
+	*dst = *src;
+}
+
+
+/**
+ * \brief Get the client id of a queue_info container
+ * \param info client_pool container
+ * \return client id
+ */
+int snd_seq_client_pool_get_client(const snd_seq_client_pool_t *info)
+{
+	assert(info);
+	return info->client;
+}
+
+/**
+ * \brief Get the output pool size of a queue_info container
+ * \param info client_pool container
+ * \return output pool size
+ */
+size_t snd_seq_client_pool_get_output_pool(const snd_seq_client_pool_t *info)
+{
+	assert(info);
+	return info->output_pool;
+}
+
+/**
+ * \brief Get the input pool size of a queue_info container
+ * \param info client_pool container
+ * \return input pool size
+ */
+size_t snd_seq_client_pool_get_input_pool(const snd_seq_client_pool_t *info)
+{
+	assert(info);
+	return info->input_pool;
+}
+
+/**
+ * \brief Get the output room size of a queue_info container
+ * \param info client_pool container
+ * \return output room size
+ */
+size_t snd_seq_client_pool_get_output_room(const snd_seq_client_pool_t *info)
+{
+	assert(info);
+	return info->output_room;
+}
+
+/**
+ * \brief Get the available size on output pool of a queue_info container
+ * \param info client_pool container
+ * \return available output size
+ */
+size_t snd_seq_client_pool_get_output_free(const snd_seq_client_pool_t *info)
+{
+	assert(info);
+	return info->output_free;
+}
+
+/**
+ * \brief Get the available size on input pool of a queue_info container
+ * \param info client_pool container
+ * \return available input size
+ */
+size_t snd_seq_client_pool_get_input_free(const snd_seq_client_pool_t *info)
+{
+	assert(info);
+	return info->input_free;
+}
+
+/**
+ * \brief Set the output pool size of a queue_info container
+ * \param info client_pool container
+ * \param size output pool size
+ */
+void snd_seq_client_pool_set_output_pool(snd_seq_client_pool_t *info, size_t size)
+{
+	assert(info);
+	info->output_pool = size;
+}
+
+/**
+ * \brief Set the input pool size of a queue_info container
+ * \param info client_pool container
+ * \param size input pool size
+ */
+void snd_seq_client_pool_set_input_pool(snd_seq_client_pool_t *info, size_t size)
+{
+	assert(info);
+	info->input_pool = size;
+}
+
+/**
+ * \brief Set the output room size of a queue_info container
+ * \param info client_pool container
+ * \param size output room size
+ */
+void snd_seq_client_pool_set_output_room(snd_seq_client_pool_t *info, size_t size)
+{
+	assert(info);
+	info->output_room = size;
+}
+
+
+/**
+ * \brief obtain the pool information of the current client
+ * \param seq sequencer handle
+ * \param info information to be stored
+ */
+int snd_seq_get_client_pool(snd_seq_t *seq, snd_seq_client_pool_t *info)
+{
+	assert(seq && info);
+	info->client = seq->client;
+	return seq->ops->get_client_pool(seq, info);
+}
+
+/**
+ * \brief set the pool information
+ * \param seq sequencer handle
+ * \param info information to update
+ *
+ * Sets the pool information of the current client.
+ * The client field in \a info is replaced automatically with the current id.
+ */
+int snd_seq_set_client_pool(snd_seq_t *seq, snd_seq_client_pool_t *info)
+{
+	assert(seq && info);
+	info->client = seq->client;
+	return seq->ops->set_client_pool(seq, info);
+}
+
+/*----------------------------------------------------------------*/
+
+/*
+ * misc.
+ */
+
+/**
+ * \brief set a bit flag
+ */
+void snd_seq_set_bit(int nr, void *array)
+{
+	((unsigned int *)array)[nr >> 5] |= 1UL << (nr & 31);
+}
+
+/**
+ * \brief unset a bit flag
+ */
+void snd_seq_unset_bit(int nr, void *array)
+{
+       ((unsigned int *)array)[nr >> 5] &= ~(1UL << (nr & 31));
+}
+
+/**
+ * \brief change a bit flag
+ */
+int snd_seq_change_bit(int nr, void *array)
+{
+	int result;
+
+	result = ((((unsigned int *)array)[nr >> 5]) & (1UL << (nr & 31))) ? 1 : 0;
+	((unsigned int *)array)[nr >> 5] ^= 1UL << (nr & 31);
+	return result;
+}
+
+/**
+ * \brief get a bit flag state
+ */
+int snd_seq_get_bit(int nr, void *array)
+{
+	return ((((unsigned int *)array)[nr >> 5]) & (1UL << (nr & 31))) ? 1 : 0;
+}
diff --git a/src/seq/seq_event.c b/src/seq/seq_event.c
new file mode 100644
index 0000000..0571b21
--- /dev/null
+++ b/src/seq/seq_event.c
@@ -0,0 +1,49 @@
+/**
+ * \file seq/seq_event.c
+ * \brief Sequencer Event Types
+ * \author Takashi Iwai <tiwai@suse.de>
+ * \date 2001
+ */
+
+#include "local.h"
+
+#ifndef DOC_HIDDEN
+#define FIXED_EV(x)	(_SND_SEQ_TYPE(SND_SEQ_EVFLG_FIXED) | _SND_SEQ_TYPE(x))
+#endif
+
+/** Event types conversion array */
+const unsigned int snd_seq_event_types[256] = {
+	[SND_SEQ_EVENT_SYSTEM ... SND_SEQ_EVENT_RESULT]
+	= FIXED_EV(SND_SEQ_EVFLG_RESULT),
+	[SND_SEQ_EVENT_NOTE]
+	= FIXED_EV(SND_SEQ_EVFLG_NOTE) | _SND_SEQ_TYPE_OPT(SND_SEQ_EVFLG_NOTE_TWOARG),
+	[SND_SEQ_EVENT_NOTEON ... SND_SEQ_EVENT_KEYPRESS]
+	= FIXED_EV(SND_SEQ_EVFLG_NOTE),
+	[SND_SEQ_EVENT_CONTROLLER ... SND_SEQ_EVENT_REGPARAM]
+	= FIXED_EV(SND_SEQ_EVFLG_CONTROL),
+	[SND_SEQ_EVENT_START ... SND_SEQ_EVENT_STOP]
+	= FIXED_EV(SND_SEQ_EVFLG_QUEUE),
+	[SND_SEQ_EVENT_SETPOS_TICK]
+	= FIXED_EV(SND_SEQ_EVFLG_QUEUE) | _SND_SEQ_TYPE_OPT(SND_SEQ_EVFLG_QUEUE_TICK),
+	[SND_SEQ_EVENT_SETPOS_TIME]
+	= FIXED_EV(SND_SEQ_EVFLG_QUEUE) | _SND_SEQ_TYPE_OPT(SND_SEQ_EVFLG_QUEUE_TIME),
+	[SND_SEQ_EVENT_TEMPO ... SND_SEQ_EVENT_SYNC_POS]
+	= FIXED_EV(SND_SEQ_EVFLG_QUEUE) | _SND_SEQ_TYPE_OPT(SND_SEQ_EVFLG_QUEUE_VALUE),
+	[SND_SEQ_EVENT_TUNE_REQUEST ... SND_SEQ_EVENT_SENSING]
+	= FIXED_EV(SND_SEQ_EVFLG_NONE),
+	[SND_SEQ_EVENT_ECHO ... SND_SEQ_EVENT_OSS]
+	= FIXED_EV(SND_SEQ_EVFLG_RAW) | FIXED_EV(SND_SEQ_EVFLG_SYSTEM),
+	[SND_SEQ_EVENT_CLIENT_START ... SND_SEQ_EVENT_PORT_CHANGE]
+	= FIXED_EV(SND_SEQ_EVFLG_MESSAGE),
+	[SND_SEQ_EVENT_PORT_SUBSCRIBED ... SND_SEQ_EVENT_PORT_UNSUBSCRIBED]
+	= FIXED_EV(SND_SEQ_EVFLG_CONNECTION),
+	[SND_SEQ_EVENT_USR0 ... SND_SEQ_EVENT_USR9]
+	= FIXED_EV(SND_SEQ_EVFLG_RAW) | FIXED_EV(SND_SEQ_EVFLG_USERS),
+	[SND_SEQ_EVENT_SYSEX ... SND_SEQ_EVENT_BOUNCE]
+	= _SND_SEQ_TYPE(SND_SEQ_EVFLG_VARIABLE),
+	[SND_SEQ_EVENT_USR_VAR0 ... SND_SEQ_EVENT_USR_VAR4]
+	= _SND_SEQ_TYPE(SND_SEQ_EVFLG_VARIABLE) | _SND_SEQ_TYPE(SND_SEQ_EVFLG_USERS),
+	[SND_SEQ_EVENT_NONE]
+	= FIXED_EV(SND_SEQ_EVFLG_NONE),
+};
+
diff --git a/src/seq/seq_hw.c b/src/seq/seq_hw.c
new file mode 100644
index 0000000..5bb26f3
--- /dev/null
+++ b/src/seq/seq_hw.c
@@ -0,0 +1,557 @@
+/*
+ *  Sequencer Interface - main file
+ *  Copyright (c) 2000 by Jaroslav Kysela <perex@perex.cz>
+ *                        Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include "seq_local.h"
+
+#ifndef PIC
+/* entry for static linking */
+const char *_snd_module_seq_hw = "";
+#endif
+
+#ifndef DOC_HIDDEN
+#define SNDRV_FILE_SEQ		ALSA_DEVICE_DIRECTORY "seq"
+#define SNDRV_FILE_ALOADSEQ	ALOAD_DEVICE_DIRECTORY "aloadSEQ"
+#define SNDRV_SEQ_VERSION_MAX	SNDRV_PROTOCOL_VERSION(1, 0, 1)
+
+typedef struct {
+	int fd;
+} snd_seq_hw_t;
+#endif /* DOC_HIDDEN */
+
+static int snd_seq_hw_close(snd_seq_t *seq)
+{
+	snd_seq_hw_t *hw = seq->private_data;
+	int err = 0;
+
+	if (close(hw->fd)) {
+		err = -errno;
+		SYSERR("close failed\n");
+	}
+	free(hw);
+	return err;
+}
+
+static int snd_seq_hw_nonblock(snd_seq_t *seq, int nonblock)
+{
+	snd_seq_hw_t *hw = seq->private_data;
+	long flags;
+
+	if ((flags = fcntl(hw->fd, F_GETFL)) < 0) {
+		SYSERR("F_GETFL failed");
+		return -errno;
+	}
+	if (nonblock)
+		flags |= O_NONBLOCK;
+	else
+		flags &= ~O_NONBLOCK;
+	if (fcntl(hw->fd, F_SETFL, flags) < 0) {
+		SYSERR("F_SETFL for O_NONBLOCK failed");
+		return -errno;
+	}
+	return 0;
+}
+
+static int snd_seq_hw_client_id(snd_seq_t *seq)
+{
+	snd_seq_hw_t *hw = seq->private_data;
+	int client;
+	if (ioctl(hw->fd, SNDRV_SEQ_IOCTL_CLIENT_ID, &client) < 0) {
+		SYSERR("SNDRV_SEQ_IOCTL_CLIENT_ID failed");
+		return -errno;
+	}
+	return client;
+}
+
+static int snd_seq_hw_system_info(snd_seq_t *seq, snd_seq_system_info_t * info)
+{
+	snd_seq_hw_t *hw = seq->private_data;
+	if (ioctl(hw->fd, SNDRV_SEQ_IOCTL_SYSTEM_INFO, info) < 0) {
+		SYSERR("SNDRV_SEQ_IOCTL_SYSTEM_INFO failed");
+		return -errno;
+	}
+	return 0;
+}
+
+static int snd_seq_hw_get_client_info(snd_seq_t *seq, snd_seq_client_info_t * info)
+{
+	snd_seq_hw_t *hw = seq->private_data;
+	if (ioctl(hw->fd, SNDRV_SEQ_IOCTL_GET_CLIENT_INFO, info) < 0) {
+		/*SYSERR("SNDRV_SEQ_IOCTL_GET_CLIENT_INFO failed");*/
+		return -errno;
+	}
+	return 0;
+}
+
+static int snd_seq_hw_set_client_info(snd_seq_t *seq, snd_seq_client_info_t * info)
+{
+	snd_seq_hw_t *hw = seq->private_data;
+	if (ioctl(hw->fd, SNDRV_SEQ_IOCTL_SET_CLIENT_INFO, info) < 0) {
+		/*SYSERR("SNDRV_SEQ_IOCTL_SET_CLIENT_INFO failed");*/
+		return -errno;
+	}
+	return 0;
+}
+
+static int snd_seq_hw_create_port(snd_seq_t *seq, snd_seq_port_info_t * port)
+{
+	snd_seq_hw_t *hw = seq->private_data;
+	if (ioctl(hw->fd, SNDRV_SEQ_IOCTL_CREATE_PORT, port) < 0) {
+		/*SYSERR("SNDRV_SEQ_IOCTL_CREATE_PORT failed");*/
+		return -errno;
+	}
+	return 0;
+}
+
+static int snd_seq_hw_delete_port(snd_seq_t *seq, snd_seq_port_info_t * port)
+{
+	snd_seq_hw_t *hw = seq->private_data;
+	if (ioctl(hw->fd, SNDRV_SEQ_IOCTL_DELETE_PORT, port) < 0) {
+		/*SYSERR("SNDRV_SEQ_IOCTL_DELETE_PORT failed");*/
+		return -errno;
+	}
+	return 0;
+}
+
+static int snd_seq_hw_get_port_info(snd_seq_t *seq, snd_seq_port_info_t * info)
+{
+	snd_seq_hw_t *hw = seq->private_data;
+	if (ioctl(hw->fd, SNDRV_SEQ_IOCTL_GET_PORT_INFO, info) < 0) {
+		/*SYSERR("SNDRV_SEQ_IOCTL_GET_PORT_INFO failed");*/
+		return -errno;
+	}
+	return 0;
+}
+
+static int snd_seq_hw_set_port_info(snd_seq_t *seq, snd_seq_port_info_t * info)
+{
+	snd_seq_hw_t *hw = seq->private_data;
+	if (ioctl(hw->fd, SNDRV_SEQ_IOCTL_SET_PORT_INFO, info) < 0) {
+		/*SYSERR("SNDRV_SEQ_IOCTL_SET_PORT_INFO failed");*/
+		return -errno;
+	}
+	return 0;
+}
+
+static int snd_seq_hw_get_port_subscription(snd_seq_t *seq, snd_seq_port_subscribe_t * sub)
+{
+	snd_seq_hw_t *hw = seq->private_data;
+	if (ioctl(hw->fd, SNDRV_SEQ_IOCTL_GET_SUBSCRIPTION, sub) < 0) {
+		/*SYSERR("SNDRV_SEQ_IOCTL_GET_SUBSCRIPTION failed");*/
+		return -errno;
+	}
+	return 0;
+}
+
+static int snd_seq_hw_subscribe_port(snd_seq_t *seq, snd_seq_port_subscribe_t * sub)
+{
+	snd_seq_hw_t *hw = seq->private_data;
+	if (ioctl(hw->fd, SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT, sub) < 0) {
+		/*SYSERR("SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT failed");*/
+		return -errno;
+	}
+	return 0;
+}
+
+static int snd_seq_hw_unsubscribe_port(snd_seq_t *seq, snd_seq_port_subscribe_t * sub)
+{
+	snd_seq_hw_t *hw = seq->private_data;
+	if (ioctl(hw->fd, SNDRV_SEQ_IOCTL_UNSUBSCRIBE_PORT, sub) < 0) {
+		/*SYSERR("SNDRV_SEQ_IOCTL_UNSUBSCRIBE_PORT failed");*/
+		return -errno;
+	}
+	return 0;
+}
+
+static int snd_seq_hw_query_port_subscribers(snd_seq_t *seq, snd_seq_query_subscribe_t * subs)
+{
+	snd_seq_hw_t *hw = seq->private_data;
+	if (ioctl(hw->fd, SNDRV_SEQ_IOCTL_QUERY_SUBS, subs) < 0) {
+		/*SYSERR("SNDRV_SEQ_IOCTL_QUERY_SUBS failed");*/
+		return -errno;
+	}
+	return 0;
+}
+
+static int snd_seq_hw_get_queue_status(snd_seq_t *seq, snd_seq_queue_status_t * status)
+{
+	snd_seq_hw_t *hw = seq->private_data;
+	if (ioctl(hw->fd, SNDRV_SEQ_IOCTL_GET_QUEUE_STATUS, status) < 0) {
+		/*SYSERR("SNDRV_SEQ_IOCTL_GET_QUEUE_STATUS failed");*/
+		return -errno;
+	}
+	return 0;
+}
+
+static int snd_seq_hw_get_queue_tempo(snd_seq_t *seq, snd_seq_queue_tempo_t * tempo)
+{
+	snd_seq_hw_t *hw = seq->private_data;
+	if (ioctl(hw->fd, SNDRV_SEQ_IOCTL_GET_QUEUE_TEMPO, tempo) < 0) {
+		/*SYSERR("SNDRV_SEQ_IOCTL_GET_QUEUE_TEMPO failed");*/
+		return -errno;
+	}
+	return 0;
+}
+
+static int snd_seq_hw_set_queue_tempo(snd_seq_t *seq, snd_seq_queue_tempo_t * tempo)
+{
+	snd_seq_hw_t *hw = seq->private_data;
+	if (ioctl(hw->fd, SNDRV_SEQ_IOCTL_SET_QUEUE_TEMPO, tempo) < 0) {
+		/*SYSERR("SNDRV_SEQ_IOCTL_SET_QUEUE_TEMPO failed");*/
+		return -errno;
+	}
+	return 0;
+}
+
+static int snd_seq_hw_get_queue_timer(snd_seq_t *seq, snd_seq_queue_timer_t * timer)
+{
+	snd_seq_hw_t *hw = seq->private_data;
+	if (ioctl(hw->fd, SNDRV_SEQ_IOCTL_GET_QUEUE_TIMER, timer) < 0) {
+		/*SYSERR("SNDRV_SEQ_IOCTL_GET_QUEUE_TIMER failed");*/
+		return -errno;
+	}
+	return 0;
+}
+
+static int snd_seq_hw_set_queue_timer(snd_seq_t *seq, snd_seq_queue_timer_t * timer)
+{
+	snd_seq_hw_t *hw = seq->private_data;
+	if (ioctl(hw->fd, SNDRV_SEQ_IOCTL_SET_QUEUE_TIMER, timer) < 0) {
+		/*SYSERR("SNDRV_SEQ_IOCTL_SET_QUEUE_TIMER failed");*/
+		return -errno;
+	}
+	return 0;
+}
+
+static int snd_seq_hw_get_queue_client(snd_seq_t *seq, snd_seq_queue_client_t * info)
+{
+	snd_seq_hw_t *hw = seq->private_data;
+	if (ioctl(hw->fd, SNDRV_SEQ_IOCTL_GET_QUEUE_CLIENT, info) < 0) {
+		/*SYSERR("SNDRV_SEQ_IOCTL_GET_QUEUE_CLIENT failed");*/
+		return -errno;
+	}
+	return 0;
+}
+
+static int snd_seq_hw_set_queue_client(snd_seq_t *seq, snd_seq_queue_client_t * info)
+{
+	snd_seq_hw_t *hw = seq->private_data;
+	if (ioctl(hw->fd, SNDRV_SEQ_IOCTL_SET_QUEUE_CLIENT, info) < 0) {
+		/*SYSERR("SNDRV_SEQ_IOCTL_SET_QUEUE_CLIENT failed");*/
+		return -errno;
+	}
+	return 0;
+}
+
+static int snd_seq_hw_create_queue(snd_seq_t *seq, snd_seq_queue_info_t *info)
+{
+	snd_seq_hw_t *hw = seq->private_data;
+	if (ioctl(hw->fd, SNDRV_SEQ_IOCTL_CREATE_QUEUE, info) < 0) {
+		/*SYSERR("SNDRV_SEQ_IOCTL_CREATE_QUEUE failed");*/
+		return -errno;
+	}
+	return 0;
+}
+
+static int snd_seq_hw_delete_queue(snd_seq_t *seq, snd_seq_queue_info_t *info)
+{
+	snd_seq_hw_t *hw = seq->private_data;
+	if (ioctl(hw->fd, SNDRV_SEQ_IOCTL_DELETE_QUEUE, info) < 0) {
+		/*SYSERR("SNDRV_SEQ_IOCTL_DELETE_QUEUE failed");*/
+		return -errno;
+	}
+	return 0;
+}
+
+static int snd_seq_hw_get_queue_info(snd_seq_t *seq, snd_seq_queue_info_t *info)
+{
+	snd_seq_hw_t *hw = seq->private_data;
+	if (ioctl(hw->fd, SNDRV_SEQ_IOCTL_GET_QUEUE_INFO, info) < 0) {
+		/*SYSERR("SNDRV_SEQ_IOCTL_GET_QUEUE_INFO failed");*/
+		return -errno;
+	}
+	return 0;
+}
+
+static int snd_seq_hw_set_queue_info(snd_seq_t *seq, snd_seq_queue_info_t *info)
+{
+	snd_seq_hw_t *hw = seq->private_data;
+	if (ioctl(hw->fd, SNDRV_SEQ_IOCTL_SET_QUEUE_INFO, info) < 0) {
+		/*SYSERR("SNDRV_SEQ_IOCTL_SET_QUEUE_INFO failed");*/
+		return -errno;
+	}
+	return 0;
+}
+
+static int snd_seq_hw_get_named_queue(snd_seq_t *seq, snd_seq_queue_info_t *info)
+{
+	snd_seq_hw_t *hw = seq->private_data;
+	if (ioctl(hw->fd, SNDRV_SEQ_IOCTL_GET_NAMED_QUEUE, info) < 0) {
+		/*SYSERR("SNDRV_SEQ_IOCTL_GET_NAMED_QUEUE failed");*/
+		return -errno;
+	}
+	return 0;
+}
+
+static ssize_t snd_seq_hw_write(snd_seq_t *seq, void *buf, size_t len)
+{
+	snd_seq_hw_t *hw = seq->private_data;
+	ssize_t result = write(hw->fd, buf, len);
+	if (result < 0)
+		return -errno;
+	return result;
+}
+
+static ssize_t snd_seq_hw_read(snd_seq_t *seq, void *buf, size_t len)
+{
+	snd_seq_hw_t *hw = seq->private_data;
+	ssize_t result = read(hw->fd, buf, len);
+	if (result < 0)
+		return -errno;
+	return result;
+}
+
+static int snd_seq_hw_remove_events(snd_seq_t *seq, snd_seq_remove_events_t *rmp)
+{
+	snd_seq_hw_t *hw = seq->private_data;
+	if (ioctl(hw->fd, SNDRV_SEQ_IOCTL_REMOVE_EVENTS, rmp) < 0) {
+		/*SYSERR("SNDRV_SEQ_IOCTL_REMOVE_EVENTS failed");*/
+		return -errno;
+	}
+	return 0;
+}
+
+static int snd_seq_hw_get_client_pool(snd_seq_t *seq, snd_seq_client_pool_t *info)
+{
+	snd_seq_hw_t *hw = seq->private_data;
+	if (ioctl(hw->fd, SNDRV_SEQ_IOCTL_GET_CLIENT_POOL, info) < 0) {
+		/*SYSERR("SNDRV_SEQ_IOCTL_GET_CLIENT_POOL failed");*/
+		return -errno;
+	}
+	return 0;
+}
+
+static int snd_seq_hw_set_client_pool(snd_seq_t *seq, snd_seq_client_pool_t *info)
+{
+	snd_seq_hw_t *hw = seq->private_data;
+	if (ioctl(hw->fd, SNDRV_SEQ_IOCTL_SET_CLIENT_POOL, info) < 0) {
+		/*SYSERR("SNDRV_SEQ_IOCTL_SET_CLIENT_POOL failed");*/
+		return -errno;
+	}
+	return 0;
+}
+
+static int snd_seq_hw_query_next_client(snd_seq_t *seq, snd_seq_client_info_t *info)
+{
+	snd_seq_hw_t *hw = seq->private_data;
+	if (ioctl(hw->fd, SNDRV_SEQ_IOCTL_QUERY_NEXT_CLIENT, info) < 0) {
+		/*SYSERR("SNDRV_SEQ_IOCTL_QUERY_NEXT_CLIENT failed");*/
+		return -errno;
+	}
+	return 0;
+}
+
+static int snd_seq_hw_query_next_port(snd_seq_t *seq, snd_seq_port_info_t *info)
+{
+	snd_seq_hw_t *hw = seq->private_data;
+	if (ioctl(hw->fd, SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT, info) < 0) {
+		/*SYSERR("SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT failed");*/
+		return -errno;
+	}
+	return 0;
+}
+
+static const snd_seq_ops_t snd_seq_hw_ops = {
+	.close = snd_seq_hw_close,
+	.nonblock = snd_seq_hw_nonblock,
+	.system_info = snd_seq_hw_system_info,
+	.get_client_info = snd_seq_hw_get_client_info,
+	.set_client_info = snd_seq_hw_set_client_info,
+	.create_port = snd_seq_hw_create_port,
+	.delete_port = snd_seq_hw_delete_port,
+	.get_port_info = snd_seq_hw_get_port_info,
+	.set_port_info = snd_seq_hw_set_port_info,
+	.get_port_subscription = snd_seq_hw_get_port_subscription,
+	.subscribe_port = snd_seq_hw_subscribe_port,
+	.unsubscribe_port = snd_seq_hw_unsubscribe_port,
+	.query_port_subscribers = snd_seq_hw_query_port_subscribers,
+	.get_queue_status = snd_seq_hw_get_queue_status,
+	.get_queue_tempo = snd_seq_hw_get_queue_tempo,
+	.set_queue_tempo = snd_seq_hw_set_queue_tempo,
+	.get_queue_timer = snd_seq_hw_get_queue_timer,
+	.set_queue_timer = snd_seq_hw_set_queue_timer,
+	.get_queue_client = snd_seq_hw_get_queue_client,
+	.set_queue_client = snd_seq_hw_set_queue_client,
+	.create_queue = snd_seq_hw_create_queue,
+	.delete_queue = snd_seq_hw_delete_queue,
+	.get_queue_info = snd_seq_hw_get_queue_info,
+	.set_queue_info = snd_seq_hw_set_queue_info,
+	.get_named_queue = snd_seq_hw_get_named_queue,
+	.write = snd_seq_hw_write,
+	.read = snd_seq_hw_read,
+	.remove_events = snd_seq_hw_remove_events,
+	.get_client_pool = snd_seq_hw_get_client_pool,
+	.set_client_pool = snd_seq_hw_set_client_pool,
+	.query_next_client = snd_seq_hw_query_next_client,
+	.query_next_port = snd_seq_hw_query_next_port,
+};
+
+int snd_seq_hw_open(snd_seq_t **handle, const char *name, int streams, int mode)
+{
+	int fd, ver, client, fmode, ret;
+	const char *filename;
+	snd_seq_t *seq;
+	snd_seq_hw_t *hw;
+
+	*handle = NULL;
+
+	switch (streams) {
+	case SND_SEQ_OPEN_OUTPUT:
+		fmode = O_WRONLY;
+		break;
+	case SND_SEQ_OPEN_INPUT:
+		fmode = O_RDONLY;
+		break;
+	case SND_SEQ_OPEN_DUPLEX:
+		fmode = O_RDWR;
+		break;
+	default:
+		assert(0);
+		return -EINVAL;
+	}
+	
+	if (mode & SND_SEQ_NONBLOCK)
+		fmode |= O_NONBLOCK;
+
+	filename = SNDRV_FILE_SEQ;
+	fd = snd_open_device(filename, fmode);
+#ifdef SUPPORT_ALOAD
+	if (fd < 0) {
+		fd = snd_open_device(SNDRV_FILE_ALOADSEQ, fmode);
+		if (fd >= 0)
+			close(fd);
+		fd = snd_open_device(filename, fmode);
+	}
+#endif
+	if (fd < 0) {
+		SYSERR("open %s failed", filename);
+		return -errno;
+	}
+	if (ioctl(fd, SNDRV_SEQ_IOCTL_PVERSION, &ver) < 0) {
+		SYSERR("SNDRV_SEQ_IOCTL_PVERSION failed");
+		ret = -errno;
+		close(fd);
+		return ret;
+	}
+	if (SNDRV_PROTOCOL_INCOMPATIBLE(ver, SNDRV_SEQ_VERSION_MAX)) {
+		close(fd);
+		return -SND_ERROR_INCOMPATIBLE_VERSION;
+	}
+	hw = calloc(1, sizeof(snd_seq_hw_t));
+	if (hw == NULL) {
+		close(fd);
+		return -ENOMEM;
+	}
+
+	seq = calloc(1, sizeof(snd_seq_t));
+	if (seq == NULL) {
+		free(hw);
+		close(fd);
+		return -ENOMEM;
+	}
+	hw->fd = fd;
+	if (streams & SND_SEQ_OPEN_OUTPUT) {
+		seq->obuf = (char *) malloc(seq->obufsize = SND_SEQ_OBUF_SIZE);
+		if (!seq->obuf) {
+			free(hw);
+			free(seq);
+			close(fd);
+			return -ENOMEM;
+		}
+	}
+	if (streams & SND_SEQ_OPEN_INPUT) {
+		seq->ibuf = (snd_seq_event_t *) calloc(sizeof(snd_seq_event_t), seq->ibufsize = SND_SEQ_IBUF_SIZE);
+		if (!seq->ibuf) {
+			free(seq->obuf);
+			free(hw);
+			free(seq);
+			close(fd);
+			return -ENOMEM;
+		}
+	}
+	if (name)
+		seq->name = strdup(name);
+	seq->type = SND_SEQ_TYPE_HW;
+	seq->streams = streams;
+	seq->mode = mode;
+	seq->tmpbuf = NULL;
+	seq->tmpbufsize = 0;
+	seq->poll_fd = fd;
+	seq->ops = &snd_seq_hw_ops;
+	seq->private_data = hw;
+	client = snd_seq_hw_client_id(seq);
+	if (client < 0) {
+		snd_seq_close(seq);
+		return client;
+	} else
+		seq->client = client;
+
+#ifdef SNDRV_SEQ_IOCTL_RUNNING_MODE
+	{
+		struct sndrv_seq_running_info run_mode;
+		/* check running mode */
+		memset(&run_mode, 0, sizeof(run_mode));
+		run_mode.client = client;
+#ifdef SNDRV_BIG_ENDIAN
+		run_mode.big_endian = 1;
+#else
+		run_mode.big_endian = 0;
+#endif
+		run_mode.cpu_mode = sizeof(long);
+		ioctl(fd, SNDRV_SEQ_IOCTL_RUNNING_MODE, &run_mode);
+	}
+#endif
+
+	*handle = seq;
+	return 0;
+}
+
+int _snd_seq_hw_open(snd_seq_t **handlep, char *name,
+		     snd_config_t *root ATTRIBUTE_UNUSED, snd_config_t *conf,
+		     int streams, int mode)
+{
+	snd_config_iterator_t i, next;
+	snd_config_for_each(i, next, conf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (strcmp(id, "comment") == 0)
+			continue;
+		if (strcmp(id, "type") == 0)
+			continue;
+		return -EINVAL;
+	}
+	return snd_seq_hw_open(handlep, name, streams, mode);
+}
+SND_DLSYM_BUILD_VERSION(_snd_seq_hw_open, SND_SEQ_DLSYM_VERSION);
diff --git a/src/seq/seq_local.h b/src/seq/seq_local.h
new file mode 100644
index 0000000..f0a0acd
--- /dev/null
+++ b/src/seq/seq_local.h
@@ -0,0 +1,97 @@
+/*
+ *  Sequencer Interface - definition of sequencer event handler
+ *  Copyright (c) 2000 by Jaroslav Kysela <perex@perex.cz>
+ *                        Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#ifndef __SEQ_LOCAL_H
+#define __SEQ_LOCAL_H
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include "local.h"
+
+#define SND_SEQ_OBUF_SIZE	(16*1024)	/* default size */
+#define SND_SEQ_IBUF_SIZE	500		/* in event_size aligned */
+#define DEFAULT_TMPBUF_SIZE	20
+
+typedef struct sndrv_seq_queue_client snd_seq_queue_client_t;
+
+
+typedef struct {
+	int (*close)(snd_seq_t *seq);
+	int (*nonblock)(snd_seq_t *seq, int nonblock);
+	int (*system_info)(snd_seq_t *seq, snd_seq_system_info_t * info);
+	int (*get_client_info)(snd_seq_t *seq, snd_seq_client_info_t * info);
+	int (*set_client_info)(snd_seq_t *seq, snd_seq_client_info_t * info);
+	int (*create_port)(snd_seq_t *seq, snd_seq_port_info_t * port);
+	int (*delete_port)(snd_seq_t *seq, snd_seq_port_info_t * port);
+	int (*get_port_info)(snd_seq_t *seq, snd_seq_port_info_t * info);
+	int (*set_port_info)(snd_seq_t *seq, snd_seq_port_info_t * info);
+	int (*get_port_subscription)(snd_seq_t *seq, snd_seq_port_subscribe_t * sub);
+	int (*subscribe_port)(snd_seq_t *seq, snd_seq_port_subscribe_t * sub);
+	int (*unsubscribe_port)(snd_seq_t *seq, snd_seq_port_subscribe_t * sub);
+	int (*query_port_subscribers)(snd_seq_t *seq, snd_seq_query_subscribe_t * subs);
+	int (*get_queue_status)(snd_seq_t *seq, snd_seq_queue_status_t * status);
+	int (*get_queue_tempo)(snd_seq_t *seq, snd_seq_queue_tempo_t * tempo);
+	int (*set_queue_tempo)(snd_seq_t *seq, snd_seq_queue_tempo_t * tempo);
+	int (*get_queue_timer)(snd_seq_t *seq, snd_seq_queue_timer_t * timer);
+	int (*set_queue_timer)(snd_seq_t *seq, snd_seq_queue_timer_t * timer);
+	int (*get_queue_client)(snd_seq_t *seq, snd_seq_queue_client_t * client);
+	int (*set_queue_client)(snd_seq_t *seq, snd_seq_queue_client_t * client);
+	int (*create_queue)(snd_seq_t *seq, snd_seq_queue_info_t *info);
+	int (*delete_queue)(snd_seq_t *seq, snd_seq_queue_info_t *info);
+	int (*get_queue_info)(snd_seq_t *seq, snd_seq_queue_info_t *info);
+	int (*set_queue_info)(snd_seq_t *seq, snd_seq_queue_info_t *info);
+	int (*get_named_queue)(snd_seq_t *seq, snd_seq_queue_info_t *info);
+	ssize_t (*write)(snd_seq_t *seq, void *buf, size_t len);
+	ssize_t (*read)(snd_seq_t *seq, void *buf, size_t len);
+	int (*remove_events)(snd_seq_t *seq, snd_seq_remove_events_t *rmp);
+	int (*get_client_pool)(snd_seq_t *seq, snd_seq_client_pool_t *info);
+	int (*set_client_pool)(snd_seq_t *seq, snd_seq_client_pool_t *info);
+	int (*query_next_client)(snd_seq_t *seq, snd_seq_client_info_t *info);
+	int (*query_next_port)(snd_seq_t *seq, snd_seq_port_info_t *info);
+} snd_seq_ops_t;
+
+struct _snd_seq {
+	char *name;
+	snd_seq_type_t type;
+	int streams;
+	int mode;
+	int poll_fd;
+	void *dl_handle;
+	const snd_seq_ops_t *ops;
+	void *private_data;
+	int client;		/* client number */
+	/* buffers */
+	char *obuf;		/* output buffer */
+	size_t obufsize;		/* output buffer size */
+	size_t obufused;		/* output buffer used size */
+	snd_seq_event_t *ibuf;	/* input buffer */
+	size_t ibufptr;		/* current pointer of input buffer */
+	size_t ibuflen;		/* queued length */
+	size_t ibufsize;		/* input buffer size */
+	snd_seq_event_t *tmpbuf;	/* temporary event for extracted event */
+	size_t tmpbufsize;		/* size of errbuf */
+};
+
+int snd_seq_hw_open(snd_seq_t **handle, const char *name, int streams, int mode);
+
+#endif
diff --git a/src/seq/seq_midi_event.c b/src/seq/seq_midi_event.c
new file mode 100644
index 0000000..ddaac5a
--- /dev/null
+++ b/src/seq/seq_midi_event.c
@@ -0,0 +1,727 @@
+/**
+ * \file seq/seq_midi_event.c
+ * \brief MIDI byte <-> sequencer event coder
+ * \author Takashi Iwai <tiwai@suse.de>
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \date 2000-2001
+ */
+
+/*
+ *  MIDI byte <-> sequencer event coder
+ *
+ *  Copyright (C) 1998,99,2000 Takashi Iwai <tiwai@suse.de>,
+ *			       Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <malloc.h>
+#include "local.h"
+
+#ifndef DOC_HIDDEN
+
+/* midi status */
+struct snd_midi_event {
+	size_t qlen;	/* queue length */
+	size_t read;	/* chars read */
+	int type;	/* current event type */
+	unsigned char lastcmd;
+	unsigned char nostat;
+	size_t bufsize;
+	unsigned char *buf;	/* input buffer */
+};
+
+
+/* event type, index into status_event[] */
+/* from 0 to 6 are normal commands (note off, on, etc.) for 0x8?-0xe? */
+#define ST_INVALID	7
+#define ST_SPECIAL	8
+#define ST_SYSEX	ST_SPECIAL
+/* from 8 to 15 are events for 0xf0-0xf7 */
+
+
+/* status event types */
+typedef void (*event_encode_t)(snd_midi_event_t *dev, snd_seq_event_t *ev);
+typedef void (*event_decode_t)(const snd_seq_event_t *ev, unsigned char *buf);
+
+#endif /* DOC_HIDDEN */
+
+/*
+ * prototypes
+ */
+static void note_event(snd_midi_event_t *dev, snd_seq_event_t *ev);
+static void one_param_ctrl_event(snd_midi_event_t *dev, snd_seq_event_t *ev);
+static void pitchbend_ctrl_event(snd_midi_event_t *dev, snd_seq_event_t *ev);
+static void two_param_ctrl_event(snd_midi_event_t *dev, snd_seq_event_t *ev);
+static void one_param_event(snd_midi_event_t *dev, snd_seq_event_t *ev);
+static void songpos_event(snd_midi_event_t *dev, snd_seq_event_t *ev);
+static void note_decode(const snd_seq_event_t *ev, unsigned char *buf);
+static void one_param_decode(const snd_seq_event_t *ev, unsigned char *buf);
+static void pitchbend_decode(const snd_seq_event_t *ev, unsigned char *buf);
+static void two_param_decode(const snd_seq_event_t *ev, unsigned char *buf);
+static void songpos_decode(const snd_seq_event_t *ev, unsigned char *buf);
+
+/*
+ * event list
+ */
+#ifndef DOC_HIDDEN
+static const struct status_event_list_t {
+	int event;
+	int qlen;
+	event_encode_t encode;
+	event_decode_t decode;
+} status_event[] = {
+	/* 0x80 - 0xef */
+	{SND_SEQ_EVENT_NOTEOFF,		 2, note_event, note_decode},
+	{SND_SEQ_EVENT_NOTEON,		 2, note_event, note_decode},
+	{SND_SEQ_EVENT_KEYPRESS,	 2, note_event, note_decode},
+	{SND_SEQ_EVENT_CONTROLLER,	 2, two_param_ctrl_event, two_param_decode},
+	{SND_SEQ_EVENT_PGMCHANGE,	 1, one_param_ctrl_event, one_param_decode},
+	{SND_SEQ_EVENT_CHANPRESS,	 1, one_param_ctrl_event, one_param_decode},
+	{SND_SEQ_EVENT_PITCHBEND,	 2, pitchbend_ctrl_event, pitchbend_decode},
+	/* invalid */
+	{SND_SEQ_EVENT_NONE,		-1, NULL, NULL},
+	/* 0xf0 - 0xff */
+	{SND_SEQ_EVENT_SYSEX,		 1, NULL, NULL}, /* sysex: 0xf0 */
+	{SND_SEQ_EVENT_QFRAME,		 1, one_param_event, one_param_decode}, /* 0xf1 */
+	{SND_SEQ_EVENT_SONGPOS,		 2, songpos_event, songpos_decode}, /* 0xf2 */
+	{SND_SEQ_EVENT_SONGSEL,		 1, one_param_event, one_param_decode}, /* 0xf3 */
+	{SND_SEQ_EVENT_NONE,		-1, NULL, NULL}, /* 0xf4 */
+	{SND_SEQ_EVENT_NONE,		-1, NULL, NULL}, /* 0xf5 */
+	{SND_SEQ_EVENT_TUNE_REQUEST,	 0, NULL, NULL}, /* 0xf6 */
+	{SND_SEQ_EVENT_NONE,		-1, NULL, NULL}, /* 0xf7 */
+	{SND_SEQ_EVENT_CLOCK,		 0, NULL, NULL}, /* 0xf8 */
+	{SND_SEQ_EVENT_NONE,		-1, NULL, NULL}, /* 0xf9 */
+	{SND_SEQ_EVENT_START,		 0, NULL, NULL}, /* 0xfa */
+	{SND_SEQ_EVENT_CONTINUE,	 0, NULL, NULL}, /* 0xfb */
+	{SND_SEQ_EVENT_STOP, 		 0, NULL, NULL}, /* 0xfc */
+	{SND_SEQ_EVENT_NONE, 		-1, NULL, NULL}, /* 0xfd */
+	{SND_SEQ_EVENT_SENSING, 	 0, NULL, NULL}, /* 0xfe */
+	{SND_SEQ_EVENT_RESET, 		 0, NULL, NULL}, /* 0xff */
+};
+
+static int extra_decode_ctrl14(snd_midi_event_t *dev, unsigned char *buf, int len, const snd_seq_event_t *ev);
+static int extra_decode_xrpn(snd_midi_event_t *dev, unsigned char *buf, int count, const snd_seq_event_t *ev);
+
+static const struct extra_event_list_t {
+	int event;
+	int (*decode)(snd_midi_event_t *dev, unsigned char *buf, int len, const snd_seq_event_t *ev);
+} extra_event[] = {
+	{SND_SEQ_EVENT_CONTROL14, extra_decode_ctrl14},
+	{SND_SEQ_EVENT_NONREGPARAM, extra_decode_xrpn},
+	{SND_SEQ_EVENT_REGPARAM, extra_decode_xrpn},
+};
+
+#define numberof(ary)	(sizeof(ary)/sizeof(ary[0]))
+#endif /* DOC_HIDDEN */
+
+/**
+ * \brief Creates a MIDI event parser.
+ * \param[in] bufsize Size of the buffer used for encoding; this should be
+ *                    large enough to hold the largest MIDI message to be
+ *                    encoded.
+ * \param[out] rdev The new MIDI event parser.
+ * \return Zero on success, otherwise a negative error code.
+ *
+ * This function creates and initializes a MIDI parser object that can be used
+ * to convert a MIDI byte stream to sequencer events (encoding) and/or to
+ * convert sequencer events to a MIDI byte stream (decoding).
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-ENOMEM<dd>Out of memory.
+ *
+ * \par Conforming to:
+ * LSB 3.2
+ */
+int snd_midi_event_new(size_t bufsize, snd_midi_event_t **rdev)
+{
+	snd_midi_event_t *dev;
+
+	*rdev = NULL;
+	dev = (snd_midi_event_t *)calloc(1, sizeof(snd_midi_event_t));
+	if (dev == NULL)
+		return -ENOMEM;
+	if (bufsize > 0) {
+		dev->buf = malloc(bufsize);
+		if (dev->buf == NULL) {
+			free(dev);
+			return -ENOMEM;
+		}
+	}
+	dev->bufsize = bufsize;
+	dev->lastcmd = 0xff;
+	dev->type = ST_INVALID;
+	*rdev = dev;
+	return 0;
+}
+
+/**
+ * \brief Frees a MIDI event parser.
+ * \param dev MIDI event parser.
+ *
+ * Frees a MIDI event parser.
+ *
+ * \par Conforming to:
+ * LSB 3.2
+ */
+void snd_midi_event_free(snd_midi_event_t *dev)
+{
+	if (dev != NULL) {
+		free(dev->buf);
+		free(dev);
+	}
+}
+
+/**
+ * \brief Enables/disables MIDI command merging.
+ * \param dev MIDI event parser.
+ * \param on 0 to enable MIDI command merging,
+ *           1 to always write the command byte.
+ *
+ * This function enables or disables MIDI command merging (running status).
+ *
+ * When MIDI command merging is not disabled, #snd_midi_event_decode is allowed
+ * to omit any status byte that is identical to the previous status byte.
+ */
+void snd_midi_event_no_status(snd_midi_event_t *dev, int on)
+{
+	dev->nostat = on ? 1 : 0;
+}
+
+/*
+ * initialize record
+ */
+inline static void reset_encode(snd_midi_event_t *dev)
+{
+	dev->read = 0;
+	dev->qlen = 0;
+	dev->type = ST_INVALID;
+}
+
+/**
+ * \brief Resets MIDI encode parser.
+ * \param dev MIDI event parser.
+ *
+ * This function resets the MIDI encoder of the parser \a dev.
+ * Any partially encoded MIDI message is dropped,
+ * and running status state is cleared.
+ *
+ * \par Conforming to:
+ * LSB 3.2
+ */
+void snd_midi_event_reset_encode(snd_midi_event_t *dev)
+{
+	reset_encode(dev);
+}
+
+/**
+ * \brief Resets MIDI decode parser.
+ * \param dev MIDI event parser.
+ *
+ * This function resets the MIDI decoder of the parser \a dev.
+ * The next decoded message does not use running status from before the call to
+ * \a snd_midi_event_reset_decode.
+ *
+ * \par Conforming to:
+ * LSB 3.2
+ */
+void snd_midi_event_reset_decode(snd_midi_event_t *dev)
+{
+	dev->lastcmd = 0xff;
+}
+
+/**
+ * \brief Resets MIDI encode/decode parsers.
+ * \param dev MIDI event parser.
+ *
+ * This function resets both encoder and decoder of the MIDI event parser.
+ * \sa snd_midi_event_reset_encode, snd_midi_event_reset_decode
+ *
+ * \par Conforming to:
+ * LSB 3.2
+ */
+void snd_midi_event_init(snd_midi_event_t *dev)
+{
+	snd_midi_event_reset_encode(dev);
+	snd_midi_event_reset_decode(dev);
+}
+
+/**
+ * \brief Resizes the MIDI message encoding buffer.
+ * \param dev MIDI event parser.
+ * \param bufsize The new buffer size.
+ * \return Zero on success, otherwise a negative error code.
+ *
+ * This function resizes the buffer that is used to hold partially encoded MIDI
+ * messages.
+ *
+ * If there is a partially encoded message in the buffer, it is dropped.
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-ENOMEM<dd>Out of memory.
+ *
+ * \sa snd_midi_event_encode, snd_midi_event_reset_encode
+ */
+int snd_midi_event_resize_buffer(snd_midi_event_t *dev, size_t bufsize)
+{
+	unsigned char *new_buf, *old_buf;
+
+	if (bufsize == dev->bufsize)
+		return 0;
+	new_buf = malloc(bufsize);
+	if (new_buf == NULL)
+		return -ENOMEM;
+	old_buf = dev->buf;
+	dev->buf = new_buf;
+	dev->bufsize = bufsize;
+	reset_encode(dev);
+	free(old_buf);
+	return 0;
+}
+
+/**
+ * \brief Encodes bytes to sequencer event.
+ * \param[in] dev MIDI event parser.
+ * \param[in] buf Buffer containing bytes of a raw MIDI stream.
+ * \param[in] count Number of bytes in \a buf.
+ * \param[out] ev Sequencer event.
+ * \return The number of bytes consumed, or a negative error code.
+ *
+ * This function tries to use up to \a count bytes from the beginning of the
+ * buffer to encode a sequencer event.  If a complete MIDI message has been
+ * encoded, the sequencer event is written to \a ev; otherwise, \a ev->type is
+ * set to #SND_SEQ_EVENT_NONE, and further bytes are required to complete
+ * a message.
+ *
+ * The buffer in \a dev is used to hold any bytes of a not-yet-complete MIDI
+ * message.  If a System Exclusive message is larger than the buffer, the
+ * message is split into multiple parts, and a sequencer event is returned at
+ * the end of each part.
+ *
+ * Any bytes that are not part of a valid MIDI message are silently ignored,
+ * i.e., they are consumed without signaling an error.
+ *
+ * When this function returns a system exclusive sequencer event (\a ev->type
+ * is #SND_SEQ_EVENT_SYSEX), the data pointer (\a ev->data.ext.ptr) points into
+ * the MIDI event parser's buffer.  Therefore, the sequencer event can only be
+ * used as long as that buffer remains valid, i.e., until the next call to
+ * #snd_midi_event_encode, #snd_midi_event_encode_byte,
+ * #snd_midi_event_resize_buffer, #snd_midi_event_init,
+ * #snd_midi_event_reset_encode, or #snd_midi_event_free for that MIDI event
+ * parser.
+ *
+ * This function can generate any sequencer event that corresponds to a MIDI
+ * message, i.e.:
+ * - #SND_SEQ_EVENT_NOTEOFF
+ * - #SND_SEQ_EVENT_NOTEON
+ * - #SND_SEQ_EVENT_KEYPRESS
+ * - #SND_SEQ_EVENT_CONTROLLER
+ * - #SND_SEQ_EVENT_PGMCHANGE
+ * - #SND_SEQ_EVENT_CHANPRESS
+ * - #SND_SEQ_EVENT_PITCHBEND
+ * - #SND_SEQ_EVENT_SYSEX
+ * - #SND_SEQ_EVENT_QFRAME
+ * - #SND_SEQ_EVENT_SONGPOS
+ * - #SND_SEQ_EVENT_SONGSEL
+ * - #SND_SEQ_EVENT_TUNE_REQUEST
+ * - #SND_SEQ_EVENT_CLOCK
+ * - #SND_SEQ_EVENT_START
+ * - #SND_SEQ_EVENT_CONTINUE
+ * - #SND_SEQ_EVENT_STOP
+ * - #SND_SEQ_EVENT_SENSING
+ * - #SND_SEQ_EVENT_RESET
+ * .
+ * Some implementations may also be able to generate the following events
+ * for a sequence of controller change messages:
+ * - #SND_SEQ_EVENT_CONTROL14
+ * - #SND_SEQ_EVENT_NONREGPARAM
+ * - #SND_SEQ_EVENT_REGPARAM
+ *
+ * \par Conforming to:
+ * LSB 3.2
+ *
+ * \sa snd_midi_event_new, snd_midi_event_reset_encode, snd_midi_event_encode_byte
+ */
+long snd_midi_event_encode(snd_midi_event_t *dev, const unsigned char *buf, long count, snd_seq_event_t *ev)
+{
+	long result = 0;
+	int rc;
+
+	ev->type = SND_SEQ_EVENT_NONE;
+
+	while (count-- > 0) {
+		rc = snd_midi_event_encode_byte(dev, *buf++, ev);
+		result++;
+		if (rc < 0)
+			return rc;
+		else if (rc > 0)
+			return result;
+	}
+
+	return result;
+}
+
+/**
+ * \brief Encodes byte to sequencer event.
+ * \param[in] dev MIDI event parser.
+ * \param[in] c A byte of a raw MIDI stream.
+ * \param[out] ev Sequencer event.
+ * \return 1 if a sequenver event has been completed, 0 if more bytes are
+ *         required to complete an event, or a negative error code.
+ *
+ * This function tries to use the byte \a c to encode a sequencer event.  If
+ * a complete MIDI message has been encoded, the sequencer event is written to
+ * \a ev; otherwise, further bytes are required to complete a message.
+ *
+ * See also the description of #snd_midi_event_encode.
+ *
+ * \par Conforming to:
+ * LSB 3.2
+ *
+ * \sa snd_midi_event_new, snd_midi_event_reset_encode, snd_midi_event_encode
+ */
+int snd_midi_event_encode_byte(snd_midi_event_t *dev, int c, snd_seq_event_t *ev)
+{
+	int rc = 0;
+
+	c &= 0xff;
+
+	if (c >= MIDI_CMD_COMMON_CLOCK) {
+		/* real-time event */
+		ev->type = status_event[ST_SPECIAL + c - 0xf0].event;
+		ev->flags &= ~SND_SEQ_EVENT_LENGTH_MASK;
+		ev->flags |= SND_SEQ_EVENT_LENGTH_FIXED;
+		return ev->type != SND_SEQ_EVENT_NONE;
+	}
+
+	if ((c & 0x80) &&
+	    (c != MIDI_CMD_COMMON_SYSEX_END || dev->type != ST_SYSEX)) {
+		/* new command */
+		dev->buf[0] = c;
+		if ((c & 0xf0) == 0xf0) /* system message */
+			dev->type = (c & 0x0f) + ST_SPECIAL;
+		else
+			dev->type = (c >> 4) & 0x07;
+		dev->read = 1;
+		dev->qlen = status_event[dev->type].qlen;
+	} else {
+		if (dev->qlen > 0) {
+			/* rest of command */
+			dev->buf[dev->read++] = c;
+			if (dev->type != ST_SYSEX)
+				dev->qlen--;
+		} else {
+			/* running status */
+			dev->buf[1] = c;
+			dev->qlen = status_event[dev->type].qlen - 1;
+			dev->read = 2;
+		}
+	}
+	if (dev->qlen == 0) {
+		ev->type = status_event[dev->type].event;
+		ev->flags &= ~SND_SEQ_EVENT_LENGTH_MASK;
+		ev->flags |= SND_SEQ_EVENT_LENGTH_FIXED;
+		if (status_event[dev->type].encode) /* set data values */
+			status_event[dev->type].encode(dev, ev);
+		if (dev->type >= ST_SPECIAL)
+			dev->type = ST_INVALID;
+		rc = 1;
+	} else 	if (dev->type == ST_SYSEX) {
+		if (c == MIDI_CMD_COMMON_SYSEX_END ||
+		    dev->read >= dev->bufsize) {
+			ev->flags &= ~SND_SEQ_EVENT_LENGTH_MASK;
+			ev->flags |= SND_SEQ_EVENT_LENGTH_VARIABLE;
+			ev->type = SND_SEQ_EVENT_SYSEX;
+			ev->data.ext.len = dev->read;
+			ev->data.ext.ptr = dev->buf;
+			if (c != MIDI_CMD_COMMON_SYSEX_END)
+				dev->read = 0; /* continue to parse */
+			else
+				reset_encode(dev); /* all parsed */
+			rc = 1;
+		}
+	}
+
+	return rc;
+}
+
+/* encode note event */
+static void note_event(snd_midi_event_t *dev, snd_seq_event_t *ev)
+{
+	ev->data.note.channel = dev->buf[0] & 0x0f;
+	ev->data.note.note = dev->buf[1];
+	ev->data.note.velocity = dev->buf[2];
+}
+
+/* encode one parameter controls */
+static void one_param_ctrl_event(snd_midi_event_t *dev, snd_seq_event_t *ev)
+{
+	ev->data.control.channel = dev->buf[0] & 0x0f;
+	ev->data.control.value = dev->buf[1];
+}
+
+/* encode pitch wheel change */
+static void pitchbend_ctrl_event(snd_midi_event_t *dev, snd_seq_event_t *ev)
+{
+	ev->data.control.channel = dev->buf[0] & 0x0f;
+	ev->data.control.value = (int)dev->buf[2] * 128 + (int)dev->buf[1] - 8192;
+}
+
+/* encode midi control change */
+static void two_param_ctrl_event(snd_midi_event_t *dev, snd_seq_event_t *ev)
+{
+	ev->data.control.channel = dev->buf[0] & 0x0f;
+	ev->data.control.param = dev->buf[1];
+	ev->data.control.value = dev->buf[2];
+}
+
+/* encode one parameter value*/
+static void one_param_event(snd_midi_event_t *dev, snd_seq_event_t *ev)
+{
+	ev->data.control.value = dev->buf[1];
+}
+
+/* encode song position */
+static void songpos_event(snd_midi_event_t *dev, snd_seq_event_t *ev)
+{
+	ev->data.control.value = (int)dev->buf[2] * 128 + (int)dev->buf[1];
+}
+
+/**
+ * \brief Decodes sequencer event to MIDI byte stream.
+ * \param[in] dev MIDI event parser.
+ * \param[out] buf Buffer for the resulting MIDI byte stream.
+ * \param[in] count Number of bytes in \a buf.
+ * \param[in] ev The sequencer event to decode.
+ * \return The number of bytes written to \a buf, or a negative error code.
+ *
+ * This function tries to decode the sequencer event into one or more MIDI
+ * messages, and writes the raw MIDI byte(s) into \a buf.
+ *
+ * The generated MIDI messages may use running status, unless disabled with
+ * #snd_midi_event_no_status.
+ *
+ * The required buffer size for a sequencer event it as most 12 bytes, except
+ * for System Exclusive events (\a ev->type == #SND_SEQ_EVENT_SYSEX) which can
+ * have any length (as specified by \a ev->data.ext.len).
+ *
+ * The following sequencer events correspond to MIDI messages:
+ * - #SND_SEQ_EVENT_NOTEOFF
+ * - #SND_SEQ_EVENT_NOTEON
+ * - #SND_SEQ_EVENT_KEYPRESS
+ * - #SND_SEQ_EVENT_CONTROLLER
+ * - #SND_SEQ_EVENT_PGMCHANGE
+ * - #SND_SEQ_EVENT_CHANPRESS
+ * - #SND_SEQ_EVENT_PITCHBEND
+ * - #SND_SEQ_EVENT_SYSEX
+ * - #SND_SEQ_EVENT_QFRAME
+ * - #SND_SEQ_EVENT_SONGPOS
+ * - #SND_SEQ_EVENT_SONGSEL
+ * - #SND_SEQ_EVENT_TUNE_REQUEST
+ * - #SND_SEQ_EVENT_CLOCK
+ * - #SND_SEQ_EVENT_START
+ * - #SND_SEQ_EVENT_CONTINUE
+ * - #SND_SEQ_EVENT_STOP
+ * - #SND_SEQ_EVENT_SENSING
+ * - #SND_SEQ_EVENT_RESET
+ * - #SND_SEQ_EVENT_CONTROL14
+ * - #SND_SEQ_EVENT_NONREGPARAM
+ * - #SND_SEQ_EVENT_REGPARAM
+ *
+ * \par Errors:
+ * <dl>
+ * <dt>-EINVAL<dd>\a ev is not a valid sequencer event.
+ * <dt>-ENOENT<dd>The sequencer event does not correspond to one or more MIDI messages.
+ * <dt>-ENOMEM<dd>The MIDI message(s) would not fit into \a count bytes.
+ *
+ * \par Conforming to:
+ * LSB 3.2
+ *
+ * \sa snd_midi_event_reset_decode, snd_midi_event_no_status
+ */
+long snd_midi_event_decode(snd_midi_event_t *dev, unsigned char *buf, long count, const snd_seq_event_t *ev)
+{
+	int cmd;
+	long qlen;
+	unsigned int type;
+
+	if (ev->type == SND_SEQ_EVENT_NONE)
+		return -ENOENT;
+
+	for (type = 0; type < numberof(status_event); type++) {
+		if (ev->type == status_event[type].event)
+			goto __found;
+	}
+	for (type = 0; type < numberof(extra_event); type++) {
+		if (ev->type == extra_event[type].event)
+			return extra_event[type].decode(dev, buf, count, ev);
+	}
+	return -ENOENT;
+
+      __found:
+	if (type >= ST_SPECIAL)
+		cmd = 0xf0 + (type - ST_SPECIAL);
+	else
+		/* data.note.channel and data.control.channel is identical */
+		cmd = 0x80 | (type << 4) | (ev->data.note.channel & 0x0f);
+
+
+	if (cmd == MIDI_CMD_COMMON_SYSEX) {
+		snd_midi_event_reset_decode(dev);
+		qlen = ev->data.ext.len;
+		if (count < qlen)
+			return -ENOMEM;
+		switch (ev->flags & SND_SEQ_EVENT_LENGTH_MASK) {
+		case SND_SEQ_EVENT_LENGTH_FIXED:
+			return -EINVAL;	/* invalid event */
+		}
+		memcpy(buf, ev->data.ext.ptr, qlen);
+		return qlen;
+	} else {
+		unsigned char xbuf[4];
+
+		if ((cmd & 0xf0) == 0xf0 || dev->lastcmd != cmd || dev->nostat) {
+			dev->lastcmd = cmd;
+			xbuf[0] = cmd;
+			if (status_event[type].decode)
+				status_event[type].decode(ev, xbuf + 1);
+			qlen = status_event[type].qlen + 1;
+		} else {
+			if (status_event[type].decode)
+				status_event[type].decode(ev, xbuf + 0);
+			qlen = status_event[type].qlen;
+		}
+		if (count < qlen)
+			return -ENOMEM;
+		memcpy(buf, xbuf, qlen);
+		return qlen;
+	}
+}
+
+
+/* decode note event */
+static void note_decode(const snd_seq_event_t *ev, unsigned char *buf)
+{
+	buf[0] = ev->data.note.note & 0x7f;
+	buf[1] = ev->data.note.velocity & 0x7f;
+}
+
+/* decode one parameter controls */
+static void one_param_decode(const snd_seq_event_t *ev, unsigned char *buf)
+{
+	buf[0] = ev->data.control.value & 0x7f;
+}
+
+/* decode pitch wheel change */
+static void pitchbend_decode(const snd_seq_event_t *ev, unsigned char *buf)
+{
+	int value = ev->data.control.value + 8192;
+	buf[0] = value & 0x7f;
+	buf[1] = (value >> 7) & 0x7f;
+}
+
+/* decode midi control change */
+static void two_param_decode(const snd_seq_event_t *ev, unsigned char *buf)
+{
+	buf[0] = ev->data.control.param & 0x7f;
+	buf[1] = ev->data.control.value & 0x7f;
+}
+
+/* decode song position */
+static void songpos_decode(const snd_seq_event_t *ev, unsigned char *buf)
+{
+	buf[0] = ev->data.control.value & 0x7f;
+	buf[1] = (ev->data.control.value >> 7) & 0x7f;
+}
+
+/* decode 14bit control */
+static int extra_decode_ctrl14(snd_midi_event_t *dev, unsigned char *buf, int count, const snd_seq_event_t *ev)
+{
+	unsigned char cmd;
+	int idx = 0;
+
+	cmd = MIDI_CMD_CONTROL|(ev->data.control.channel & 0x0f);
+	if (ev->data.control.param < 32) {
+		if (count < 4)
+			return -ENOMEM;
+		if (dev->nostat && count < 6)
+			return -ENOMEM;
+		if (cmd != dev->lastcmd || dev->nostat) {
+			if (count < 5)
+				return -ENOMEM;
+			buf[idx++] = dev->lastcmd = cmd;
+		}
+		buf[idx++] = ev->data.control.param;
+		buf[idx++] = (ev->data.control.value >> 7) & 0x7f;
+		if (dev->nostat)
+			buf[idx++] = cmd;
+		buf[idx++] = ev->data.control.param + 32;
+		buf[idx++] = ev->data.control.value & 0x7f;
+	} else {
+		if (count < 2)
+			return -ENOMEM;
+		if (cmd != dev->lastcmd || dev->nostat) {
+			if (count < 3)
+				return -ENOMEM;
+			buf[idx++] = dev->lastcmd = cmd;
+		}
+		buf[idx++] = ev->data.control.param & 0x7f;
+		buf[idx++] = ev->data.control.value & 0x7f;
+	}
+	return idx;
+}
+
+/* decode reg/nonreg param */
+static int extra_decode_xrpn(snd_midi_event_t *dev, unsigned char *buf, int count, const snd_seq_event_t *ev)
+{
+	unsigned char cmd;
+	const char *cbytes;
+	static const char cbytes_nrpn[4] = { MIDI_CTL_NONREG_PARM_NUM_MSB,
+				       MIDI_CTL_NONREG_PARM_NUM_LSB,
+				       MIDI_CTL_MSB_DATA_ENTRY,
+				       MIDI_CTL_LSB_DATA_ENTRY };
+	static const char cbytes_rpn[4] =  { MIDI_CTL_REGIST_PARM_NUM_MSB,
+				       MIDI_CTL_REGIST_PARM_NUM_LSB,
+				       MIDI_CTL_MSB_DATA_ENTRY,
+				       MIDI_CTL_LSB_DATA_ENTRY };
+	unsigned char bytes[4];
+	int idx = 0, i;
+
+	if (count < 8)
+		return -ENOMEM;
+	if (dev->nostat && count < 12)
+		return -ENOMEM;
+	cmd = MIDI_CMD_CONTROL|(ev->data.control.channel & 0x0f);
+	bytes[0] = (ev->data.control.param & 0x3f80) >> 7;
+	bytes[1] = ev->data.control.param & 0x007f;
+	bytes[2] = (ev->data.control.value & 0x3f80) >> 7;
+	bytes[3] = ev->data.control.value & 0x007f;
+	if (cmd != dev->lastcmd && !dev->nostat) {
+		if (count < 9)
+			return -ENOMEM;
+		buf[idx++] = dev->lastcmd = cmd;
+	}
+	cbytes = ev->type == SND_SEQ_EVENT_NONREGPARAM ? cbytes_nrpn : cbytes_rpn;
+	for (i = 0; i < 4; i++) {
+		if (dev->nostat)
+			buf[idx++] = dev->lastcmd = cmd;
+		buf[idx++] = cbytes[i];
+		buf[idx++] = bytes[i];
+	}
+	return idx;
+}
diff --git a/src/seq/seq_old.c b/src/seq/seq_old.c
new file mode 100644
index 0000000..60d5f4b
--- /dev/null
+++ b/src/seq/seq_old.c
@@ -0,0 +1,222 @@
+/*
+ * place-holders to keep libasound linkable to old binaries
+ */
+
+#ifndef DOXYGEN
+
+#include "local.h"
+
+size_t snd_instr_header_sizeof(void)
+{
+	return 0;
+}
+
+int snd_instr_header_malloc(void **ptr ATTRIBUTE_UNUSED,
+			    size_t len ATTRIBUTE_UNUSED)
+{
+	return -ENOMEM;
+}
+
+void snd_instr_header_free(void *obj ATTRIBUTE_UNUSED)
+{
+}
+
+void snd_instr_header_copy(void *dst ATTRIBUTE_UNUSED,
+			   const void *src ATTRIBUTE_UNUSED)
+{
+}
+
+const void *snd_instr_header_get_id(const void *info ATTRIBUTE_UNUSED)
+{
+	return NULL;
+}
+
+int snd_instr_header_get_cluster(const void *info ATTRIBUTE_UNUSED)
+{
+	return 0;
+}
+
+unsigned int snd_instr_header_get_cmd(const void *info ATTRIBUTE_UNUSED)
+{
+	return 0;
+}
+
+size_t snd_instr_header_get_len(const void *info ATTRIBUTE_UNUSED)
+{
+	return 0;
+}
+
+const char *snd_instr_header_get_name(const void *info ATTRIBUTE_UNUSED)
+{
+	return NULL;
+}
+
+int snd_instr_header_get_type(const void *info ATTRIBUTE_UNUSED)
+{
+	return 0;
+}
+
+const char *snd_instr_header_get_format(const void *info ATTRIBUTE_UNUSED)
+{
+	return NULL;
+}
+
+const void *snd_instr_header_get_alias(const void *info ATTRIBUTE_UNUSED)
+{
+	return NULL;
+}
+
+void *snd_instr_header_get_data(const void *info ATTRIBUTE_UNUSED)
+{
+	return NULL;
+}
+
+int snd_instr_header_get_follow_alias(const void *info ATTRIBUTE_UNUSED)
+{
+	return 0;
+}
+
+void snd_instr_header_set_id(void *info ATTRIBUTE_UNUSED,
+			     const void *id ATTRIBUTE_UNUSED)
+{
+}
+
+void snd_instr_header_set_cluster(void *info ATTRIBUTE_UNUSED,
+				  int cluster ATTRIBUTE_UNUSED)
+{
+}
+
+void snd_instr_header_set_cmd(void *info ATTRIBUTE_UNUSED,
+			      unsigned int cmd ATTRIBUTE_UNUSED)
+{
+}
+
+void snd_instr_header_set_len(void *info ATTRIBUTE_UNUSED,
+			      size_t len ATTRIBUTE_UNUSED)
+{
+}
+
+void snd_instr_header_set_name(void *info ATTRIBUTE_UNUSED,
+			       const char *name ATTRIBUTE_UNUSED)
+{
+}
+
+void snd_instr_header_set_type(void *info ATTRIBUTE_UNUSED,
+			       int type ATTRIBUTE_UNUSED)
+{
+}
+
+void snd_instr_header_set_format(void *info ATTRIBUTE_UNUSED,
+				 const char *format ATTRIBUTE_UNUSED)
+{
+}
+
+void snd_instr_header_set_alias(void *info ATTRIBUTE_UNUSED,
+				const void *instr ATTRIBUTE_UNUSED)
+{
+}
+
+void snd_instr_header_set_follow_alias(void *info ATTRIBUTE_UNUSED,
+				       int val ATTRIBUTE_UNUSED)
+{
+}
+
+int snd_instr_fm_free(void *fm ATTRIBUTE_UNUSED)
+{
+	return 0;
+}
+
+int snd_instr_fm_convert_to_stream(void *fm ATTRIBUTE_UNUSED,
+				   const char *name ATTRIBUTE_UNUSED,
+				   void **__data ATTRIBUTE_UNUSED,
+				   size_t *__size ATTRIBUTE_UNUSED)
+{
+	return -ENXIO;
+}
+
+int snd_instr_fm_convert_from_stream(void *__data ATTRIBUTE_UNUSED,
+				     size_t size ATTRIBUTE_UNUSED,
+				     void **simple ATTRIBUTE_UNUSED)
+{
+	return -ENXIO;
+}
+
+
+int snd_instr_iwffff_open(void **handle ATTRIBUTE_UNUSED,
+			  const char *name_fff ATTRIBUTE_UNUSED,
+			  const char *name_dat ATTRIBUTE_UNUSED)
+{
+	return -ENXIO;
+}
+
+int snd_instr_iwffff_open_rom(void **handle ATTRIBUTE_UNUSED,
+			      int card ATTRIBUTE_UNUSED,
+			      int bank ATTRIBUTE_UNUSED,
+			      int file ATTRIBUTE_UNUSED)
+{
+	return -ENXIO;
+}
+
+int snd_instr_iwffff_open_rom_file(void **handle ATTRIBUTE_UNUSED,
+				   const char *name ATTRIBUTE_UNUSED,
+				   int bank ATTRIBUTE_UNUSED,
+				   int file ATTRIBUTE_UNUSED)
+{
+	return -ENXIO;
+}
+
+int snd_instr_iwffff_close(void *handle ATTRIBUTE_UNUSED)
+{
+	return 0;
+}
+
+int snd_instr_iwffff_free(void *__instr ATTRIBUTE_UNUSED)
+{
+	return 0;
+}
+
+int snd_instr_iwffff_load(void *iwf ATTRIBUTE_UNUSED,
+			  int bank ATTRIBUTE_UNUSED,
+			  int prg ATTRIBUTE_UNUSED,
+			  void **__iwffff ATTRIBUTE_UNUSED)
+{
+	return -ENXIO;
+}
+
+int snd_instr_iwffff_convert_to_stream(void *iwffff ATTRIBUTE_UNUSED,
+				       const char *name ATTRIBUTE_UNUSED,
+				       void **__data ATTRIBUTE_UNUSED,
+				       size_t *__size ATTRIBUTE_UNUSED)
+{
+	return -ENXIO;
+}
+
+int snd_instr_iwffff_convert_from_stream(void *data ATTRIBUTE_UNUSED,
+					 size_t size ATTRIBUTE_UNUSED,
+					 void **iwffff ATTRIBUTE_UNUSED)
+{
+	return -ENXIO;
+}
+
+
+int snd_instr_simple_free(void *simple ATTRIBUTE_UNUSED)
+{
+	return 0;
+}
+
+int snd_instr_simple_convert_to_stream(void *simple ATTRIBUTE_UNUSED,
+				       const char *name ATTRIBUTE_UNUSED,
+				       void **__data ATTRIBUTE_UNUSED,
+				       size_t *__size ATTRIBUTE_UNUSED)
+{
+	return -ENXIO;
+}
+
+int snd_instr_simple_convert_from_stream(void *__data ATTRIBUTE_UNUSED,
+					 size_t size ATTRIBUTE_UNUSED,
+					 void **simple ATTRIBUTE_UNUSED)
+{
+	return -ENXIO;
+}
+
+#endif /* !DOXYGEN */
diff --git a/src/seq/seq_symbols.c b/src/seq/seq_symbols.c
new file mode 100644
index 0000000..1d30133
--- /dev/null
+++ b/src/seq/seq_symbols.c
@@ -0,0 +1,34 @@
+/*
+ *  Sequencer Symbols
+ *  Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#ifndef PIC
+
+extern const char *_snd_module_seq_hw;
+
+static const char **snd_seq_open_objects[] = {
+	&_snd_module_seq_hw
+};
+	
+void *snd_seq_open_symbols(void)
+{
+	return snd_seq_open_objects;
+}
+
+#endif /* !PIC */
diff --git a/src/seq/seqmid.c b/src/seq/seqmid.c
new file mode 100644
index 0000000..894c3a2
--- /dev/null
+++ b/src/seq/seqmid.c
@@ -0,0 +1,427 @@
+/*
+ *  Sequencer Interface - middle-level routines
+ *
+ *  Copyright (c) 1999 by Takashi Iwai <tiwai@suse.de>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <sys/ioctl.h>
+#include "seq_local.h"
+
+/**
+ * \brief queue controls - start/stop/continue
+ * \param seq sequencer handle
+ * \param q queue id to control
+ * \param type event type
+ * \param value event value
+ * \param ev event instance
+ *
+ * This function sets up general queue control event and sends it.
+ * To send at scheduled time, set the schedule in \a ev.
+ * If \a ev is NULL, the event is composed locally and sent immediately
+ * to the specified queue.  In any cases, you need to call #snd_seq_drain_output()
+ * appropriately to feed the event.
+ *
+ * \sa snd_seq_alloc_queue()
+ */
+int snd_seq_control_queue(snd_seq_t *seq, int q, int type, int value, snd_seq_event_t *ev)
+{
+	snd_seq_event_t tmpev;
+	if (ev == NULL) {
+		snd_seq_ev_clear(&tmpev);
+		ev = &tmpev;
+		snd_seq_ev_set_direct(ev);
+	}
+	snd_seq_ev_set_queue_control(ev, type, q, value);
+	return snd_seq_event_output(seq, ev);
+}
+
+
+/**
+ * \brief create a port - simple version
+ * \param seq sequencer handle
+ * \param name the name of the port
+ * \param caps capability bits
+ * \param type type bits
+ * \return the created port number or negative error code
+ *
+ * Creates a port with the given capability and type bits.
+ *
+ * \sa snd_seq_create_port(), snd_seq_delete_simple_port()
+ */
+int snd_seq_create_simple_port(snd_seq_t *seq, const char *name,
+			       unsigned int caps, unsigned int type)
+{
+	snd_seq_port_info_t pinfo;
+	int result;
+
+	memset(&pinfo, 0, sizeof(pinfo));
+	if (name)
+		strncpy(pinfo.name, name, sizeof(pinfo.name) - 1);
+	pinfo.capability = caps;
+	pinfo.type = type;
+	pinfo.midi_channels = 16;
+	pinfo.midi_voices = 64; /* XXX */
+	pinfo.synth_voices = 0; /* XXX */
+
+	result = snd_seq_create_port(seq, &pinfo);
+	if (result < 0)
+		return result;
+	else
+		return pinfo.addr.port;
+}
+
+/**
+ * \brief delete the port
+ * \param seq sequencer handle
+ * \param port port id
+ * \return 0 on success or negative error code
+ *
+ * \sa snd_seq_delete_port(), snd_seq_create_simple_port()
+ */
+int snd_seq_delete_simple_port(snd_seq_t *seq, int port)
+{
+	return snd_seq_delete_port(seq, port);
+}
+
+/**
+ * \brief simple subscription (w/o exclusive & time conversion)
+ * \param seq sequencer handle
+ * \param myport the port id as receiver
+ * \param src_client sender client id
+ * \param src_port sender port id
+ * \return 0 on success or negative error code
+ *
+ * Connect from the given sender client:port to the given destination port in the
+ * current client.
+ *
+ * \sa snd_seq_subscribe_port(), snd_seq_disconnect_from()
+ */
+int snd_seq_connect_from(snd_seq_t *seq, int myport, int src_client, int src_port)
+{
+	snd_seq_port_subscribe_t subs;
+	
+	memset(&subs, 0, sizeof(subs));
+	subs.sender.client = src_client;
+	subs.sender.port = src_port;
+	/*subs.dest.client = seq->client;*/
+	subs.dest.client = snd_seq_client_id(seq);
+	subs.dest.port = myport;
+
+	return snd_seq_subscribe_port(seq, &subs);
+}
+
+/**
+ * \brief simple subscription (w/o exclusive & time conversion)
+ * \param seq sequencer handle
+ * \param myport the port id as sender
+ * \param dest_client destination client id
+ * \param dest_port destination port id
+ * \return 0 on success or negative error code
+ *
+ * Connect from the given receiver port in the current client
+ * to the given destination client:port.
+ *
+ * \sa snd_seq_subscribe_port(), snd_seq_disconnect_to()
+ */
+int snd_seq_connect_to(snd_seq_t *seq, int myport, int dest_client, int dest_port)
+{
+	snd_seq_port_subscribe_t subs;
+	
+	memset(&subs, 0, sizeof(subs));
+	/*subs.sender.client = seq->client;*/
+	subs.sender.client = snd_seq_client_id(seq);
+	subs.sender.port = myport;
+	subs.dest.client = dest_client;
+	subs.dest.port = dest_port;
+
+	return snd_seq_subscribe_port(seq, &subs);
+}
+
+/**
+ * \brief simple disconnection
+ * \param seq sequencer handle
+ * \param myport the port id as receiver
+ * \param src_client sender client id
+ * \param src_port sender port id
+ * \return 0 on success or negative error code
+ *
+ * Remove connection from the given sender client:port
+ * to the given destination port in the current client.
+ *
+ * \sa snd_seq_unsubscribe_port(), snd_seq_connect_from()
+ */
+int snd_seq_disconnect_from(snd_seq_t *seq, int myport, int src_client, int src_port)
+{
+	snd_seq_port_subscribe_t subs;
+	
+	memset(&subs, 0, sizeof(subs));
+	subs.sender.client = src_client;
+	subs.sender.port = src_port;
+	/*subs.dest.client = seq->client;*/
+	subs.dest.client = snd_seq_client_id(seq);
+	subs.dest.port = myport;
+
+	return snd_seq_unsubscribe_port(seq, &subs);
+}
+
+/**
+ * \brief simple disconnection
+ * \param seq sequencer handle
+ * \param myport the port id as sender
+ * \param dest_client destination client id
+ * \param dest_port destination port id
+ * \return 0 on success or negative error code
+ *
+ * Remove connection from the given sender client:port
+ * to the given destination port in the current client.
+ *
+ * \sa snd_seq_unsubscribe_port(), snd_seq_connect_to()
+ */
+int snd_seq_disconnect_to(snd_seq_t *seq, int myport, int dest_client, int dest_port)
+{
+	snd_seq_port_subscribe_t subs;
+	
+	memset(&subs, 0, sizeof(subs));
+	/*subs.sender.client = seq->client;*/
+	subs.sender.client = snd_seq_client_id(seq);
+	subs.sender.port = myport;
+	subs.dest.client = dest_client;
+	subs.dest.port = dest_port;
+
+	return snd_seq_unsubscribe_port(seq, &subs);
+}
+
+/*
+ * set client information
+ */
+
+/**
+ * \brief set client name
+ * \param seq sequencer handle
+ * \param name name string
+ * \return 0 on success or negative error code
+ *
+ * \sa snd_seq_set_client_info()
+ */
+int snd_seq_set_client_name(snd_seq_t *seq, const char *name)
+{
+	snd_seq_client_info_t info;
+	int err;
+
+	if ((err = snd_seq_get_client_info(seq, &info)) < 0)
+		return err;
+	strncpy(info.name, name, sizeof(info.name) - 1);
+	return snd_seq_set_client_info(seq, &info);
+}
+
+/**
+ * \brief add client event filter
+ * \param seq sequencer handle
+ * \param event_type event type to be added
+ * \return 0 on success or negative error code
+ *
+ * \sa snd_seq_set_client_info()
+ */
+int snd_seq_set_client_event_filter(snd_seq_t *seq, int event_type)
+{
+	snd_seq_client_info_t info;
+	int err;
+
+	if ((err = snd_seq_get_client_info(seq, &info)) < 0)
+		return err;
+	snd_seq_client_info_event_filter_add(&info, event_type);
+	return snd_seq_set_client_info(seq, &info);
+}
+
+/**
+ * \brief change the output pool size of the given client
+ * \param seq sequencer handle
+ * \param size output pool size
+ * \return 0 on success or negative error code
+ *
+ * \sa snd_seq_set_client_pool()
+ */
+int snd_seq_set_client_pool_output(snd_seq_t *seq, size_t size)
+{
+	snd_seq_client_pool_t info;
+	int err;
+
+	if ((err = snd_seq_get_client_pool(seq, &info)) < 0)
+		return err;
+	info.output_pool = size;
+	return snd_seq_set_client_pool(seq, &info);
+}
+
+/**
+ * \brief change the output room size of the given client
+ * \param seq sequencer handle
+ * \param size output room size
+ * \return 0 on success or negative error code
+ *
+ * \sa snd_seq_set_client_pool()
+ */
+int snd_seq_set_client_pool_output_room(snd_seq_t *seq, size_t size)
+{
+	snd_seq_client_pool_t info;
+	int err;
+
+	if ((err = snd_seq_get_client_pool(seq, &info)) < 0)
+		return err;
+	info.output_room = size;
+	return snd_seq_set_client_pool(seq, &info);
+}
+
+/**
+ * \brief change the input pool size of the given client
+ * \param seq sequencer handle
+ * \param size input pool size
+ * \return 0 on success or negative error code
+ *
+ * \sa snd_seq_set_client_pool()
+ */
+int snd_seq_set_client_pool_input(snd_seq_t *seq, size_t size)
+{
+	snd_seq_client_pool_t info;
+	int err;
+
+	if ((err = snd_seq_get_client_pool(seq, &info)) < 0)
+		return err;
+	info.input_pool = size;
+	return snd_seq_set_client_pool(seq, &info);
+}
+
+/**
+ * \brief reset client output pool
+ * \param seq sequencer handle
+ * \return 0 on success or negative error code
+ *
+ * So far, this works identically like #snd_seq_drop_output().
+ */
+int snd_seq_reset_pool_output(snd_seq_t *seq)
+{
+	return snd_seq_drop_output(seq);
+}
+
+/**
+ * \brief reset client input pool
+ * \param seq sequencer handle
+ * \return 0 on success or negative error code
+ *
+ * So far, this works identically like #snd_seq_drop_input().
+ */
+int snd_seq_reset_pool_input(snd_seq_t *seq)
+{
+	return snd_seq_drop_input(seq);
+}
+
+/**
+ * \brief wait until all events are processed
+ * \param seq sequencer handle
+ * \return 0 on success or negative error code
+ *
+ * This function waits until all events of this client are processed.
+ *
+ * \sa snd_seq_drain_output()
+ */
+int snd_seq_sync_output_queue(snd_seq_t *seq)
+{
+	int err;
+	snd_seq_client_pool_t info;
+	int saved_room;
+	struct pollfd pfd;
+
+	assert(seq);
+	/* reprogram the room size to full */
+	if ((err = snd_seq_get_client_pool(seq, &info)) < 0)
+		return err;
+	saved_room = info.output_room;
+	info.output_room = info.output_pool; /* wait until all gone */
+	if ((err = snd_seq_set_client_pool(seq, &info)) < 0)
+		return err;
+	/* wait until all events are purged */
+	pfd.fd = seq->poll_fd;
+	pfd.events = POLLOUT;
+	err = poll(&pfd, 1, -1);
+	/* restore the room size */ 
+	info.output_room = saved_room;
+	snd_seq_set_client_pool(seq, &info);
+	return err;
+}
+
+/**
+ * \brief parse the given string and get the sequencer address
+ * \param seq sequencer handle
+ * \param addr the address pointer to be returned
+ * \param arg the string to be parsed
+ * \return 0 on success or negative error code
+ *
+ * This function parses the sequencer client and port numbers from the given string.
+ * The client and port tokes are separated by either colon or period, e.g. 128:1.
+ * When \a seq is not NULL, the function accepts also a client name not only
+ * digit numbers.
+ */
+int snd_seq_parse_address(snd_seq_t *seq, snd_seq_addr_t *addr, const char *arg)
+{
+	char *p;
+	int client, port;
+	int len;
+
+	assert(addr && arg);
+
+	if ((p = strpbrk(arg, ":.")) != NULL) {
+		if ((port = atoi(p + 1)) < 0)
+			return -EINVAL;
+		len = (int)(p - arg); /* length of client name */
+	} else {
+		port = 0;
+		len = strlen(arg);
+	}
+	addr->port = port;
+	if (isdigit(*arg)) {
+		client = atoi(arg);
+		if (client < 0)
+			return -EINVAL;
+		addr->client = client;
+	} else {
+		/* convert from the name */
+		snd_seq_client_info_t cinfo;
+
+		if (! seq)
+			return -EINVAL;
+		if (len <= 0)
+			return -EINVAL;
+		cinfo.client = -1;
+		while (snd_seq_query_next_client(seq, &cinfo) >= 0) {
+			if ((strlen(cinfo.name) == len) &&
+				! strncmp(arg, cinfo.name, len)) {
+				addr->client = cinfo.client;
+				return 0;
+			}
+		}
+		return -ENOENT; /* not found */
+	}
+	return 0;
+}
+
diff --git a/src/shmarea.c b/src/shmarea.c
new file mode 100644
index 0000000..071f9f3
--- /dev/null
+++ b/src/shmarea.c
@@ -0,0 +1,108 @@
+/*
+ *  IPC SHM area manager
+ *  Copyright (c) 2003 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+#include <stdio.h>
+#include <malloc.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <sys/mman.h>
+#include <sys/shm.h>
+#include "list.h"
+
+#ifndef DOC_HIDDEN
+struct snd_shm_area {
+	struct list_head list;
+	int shmid;
+	void *ptr;
+	int share;
+};
+#endif
+
+static LIST_HEAD(shm_areas);
+
+/**
+ * \brief Create a shm area record
+ * \param shmid IPC SHM ID
+ * \param ptr the shared area pointer
+ * \return The allocated shm area record, NULL if fail
+ *
+ * Allocates a shared area record with the given SHM ID and pointer.
+ * The record has a reference counter, which is initialized to 1 by this function.
+ */
+struct snd_shm_area *snd_shm_area_create(int shmid, void *ptr)
+{
+	struct snd_shm_area *area = malloc(sizeof(*area));
+	if (area) {
+		area->shmid = shmid;
+		area->ptr = ptr;
+		area->share = 1;
+		list_add_tail(&area->list, &shm_areas);
+	}
+	return area;
+}
+
+/**
+ * \brief Increase the reference counter of shm area record
+ * \param area shm area record
+ * \return the shm area record (identical with the argument)
+ *
+ * Increases the reference counter of the given shared area record.
+ */
+struct snd_shm_area *snd_shm_area_share(struct snd_shm_area *area)
+{
+	if (area == NULL)
+		return NULL;
+	area->share++;
+	return area;
+}
+
+/**
+ * \brief Release the shared area record
+ * \param area the shared are record
+ * \return 0 if successful, or a negative error code
+ *
+ * Decreases the reference counter of the given shared area record, and
+ * releases the resources automaticall if it reaches to 0.
+ */
+int snd_shm_area_destroy(struct snd_shm_area *area)
+{
+	if (area == NULL)
+		return -ENOENT;
+	if (--area->share)
+		return 0;
+	list_del(&area->list);
+	shmdt(area->ptr);
+	free(area);
+	return 0;
+}
+
+void snd_shm_area_destructor(void) __attribute__ ((destructor));
+
+void snd_shm_area_destructor(void)
+{
+	struct list_head *pos;
+	struct snd_shm_area *area;
+
+	list_for_each(pos, &shm_areas) {
+		area = list_entry(pos, struct snd_shm_area, list);
+		shmdt(area->ptr);
+	}
+}
diff --git a/src/socket.c b/src/socket.c
new file mode 100644
index 0000000..97c919f
--- /dev/null
+++ b/src/socket.c
@@ -0,0 +1,158 @@
+/**
+ * \file socket.c
+ * \brief Socket helper routines
+ * \author Abramo Bagnara <abramo@alsa-project.org>
+ * \date 2003
+ */
+/*
+ *  Socket helper routines
+ *  Copyright (c) 2003 by Abramo Bagnara <abramo@alsa-project.org>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <sys/ioctl.h>
+#include <net/if.h>
+#include <netdb.h>
+#include "local.h"
+
+#ifndef DOC_HIDDEN
+int snd_send_fd(int sock, void *data, size_t len, int fd)
+{
+	int ret;
+	size_t cmsg_len = CMSG_LEN(sizeof(int));
+	struct cmsghdr *cmsg = alloca(cmsg_len);
+	int *fds = (int *) CMSG_DATA(cmsg);
+	struct msghdr msghdr;
+	struct iovec vec;
+
+	vec.iov_base = (void *)&data;
+	vec.iov_len = len;
+
+	cmsg->cmsg_len = cmsg_len;
+	cmsg->cmsg_level = SOL_SOCKET;
+	cmsg->cmsg_type = SCM_RIGHTS;
+	*fds = fd;
+
+	msghdr.msg_name = NULL;
+	msghdr.msg_namelen = 0;
+	msghdr.msg_iov = &vec;
+ 	msghdr.msg_iovlen = 1;
+	msghdr.msg_control = cmsg;
+	msghdr.msg_controllen = cmsg_len;
+	msghdr.msg_flags = 0;
+
+	ret = sendmsg(sock, &msghdr, 0 );
+	if (ret < 0) {
+		SYSERR("sendmsg failed");
+		return -errno;
+	}
+	return ret;
+}
+
+int snd_receive_fd(int sock, void *data, size_t len, int *fd)
+{
+	int ret;
+	size_t cmsg_len = CMSG_LEN(sizeof(int));
+	struct cmsghdr *cmsg = alloca(cmsg_len);
+	int *fds = (int *) CMSG_DATA(cmsg);
+	struct msghdr msghdr;
+	struct iovec vec;
+
+	vec.iov_base = (void *)&data;
+	vec.iov_len = len;
+
+	cmsg->cmsg_len = cmsg_len;
+	cmsg->cmsg_level = SOL_SOCKET;
+	cmsg->cmsg_type = SCM_RIGHTS;
+	*fds = -1;
+
+	msghdr.msg_name = NULL;
+	msghdr.msg_namelen = 0;
+	msghdr.msg_iov = &vec;
+	msghdr.msg_iovlen = 1;
+	msghdr.msg_control = cmsg;
+	msghdr.msg_controllen = cmsg_len;
+	msghdr.msg_flags = 0;
+
+	ret = recvmsg(sock, &msghdr, 0);
+	if (ret < 0) {
+		SYSERR("recvmsg failed");
+		return -errno;
+	}
+	*fd = *fds;
+	return ret;
+}
+
+int snd_is_local(struct hostent *hent)
+{
+	int s;
+	int err;
+	struct ifconf conf;
+	size_t numreqs = 10;
+	size_t i;
+	struct in_addr *haddr = (struct in_addr*) hent->h_addr_list[0];
+	
+	s = socket(PF_INET, SOCK_STREAM, 0);
+	if (s < 0) {
+		SYSERR("socket failed");
+		return -errno;
+	}
+	
+	conf.ifc_len = numreqs * sizeof(struct ifreq);
+	conf.ifc_buf = malloc((unsigned int) conf.ifc_len);
+	if (! conf.ifc_buf)
+		return -ENOMEM;
+	while (1) {
+		err = ioctl(s, SIOCGIFCONF, &conf);
+		if (err < 0) {
+			SYSERR("SIOCGIFCONF failed");
+			return -errno;
+		}
+		if ((size_t)conf.ifc_len < numreqs * sizeof(struct ifreq))
+			break;
+		numreqs *= 2;
+		conf.ifc_len = numreqs * sizeof(struct ifreq);
+		conf.ifc_buf = realloc(conf.ifc_buf, (unsigned int) conf.ifc_len);
+		if (! conf.ifc_buf)
+			return -ENOMEM;
+	}
+	numreqs = conf.ifc_len / sizeof(struct ifreq);
+	for (i = 0; i < numreqs; ++i) {
+		struct ifreq *req = &conf.ifc_req[i];
+		struct sockaddr_in *s_in = (struct sockaddr_in *)&req->ifr_addr;
+		s_in->sin_family = AF_INET;
+		err = ioctl(s, SIOCGIFADDR, req);
+		if (err < 0)
+			continue;
+		if (haddr->s_addr == s_in->sin_addr.s_addr)
+			break;
+	}
+	close(s);
+	free(conf.ifc_buf);
+	return i < numreqs;
+}
+#endif
diff --git a/src/timer/Makefile.am b/src/timer/Makefile.am
new file mode 100644
index 0000000..e7cf77b
--- /dev/null
+++ b/src/timer/Makefile.am
@@ -0,0 +1,9 @@
+EXTRA_LTLIBRARIES=libtimer.la
+
+libtimer_la_SOURCES = timer.c timer_hw.c timer_query.c timer_query_hw.c \
+	              timer_symbols.c
+noinst_HEADERS = timer_local.h
+all: libtimer.la
+
+
+INCLUDES=-I$(top_srcdir)/include
diff --git a/src/timer/timer.c b/src/timer/timer.c
new file mode 100644
index 0000000..b71a9f8
--- /dev/null
+++ b/src/timer/timer.c
@@ -0,0 +1,959 @@
+/**
+ * \file timer/timer.c
+ * \brief Timer Interface
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \date 1998-2001
+ *
+ * Timer Interface is designed to access timers.
+ * See \ref timer page for more details.
+ */
+/*
+ *  Timer Interface - main file
+ *  Copyright (c) 1998-2001 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+/*! \page timer Timer interface
+
+<P> Timer interface is designed to use internal timers in sound hardware, but
+it can be driven with any timer.
+
+\section timer_general_overview General overview
+
+The timer implementation uses ring buffer to store information about timing
+events. In this buffer is recorded count of ticks and current tick resolution
+in nanoseconds.
+
+\section timer_open Opening
+
+Timer devices can be opened in two ways. When #SND_TIMER_OPEN_NONBLOCK flag
+is used, then the open functions return immediately with -EBUSY error code when
+resources are occupied with another application. When #SND_TIMER_OPEN_NONBLOCK
+is not used (by default) the open functions block the application requesting
+device until resources are not free.
+
+\section timer_events Events
+
+Events are read via snd_timer_read() function.
+
+\section timer_examples Examples
+
+The full featured examples with cross-links:
+
+\par Simple timer test program
+\ref example_test_timer "example code"
+\par
+This example shows opening a timer device and reading of timer events.
+
+*/
+
+/**
+ * \example ../test/timer.c
+ * \anchor example_test_timer
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/ioctl.h>
+#include "timer_local.h"
+
+static int snd_timer_open_conf(snd_timer_t **timer,
+			       const char *name, snd_config_t *timer_root,
+			       snd_config_t *timer_conf, int mode)
+{
+	const char *str;
+	char buf[256];
+	int err;
+	snd_config_t *conf, *type_conf = NULL;
+	snd_config_iterator_t i, next;
+	const char *id;
+	const char *lib = NULL, *open_name = NULL;
+	int (*open_func)(snd_timer_t **, const char *, snd_config_t *, snd_config_t *, int) = NULL;
+#ifndef PIC
+	extern void *snd_timer_open_symbols(void);
+#endif
+	void *h = NULL;
+	if (snd_config_get_type(timer_conf) != SND_CONFIG_TYPE_COMPOUND) {
+		if (name)
+			SNDERR("Invalid type for TIMER %s definition", name);
+		else
+			SNDERR("Invalid type for TIMER definition");
+		return -EINVAL;
+	}
+	err = snd_config_search(timer_conf, "type", &conf);
+	if (err < 0) {
+		SNDERR("type is not defined");
+		return err;
+	}
+	err = snd_config_get_id(conf, &id);
+	if (err < 0) {
+		SNDERR("unable to get id");
+		return err;
+	}
+	err = snd_config_get_string(conf, &str);
+	if (err < 0) {
+		SNDERR("Invalid type for %s", id);
+		return err;
+	}
+	err = snd_config_search_definition(timer_root, "timer_type", str, &type_conf);
+	if (err >= 0) {
+		if (snd_config_get_type(type_conf) != SND_CONFIG_TYPE_COMPOUND) {
+			SNDERR("Invalid type for TIMER type %s definition", str);
+			goto _err;
+		}
+		snd_config_for_each(i, next, type_conf) {
+			snd_config_t *n = snd_config_iterator_entry(i);
+			const char *id;
+			if (snd_config_get_id(n, &id) < 0)
+				continue;
+			if (strcmp(id, "comment") == 0)
+				continue;
+			if (strcmp(id, "lib") == 0) {
+				err = snd_config_get_string(n, &lib);
+				if (err < 0) {
+					SNDERR("Invalid type for %s", id);
+					goto _err;
+				}
+				continue;
+			}
+			if (strcmp(id, "open") == 0) {
+				err = snd_config_get_string(n, &open_name);
+				if (err < 0) {
+					SNDERR("Invalid type for %s", id);
+					goto _err;
+				}
+				continue;
+			}
+			SNDERR("Unknown field %s", id);
+			err = -EINVAL;
+			goto _err;
+		}
+	}
+	if (!open_name) {
+		open_name = buf;
+		snprintf(buf, sizeof(buf), "_snd_timer_%s_open", str);
+	}
+#ifndef PIC
+	snd_timer_open_symbols();
+#endif
+	h = snd_dlopen(lib, RTLD_NOW);
+	if (h)
+		open_func = snd_dlsym(h, open_name, SND_DLSYM_VERSION(SND_TIMER_DLSYM_VERSION));
+	err = 0;
+	if (!h) {
+		SNDERR("Cannot open shared library %s", lib);
+		err = -ENOENT;
+	} else if (!open_func) {
+		SNDERR("symbol %s is not defined inside %s", open_name, lib);
+		snd_dlclose(h);
+		err = -ENXIO;
+	}
+       _err:
+	if (type_conf)
+		snd_config_delete(type_conf);
+	if (! err) {
+		err = open_func(timer, name, timer_root, timer_conf, mode);
+		if (err < 0)
+			snd_dlclose(h);
+		else
+			(*timer)->dl_handle = h;
+	}
+	return err;
+}
+
+static int snd_timer_open_noupdate(snd_timer_t **timer, snd_config_t *root, const char *name, int mode)
+{
+	int err;
+	snd_config_t *timer_conf;
+	err = snd_config_search_definition(root, "timer", name, &timer_conf);
+	if (err < 0) {
+		SNDERR("Unknown timer %s", name);
+		return err;
+	}
+	err = snd_timer_open_conf(timer, name, root, timer_conf, mode);
+	snd_config_delete(timer_conf);
+	return err;
+}
+
+/**
+ * \brief Opens a new connection to the timer interface.
+ * \param timer Returned handle (NULL if not wanted)
+ * \param name ASCII identifier of the timer handle
+ * \param mode Open mode
+ * \return 0 on success otherwise a negative error code
+ *
+ * Opens a new connection to the timer interface specified with
+ * an ASCII identifier and mode.
+ */
+int snd_timer_open(snd_timer_t **timer, const char *name, int mode)
+{
+	int err;
+	assert(timer && name);
+	err = snd_config_update();
+	if (err < 0)
+		return err;
+	return snd_timer_open_noupdate(timer, snd_config, name, mode);
+}
+
+/**
+ * \brief Opens a new connection to the timer interface using local configuration
+ * \param timer Returned handle (NULL if not wanted)
+ * \param name ASCII identifier of the timer handle
+ * \param mode Open mode
+ * \param lconf Local configuration
+ * \return 0 on success otherwise a negative error code
+ *
+ * Opens a new connection to the timer interface specified with
+ * an ASCII identifier and mode.
+ */
+int snd_timer_open_lconf(snd_timer_t **timer, const char *name,
+			 int mode, snd_config_t *lconf)
+{
+	assert(timer && name && lconf);
+	return snd_timer_open_noupdate(timer, lconf, name, mode);
+}
+
+/**
+ * \brief close timer handle
+ * \param timer timer handle
+ * \return 0 on success otherwise a negative error code
+ *
+ * Closes the specified timer handle and frees all associated
+ * resources.
+ */
+int snd_timer_close(snd_timer_t *timer)
+{
+	int err;
+  	assert(timer);
+	while (!list_empty(&timer->async_handlers)) {
+		snd_async_handler_t *h = list_entry(timer->async_handlers.next, snd_async_handler_t, hlist);
+		snd_async_del_handler(h);
+	}
+	err = timer->ops->close(timer);
+	if (timer->dl_handle)
+		snd_dlclose(timer->dl_handle);
+	free(timer->name);
+	free(timer);
+	return err;
+}
+
+/**
+ * \brief get identifier of timer handle
+ * \param timer a timer handle
+ * \return ascii identifier of timer handle
+ *
+ * Returns the ASCII identifier of given timer handle. It's the same
+ * identifier specified in snd_timer_open().
+ */
+const char *snd_timer_name(snd_timer_t *timer)
+{
+	assert(timer);
+	return timer->name;
+}
+
+/**
+ * \brief get type of timer handle
+ * \param timer a timer handle
+ * \return type of timer handle
+ *
+ * Returns the type #snd_timer_type_t of given timer handle.
+ */
+snd_timer_type_t snd_timer_type(snd_timer_t *timer)
+{
+	assert(timer);
+	return timer->type;
+}
+
+/**
+ * \brief Add an async handler for a timer
+ * \param handler Returned handler handle
+ * \param timer timer handle
+ * \param callback Callback function
+ * \param private_data Callback private data
+ * \return 0 otherwise a negative error code on failure
+ *
+ * The asynchronous callback is called when new timer event occurs.
+ */
+int snd_async_add_timer_handler(snd_async_handler_t **handler, snd_timer_t *timer,
+				snd_async_callback_t callback, void *private_data)
+{
+	int err;
+	int was_empty;
+	snd_async_handler_t *h;
+	err = snd_async_add_handler(&h, timer->poll_fd,
+				    callback, private_data);
+	if (err < 0)
+		return err;
+	h->type = SND_ASYNC_HANDLER_TIMER;
+	h->u.timer = timer;
+	was_empty = list_empty(&timer->async_handlers);
+	list_add_tail(&h->hlist, &timer->async_handlers);
+	if (was_empty) {
+		err = snd_timer_async(timer, snd_async_handler_get_signo(h), getpid());
+		if (err < 0) {
+			snd_async_del_handler(h);
+			return err;
+		}
+	}
+	*handler = h;
+	return 0;
+}
+
+/**
+ * \brief Return timer handle related to an async handler
+ * \param handler Async handler handle
+ * \return timer handle
+ */
+snd_timer_t *snd_async_handler_get_timer(snd_async_handler_t *handler)
+{
+	if (handler->type != SND_ASYNC_HANDLER_TIMER) {
+		SNDMSG("invalid handler type %d", handler->type);
+		return NULL;
+	}
+	return handler->u.timer;
+}                                                            
+
+/**
+ * \brief get count of poll descriptors for timer handle
+ * \param timer timer handle
+ * \return count of poll descriptors
+ */
+int snd_timer_poll_descriptors_count(snd_timer_t *timer)
+{
+	assert(timer);
+	return 1;
+}
+
+/**
+ * \brief get poll descriptors
+ * \param timer timer handle
+ * \param pfds array of poll descriptors
+ * \param space space in the poll descriptor array
+ * \return count of filled descriptors
+ */
+int snd_timer_poll_descriptors(snd_timer_t *timer, struct pollfd *pfds, unsigned int space)
+{
+	assert(timer);
+	if (space >= 1) {
+		pfds->fd = timer->poll_fd;
+		switch (timer->mode & O_ACCMODE) {
+		case O_WRONLY:
+			pfds->events = POLLOUT|POLLERR|POLLNVAL;
+			break;
+		case O_RDONLY:
+			pfds->events = POLLIN|POLLERR|POLLNVAL;
+			break;
+		case O_RDWR:
+			pfds->events = POLLOUT|POLLIN|POLLERR|POLLNVAL;
+			break;
+		default:
+			return -EIO;
+		}
+		return 1;
+	}
+	return 0;
+}
+
+/**
+ * \brief get returned events from poll descriptors
+ * \param timer timer handle
+ * \param pfds array of poll descriptors
+ * \param nfds count of poll descriptors
+ * \param revents returned events
+ * \return zero if success, otherwise a negative error code
+ */
+int snd_timer_poll_descriptors_revents(snd_timer_t *timer, struct pollfd *pfds, unsigned int nfds, unsigned short *revents)
+{
+        assert(timer && pfds && revents);
+        if (nfds == 1) {
+                *revents = pfds->revents;
+                return 0;
+        }
+        return -EINVAL;
+}
+
+/**
+ * \brief set nonblock mode
+ * \param timer timer handle
+ * \param nonblock 0 = block, 1 = nonblock mode
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_timer_nonblock(snd_timer_t *timer, int nonblock)
+{
+	int err;
+	assert(timer);
+	if ((err = timer->ops->nonblock(timer, nonblock)) < 0)
+		return err;
+	if (nonblock)
+		timer->mode |= SND_TIMER_OPEN_NONBLOCK;
+	else
+		timer->mode &= ~SND_TIMER_OPEN_NONBLOCK;
+	return 0;
+}
+
+#ifndef DOC_HIDDEN
+/**
+ * \brief set async mode
+ * \param timer timer handle
+ * \param sig Signal to raise: < 0 disable, 0 default (SIGIO)
+ * \param pid Process ID to signal: 0 current
+ * \return 0 on success otherwise a negative error code
+ *
+ * A signal is raised every period.
+ */
+int snd_timer_async(snd_timer_t *timer, int sig, pid_t pid)
+{
+	assert(timer);
+        if (sig == 0)
+                sig = SIGIO;
+	if (pid == 0)
+		pid = getpid();
+	return timer->ops->async(timer, sig, pid);
+}
+#endif
+
+/**
+ * \brief get size of the snd_timer_info_t structure in bytes
+ * \return size of the snd_timer_info_t structure in bytes
+ */
+size_t snd_timer_info_sizeof()
+{
+	return sizeof(snd_timer_info_t);
+}
+
+/**
+ * \brief allocate a new snd_timer_info_t structure
+ * \param info returned pointer
+ * \return 0 on success otherwise a negative error code if fails
+ *
+ * Allocates a new snd_timer_info_t structure using the standard
+ * malloc C library function.
+ */
+int snd_timer_info_malloc(snd_timer_info_t **info)
+{
+	assert(info);
+	*info = calloc(1, sizeof(snd_timer_info_t));
+	if (!*info)
+		return -ENOMEM;
+	return 0;
+}
+
+/**
+ * \brief frees the snd_timer_info_t structure
+ * \param info pointer to the snd_timer_info_t structure to free
+ *
+ * Frees the given snd_timer_info_t structure using the standard
+ * free C library function.
+ */
+void snd_timer_info_free(snd_timer_info_t *info)
+{
+	assert(info);
+	free(info);
+}
+
+/**
+ * \brief copy one snd_timer_info_t structure to another
+ * \param dst destination snd_timer_info_t structure
+ * \param src source snd_timer_info_t structure
+ */
+void snd_timer_info_copy(snd_timer_info_t *dst, const snd_timer_info_t *src)
+{
+	assert(dst && src);
+	*dst = *src;
+}
+
+/**
+ * \brief determine, if timer is slave
+ * \param info pointer to #snd_timer_info_t structure
+ * \return nonzero if timer is slave
+ */
+int snd_timer_info_is_slave(snd_timer_info_t * info)
+{
+	assert(info);
+	return info->flags & SNDRV_TIMER_FLG_SLAVE ? 1 : 0;
+}
+
+/**
+ * \brief get timer card
+ * \param info pointer to #snd_timer_info_t structure
+ * \return timer card number
+ */
+int snd_timer_info_get_card(snd_timer_info_t * info)
+{
+	assert(info);
+	return info->card;
+}
+
+/**
+ * \brief get timer id
+ * \param info pointer to #snd_timer_info_t structure
+ * \return timer id
+ */
+const char *snd_timer_info_get_id(snd_timer_info_t * info)
+{
+	assert(info);
+	return (const char *)info->id;
+}
+
+/**
+ * \brief get timer name
+ * \param info pointer to #snd_timer_info_t structure
+ * \return timer name
+ */
+const char *snd_timer_info_get_name(snd_timer_info_t * info)
+{
+	assert(info);
+	return (const char *)info->name;
+}
+
+
+/**
+ * \brief get timer resolution in us
+ * \param info pointer to #snd_timer_info_t structure
+ * \return timer resolution
+ */
+long snd_timer_info_get_resolution(snd_timer_info_t * info)
+{
+	assert(info);
+	return info->resolution;
+}
+
+/**
+ * \brief get information about timer handle
+ * \param timer timer handle
+ * \param info pointer to a snd_timer_info_t structure to be filled
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_timer_info(snd_timer_t *timer, snd_timer_info_t * info)
+{
+	assert(timer);
+	assert(info);
+	return timer->ops->info(timer, info);
+}
+
+/**
+ * \brief get size of the snd_timer_params_t structure in bytes
+ * \return size of the snd_timer_params_t structure in bytes
+ */
+size_t snd_timer_params_sizeof()
+{
+	return sizeof(snd_timer_params_t);
+}
+
+/**
+ * \brief allocate a new snd_timer_params_t structure
+ * \param params returned pointer
+ * \return 0 on success otherwise a negative error code if fails
+ *
+ * Allocates a new snd_timer_params_t structure using the standard
+ * malloc C library function.
+ */
+int snd_timer_params_malloc(snd_timer_params_t **params)
+{
+	assert(params);
+	*params = calloc(1, sizeof(snd_timer_params_t));
+	if (!*params)
+		return -ENOMEM;
+	return 0;
+}
+
+/**
+ * \brief frees the snd_timer_params_t structure
+ * \param params pointer to the snd_timer_params_t structure to free
+ *
+ * Frees the given snd_timer_params_t structure using the standard
+ * free C library function.
+ */
+void snd_timer_params_free(snd_timer_params_t *params)
+{
+	assert(params);
+	free(params);
+}
+
+/**
+ * \brief copy one snd_timer_params_t structure to another
+ * \param dst destination snd_timer_params_t structure
+ * \param src source snd_timer_params_t structure
+ */
+void snd_timer_params_copy(snd_timer_params_t *dst, const snd_timer_params_t *src)
+{
+	assert(dst && src);
+	*dst = *src;
+}
+
+/**
+ * \brief set timer auto start
+ * \param params pointer to #snd_timer_params_t structure
+ * \param auto_start The boolean value to set
+ */
+int snd_timer_params_set_auto_start(snd_timer_params_t * params, int auto_start)
+{
+	assert(params);
+	if (auto_start)
+		params->flags |= SNDRV_TIMER_PSFLG_AUTO;
+	else
+		params->flags &= ~SNDRV_TIMER_PSFLG_AUTO;
+	return 0;
+}
+
+/**
+ * \brief determine if timer has auto start flag
+ * \param params pointer to #snd_timer_params_t structure
+ * \return nonzero if timer has auto start flag
+ */
+int snd_timer_params_get_auto_start(snd_timer_params_t * params)
+{
+	assert(params);
+	return params->flags & SNDRV_TIMER_PSFLG_AUTO ? 1 : 0;
+}
+
+/**
+ * \brief set timer exclusive use
+ * \param params pointer to #snd_timer_params_t structure
+ * \param exclusive The boolean value to set
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_timer_params_set_exclusive)(snd_timer_params_t * params, int exclusive)
+#else
+int snd_timer_params_set_exclusive(snd_timer_params_t * params, int exclusive)
+#endif
+{
+	assert(params);
+	if (exclusive)
+		params->flags |= SNDRV_TIMER_PSFLG_EXCLUSIVE;
+	else
+		params->flags &= ~SNDRV_TIMER_PSFLG_EXCLUSIVE;
+	return 0;
+}
+use_default_symbol_version(__snd_timer_params_set_exclusive, snd_timer_params_set_exclusive, ALSA_0.9.0);
+
+/**
+ * \brief determine if timer has exclusive flag
+ * \param params pointer to #snd_timer_params_t structure
+ * \return nonzero if timer has exclusive flag
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_timer_params_get_exclusive)(snd_timer_params_t * params)
+#else
+int snd_timer_params_get_exclusive(snd_timer_params_t * params)
+#endif
+{
+	assert(params);
+	return params->flags & SNDRV_TIMER_PSFLG_EXCLUSIVE ? 1 : 0;
+}
+use_default_symbol_version(__snd_timer_params_get_exclusive, snd_timer_params_get_exclusive, ALSA_0.9.0);
+
+/**
+ * \brief set timer early event
+ * \param params pointer to #snd_timer_params_t structure
+ * \param early_event The boolean value to set
+ */
+int snd_timer_params_set_early_event(snd_timer_params_t * params, int early_event)
+{
+	assert(params);
+	if (early_event)
+		params->flags |= SNDRV_TIMER_PSFLG_EARLY_EVENT;
+	else
+		params->flags &= ~SNDRV_TIMER_PSFLG_EARLY_EVENT;
+	return 0;
+}
+
+/**
+ * \brief determine if timer has early event flag
+ * \param params pointer to #snd_timer_params_t structure
+ * \return nonzero if timer has early event flag set
+ */
+int snd_timer_params_get_early_event(snd_timer_params_t * params)
+{
+	assert(params);
+	return params->flags & SNDRV_TIMER_PSFLG_EARLY_EVENT ? 1 : 0;
+}
+
+/**
+ * \brief set timer ticks
+ * \param params pointer to #snd_timer_params_t structure
+ * \param ticks Ticks to set
+ */
+void snd_timer_params_set_ticks(snd_timer_params_t * params, long ticks)
+{
+	assert(params);
+	params->ticks = ticks;
+}
+
+/**
+ * \brief get timer ticks
+ * \param params pointer to #snd_timer_params_t structure
+ * \return timer ticks
+ */
+long snd_timer_params_get_ticks(snd_timer_params_t * params)
+{
+	assert(params);
+	return params->ticks;
+}
+
+/**
+ * \brief set timer queue size (32-1024)
+ * \param params pointer to #snd_timer_params_t structure
+ * \param queue_size The queue size to set
+ */
+void snd_timer_params_set_queue_size(snd_timer_params_t * params, long queue_size)
+{
+	assert(params);
+	params->queue_size = queue_size;
+}
+
+/**
+ * \brief get queue size
+ * \param params pointer to #snd_timer_params_t structure
+ * \return queue size
+ */
+long snd_timer_params_get_queue_size(snd_timer_params_t * params)
+{
+	assert(params);
+	return params->queue_size;
+}
+
+/**
+ * \brief set timer event filter
+ * \param params pointer to #snd_timer_params_t structure
+ * \param filter The event filter bits to set
+ */
+#ifndef DOXYGEN
+void INTERNAL(snd_timer_params_set_filter)(snd_timer_params_t * params, unsigned int filter)
+#else
+void snd_timer_params_set_filter(snd_timer_params_t * params, unsigned int filter)
+#endif
+{
+	assert(params);
+	params->filter = filter;
+}
+use_default_symbol_version(__snd_timer_params_set_filter, snd_timer_params_set_filter, ALSA_0.9.0);
+
+/**
+ * \brief get timer event filter
+ * \param params pointer to #snd_timer_params_t structure
+ * \return timer event filter
+ */
+#ifndef DOXYGEN
+unsigned int INTERNAL(snd_timer_params_get_filter)(snd_timer_params_t * params)
+#else
+unsigned int snd_timer_params_get_filter(snd_timer_params_t * params)
+#endif
+{
+	assert(params);
+	return params->filter;
+}
+use_default_symbol_version(__snd_timer_params_get_filter, snd_timer_params_get_filter, ALSA_0.9.0);
+
+/**
+ * \brief set parameters for timer handle
+ * \param timer timer handle
+ * \param params pointer to a #snd_timer_params_t structure
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_timer_params(snd_timer_t *timer, snd_timer_params_t * params)
+{
+	assert(timer);
+	assert(params);
+	return timer->ops->params(timer, params);
+}
+
+/**
+ * \brief get size of the snd_timer_status_t structure in bytes
+ * \return size of the snd_timer_status_t structure in bytes
+ */
+size_t snd_timer_status_sizeof()
+{
+	return sizeof(snd_timer_status_t);
+}
+
+/**
+ * \brief allocate a new snd_timer_status_t structure
+ * \param status returned pointer
+ * \return 0 on success otherwise a negative error code if fails
+ *
+ * Allocates a new snd_timer_status_t structure using the standard
+ * malloc C library function.
+ */
+int snd_timer_status_malloc(snd_timer_status_t **status)
+{
+	assert(status);
+	*status = calloc(1, sizeof(snd_timer_status_t));
+	if (!*status)
+		return -ENOMEM;
+	return 0;
+}
+
+/**
+ * \brief frees the snd_timer_status_t structure
+ * \param status pointer to the snd_timer_status_t structure to free
+ *
+ * Frees the given snd_timer_status_t structure using the standard
+ * free C library function.
+ */
+void snd_timer_status_free(snd_timer_status_t *status)
+{
+	assert(status);
+	free(status);
+}
+
+/**
+ * \brief copy one snd_timer_status_t structure to another
+ * \param dst destination snd_timer_status_t structure
+ * \param src source snd_timer_status_t structure
+ */
+void snd_timer_status_copy(snd_timer_status_t *dst, const snd_timer_status_t *src)
+{
+	assert(dst && src);
+	*dst = *src;
+}
+
+
+
+/**
+ * \brief get timestamp
+ * \param status pointer to #snd_timer_status_t structure
+ * \return timestamp
+ */
+snd_htimestamp_t snd_timer_status_get_timestamp(snd_timer_status_t * status)
+{
+	assert(status);
+	return status->tstamp;
+}
+
+/**
+ * \brief get resolution in us
+ * \param status pointer to #snd_timer_status_t structure
+ * \return resolution
+ */
+long snd_timer_status_get_resolution(snd_timer_status_t * status)
+{
+	assert(status);
+	return status->resolution;
+}
+
+/**
+ * \brief get master tick lost count
+ * \param status pointer to #snd_timer_status_t structure
+ * \return master tick lost count
+ */
+long snd_timer_status_get_lost(snd_timer_status_t * status)
+{
+	assert(status);
+	return status->lost;
+}
+
+/**
+ * \brief get overrun count
+ * \param status pointer to #snd_timer_status_t structure
+ * \return overrun count
+ */
+long snd_timer_status_get_overrun(snd_timer_status_t * status)
+{
+	assert(status);
+	return status->overrun;
+}
+
+/**
+ * \brief get count of used queue elements
+ * \param status pointer to #snd_timer_status_t structure
+ * \return count of used queue elements
+ */
+long snd_timer_status_get_queue(snd_timer_status_t * status)
+{
+	assert(status);
+	return status->queue;
+}
+
+/**
+ * \brief get status from timer handle
+ * \param timer timer handle
+ * \param status pointer to a #snd_timer_status_t structure to be filled
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_timer_status(snd_timer_t *timer, snd_timer_status_t * status)
+{
+	assert(timer);
+	assert(status);
+	return timer->ops->status(timer, status);
+}
+
+/**
+ * \brief start the timer
+ * \param timer timer handle
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_timer_start(snd_timer_t *timer)
+{
+	assert(timer);
+	return timer->ops->rt_start(timer);
+}
+
+/**
+ * \brief stop the timer
+ * \param timer timer handle
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_timer_stop(snd_timer_t *timer)
+{
+	assert(timer);
+	return timer->ops->rt_stop(timer);
+}
+
+/**
+ * \brief continue the timer
+ * \param timer timer handle
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_timer_continue(snd_timer_t *timer)
+{
+	assert(timer);
+	return timer->ops->rt_continue(timer);
+}
+
+/**
+ * \brief read bytes using timer handle
+ * \param timer timer handle
+ * \param buffer buffer to store the input bytes
+ * \param size input buffer size in bytes
+ */
+ssize_t snd_timer_read(snd_timer_t *timer, void *buffer, size_t size)
+{
+	assert(timer);
+	assert(((timer->mode & O_ACCMODE) == O_RDONLY) || ((timer->mode & O_ACCMODE) == O_RDWR));
+	assert(buffer || size == 0);
+	return (timer->ops->read)(timer, buffer, size);
+}
+
+/**
+ * \brief (DEPRECATED) get maximum timer ticks
+ * \param info pointer to #snd_timer_info_t structure
+ * \return maximum timer ticks
+ */
+long snd_timer_info_get_ticks(snd_timer_info_t * info)
+{
+	assert(info);
+	return 1;
+}
+#ifndef DOC_HIDDEN
+link_warning(snd_timer_info_get_ticks, "Warning: snd_timer_info_get_ticks is deprecated");
+#endif
diff --git a/src/timer/timer_hw.c b/src/timer/timer_hw.c
new file mode 100644
index 0000000..2fae75e
--- /dev/null
+++ b/src/timer/timer_hw.c
@@ -0,0 +1,349 @@
+/*
+ *  Timer Interface - main file
+ *  Copyright (c) 1998-2001 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include "timer_local.h"
+
+#ifndef PIC
+/* entry for static linking */
+const char *_snd_module_timer_hw = "";
+#endif
+
+#define SNDRV_FILE_TIMER		ALSA_DEVICE_DIRECTORY "timer"
+#define SNDRV_TIMER_VERSION_MAX	SNDRV_PROTOCOL_VERSION(2, 0, 5)
+
+#define SNDRV_TIMER_IOCTL_STATUS_OLD	_IOW('T', 0x14, struct sndrv_timer_status)
+
+enum {
+	SNDRV_TIMER_IOCTL_START_OLD = _IO('T', 0x20),
+	SNDRV_TIMER_IOCTL_STOP_OLD = _IO('T', 0x21),
+	SNDRV_TIMER_IOCTL_CONTINUE_OLD = _IO('T', 0x22),
+	SNDRV_TIMER_IOCTL_PAUSE_OLD = _IO('T', 0x23),
+};
+
+static int snd_timer_hw_close(snd_timer_t *handle)
+{
+	snd_timer_t *tmr = handle;
+	int res;
+
+	if (!tmr)
+		return -EINVAL;
+	res = close(tmr->poll_fd) < 0 ? -errno : 0;
+	return res;
+}
+
+static int snd_timer_hw_nonblock(snd_timer_t *timer, int nonblock)
+{
+	long flags;
+	assert(timer);
+	if ((flags = fcntl(timer->poll_fd, F_GETFL)) < 0)
+		return -errno;
+	if (nonblock)
+		flags |= O_NONBLOCK;
+	else
+		flags &= ~O_NONBLOCK;
+	if (fcntl(timer->poll_fd, F_SETFL, flags) < 0)
+		return -errno;
+	return 0;
+}
+
+static int snd_timer_hw_async(snd_timer_t *timer, int sig, pid_t pid)
+{
+	long flags;
+	int fd;
+
+	assert(timer);
+	fd = timer->poll_fd;
+	if ((flags = fcntl(fd, F_GETFL)) < 0) {
+		SYSERR("F_GETFL failed");
+		return -errno;
+	}
+	if (sig >= 0)
+		flags |= O_ASYNC;
+	else
+		flags &= ~O_ASYNC;
+	if (fcntl(fd, F_SETFL, flags) < 0) {
+		SYSERR("F_SETFL for O_ASYNC failed");
+		return -errno;
+	}
+	if (sig < 0)
+		return 0;
+	if (fcntl(fd, F_SETSIG, (long)sig) < 0) {
+		SYSERR("F_SETSIG failed");
+		return -errno;
+	}
+	if (fcntl(fd, F_SETOWN, (long)pid) < 0) {
+		SYSERR("F_SETOWN failed");
+		return -errno;
+	}
+	return 0;
+}
+
+static int snd_timer_hw_info(snd_timer_t *handle, snd_timer_info_t * info)
+{
+	snd_timer_t *tmr;
+
+	tmr = handle;
+	if (!tmr || !info)
+		return -EINVAL;
+	if (ioctl(tmr->poll_fd, SNDRV_TIMER_IOCTL_INFO, info) < 0)
+		return -errno;
+	return 0;
+}
+
+static int snd_timer_hw_params(snd_timer_t *handle, snd_timer_params_t * params)
+{
+	snd_timer_t *tmr;
+
+	tmr = handle;
+	if (!tmr || !params)
+		return -EINVAL;
+	if (ioctl(tmr->poll_fd, SNDRV_TIMER_IOCTL_PARAMS, params) < 0)
+		return -errno;
+	return 0;
+}
+
+static int snd_timer_hw_status(snd_timer_t *handle, snd_timer_status_t * status)
+{
+	snd_timer_t *tmr;
+	int cmd;
+
+	tmr = handle;
+	if (!tmr || !status)
+		return -EINVAL;
+	if (tmr->version < SNDRV_PROTOCOL_VERSION(2, 0, 1))
+		cmd = SNDRV_TIMER_IOCTL_STATUS_OLD;
+	else
+		cmd = SNDRV_TIMER_IOCTL_STATUS;
+	if (ioctl(tmr->poll_fd, cmd, status) < 0)
+		return -errno;
+	return 0;
+}
+
+static int snd_timer_hw_start(snd_timer_t *handle)
+{
+	snd_timer_t *tmr;
+	unsigned int cmd;
+
+	tmr = handle;
+	if (!tmr)
+		return -EINVAL;
+	if (tmr->version < SNDRV_PROTOCOL_VERSION(2, 0, 4))
+		cmd = SNDRV_TIMER_IOCTL_START_OLD;
+	else
+		cmd = SNDRV_TIMER_IOCTL_START;
+	if (ioctl(tmr->poll_fd, cmd) < 0)
+		return -errno;
+	return 0;
+}
+
+static int snd_timer_hw_stop(snd_timer_t *handle)
+{
+	snd_timer_t *tmr;
+	unsigned int cmd;
+
+	tmr = handle;
+	if (!tmr)
+		return -EINVAL;
+	if (tmr->version < SNDRV_PROTOCOL_VERSION(2, 0, 4))
+		cmd = SNDRV_TIMER_IOCTL_STOP_OLD;
+	else
+		cmd = SNDRV_TIMER_IOCTL_STOP;
+	if (ioctl(tmr->poll_fd, cmd) < 0)
+		return -errno;
+	return 0;
+}
+
+static int snd_timer_hw_continue(snd_timer_t *handle)
+{
+	snd_timer_t *tmr;
+	unsigned int cmd;
+
+	tmr = handle;
+	if (!tmr)
+		return -EINVAL;
+	if (tmr->version < SNDRV_PROTOCOL_VERSION(2, 0, 4))
+		cmd = SNDRV_TIMER_IOCTL_CONTINUE_OLD;
+	else
+		cmd = SNDRV_TIMER_IOCTL_CONTINUE;
+	if (ioctl(tmr->poll_fd, cmd) < 0)
+		return -errno;
+	return 0;
+}
+
+static ssize_t snd_timer_hw_read(snd_timer_t *handle, void *buffer, size_t size)
+{
+	snd_timer_t *tmr;
+	ssize_t result;
+
+	tmr = handle;
+	if (!tmr || (!buffer && size > 0))
+		return -EINVAL;
+	result = read(tmr->poll_fd, buffer, size);
+	if (result < 0)
+		return -errno;
+	return result;
+}
+
+static const snd_timer_ops_t snd_timer_hw_ops = {
+	.close = snd_timer_hw_close,
+	.nonblock = snd_timer_hw_nonblock,
+	.async = snd_timer_hw_async,
+	.info = snd_timer_hw_info,
+	.params = snd_timer_hw_params,
+	.status = snd_timer_hw_status,
+	.rt_start = snd_timer_hw_start,
+	.rt_stop = snd_timer_hw_stop,
+	.rt_continue = snd_timer_hw_continue,
+	.read = snd_timer_hw_read,
+};
+
+int snd_timer_hw_open(snd_timer_t **handle, const char *name, int dev_class, int dev_sclass, int card, int device, int subdevice, int mode)
+{
+	int fd, ver, tmode, ret;
+	snd_timer_t *tmr;
+	struct sndrv_timer_select sel;
+
+	*handle = NULL;
+
+	tmode = O_RDONLY;
+	if (mode & SND_TIMER_OPEN_NONBLOCK)
+		tmode |= O_NONBLOCK;	
+	fd = snd_open_device(SNDRV_FILE_TIMER, tmode);
+	if (fd < 0)
+		return -errno;
+	if (ioctl(fd, SNDRV_TIMER_IOCTL_PVERSION, &ver) < 0) {
+		ret = -errno;
+		close(fd);
+		return ret;
+	}
+	if (SNDRV_PROTOCOL_INCOMPATIBLE(ver, SNDRV_TIMER_VERSION_MAX)) {
+		close(fd);
+		return -SND_ERROR_INCOMPATIBLE_VERSION;
+	}
+	if (mode & SND_TIMER_OPEN_TREAD) {
+		int arg = 1;
+		if (ver < SNDRV_PROTOCOL_VERSION(2, 0, 3)) {
+			ret = -ENOTTY;
+			goto __no_tread;
+		}
+		if (ioctl(fd, SNDRV_TIMER_IOCTL_TREAD, &arg) < 0) {
+			ret = -errno;
+		      __no_tread:
+			close(fd);
+			SNDMSG("extended read is not supported (SNDRV_TIMER_IOCTL_TREAD)");
+			return ret;
+		}
+	}
+	memset(&sel, 0, sizeof(sel));
+	sel.id.dev_class = dev_class;
+	sel.id.dev_sclass = dev_sclass;
+	sel.id.card = card;
+	sel.id.device = device;
+	sel.id.subdevice = subdevice;
+	if (ioctl(fd, SNDRV_TIMER_IOCTL_SELECT, &sel) < 0) {
+		ret = -errno;
+		close(fd);
+		return ret;
+	}
+	tmr = (snd_timer_t *) calloc(1, sizeof(snd_timer_t));
+	if (tmr == NULL) {
+		close(fd);
+		return -ENOMEM;
+	}
+	tmr->type = SND_TIMER_TYPE_HW;
+	tmr->version = ver;
+	tmr->mode = tmode;
+	tmr->name = strdup(name);
+	tmr->poll_fd = fd;
+	tmr->ops = &snd_timer_hw_ops;
+	INIT_LIST_HEAD(&tmr->async_handlers);
+	*handle = tmr;
+	return 0;
+}
+
+int _snd_timer_hw_open(snd_timer_t **timer, char *name,
+		       snd_config_t *root ATTRIBUTE_UNUSED,
+		       snd_config_t *conf, int mode)
+{
+	snd_config_iterator_t i, next;
+	long dev_class = SND_TIMER_CLASS_GLOBAL, dev_sclass = SND_TIMER_SCLASS_NONE;
+	long card = 0, device = 0, subdevice = 0;
+	const char *str;
+	int err;
+	snd_config_for_each(i, next, conf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (strcmp(id, "comment") == 0)
+			continue;
+		if (strcmp(id, "type") == 0)
+			continue;
+		if (strcmp(id, "class") == 0) {
+			err = snd_config_get_integer(n, &dev_class);
+			if (err < 0)
+				return err;
+			continue;
+		}
+		if (strcmp(id, "sclass") == 0) {
+			err = snd_config_get_integer(n, &dev_sclass);
+			if (err < 0)
+				return err;
+			continue;
+		}
+		if (strcmp(id, "card") == 0) {
+			err = snd_config_get_integer(n, &card);
+			if (err < 0) {
+				err = snd_config_get_string(n, &str);
+				if (err < 0)
+					return -EINVAL;
+				card = snd_card_get_index(str);
+				if (card < 0)
+					return card;
+			}
+			continue;
+		}
+		if (strcmp(id, "device") == 0) {
+			err = snd_config_get_integer(n, &device);
+			if (err < 0)
+				return err;
+			continue;
+		}
+		if (strcmp(id, "subdevice") == 0) {
+			err = snd_config_get_integer(n, &subdevice);
+			if (err < 0)
+				return err;
+			continue;
+		}
+		SNDERR("Unexpected field %s", id);
+		return -EINVAL;
+	}
+	if (card < 0)
+		return -EINVAL;
+	return snd_timer_hw_open(timer, name, dev_class, dev_sclass, card, device, subdevice, mode);
+}
+SND_DLSYM_BUILD_VERSION(_snd_timer_hw_open, SND_TIMER_DLSYM_VERSION);
diff --git a/src/timer/timer_local.h b/src/timer/timer_local.h
new file mode 100644
index 0000000..8040b05
--- /dev/null
+++ b/src/timer/timer_local.h
@@ -0,0 +1,76 @@
+/*
+ *  Timer interface - local header file
+ *  Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include "local.h"
+
+#ifndef DOC_HIDDEN
+typedef struct {
+	int (*close)(snd_timer_t *timer);
+	int (*nonblock)(snd_timer_t *timer, int nonblock);
+	int (*async)(snd_timer_t *timer, int sig, pid_t pid);
+	int (*info)(snd_timer_t *timer, snd_timer_info_t *info);
+	int (*params)(snd_timer_t *timer, snd_timer_params_t *params);
+	int (*status)(snd_timer_t *timer, snd_timer_status_t *status);
+	int (*rt_start)(snd_timer_t *timer);
+	int (*rt_stop)(snd_timer_t *timer);
+	int (*rt_continue)(snd_timer_t *timer);
+	ssize_t (*read)(snd_timer_t *timer, void *buffer, size_t size);
+} snd_timer_ops_t;
+
+struct _snd_timer {
+	unsigned int version;
+	void *dl_handle;
+	char *name;
+	snd_timer_type_t type;
+	int mode;
+	int poll_fd;
+	const snd_timer_ops_t *ops;
+	void *private_data;
+	struct list_head async_handlers;
+};
+
+typedef struct {
+	int (*close)(snd_timer_query_t *timer);
+	int (*next_device)(snd_timer_query_t *timer, snd_timer_id_t *tid);
+	int (*info)(snd_timer_query_t *timer, snd_timer_ginfo_t *info);
+	int (*params)(snd_timer_query_t *timer, snd_timer_gparams_t *info);
+	int (*status)(snd_timer_query_t *timer, snd_timer_gstatus_t *info);
+} snd_timer_query_ops_t;
+
+struct _snd_timer_query {
+	void *dl_handle;
+	char *name;
+	snd_timer_type_t type;
+	int mode;
+	int poll_fd;
+	const snd_timer_query_ops_t *ops;
+	void *private_data;
+};
+#endif /* DOC_HIDDEN */
+
+int snd_timer_hw_open(snd_timer_t **handle, const char *name, int dev_class, int dev_sclass, int card, int device, int subdevice, int mode);
+
+int snd_timer_query_hw_open(snd_timer_query_t **handle, const char *name, int mode);
+
+int snd_timer_async(snd_timer_t *timer, int sig, pid_t pid);
diff --git a/src/timer/timer_query.c b/src/timer/timer_query.c
new file mode 100644
index 0000000..50b098a
--- /dev/null
+++ b/src/timer/timer_query.c
@@ -0,0 +1,594 @@
+/**
+ * \file timer/timer_query.c
+ * \author Jaroslav Kysela <perex@perex.cz>
+ * \date 2001
+ *
+ * Timer Query Interface is designed to obtain identification of timers.
+ */
+/*
+ *  Timer Query Interface - main file
+ *  Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include "timer_local.h"
+
+static int snd_timer_query_open_conf(snd_timer_query_t **timer,
+				     const char *name, snd_config_t *timer_root,
+				     snd_config_t *timer_conf, int mode)
+{
+	const char *str;
+	char buf[256];
+	int err;
+	snd_config_t *conf, *type_conf = NULL;
+	snd_config_iterator_t i, next;
+	const char *id;
+	const char *lib = NULL, *open_name = NULL;
+	int (*open_func)(snd_timer_query_t **, const char *, snd_config_t *, snd_config_t *, int) = NULL;
+#ifndef PIC
+	extern void *snd_timer_query_open_symbols(void);
+#endif
+	void *h = NULL;
+	if (snd_config_get_type(timer_conf) != SND_CONFIG_TYPE_COMPOUND) {
+		if (name)
+			SNDERR("Invalid type for TIMER %s definition", name);
+		else
+			SNDERR("Invalid type for TIMER definition");
+		return -EINVAL;
+	}
+	err = snd_config_search(timer_conf, "type", &conf);
+	if (err < 0) {
+		SNDERR("type is not defined");
+		return err;
+	}
+	err = snd_config_get_id(conf, &id);
+	if (err < 0) {
+		SNDERR("unable to get id");
+		return err;
+	}
+	err = snd_config_get_string(conf, &str);
+	if (err < 0) {
+		SNDERR("Invalid type for %s", id);
+		return err;
+	}
+	err = snd_config_search_definition(timer_root, "timer_query_type", str, &type_conf);
+	if (err >= 0) {
+		if (snd_config_get_type(type_conf) != SND_CONFIG_TYPE_COMPOUND) {
+			SNDERR("Invalid type for TIMER type %s definition", str);
+			err = -EINVAL;
+			goto _err;
+		}
+		snd_config_for_each(i, next, type_conf) {
+			snd_config_t *n = snd_config_iterator_entry(i);
+			const char *id;
+			if (snd_config_get_id(n, &id) < 0)
+				continue;
+			if (strcmp(id, "comment") == 0)
+				continue;
+			if (strcmp(id, "lib") == 0) {
+				err = snd_config_get_string(n, &lib);
+				if (err < 0) {
+					SNDERR("Invalid type for %s", id);
+					goto _err;
+				}
+				continue;
+			}
+			if (strcmp(id, "open") == 0) {
+				err = snd_config_get_string(n, &open_name);
+				if (err < 0) {
+					SNDERR("Invalid type for %s", id);
+					goto _err;
+				}
+				continue;
+			}
+			SNDERR("Unknown field %s", id);
+			err = -EINVAL;
+			goto _err;
+		}
+	}
+	if (!open_name) {
+		open_name = buf;
+		snprintf(buf, sizeof(buf), "_snd_timer_query_%s_open", str);
+	}
+#ifndef PIC
+	snd_timer_query_open_symbols();
+#endif
+	h = snd_dlopen(lib, RTLD_NOW);
+	if (h)
+		open_func = snd_dlsym(h, open_name, SND_DLSYM_VERSION(SND_TIMER_QUERY_DLSYM_VERSION));
+	err = 0;
+	if (!h) {
+		SNDERR("Cannot open shared library %s", lib);
+		err = -ENOENT;
+	} else if (!open_func) {
+		SNDERR("symbol %s is not defined inside %s", open_name, lib);
+		snd_dlclose(h);
+		err = -ENXIO;
+	}
+       _err:
+	if (type_conf)
+		snd_config_delete(type_conf);
+	if (! err) {
+		err = open_func(timer, name, timer_root, timer_conf, mode);
+		if (err < 0)
+			snd_dlclose(h);
+		else
+			(*timer)->dl_handle = h;
+	}
+	return err;
+}
+
+static int snd_timer_query_open_noupdate(snd_timer_query_t **timer, snd_config_t *root, const char *name, int mode)
+{
+	int err;
+	snd_config_t *timer_conf;
+	err = snd_config_search_definition(root, "timer_query", name, &timer_conf);
+	if (err < 0) {
+		SNDERR("Unknown timer %s", name);
+		return err;
+	}
+	err = snd_timer_query_open_conf(timer, name, root, timer_conf, mode);
+	snd_config_delete(timer_conf);
+	return err;
+}
+
+/**
+ * \brief Opens a new connection to the timer query interface.
+ * \param timer Returned handle (NULL if not wanted)
+ * \param name ASCII identifier of the RawMidi handle
+ * \param mode Open mode
+ * \return 0 on success otherwise a negative error code
+ *
+ * Opens a new connection to the RawMidi interface specified with
+ * an ASCII identifier and mode.
+ */
+int snd_timer_query_open(snd_timer_query_t **timer, const char *name, int mode)
+{
+	int err;
+	assert(timer && name);
+	err = snd_config_update();
+	if (err < 0)
+		return err;
+	return snd_timer_query_open_noupdate(timer, snd_config, name, mode);
+}
+
+/**
+ * \brief Opens a new connection to the timer query interface using local configuration
+ * \param timer Returned handle (NULL if not wanted)
+ * \param name ASCII identifier of the RawMidi handle
+ * \param mode Open mode
+ * \param lconf Local configuration
+ * \return 0 on success otherwise a negative error code
+ *
+ * Opens a new connection to the RawMidi interface specified with
+ * an ASCII identifier and mode.
+ */
+int snd_timer_query_open_lconf(snd_timer_query_t **timer, const char *name,
+			       int mode, snd_config_t *lconf)
+{
+	assert(timer && name && lconf);
+	return snd_timer_query_open_noupdate(timer, lconf, name, mode);
+}
+
+/**
+ * \brief close timer query handle
+ * \param timer timer handle
+ * \return 0 on success otherwise a negative error code
+ *
+ * Closes the specified timer handle and frees all associated
+ * resources.
+ */
+int snd_timer_query_close(snd_timer_query_t *timer)
+{
+	int err;
+  	assert(timer);
+	err = timer->ops->close(timer);
+	if (timer->dl_handle)
+		snd_dlclose(timer->dl_handle);
+	free(timer->name);
+	free(timer);
+	return err;
+}
+
+/**
+ * \brief obtain the next timer identification
+ * \param timer timer handle
+ * \param tid timer identification
+ * \return 0 on success otherwise a negative error code
+ *
+ * if tid->dev_class is -1, then the first device is returned
+ * if result tid->dev_class is -1, no more devices are left
+ */
+int snd_timer_query_next_device(snd_timer_query_t *timer, snd_timer_id_t *tid)
+{
+  	assert(timer);
+  	assert(tid);
+	return timer->ops->next_device(timer, tid);
+}
+
+/**
+ * \brief get size of the snd_timer_ginfo_t structure in bytes
+ * \return size of the snd_timer_ginfo_t structure in bytes
+ */
+size_t snd_timer_ginfo_sizeof(void)
+{
+	return sizeof(snd_timer_ginfo_t);
+}
+
+/**
+ * \brief allocate a new snd_timer_ginfo_t structure
+ * \param info returned pointer
+ * \return 0 on success otherwise a negative error code if fails
+ *
+ * Allocates a new snd_timer_info_t structure using the standard
+ * malloc C library function.
+ */
+int snd_timer_ginfo_malloc(snd_timer_ginfo_t **info)
+{
+	assert(info);
+	*info = calloc(1, sizeof(snd_timer_ginfo_t));
+	if (!*info)
+		return -ENOMEM;
+	return 0;
+}
+
+/**
+ * \brief frees the snd_timer_ginfo_t structure
+ * \param info pointer to the snd_timer_ginfo_t structure to free
+ *
+ * Frees the given snd_timer_info_t structure using the standard
+ * free C library function.
+ */
+void snd_timer_ginfo_free(snd_timer_ginfo_t *info)
+{
+	assert(info);
+	free(info);  
+}
+  
+/**
+ * \brief copy one snd_timer_info_t structure to another
+ * \param dst destination snd_timer_info_t structure
+ * \param src source snd_timer_info_t structure
+ */
+void snd_timer_ginfo_copy(snd_timer_ginfo_t *dst, const snd_timer_ginfo_t *src)
+{
+	assert(dst && src);
+	*dst = *src;
+}
+
+/**
+ * \brief set timer identification
+ * \param obj pointer to #snd_timer_ginfo_t structure
+ * \param tid pointer to #snd_timer_id_t structure
+ * \return zero on success otherwise a negative error number
+ */
+int snd_timer_ginfo_set_tid(snd_timer_ginfo_t *obj, snd_timer_id_t *tid)
+{
+	obj->tid = *((snd_timer_id_t *)tid);
+	return 0;
+}
+
+/**
+ * \brief get timer identification
+ * \param obj pointer to #snd_timer_ginfo_t structure
+ * \return pointer to snd_timer_id_t
+ */
+snd_timer_id_t *snd_timer_ginfo_get_tid(snd_timer_ginfo_t *obj)
+{
+	return (snd_timer_id_t *)&obj->tid;
+}
+
+/**
+ * \brief get timer flags
+ * \param obj pointer to #snd_timer_ginfo_t structure
+ * \return timer flags
+ */
+unsigned int snd_timer_ginfo_get_flags(snd_timer_ginfo_t *obj)
+{
+	return obj->flags;
+}
+
+/**
+ * \brief get associated card with timer
+ * \param obj pointer to #snd_timer_ginfo_t structure
+ * \return associated card
+ */
+int snd_timer_ginfo_get_card(snd_timer_ginfo_t *obj)
+{
+	return obj->card;
+}
+
+/**
+ * \brief get timer identification
+ * \param obj pointer to #snd_timer_ginfo_t structure
+ * \return timer identification
+ */
+char *snd_timer_ginfo_get_id(snd_timer_ginfo_t *obj)
+{
+	return (char *)obj->id;
+}
+
+/**
+ * \brief get timer name
+ * \param obj pointer to #snd_timer_ginfo_t structure
+ * \return timer name
+ */
+char *snd_timer_ginfo_get_name(snd_timer_ginfo_t *obj)
+{
+	return (char *)obj->name;
+}
+
+/**
+ * \brief get timer resolution in ns
+ * \param obj pointer to #snd_timer_ginfo_t structure
+ * \return timer resolution in ns
+ */
+unsigned long snd_timer_ginfo_get_resolution(snd_timer_ginfo_t *obj)
+{
+	return obj->resolution;
+}
+
+/**
+ * \brief get timer minimal resolution in ns
+ * \param obj pointer to #snd_timer_ginfo_t structure
+ * \return timer minimal resolution in ns
+ */
+unsigned long snd_timer_ginfo_get_resolution_min(snd_timer_ginfo_t *obj)
+{
+	return obj->resolution_min;
+}
+
+/**
+ * \brief get timer maximal resolution in ns
+ * \param obj pointer to #snd_timer_ginfo_t structure
+ * \return timer maximal resolution in ns
+ */
+unsigned long snd_timer_ginfo_get_resolution_max(snd_timer_ginfo_t *obj)
+{
+	return obj->resolution_max;
+}
+
+/**
+ * \brief get current timer clients
+ * \param obj pointer to #snd_timer_ginfo_t structure
+ * \return current timer clients
+ */
+unsigned int snd_timer_ginfo_get_clients(snd_timer_ginfo_t *obj)
+{
+	return obj->clients;
+}
+
+/**
+ * \brief obtain the timer global information
+ * \param timer timer handle
+ * \param info timer information
+ * \return 0 on success otherwise a negative error code
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_timer_query_info)(snd_timer_query_t *timer, snd_timer_ginfo_t *info)
+#else
+int snd_timer_query_info(snd_timer_query_t *timer, snd_timer_ginfo_t *info)
+#endif
+{
+  	assert(timer);
+  	assert(info);
+	return timer->ops->info(timer, info);
+}
+use_default_symbol_version(__snd_timer_query_info, snd_timer_query_info, ALSA_0.9.0);
+
+/**
+ * \brief set the timer global parameters
+ * \param timer timer handle
+ * \param params timer parameters
+ * \return 0 on success otherwise a negative error code
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_timer_query_params)(snd_timer_query_t *timer, snd_timer_gparams_t *params)
+#else
+int snd_timer_query_params(snd_timer_query_t *timer, snd_timer_gparams_t *params)
+#endif
+{
+  	assert(timer);
+  	assert(params);
+	return timer->ops->params(timer, params);
+}
+use_default_symbol_version(__snd_timer_query_params, snd_timer_query_params, ALSA_0.9.0);
+
+/**
+ * \brief get the timer global status
+ * \param timer timer handle
+ * \param status timer status
+ * \return 0 on success otherwise a negative error code
+ */
+#ifndef DOXYGEN
+int INTERNAL(snd_timer_query_status)(snd_timer_query_t *timer, snd_timer_gstatus_t *status)
+#else
+int snd_timer_query_status(snd_timer_query_t *timer, snd_timer_gstatus_t *status)
+#endif
+{
+  	assert(timer);
+  	assert(status);
+	return timer->ops->status(timer, status);
+}
+use_default_symbol_version(__snd_timer_query_status, snd_timer_query_status, ALSA_0.9.0);
+
+/**
+ * \brief get size of the snd_timer_id_t structure in bytes
+ * \return size of the snd_timer_id_t structure in bytes
+ */
+size_t snd_timer_id_sizeof()
+{
+	return sizeof(snd_timer_id_t);
+}
+
+/**
+ * \brief allocate a new snd_timer_id_t structure
+ * \param info returned pointer
+ * \return 0 on success otherwise a negative error code if fails
+ *
+ * Allocates a new snd_timer_id_t structure using the standard
+ * malloc C library function.
+ */
+int snd_timer_id_malloc(snd_timer_id_t **info)
+{
+	assert(info);
+	*info = calloc(1, sizeof(snd_timer_id_t));
+	if (!*info)
+		return -ENOMEM;
+	return 0;
+}
+
+/**
+ * \brief frees the snd_timer_id_t structure
+ * \param info pointer to the snd_timer_id_t structure to free
+ *
+ * Frees the given snd_timer_id_t structure using the standard
+ * free C library function.
+ */
+void snd_timer_id_free(snd_timer_id_t *info)
+{
+	assert(info);
+	free(info);
+}
+
+/**
+ * \brief copy one snd_timer_id_t structure to another
+ * \param dst destination snd_timer_id_t structure
+ * \param src source snd_timer_id_t structure
+ */
+void snd_timer_id_copy(snd_timer_id_t *dst, const snd_timer_id_t *src)
+{
+	assert(dst && src);
+	*dst = *src;
+}
+
+/**
+ * \brief set timer class
+ * \param tid pointer to #snd_timer_id_t structure
+ * \param dev_class class of timer device
+ */
+void snd_timer_id_set_class(snd_timer_id_t * tid, int dev_class)
+{
+	assert(tid);
+	tid->dev_class = dev_class;
+}
+
+/**
+ * \brief get timer class
+ * \param tid pointer to #snd_timer_id_t structure
+ * \return timer class
+ */
+int snd_timer_id_get_class(snd_timer_id_t * tid)
+{
+	assert(tid);
+	return tid->dev_class;
+}
+
+/**
+ * \brief set timer sub-class
+ * \param tid pointer to #snd_timer_id_t structure
+ * \param dev_sclass sub-class of timer device
+ */
+void snd_timer_id_set_sclass(snd_timer_id_t * tid, int dev_sclass)
+{
+	assert(tid);
+	tid->dev_sclass = dev_sclass;
+}
+
+/**
+ * \brief get timer sub-class
+ * \param tid pointer to #snd_timer_id_t structure
+ * \return timer sub-class
+ */
+int snd_timer_id_get_sclass(snd_timer_id_t * tid)
+{
+	assert(tid);
+	return tid->dev_sclass;
+}
+
+/**
+ * \brief set timer card
+ * \param tid pointer to #snd_timer_id_t structure
+ * \param card card number
+ */
+void snd_timer_id_set_card(snd_timer_id_t * tid, int card)
+{
+	assert(tid);
+	tid->card = card;
+}
+
+/**
+ * \brief get timer card
+ * \param tid pointer to #snd_timer_id_t structure
+ * \return timer card number
+ */
+int snd_timer_id_get_card(snd_timer_id_t * tid)
+{
+	assert(tid);
+	return tid->card;
+}
+
+/**
+ * \brief set timer device
+ * \param tid pointer to #snd_timer_id_t structure
+ * \param device device number
+ */
+void snd_timer_id_set_device(snd_timer_id_t * tid, int device)
+{
+	assert(tid);
+	tid->device = device;
+}
+
+/**
+ * \brief get timer device
+ * \param tid pointer to #snd_timer_id_t structure
+ * \return timer device number
+ */
+int snd_timer_id_get_device(snd_timer_id_t * tid)
+{
+	assert(tid);
+	return tid->device;
+}
+
+/**
+ * \brief set timer subdevice
+ * \param tid pointer to #snd_timer_id_t structure
+ * \param subdevice subdevice number
+ */
+void snd_timer_id_set_subdevice(snd_timer_id_t * tid, int subdevice)
+{
+	assert(tid);
+	tid->subdevice = subdevice;
+}
+
+/**
+ * \brief get timer subdevice
+ * \param tid pointer to #snd_timer_id_t structure
+ * \return timer subdevice number
+ */
+int snd_timer_id_get_subdevice(snd_timer_id_t * tid)
+{
+	assert(tid);
+	return tid->subdevice;
+}
diff --git a/src/timer/timer_query_hw.c b/src/timer/timer_query_hw.c
new file mode 100644
index 0000000..9f62b78
--- /dev/null
+++ b/src/timer/timer_query_hw.c
@@ -0,0 +1,146 @@
+/*
+ *  Timer Interface - main file
+ *  Copyright (c) 1998-2001 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include "timer_local.h"
+
+#ifndef PIC
+/* entry for static linking */
+const char *_snd_module_timer_query_hw = "";
+#endif
+
+#define SNDRV_FILE_TIMER		ALSA_DEVICE_DIRECTORY "timer"
+#define SNDRV_TIMER_VERSION_MAX	SNDRV_PROTOCOL_VERSION(2, 0, 0)
+
+static int snd_timer_query_hw_close(snd_timer_query_t *handle)
+{
+	int res;
+
+	if (!handle)
+		return -EINVAL;
+	res = close(handle->poll_fd) < 0 ? -errno : 0;
+	return res;
+}
+
+static int snd_timer_query_hw_next_device(snd_timer_query_t *handle, snd_timer_id_t * tid)
+{
+	if (!handle || !tid)
+		return -EINVAL;
+	if (ioctl(handle->poll_fd, SNDRV_TIMER_IOCTL_NEXT_DEVICE, tid) < 0)
+		return -errno;
+	return 0;
+}
+
+static int snd_timer_query_hw_info(snd_timer_query_t *handle, snd_timer_ginfo_t *info)
+{
+	if (!handle || !info)
+		return -EINVAL;
+	if (ioctl(handle->poll_fd, SNDRV_TIMER_IOCTL_GINFO, info) < 0)
+		return -errno;
+	return 0;
+}
+
+static int snd_timer_query_hw_params(snd_timer_query_t *handle, snd_timer_gparams_t *params)
+{
+	if (!handle || !params)
+		return -EINVAL;
+	if (ioctl(handle->poll_fd, SNDRV_TIMER_IOCTL_GPARAMS, params) < 0)
+		return -errno;
+	return 0;
+}
+
+static int snd_timer_query_hw_status(snd_timer_query_t *handle, snd_timer_gstatus_t *status)
+{
+	if (!handle || !status)
+		return -EINVAL;
+	if (ioctl(handle->poll_fd, SNDRV_TIMER_IOCTL_GSTATUS, status) < 0)
+		return -errno;
+	return 0;
+}
+
+static const snd_timer_query_ops_t snd_timer_query_hw_ops = {
+	.close = snd_timer_query_hw_close,
+	.next_device = snd_timer_query_hw_next_device,
+	.info = snd_timer_query_hw_info,
+	.params = snd_timer_query_hw_params,
+	.status = snd_timer_query_hw_status
+};
+
+int snd_timer_query_hw_open(snd_timer_query_t **handle, const char *name, int mode)
+{
+	int fd, ver, tmode;
+	snd_timer_query_t *tmr;
+
+	*handle = NULL;
+
+	tmode = O_RDONLY;
+	if (mode & SND_TIMER_OPEN_NONBLOCK)
+		tmode |= O_NONBLOCK;	
+	fd = snd_open_device(SNDRV_FILE_TIMER, tmode);
+	if (fd < 0)
+		return -errno;
+	if (ioctl(fd, SNDRV_TIMER_IOCTL_PVERSION, &ver) < 0) {
+		close(fd);
+		return -errno;
+	}
+	if (SNDRV_PROTOCOL_INCOMPATIBLE(ver, SNDRV_TIMER_VERSION_MAX)) {
+		close(fd);
+		return -SND_ERROR_INCOMPATIBLE_VERSION;
+	}
+	tmr = (snd_timer_query_t *) calloc(1, sizeof(snd_timer_t));
+	if (tmr == NULL) {
+		close(fd);
+		return -ENOMEM;
+	}
+	tmr->type = SND_TIMER_TYPE_HW;
+	tmr->mode = tmode;
+	tmr->name = strdup(name);
+	tmr->poll_fd = fd;
+	tmr->ops = &snd_timer_query_hw_ops;
+	*handle = tmr;
+	return 0;
+}
+
+int _snd_timer_query_hw_open(snd_timer_query_t **timer, char *name,
+			     snd_config_t *root ATTRIBUTE_UNUSED,
+			     snd_config_t *conf, int mode)
+{
+	snd_config_iterator_t i, next;
+	snd_config_for_each(i, next, conf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (strcmp(id, "comment") == 0)
+			continue;
+		if (strcmp(id, "type") == 0)
+			continue;
+		SNDERR("Unexpected field %s", id);
+		return -EINVAL;
+	}
+	return snd_timer_query_hw_open(timer, name, mode);
+}
+SND_DLSYM_BUILD_VERSION(_snd_timer_query_hw_open, SND_TIMER_QUERY_DLSYM_VERSION);
diff --git a/src/timer/timer_symbols.c b/src/timer/timer_symbols.c
new file mode 100644
index 0000000..797721e
--- /dev/null
+++ b/src/timer/timer_symbols.c
@@ -0,0 +1,46 @@
+/*
+ *  Timer Symbols
+ *  Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#ifndef PIC
+
+extern const char *_snd_module_timer_hw;
+
+static const char **snd_timer_open_objects[] = {
+	&_snd_module_timer_hw
+};
+	
+void *snd_timer_open_symbols(void)
+{
+	return (void *)snd_timer_open_objects[0];
+}
+
+
+extern const char *_snd_module_timer_query_hw;
+
+static const char **snd_timer_query_open_objects[] = {
+	&_snd_module_timer_query_hw
+};
+	
+void *snd_timer_query_open_symbols(void)
+{
+	return snd_timer_query_open_objects;
+}
+
+#endif /* !PIC */
diff --git a/src/ucm/Makefile.am b/src/ucm/Makefile.am
new file mode 100644
index 0000000..7435d90
--- /dev/null
+++ b/src/ucm/Makefile.am
@@ -0,0 +1,10 @@
+EXTRA_LTLIBRARIES = libucm.la
+
+libucm_la_SOURCES = utils.c parser.c main.c
+
+noinst_HEADERS = ucm_local.h
+
+all: libucm.la
+
+
+INCLUDES=-I$(top_srcdir)/include
diff --git a/src/ucm/main.c b/src/ucm/main.c
new file mode 100644
index 0000000..76ca151
--- /dev/null
+++ b/src/ucm/main.c
@@ -0,0 +1,1649 @@
+/*
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software  
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ *  Support for the verb/device/modifier core logic and API,
+ *  command line tool and file parser was kindly sponsored by
+ *  Texas Instruments Inc.
+ *  Support for multiple active modifiers and devices,
+ *  transition sequences, multiple client access and user defined use
+ *  cases was kindly sponsored by Wolfson Microelectronics PLC.
+ *
+ *  Copyright (C) 2008-2010 SlimLogic Ltd
+ *  Copyright (C) 2010 Wolfson Microelectronics PLC
+ *  Copyright (C) 2010 Texas Instruments Inc.
+ *  Copyright (C) 2010 Red Hat Inc.
+ *  Authors: Liam Girdwood <lrg@slimlogic.co.uk>
+ *	         Stefan Schmidt <stefan@slimlogic.co.uk>
+ *	         Justin Xu <justinx@slimlogic.co.uk>
+ *               Jaroslav Kysela <perex@perex.cz>
+ */
+
+#include "ucm_local.h"
+#include <stdarg.h>
+#include <pthread.h>
+
+/*
+ * misc
+ */
+
+static int get_value1(const char **value, struct list_head *value_list,
+                      const char *identifier);
+static int get_value3(const char **value,
+		      const char *identifier,
+		      struct list_head *value_list1,
+		      struct list_head *value_list2,
+		      struct list_head *value_list3);
+
+static int check_identifier(const char *identifier, const char *prefix)
+{
+	int len;
+
+	if (strcmp(identifier, prefix) == 0)
+		return 1;
+	len = strlen(prefix);
+	if (memcmp(identifier, prefix, len) == 0 && identifier[len] == '/')
+		return 1;
+	return 0;
+}
+
+static int list_count(struct list_head *list)
+{
+        struct list_head *pos;
+        int count = 0;
+        
+        list_for_each(pos, list) {
+                count += 1;
+        }
+        return count;
+}
+
+static int alloc_str_list(struct list_head *list, int mult, char **result[])
+{
+        char **res;
+        int cnt;
+        
+        cnt = list_count(list) * mult;
+        if (cnt == 0) {
+		*result = NULL;
+                return cnt;
+	}
+        res = calloc(mult, cnt * sizeof(char *));
+        if (res == NULL)
+                return -ENOMEM;
+        *result = res;
+        return cnt;
+}
+
+/**
+ * \brief Create an identifier
+ * \param fmt Format (sprintf like)
+ * \param ... Optional arguments for sprintf like format
+ * \return Allocated string identifier or NULL on error
+ */
+char *snd_use_case_identifier(const char *fmt, ...)
+{
+	char *str, *res;
+	int size = strlen(fmt) + 512;
+	va_list args;
+
+	str = malloc(size);
+	if (str == NULL)
+		return NULL;
+	va_start(args, fmt);
+	vsnprintf(str, size, fmt, args);
+	va_end(args);
+	str[size-1] = '\0';
+	res = realloc(str, strlen(str) + 1);
+	if (res)
+		return res;
+	return str;
+}
+
+/**
+ * \brief Free a string list
+ * \param list The string list to free
+ * \param items Count of strings
+ * \return Zero if success, otherwise a negative error code
+ */
+int snd_use_case_free_list(const char *list[], int items)
+{
+        int i;
+	if (list == NULL)
+		return 0;
+        for (i = 0; i < items; i++)
+		free((void *)list[i]);
+        free(list);
+	return 0;
+}
+
+static int open_ctl(snd_use_case_mgr_t *uc_mgr,
+		    snd_ctl_t **ctl,
+		    const char *ctl_dev)
+{
+	int err;
+
+	/* FIXME: add a list of ctl devices to uc_mgr structure and
+           cache accesses for multiple opened ctl devices */
+	if (uc_mgr->ctl_dev != NULL && strcmp(ctl_dev, uc_mgr->ctl_dev) == 0) {
+		*ctl = uc_mgr->ctl;
+		return 0;
+	}
+	if (uc_mgr->ctl_dev) {
+		free(uc_mgr->ctl_dev);
+		uc_mgr->ctl_dev = NULL;
+		snd_ctl_close(uc_mgr->ctl);
+	
+	}
+	err = snd_ctl_open(ctl, ctl_dev, 0);
+	if (err < 0)
+		return err;
+	uc_mgr->ctl_dev = strdup(ctl_dev);
+	if (uc_mgr->ctl_dev == NULL) {
+		snd_ctl_close(*ctl);
+		return -ENOMEM;
+	}
+	uc_mgr->ctl = *ctl;
+	return 0;
+}
+
+static int execute_cset(snd_ctl_t *ctl, char *cset)
+{
+	char *pos;
+	int err;
+	snd_ctl_elem_id_t *id;
+	snd_ctl_elem_value_t *value;
+	snd_ctl_elem_info_t *info;
+
+	snd_ctl_elem_id_malloc(&id);
+	snd_ctl_elem_value_malloc(&value);
+	snd_ctl_elem_info_malloc(&info);
+
+	pos = strrchr(cset, ' ');
+	if (pos == NULL) {
+		uc_error("undefined value for cset >%s<", cset);
+		err = -EINVAL;
+		goto __fail;
+	}
+	*pos = '\0';
+	err = snd_ctl_ascii_elem_id_parse(id, cset);
+	if (err < 0)
+		goto __fail;
+	snd_ctl_elem_value_set_id(value, id);
+	snd_ctl_elem_info_set_id(info, id);
+	err = snd_ctl_elem_read(ctl, value);
+	if (err < 0)
+		goto __fail;
+	err = snd_ctl_elem_info(ctl, info);
+	if (err < 0)
+		goto __fail;
+	err = snd_ctl_ascii_value_parse(ctl, value, info, pos + 1);
+	if (err < 0)
+		goto __fail;
+	err = snd_ctl_elem_write(ctl, value);
+	if (err < 0)
+		goto __fail;
+	err = 0;
+      __fail:
+	if (pos != NULL)
+		*pos = ' ';
+
+	if (id != NULL)
+		free(id);
+	if (value != NULL)
+		free(value);
+	if (info != NULL)
+		free(info);
+
+	return err;
+}
+
+/**
+ * \brief Execute the sequence
+ * \param uc_mgr Use case manager
+ * \param seq Sequence
+ * \return zero on success, otherwise a negative error code
+ */
+static int execute_sequence(snd_use_case_mgr_t *uc_mgr,
+			    struct list_head *seq,
+			    struct list_head *value_list1,
+			    struct list_head *value_list2,
+			    struct list_head *value_list3)
+{
+	struct list_head *pos;
+	struct sequence_element *s;
+	char *cdev = NULL;
+	snd_ctl_t *ctl = NULL;
+	int err = 0;
+
+	list_for_each(pos, seq) {
+		s = list_entry(pos, struct sequence_element, list);
+		switch (s->type) {
+		case SEQUENCE_ELEMENT_TYPE_CDEV:
+			cdev = strdup(s->data.cdev);
+			if (cdev == NULL)
+				goto __fail_nomem;
+			break;
+		case SEQUENCE_ELEMENT_TYPE_CSET:
+			if (cdev == NULL) {
+				const char *cdev1 = NULL, *cdev2 = NULL;
+				err = get_value3(&cdev1, "PlaybackCTL",
+						 value_list1,
+						 value_list2,
+						 value_list3);
+				if (err < 0 && err != ENOENT) {
+					uc_error("cdev is not defined!");
+					return err;
+				}
+				err = get_value3(&cdev1, "CaptureCTL",
+						 value_list1,
+						 value_list2,
+						 value_list3);
+				if (err < 0 && err != ENOENT) {
+					free((char *)cdev1);
+					uc_error("cdev is not defined!");
+					return err;
+				}
+				if (cdev1 == NULL || cdev2 == NULL ||
+                                    strcmp(cdev1, cdev2) == 0) {
+					cdev = (char *)cdev1;
+					free((char *)cdev2);
+				} else {
+					free((char *)cdev1);
+					free((char *)cdev2);
+				}
+			}
+			if (ctl == NULL) {
+				err = open_ctl(uc_mgr, &ctl, cdev);
+				if (err < 0) {
+					uc_error("unable to open ctl device '%s'", cdev);
+					goto __fail;
+				}
+			}
+			err = execute_cset(ctl, s->data.cset);
+			if (err < 0) {
+				uc_error("unable to execute cset '%s'\n", s->data.cset);
+				goto __fail;
+			}
+			break;
+		case SEQUENCE_ELEMENT_TYPE_SLEEP:
+			usleep(s->data.sleep);
+			break;
+		case SEQUENCE_ELEMENT_TYPE_EXEC:
+			err = system(s->data.exec);
+			if (err < 0)
+				goto __fail;
+			break;
+		default:
+			uc_error("unknown sequence command %i", s->type);
+			break;
+		}
+	}
+	free(cdev);
+	return 0;
+      __fail_nomem:
+	err = -ENOMEM;
+      __fail:
+	free(cdev);
+	return err;
+
+}
+
+/**
+ * \brief Import master config and execute the default sequence
+ * \param uc_mgr Use case manager
+ * \return zero on success, otherwise a negative error code
+ */
+static int import_master_config(snd_use_case_mgr_t *uc_mgr)
+{
+	int err;
+	
+	err = uc_mgr_import_master_config(uc_mgr);
+	if (err < 0)
+		return err;
+	err = execute_sequence(uc_mgr, &uc_mgr->default_list,
+			       &uc_mgr->value_list, NULL, NULL);
+	if (err < 0)
+		uc_error("Unable to execute default sequence");
+	return err;
+}
+
+/**
+ * \brief Universal find - string in a list
+ * \param list List of structures
+ * \param offset Offset of list structure
+ * \param soffset Offset of string structure
+ * \param match String to match
+ * \return structure on success, otherwise a NULL (not found)
+ */
+static void *find0(struct list_head *list,
+		   unsigned long offset,
+		   unsigned long soffset,
+		   const char *match)
+{
+	struct list_head *pos;
+	char *ptr, *str;
+
+	list_for_each(pos, list) {
+		ptr = list_entry_offset(pos, char, offset);
+		str = *((char **)(ptr + soffset));
+		if (strcmp(str, match) == 0)
+			return ptr;
+	}
+	return NULL;
+}
+
+#define find(list, type, member, value, match) \
+	find0(list, (unsigned long)(&((type *)0)->member), \
+		    (unsigned long)(&((type *)0)->value), match)
+
+/**
+ * \brief Universal string list
+ * \param list List of structures
+ * \param result Result list
+ * \param offset Offset of list structure
+ * \param s1offset Offset of string structure
+ * \return count of items on success, otherwise a negative error code
+ */
+static int get_list0(struct list_head *list,
+                     const char **result[],
+                     unsigned long offset,
+                     unsigned long s1offset)
+{
+        char **res;
+        int cnt;
+	struct list_head *pos;
+	char *ptr, *str1;
+
+	cnt = alloc_str_list(list, 1, &res);
+	if (cnt <= 0)
+	        return cnt;
+	*result = (const char **)res;
+	list_for_each(pos, list) {
+		ptr = list_entry_offset(pos, char, offset);
+		str1 = *((char **)(ptr + s1offset));
+		if (str1 != NULL) {
+		        *res = strdup(str1);
+		        if (*res == NULL)
+		                goto __fail;
+                } else {
+                        *res = NULL;
+                }
+                res++;
+	}
+	return cnt;
+      __fail:
+        snd_use_case_free_list((const char **)res, cnt);
+        return -ENOMEM;
+}
+
+#define get_list(list, result, type, member, s1) \
+	get_list0(list, result, \
+	            (unsigned long)(&((type *)0)->member), \
+		    (unsigned long)(&((type *)0)->s1))
+
+/**
+ * \brief Universal string list - pair of strings
+ * \param list List of structures
+ * \param result Result list
+ * \param offset Offset of list structure
+ * \param s1offset Offset of string structure
+ * \param s1offset Offset of string structure
+ * \return count of items on success, otherwise a negative error code
+ */
+static int get_list20(struct list_head *list,
+                      const char **result[],
+                      unsigned long offset,
+                      unsigned long s1offset,
+                      unsigned long s2offset)
+{
+        char **res;
+        int cnt;
+	struct list_head *pos;
+	char *ptr, *str1, *str2;
+
+	cnt = alloc_str_list(list, 2, &res);
+	if (cnt <= 0)
+	        return cnt;
+        *result = (const char **)res;
+	list_for_each(pos, list) {
+		ptr = list_entry_offset(pos, char, offset);
+		str1 = *((char **)(ptr + s1offset));
+		if (str1 != NULL) {
+		        *res = strdup(str1);
+		        if (*res == NULL)
+		                goto __fail;
+                } else {
+                        *res = NULL;
+                }
+                res++;
+		str2 = *((char **)(ptr + s2offset));
+		if (str2 != NULL) {
+		        *res = strdup(str2);
+		        if (*res == NULL)
+		                goto __fail;
+                } else {
+                        *res = NULL;
+                }
+                res++;
+	}
+	return cnt;
+      __fail:
+        snd_use_case_free_list((const char **)res, cnt);
+        return -ENOMEM;
+}
+
+#define get_list2(list, result, type, member, s1, s2) \
+	get_list20(list, result, \
+	            (unsigned long)(&((type *)0)->member), \
+		    (unsigned long)(&((type *)0)->s1), \
+		    (unsigned long)(&((type *)0)->s2))
+
+/**
+ * \brief Find verb
+ * \param uc_mgr Use case manager
+ * \param verb_name verb to find
+ * \return structure on success, otherwise a NULL (not found)
+ */
+static inline struct use_case_verb *find_verb(snd_use_case_mgr_t *uc_mgr,
+					      const char *verb_name)
+{
+	return find(&uc_mgr->verb_list,
+		    struct use_case_verb, list, name,
+		    verb_name);
+}
+
+static int is_devlist_supported(snd_use_case_mgr_t *uc_mgr, 
+	struct dev_list *dev_list)
+{
+	struct dev_list_node *device;
+	struct use_case_device *adev;
+	struct list_head *pos, *pos1;
+	int found_ret;
+
+	switch (dev_list->type) {
+	case DEVLIST_NONE:
+	default:
+		return 1;
+	case DEVLIST_SUPPORTED:
+		found_ret = 1;
+		break;
+	case DEVLIST_CONFLICTING:
+		found_ret = 0;
+		break;
+	}
+
+	list_for_each(pos, &dev_list->list) {
+		device = list_entry(pos, struct dev_list_node, list);
+
+		list_for_each(pos1, &uc_mgr->active_devices) {
+			adev = list_entry(pos1, struct use_case_device,
+					    active_list);
+			if (!strcmp(device->name, adev->name))
+				return found_ret;
+		}
+	}
+	return 1 - found_ret;
+}
+
+static inline int is_modifier_supported(snd_use_case_mgr_t *uc_mgr, 
+	struct use_case_modifier *modifier)
+{
+	return is_devlist_supported(uc_mgr, &modifier->dev_list);
+}
+
+static inline int is_device_supported(snd_use_case_mgr_t *uc_mgr, 
+	struct use_case_device *device)
+{
+	return is_devlist_supported(uc_mgr, &device->dev_list);
+}
+
+/**
+ * \brief Find device
+ * \param verb Use case verb
+ * \param device_name device to find
+ * \return structure on success, otherwise a NULL (not found)
+ */
+static inline struct use_case_device *
+        find_device(snd_use_case_mgr_t *uc_mgr, struct use_case_verb *verb,
+		    const char *device_name, int check_supported)
+{
+	struct use_case_device *device;
+	struct list_head *pos;
+
+	list_for_each(pos, &verb->device_list) {
+		device = list_entry(pos, struct use_case_device, list);
+
+		if (strcmp(device_name, device->name))
+			continue;
+
+		if (check_supported &&
+		    !is_device_supported(uc_mgr, device))
+			continue;
+
+		return device;
+	}
+	return NULL;
+}
+
+/**
+ * \brief Find modifier
+ * \param verb Use case verb
+ * \param modifier_name modifier to find
+ * \return structure on success, otherwise a NULL (not found)
+ */
+static struct use_case_modifier *
+        find_modifier(snd_use_case_mgr_t *uc_mgr, struct use_case_verb *verb,
+		      const char *modifier_name, int check_supported)
+{
+	struct use_case_modifier *modifier;
+	struct list_head *pos;
+
+	list_for_each(pos, &verb->modifier_list) {
+		modifier = list_entry(pos, struct use_case_modifier, list);
+
+		if (strcmp(modifier->name, modifier_name))
+			continue;
+
+		if (check_supported &&
+		    !is_modifier_supported(uc_mgr, modifier))
+			continue;
+
+		return modifier;
+	}
+	return NULL;
+}
+
+/**
+ * \brief Set verb
+ * \param uc_mgr Use case manager
+ * \param verb verb to set
+ * \param enable nonzero = enable, zero = disable
+ * \return zero on success, otherwise a negative error code
+ */
+static int set_verb(snd_use_case_mgr_t *uc_mgr,
+		    struct use_case_verb *verb,
+		    int enable)
+{
+	struct list_head *seq;
+	int err;
+
+	if (enable) {
+		seq = &verb->enable_list;
+	} else {
+		seq = &verb->disable_list;
+	}
+	err = execute_sequence(uc_mgr, seq,
+			       &verb->value_list,
+			       &uc_mgr->value_list,
+			       NULL);
+	if (enable && err >= 0)
+		uc_mgr->active_verb = verb;
+	return err;
+}
+
+/**
+ * \brief Set modifier
+ * \param uc_mgr Use case manager
+ * \param modifier modifier to set
+ * \param enable nonzero = enable, zero = disable
+ * \return zero on success, otherwise a negative error code
+ */
+static int set_modifier(snd_use_case_mgr_t *uc_mgr,
+			struct use_case_modifier *modifier,
+			int enable)
+{
+	struct list_head *seq;
+	int err;
+
+	if (enable) {
+		seq = &modifier->enable_list;
+	} else {
+		seq = &modifier->disable_list;
+	}
+	err = execute_sequence(uc_mgr, seq,
+			       &modifier->value_list,
+			       &uc_mgr->active_verb->value_list,
+			       &uc_mgr->value_list);
+	if (enable && err >= 0) {
+		list_add_tail(&modifier->active_list, &uc_mgr->active_modifiers);
+	} else if (!enable) {
+		list_del(&modifier->active_list);
+	}
+	return err;
+}
+
+/**
+ * \brief Set device
+ * \param uc_mgr Use case manager
+ * \param device device to set
+ * \param enable nonzero = enable, zero = disable
+ * \return zero on success, otherwise a negative error code
+ */
+static int set_device(snd_use_case_mgr_t *uc_mgr,
+		      struct use_case_device *device,
+		      int enable)
+{
+	struct list_head *seq;
+	int err;
+
+	if (enable) {
+		seq = &device->enable_list;
+	} else {
+		seq = &device->disable_list;
+	}
+	err = execute_sequence(uc_mgr, seq,
+			       &device->value_list,
+			       &uc_mgr->active_verb->value_list,
+			       &uc_mgr->value_list);
+	if (enable && err >= 0) {
+		list_add_tail(&device->active_list, &uc_mgr->active_devices);
+	} else if (!enable) {
+		list_del(&device->active_list);
+	}
+	return err;
+}
+
+/**
+ * \brief Init sound card use case manager.
+ * \param uc_mgr Returned use case manager pointer
+ * \param card_name name of card to open
+ * \return zero on success, otherwise a negative error code
+ */
+int snd_use_case_mgr_open(snd_use_case_mgr_t **mgr,
+			  const char *card_name)
+{
+	snd_use_case_mgr_t *uc_mgr;
+	int err;
+
+	/* create a new UCM */
+	uc_mgr = calloc(1, sizeof(snd_use_case_mgr_t));
+	if (uc_mgr == NULL)
+		return -ENOMEM;
+	INIT_LIST_HEAD(&uc_mgr->verb_list);
+	INIT_LIST_HEAD(&uc_mgr->default_list);
+	INIT_LIST_HEAD(&uc_mgr->value_list);
+	INIT_LIST_HEAD(&uc_mgr->active_modifiers);
+	INIT_LIST_HEAD(&uc_mgr->active_devices);
+	pthread_mutex_init(&uc_mgr->mutex, NULL);
+
+	uc_mgr->card_name = strdup(card_name);
+	if (uc_mgr->card_name == NULL) {
+		free(uc_mgr);
+		return -ENOMEM;
+	}
+
+	/* get info on use_cases and verify against card */
+	err = import_master_config(uc_mgr);
+	if (err < 0) {
+		uc_error("error: failed to import %s use case configuration %d",
+			card_name, err);
+		goto err;
+	}
+
+	*mgr = uc_mgr;
+	return 0;
+
+err:
+	uc_mgr_free(uc_mgr);
+	return err;
+}
+
+/**
+ * \brief Reload and reparse all use case files.
+ * \param uc_mgr Use case manager
+ * \return zero on success, otherwise a negative error code
+ */
+int snd_use_case_mgr_reload(snd_use_case_mgr_t *uc_mgr)
+{
+	int err;
+
+	pthread_mutex_lock(&uc_mgr->mutex);
+
+	uc_mgr_free_verb(uc_mgr);
+
+	/* reload all use cases */
+	err = import_master_config(uc_mgr);
+	if (err < 0) {
+		uc_error("error: failed to reload use cases\n");
+		pthread_mutex_unlock(&uc_mgr->mutex);
+		return -EINVAL;
+	}
+
+	pthread_mutex_unlock(&uc_mgr->mutex);
+	return err;
+}
+
+/**
+ * \brief Close use case manager.
+ * \param uc_mgr Use case manager
+ * \return zero on success, otherwise a negative error code
+ */
+int snd_use_case_mgr_close(snd_use_case_mgr_t *uc_mgr)
+{
+	uc_mgr_free(uc_mgr);
+
+	return 0;
+}
+
+/*
+ * Tear down current use case verb, device and modifier.
+ */
+static int dismantle_use_case(snd_use_case_mgr_t *uc_mgr)
+{
+	struct list_head *pos, *npos;
+	struct use_case_modifier *modifier;
+	struct use_case_device *device;
+	int err;
+
+	list_for_each_safe(pos, npos, &uc_mgr->active_modifiers) {
+		modifier = list_entry(pos, struct use_case_modifier,
+				      active_list);
+		err = set_modifier(uc_mgr, modifier, 0);
+		if (err < 0)
+			uc_error("Unable to disable modifier %s", modifier->name);
+	}
+	INIT_LIST_HEAD(&uc_mgr->active_modifiers);
+
+	list_for_each_safe(pos, npos, &uc_mgr->active_devices) {
+		device = list_entry(pos, struct use_case_device,
+				    active_list);
+		err = set_device(uc_mgr, device, 0);
+		if (err < 0)
+			uc_error("Unable to disable device %s", device->name);
+	}
+	INIT_LIST_HEAD(&uc_mgr->active_devices);
+
+	err = set_verb(uc_mgr, uc_mgr->active_verb, 0);
+	if (err < 0) {
+		uc_error("Unable to disable verb %s", uc_mgr->active_verb->name);
+		return err;
+	}
+	uc_mgr->active_verb = NULL;
+
+	err = execute_sequence(uc_mgr, &uc_mgr->default_list,
+			       &uc_mgr->value_list, NULL, NULL);
+	
+	return err;
+}
+
+/**
+ * \brief Reset sound card controls to default values.
+ * \param uc_mgr Use case manager
+ * \return zero on success, otherwise a negative error code
+ */
+int snd_use_case_mgr_reset(snd_use_case_mgr_t *uc_mgr)
+{
+        int err;
+
+	pthread_mutex_lock(&uc_mgr->mutex);
+	err = execute_sequence(uc_mgr, &uc_mgr->default_list,
+			       &uc_mgr->value_list, NULL, NULL);
+	INIT_LIST_HEAD(&uc_mgr->active_modifiers);
+	INIT_LIST_HEAD(&uc_mgr->active_devices);
+	uc_mgr->active_verb = NULL;
+	pthread_mutex_unlock(&uc_mgr->mutex);
+	return err;
+}
+
+/**
+ * \brief Get list of verbs in pair verbname+comment
+ * \param list Returned list
+ * \param verbname For verb (NULL = current)
+ * \return Number of list entries if success, otherwise a negative error code
+ */
+static int get_verb_list(snd_use_case_mgr_t *uc_mgr, const char **list[])
+{
+        return get_list2(&uc_mgr->verb_list, list,
+                         struct use_case_verb, list,
+                         name, comment);
+}
+
+/**
+ * \brief Get list of devices in pair devicename+comment
+ * \param list Returned list
+ * \param verbname For verb (NULL = current)
+ * \return Number of list entries if success, otherwise a negative error code
+ */
+static int get_device_list(snd_use_case_mgr_t *uc_mgr, const char **list[],
+                           char *verbname)
+{
+        struct use_case_verb *verb;
+        
+        if (verbname) {
+                verb = find_verb(uc_mgr, verbname);
+        } else {
+                verb = uc_mgr->active_verb;
+        }
+        if (verb == NULL)
+                return -ENOENT;
+        return get_list2(&verb->device_list, list,
+                         struct use_case_device, list,
+                         name, comment);
+}
+
+/**
+ * \brief Get list of modifiers in pair devicename+comment
+ * \param list Returned list
+ * \param verbname For verb (NULL = current)
+ * \return Number of list entries if success, otherwise a negative error code
+ */
+static int get_modifier_list(snd_use_case_mgr_t *uc_mgr, const char **list[],
+                             char *verbname)
+{
+        struct use_case_verb *verb;
+        
+        if (verbname) {
+                verb = find_verb(uc_mgr, verbname);
+        } else {
+                verb = uc_mgr->active_verb;
+        }
+        if (verb == NULL)
+                return -ENOENT;
+        return get_list2(&verb->modifier_list, list,
+                         struct use_case_modifier, list,
+                         name, comment);
+}
+
+/**
+ * \brief Get list of supported/conflicting devices
+ * \param list Returned list
+ * \param name Name of modifier or verb to query
+ * \param type Type of device list entries to return
+ * \return Number of list entries if success, otherwise a negative error code
+ */
+static int get_supcon_device_list(snd_use_case_mgr_t *uc_mgr,
+				  const char **list[], char *name,
+				  enum dev_list_type type)
+{
+	char *str;
+	struct use_case_verb *verb;
+	struct use_case_modifier *modifier;
+	struct use_case_device *device;
+
+	if (!name)
+		return -ENOENT;
+
+	str = strchr(name, '/');
+	if (str) {
+		*str = '\0';
+		verb = find_verb(uc_mgr, str + 1);
+	}
+	else {
+		verb = uc_mgr->active_verb;
+	}
+	if (!verb)
+		return -ENOENT;
+
+	modifier = find_modifier(uc_mgr, verb, name, 0);
+	if (modifier) {
+		if (modifier->dev_list.type != type)
+			return 0;
+		return get_list(&modifier->dev_list.list, list,
+				struct dev_list_node, list,
+				name);
+	}
+
+	device = find_device(uc_mgr, verb, name, 0);
+	if (device) {
+		if (device->dev_list.type != type)
+			return 0;
+		return get_list(&device->dev_list.list, list,
+				struct dev_list_node, list,
+				name);
+	}
+
+	return -ENOENT;
+
+}
+
+/**
+ * \brief Get list of supported devices
+ * \param list Returned list
+ * \param name Name of verb or modifier to query
+ * \return Number of list entries if success, otherwise a negative error code
+ */
+static int get_supported_device_list(snd_use_case_mgr_t *uc_mgr,
+				     const char **list[], char *name)
+{
+	return get_supcon_device_list(uc_mgr, list, name, DEVLIST_SUPPORTED);
+}
+
+/**
+ * \brief Get list of conflicting devices
+ * \param list Returned list
+ * \param name Name of verb or modifier to query
+ * \return Number of list entries if success, otherwise a negative error code
+ */
+static int get_conflicting_device_list(snd_use_case_mgr_t *uc_mgr,
+				       const char **list[], char *name)
+{
+	return get_supcon_device_list(uc_mgr, list, name, DEVLIST_CONFLICTING);
+}
+
+struct myvalue {
+        struct list_head list;
+        char *value;
+};
+
+static int add_values(struct list_head *list,
+                      const char *identifier,
+                      struct list_head *source)
+{
+        struct ucm_value *v;
+        struct myvalue *val;
+        struct list_head *pos, *pos1;
+        int match;
+        
+        list_for_each(pos, source) {
+                v = list_entry(pos, struct ucm_value, list);
+                if (check_identifier(identifier, v->name)) {
+                        match = 0;
+                        list_for_each(pos1, list) {
+                                val = list_entry(pos1, struct myvalue, list);
+                                if (strcmp(val->value, v->data) == 0) {
+                                        match = 1;
+                                        break;
+                                }
+                        }
+                        if (!match) {
+                                val = malloc(sizeof(struct myvalue));
+                                if (val == NULL)
+                                        return -ENOMEM;
+				val->value = v->data;
+                                list_add_tail(&val->list, list);
+                        }
+                }
+        }
+        return 0;
+}
+
+/**
+ * \brief Get list of values
+ * \param list Returned list
+ * \param verbname For verb (NULL = current)
+ * \return Number of list entries if success, otherwise a negative error code
+ */
+static int get_value_list(snd_use_case_mgr_t *uc_mgr,
+                          const char *identifier,
+                          const char **list[],
+                          char *verbname)
+{
+        struct list_head mylist, *pos, *npos;
+        struct myvalue *val;
+        struct use_case_verb *verb;
+        struct use_case_device *dev;
+        struct use_case_modifier *mod;
+        char **res;
+        int err;
+        
+        if (verbname) {
+                verb = find_verb(uc_mgr, verbname);
+        } else {
+                verb = uc_mgr->active_verb;
+        }
+        if (verb == NULL)
+                return -ENOENT;
+        INIT_LIST_HEAD(&mylist);
+	err = add_values(&mylist, identifier, &uc_mgr->value_list);
+	if (err < 0)
+		goto __fail;
+        err = add_values(&mylist, identifier, &verb->value_list);
+        if (err < 0)
+                goto __fail;
+        list_for_each(pos, &verb->device_list) {
+                dev = list_entry(pos, struct use_case_device, list);
+                err = add_values(&mylist, identifier, &dev->value_list);
+                if (err < 0)
+                        goto __fail;
+        }
+        list_for_each(pos, &verb->modifier_list) {
+                mod = list_entry(pos, struct use_case_modifier, list);
+                err = add_values(&mylist, identifier, &mod->value_list);
+                if (err < 0)
+                        goto __fail;
+        }
+        err = alloc_str_list(&mylist, 1, &res);
+        if (err >= 0) {
+	        *list = (const char **)res;
+                list_for_each(pos, &mylist) {
+                        val = list_entry(pos, struct myvalue, list);
+                        *res = strdup(val->value);
+                        if (*res == NULL) {
+                                snd_use_case_free_list((const char **)res, err);
+                                err = -ENOMEM;
+                                goto __fail;
+                        }
+                        res++;
+                }
+        }
+      __fail:
+        list_for_each_safe(pos, npos, &mylist) {
+                val = list_entry(pos, struct myvalue, list);
+                list_del(&val->list);
+                free(val);
+        }
+        return err;
+}
+
+/**
+ * \brief Get list of enabled devices
+ * \param list Returned list
+ * \param verbname For verb (NULL = current)
+ * \return Number of list entries if success, otherwise a negative error code
+ */
+static int get_enabled_device_list(snd_use_case_mgr_t *uc_mgr,
+                                   const char **list[])
+{
+        if (uc_mgr->active_verb == NULL)
+                return -EINVAL;
+        return get_list(&uc_mgr->active_devices, list,
+                        struct use_case_device, active_list,
+                        name);
+}
+
+/**
+ * \brief Get list of enabled modifiers
+ * \param list Returned list
+ * \param verbname For verb (NULL = current)
+ * \return Number of list entries if success, otherwise a negative error code
+ */
+static int get_enabled_modifier_list(snd_use_case_mgr_t *uc_mgr,
+                                     const char **list[])
+{
+        if (uc_mgr->active_verb == NULL)
+                return -EINVAL;
+        return get_list(&uc_mgr->active_modifiers, list,
+                        struct use_case_modifier, active_list,
+                        name);
+}
+
+/**
+ * \brief Obtain a list of entries
+ * \param uc_mgr Use case manager (may be NULL - card list)
+ * \param identifier (may be NULL - card list)
+ * \param list Returned allocated list
+ * \return Number of list entries if success, otherwise a negative error code
+ */
+int snd_use_case_get_list(snd_use_case_mgr_t *uc_mgr,
+			  const char *identifier,
+			  const char **list[])
+{
+	char *str, *str1;
+	int err;
+
+	if (uc_mgr == NULL || identifier == NULL)
+		return uc_mgr_scan_master_configs(list);
+	pthread_mutex_lock(&uc_mgr->mutex);
+	if (strcmp(identifier, "_verbs") == 0)
+		err = get_verb_list(uc_mgr, list);
+        else if (strcmp(identifier, "_enadevs") == 0)
+        	err = get_enabled_device_list(uc_mgr, list);
+        else if (strcmp(identifier, "_enamods") == 0)
+                err = get_enabled_modifier_list(uc_mgr, list);
+        else {
+                str1 = strchr(identifier, '/');
+                if (str1) {
+                        str = strdup(str1 + 1);
+                	if (str == NULL) {
+                  		err = -ENOMEM;
+                		goto __end;
+                        }
+                } else {
+                        str = NULL;
+                }
+        	if (check_identifier(identifier, "_devices"))
+          		err = get_device_list(uc_mgr, list, str);
+                else if (check_identifier(identifier, "_modifiers"))
+                        err = get_modifier_list(uc_mgr, list, str);
+                else if (check_identifier(identifier, "_supporteddevs"))
+                        err = get_supported_device_list(uc_mgr, list, str);
+                else if (check_identifier(identifier, "_conflictingdevs"))
+                        err = get_conflicting_device_list(uc_mgr, list, str);
+		else if (identifier[0] == '_')
+			err = -ENOENT;
+                else
+                        err = get_value_list(uc_mgr, identifier, list, str);
+        	if (str)
+        		free(str);
+        }
+      __end:
+	pthread_mutex_unlock(&uc_mgr->mutex);
+	return err;
+}
+
+static int get_value1(const char **value, struct list_head *value_list,
+                      const char *identifier)
+{
+        struct ucm_value *val;
+        struct list_head *pos;
+        
+	if (!value_list)
+		return -ENOENT;
+
+        list_for_each(pos, value_list) {
+              val = list_entry(pos, struct ucm_value, list);
+              if (check_identifier(identifier, val->name)) {
+                      *value = strdup(val->data);
+                      if (*value == NULL)
+                              return -ENOMEM;
+                      return 0;
+              }
+        }
+        return -ENOENT;
+}
+
+static int get_value3(const char **value,
+		      const char *identifier,
+		      struct list_head *value_list1,
+		      struct list_head *value_list2,
+		      struct list_head *value_list3)
+{
+	int err;
+
+	err = get_value1(value, value_list1, identifier);
+	if (err >= 0 || err != -ENOENT)
+		return err;
+	err = get_value1(value, value_list2, identifier);
+	if (err >= 0 || err != -ENOENT)
+		return err;
+	err = get_value1(value, value_list3, identifier);
+	if (err >= 0 || err != -ENOENT)
+		return err;
+	return -ENOENT;
+}
+
+/**
+ * \brief Get value
+ * \param uc_mgr Use case manager
+ * \param identifier Value identifier (string)
+ * \param value Returned value string
+ * \param item Modifier or Device name (string)
+ * \return Zero on success (value is filled), otherwise a negative error code
+ */
+static int get_value(snd_use_case_mgr_t *uc_mgr,
+			const char *identifier,
+			const char **value,
+			const char *mod_dev_name,
+			const char *verb_name,
+			int exact)
+{
+	struct use_case_verb *verb;
+	struct use_case_modifier *mod;
+	struct use_case_device *dev;
+	int err;
+
+	if (mod_dev_name || verb_name || !exact) {
+		if (verb_name && strlen(verb_name)) {
+			verb = find_verb(uc_mgr, verb_name);
+		} else {
+			verb = uc_mgr->active_verb;
+		}
+		if (verb) {
+			if (mod_dev_name) {
+				mod = find_modifier(uc_mgr, verb,
+						    mod_dev_name, 0);
+				if (mod) {
+					err = get_value1(value,
+							 &mod->value_list,
+							 identifier);
+					if (err >= 0 || err != -ENOENT)
+						return err;
+				}
+
+				dev = find_device(uc_mgr, verb,
+						  mod_dev_name, 0);
+				if (dev) {
+					err = get_value1(value,
+							 &dev->value_list,
+							 identifier);
+					if (err >= 0 || err != -ENOENT)
+						return err;
+				}
+
+				if (exact)
+					return -ENOENT;
+			}
+
+			err = get_value1(value, &verb->value_list, identifier);
+			if (err >= 0 || err != -ENOENT)
+				return err;
+		}
+
+		if (exact)
+			return -ENOENT;
+	}
+
+	err = get_value1(value, &uc_mgr->value_list, identifier);
+	if (err >= 0 || err != -ENOENT)
+		return err;
+
+	return -ENOENT;
+}
+
+/**
+ * \brief Get current - string
+ * \param uc_mgr Use case manager
+ * \param identifier 
+ * \param value Value pointer
+ * \return Zero if success, otherwise a negative error code
+ *
+ * Note: String is dynamically allocated, use free() to
+ * deallocate this string.
+ */      
+int snd_use_case_get(snd_use_case_mgr_t *uc_mgr,
+		     const char *identifier,
+		     const char **value)
+{
+	const char *slash1, *slash2, *mod_dev_after;
+	const char *ident, *mod_dev, *verb;
+	int exact = 0;
+        int err;
+
+	pthread_mutex_lock(&uc_mgr->mutex);
+	if (identifier == NULL) {
+	        *value = strdup(uc_mgr->card_name);
+	        if (*value == NULL) {
+	                err = -ENOMEM;
+	                goto __end;
+                }
+                err = 0;
+        } else if (strcmp(identifier, "_verb") == 0) {
+                if (uc_mgr->active_verb == NULL) {
+                        err = -ENOENT;
+			goto __end;
+		}
+                *value = strdup(uc_mgr->active_verb->name);
+                if (*value == NULL) {
+                        err = -ENOMEM;
+                        goto __end;
+                }
+	        err = 0;
+	} else if (identifier[0] == '_') {
+		err = -ENOENT;
+		goto __end;
+        } else {
+		if (identifier[0] == '=') {
+			exact = 1;
+			identifier++;
+		}
+
+		slash1 = strchr(identifier, '/');
+		if (slash1) {
+			ident = strndup(identifier, slash1 - identifier);
+
+			slash2 = strchr(slash1 + 1, '/');
+			if (slash2) {
+				mod_dev_after = slash2;
+				verb = slash2 + 1;
+			}
+			else {
+				mod_dev_after = slash1 + strlen(slash1);
+				verb = NULL;
+			}
+
+			if (mod_dev_after == slash1 + 1)
+				mod_dev = NULL;
+			else
+				mod_dev = strndup(slash1 + 1,
+						  mod_dev_after - (slash1 + 1));
+		}
+		else {
+			ident = identifier;
+			mod_dev = NULL;
+			verb = NULL;
+		}
+
+		err = get_value(uc_mgr, ident, value, mod_dev, verb, exact);
+		if (ident != identifier)
+			free((void *)ident);
+		if (mod_dev)
+			free((void *)mod_dev);
+        }
+      __end:
+	pthread_mutex_unlock(&uc_mgr->mutex);
+        return err;
+}
+
+long device_status(snd_use_case_mgr_t *uc_mgr,
+                   const char *device_name)
+{
+        struct use_case_device *dev;
+        struct list_head *pos;
+        
+        list_for_each(pos, &uc_mgr->active_devices) {
+                dev = list_entry(pos, struct use_case_device, active_list);
+                if (strcmp(dev->name, device_name) == 0)
+                        return 1;
+        }
+        return 0;
+}
+
+long modifier_status(snd_use_case_mgr_t *uc_mgr,
+                     const char *modifier_name)
+{
+        struct use_case_modifier *mod;
+        struct list_head *pos;
+        
+        list_for_each(pos, &uc_mgr->active_modifiers) {
+                mod = list_entry(pos, struct use_case_modifier, active_list);
+                if (strcmp(mod->name, modifier_name) == 0)
+                        return 1;
+        }
+        return 0;
+}
+
+
+/**
+ * \brief Get current - integer
+ * \param uc_mgr Use case manager
+ * \param identifier 
+ * \return Value if success, otherwise a negative error code 
+ */
+int snd_use_case_geti(snd_use_case_mgr_t *uc_mgr,
+		      const char *identifier,
+		      long *value)
+{
+        char *str, *str1;
+        long err;
+
+	pthread_mutex_lock(&uc_mgr->mutex);
+        if (0) {
+                /* nothing here - prepared for fixed identifiers */
+        } else {
+                str1 = strchr(identifier, '/');
+                if (str1) {
+                        str = strdup(str1 + 1);
+                	if (str == NULL) {
+                  		err = -ENOMEM;
+                		goto __end;
+                        }
+                } else {
+                        str = NULL;
+                }
+                if (check_identifier(identifier, "_devstatus")) {
+                        err = device_status(uc_mgr, str);
+			if (err >= 0) {
+				*value = err;
+				err = 0;
+			}
+		} else if (check_identifier(identifier, "_modstatus")) {
+                        err = modifier_status(uc_mgr, str);
+			if (err >= 0) {
+				*value = err;
+				err = 0;
+			}
+#if 0
+		/*
+		 * enable this block if the else clause below is expanded to query
+		 * user-supplied values
+		 */
+		} else if (identifier[0] == '_')
+			err = -ENOENT;
+#endif
+		} else
+                        err = -ENOENT;
+                if (str)
+                        free(str);
+        }
+      __end:
+	pthread_mutex_unlock(&uc_mgr->mutex);
+        return err;
+}
+
+static int handle_transition_verb(snd_use_case_mgr_t *uc_mgr,
+                                  struct use_case_verb *new_verb)
+{
+        struct list_head *pos;
+        struct transition_sequence *trans;
+        int err;
+
+        list_for_each(pos, &uc_mgr->active_verb->transition_list) {
+                trans = list_entry(pos, struct transition_sequence, list);
+                if (strcmp(trans->name, new_verb->name) == 0) {
+                        err = execute_sequence(uc_mgr, &trans->transition_list,
+					       &uc_mgr->active_verb->value_list,
+					       &uc_mgr->value_list,
+					       NULL);
+                        if (err >= 0)
+                                return 1;
+                        return err;
+                }
+        }
+        return 0;
+}
+
+static int set_verb_user(snd_use_case_mgr_t *uc_mgr,
+                         const char *verb_name)
+{
+        struct use_case_verb *verb;
+        int err;
+
+        if (uc_mgr->active_verb &&
+            strcmp(uc_mgr->active_verb->name, verb_name) == 0)
+                return 0;
+        if (strcmp(verb_name, SND_USE_CASE_VERB_INACTIVE) != 0) {
+                verb = find_verb(uc_mgr, verb_name);
+                if (verb == NULL)
+                        return -ENOENT;
+        } else {
+                verb = NULL;
+        }
+        if (uc_mgr->active_verb) {
+                err = handle_transition_verb(uc_mgr, verb);
+                if (err == 0) {
+                        err = dismantle_use_case(uc_mgr);
+                        if (err < 0)
+                                return err;
+                } else if (err == 1) {
+                        uc_mgr->active_verb = verb;
+                        verb = NULL;
+                } else {
+                        verb = NULL; /* show error */
+                }
+        }
+        if (verb) {
+                err = set_verb(uc_mgr, verb, 1);
+                if (err < 0)
+                        uc_error("error: failed to initialize new use case: %s",
+                                 verb_name);
+        }
+        return err;
+}
+
+
+static int set_device_user(snd_use_case_mgr_t *uc_mgr,
+                           const char *device_name,
+                           int enable)
+{
+        struct use_case_device *device;
+
+        if (uc_mgr->active_verb == NULL)
+                return -ENOENT;
+        device = find_device(uc_mgr, uc_mgr->active_verb, device_name, 1);
+        if (device == NULL)
+                return -ENOENT;
+        return set_device(uc_mgr, device, enable);
+}
+
+static int set_modifier_user(snd_use_case_mgr_t *uc_mgr,
+                             const char *modifier_name,
+                             int enable)
+{
+        struct use_case_modifier *modifier;
+
+        if (uc_mgr->active_verb == NULL)
+                return -ENOENT;
+
+        modifier = find_modifier(uc_mgr, uc_mgr->active_verb, modifier_name, 1);
+        if (modifier == NULL)
+                return -ENOENT;
+        return set_modifier(uc_mgr, modifier, enable);
+}
+
+static int switch_device(snd_use_case_mgr_t *uc_mgr,
+                         const char *old_device,
+                         const char *new_device)
+{
+        struct use_case_device *xold, *xnew;
+        struct transition_sequence *trans;
+        struct list_head *pos;
+        int err, seq_found = 0;
+        
+        if (uc_mgr->active_verb == NULL)
+                return -ENOENT;
+        if (device_status(uc_mgr, old_device) == 0) {
+                uc_error("error: device %s not enabled", old_device);
+                return -EINVAL;
+        }
+        if (device_status(uc_mgr, new_device) != 0) {
+                uc_error("error: device %s already enabled", new_device);
+                return -EINVAL;
+        }
+        xold = find_device(uc_mgr, uc_mgr->active_verb, old_device, 1);
+        if (xold == NULL)
+                return -ENOENT;
+        list_del(&xold->active_list);
+        xnew = find_device(uc_mgr, uc_mgr->active_verb, new_device, 1);
+        list_add_tail(&xold->active_list, &uc_mgr->active_devices);
+        if (xnew == NULL)
+                return -ENOENT;
+        err = 0;
+        list_for_each(pos, &xold->transition_list) {
+                trans = list_entry(pos, struct transition_sequence, list);
+                if (strcmp(trans->name, new_device) == 0) {
+                        err = execute_sequence(uc_mgr, &trans->transition_list,
+					       &xold->value_list,
+					       &uc_mgr->active_verb->value_list,
+					       &uc_mgr->value_list);
+                        if (err >= 0) {
+                                list_del(&xold->active_list);
+                                list_add_tail(&xnew->active_list, &uc_mgr->active_devices);
+                        }
+                        seq_found = 1;
+                        break;
+                }
+        }
+        if (!seq_found) {
+                err = set_device(uc_mgr, xold, 0);
+                if (err < 0)
+                        return err;
+                err = set_device(uc_mgr, xnew, 1);
+                if (err < 0)
+                        return err;
+        }
+        return err;
+}
+
+static int switch_modifier(snd_use_case_mgr_t *uc_mgr,
+                           const char *old_modifier,
+                           const char *new_modifier)
+{
+        struct use_case_modifier *xold, *xnew;
+        struct transition_sequence *trans;
+        struct list_head *pos;
+        int err, seq_found = 0;
+        
+        if (uc_mgr->active_verb == NULL)
+                return -ENOENT;
+        if (modifier_status(uc_mgr, old_modifier) == 0) {
+                uc_error("error: modifier %s not enabled", old_modifier);
+                return -EINVAL;
+        }
+        if (modifier_status(uc_mgr, new_modifier) != 0) {
+                uc_error("error: modifier %s already enabled", new_modifier);
+                return -EINVAL;
+        }
+        xold = find_modifier(uc_mgr, uc_mgr->active_verb, old_modifier, 1);
+        if (xold == NULL)
+                return -ENOENT;
+        xnew = find_modifier(uc_mgr, uc_mgr->active_verb, new_modifier, 1);
+        if (xnew == NULL)
+                return -ENOENT;
+        err = 0;
+        list_for_each(pos, &xold->transition_list) {
+                trans = list_entry(pos, struct transition_sequence, list);
+                if (strcmp(trans->name, new_modifier) == 0) {
+                        err = execute_sequence(uc_mgr, &trans->transition_list,
+					       &xold->value_list,
+					       &uc_mgr->active_verb->value_list,
+					       &uc_mgr->value_list);
+                        if (err >= 0) {
+                                list_del(&xold->active_list);
+                                list_add_tail(&xnew->active_list, &uc_mgr->active_modifiers);
+                        }
+                        seq_found = 1;
+                        break;
+                }
+        }
+        if (!seq_found) {
+                err = set_modifier(uc_mgr, xold, 0);
+                if (err < 0)
+                        return err;
+                err = set_modifier(uc_mgr, xnew, 1);
+                if (err < 0)
+                        return err;
+        }
+        return err;        
+}
+
+/**
+ * \brief Set new
+ * \param uc_mgr Use case manager
+ * \param identifier
+ * \param value Value
+ * \return Zero if success, otherwise a negative error code
+ */
+int snd_use_case_set(snd_use_case_mgr_t *uc_mgr,
+                     const char *identifier,
+                     const char *value)
+{
+	char *str, *str1;
+	int err;
+
+	pthread_mutex_lock(&uc_mgr->mutex);
+	if (strcmp(identifier, "_verb") == 0)
+	        err = set_verb_user(uc_mgr, value);
+        else if (strcmp(identifier, "_enadev") == 0)
+                err = set_device_user(uc_mgr, value, 1);
+        else if (strcmp(identifier, "_disdev") == 0)
+                err = set_device_user(uc_mgr, value, 0);
+        else if (strcmp(identifier, "_enamod") == 0)
+                err = set_modifier_user(uc_mgr, value, 1);
+        else if (strcmp(identifier, "_dismod") == 0)
+                err = set_modifier_user(uc_mgr, value, 0);
+        else {
+                str1 = strchr(identifier, '/');
+                if (str1) {
+                        str = strdup(str1 + 1);
+                	if (str == NULL) {
+                  		err = -ENOMEM;
+                		goto __end;
+                        }
+                } else {
+                        str = NULL;
+                }
+                if (check_identifier(identifier, "_swdev"))
+                        err = switch_device(uc_mgr, str, value);
+                else if (check_identifier(identifier, "_swmod"))
+                        err = switch_modifier(uc_mgr, str, value);
+                else
+                        err = -EINVAL;
+                if (str)
+                        free(str);
+        }
+      __end:
+	pthread_mutex_unlock(&uc_mgr->mutex);
+        return err;
+}
diff --git a/src/ucm/parser.c b/src/ucm/parser.c
new file mode 100644
index 0000000..b93d832
--- /dev/null
+++ b/src/ucm/parser.c
@@ -0,0 +1,1329 @@
+/*
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software  
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ *  Support for the verb/device/modifier core logic and API,
+ *  command line tool and file parser was kindly sponsored by
+ *  Texas Instruments Inc.
+ *  Support for multiple active modifiers and devices,
+ *  transition sequences, multiple client access and user defined use
+ *  cases was kindly sponsored by Wolfson Microelectronics PLC.
+ *
+ *  Copyright (C) 2008-2010 SlimLogic Ltd
+ *  Copyright (C) 2010 Wolfson Microelectronics PLC
+ *  Copyright (C) 2010 Texas Instruments Inc.
+ *  Copyright (C) 2010 Red Hat Inc.
+ *  Authors: Liam Girdwood <lrg@slimlogic.co.uk>
+ *	         Stefan Schmidt <stefan@slimlogic.co.uk>
+ *	         Justin Xu <justinx@slimlogic.co.uk>
+ *               Jaroslav Kysela <perex@perex.cz>
+ */
+
+#include "ucm_local.h"
+#include <dirent.h>
+
+/** The name of the environment variable containing the UCM directory */
+#define ALSA_CONFIG_UCM_VAR "ALSA_CONFIG_UCM"
+
+static int parse_sequence(snd_use_case_mgr_t *uc_mgr,
+			  struct list_head *base,
+			  snd_config_t *cfg);
+
+/*
+ * Parse string
+ */
+int parse_string(snd_config_t *n, char **res)
+{
+	int err;
+
+	err = snd_config_get_string(n, (const char **)res);
+	if (err < 0)
+		return err;
+	*res = strdup(*res);
+	if (*res == NULL)
+		return -ENOMEM;
+	return 0;
+}
+
+/*
+ * Parse safe ID
+ */
+int parse_is_name_safe(const char *name)
+{
+	if (strchr(name, '.')) {
+		uc_error("char '.' not allowed in '%s'", name);
+		return 0;
+	}
+	return 1;
+}
+
+int parse_get_safe_id(snd_config_t *n, const char **id)
+{
+	int err;
+
+	err = snd_config_get_id(n, id);
+	if (err < 0)
+		return err;
+	if (!parse_is_name_safe((char *)(*id)))
+		return -EINVAL;
+	return 0;
+}
+
+/*
+ * Parse transition
+ */
+static int parse_transition(snd_use_case_mgr_t *uc_mgr,
+			    struct list_head *tlist,
+			    snd_config_t *cfg)
+{
+	struct transition_sequence *tseq;
+	const char *id;
+	snd_config_iterator_t i, next;
+	snd_config_t *n;
+	int err;
+
+	if (snd_config_get_id(cfg, &id) < 0)
+		return -EINVAL;
+
+	if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
+		uc_error("compound type expected for %s", id);
+		return -EINVAL;
+	}
+
+	snd_config_for_each(i, next, cfg) {
+		n = snd_config_iterator_entry(i);
+
+		if (snd_config_get_id(n, &id) < 0)
+			return -EINVAL;
+
+		tseq = calloc(1, sizeof(*tseq));
+		if (tseq == NULL)
+			return -ENOMEM;
+		INIT_LIST_HEAD(&tseq->transition_list);
+
+		tseq->name = strdup(id);
+		if (tseq->name == NULL) {
+			free(tseq);
+			return -ENOMEM;
+		}
+	
+		err = parse_sequence(uc_mgr, &tseq->transition_list, n);
+		if (err < 0) {
+			uc_mgr_free_transition_element(tseq);
+			return err;
+		}
+
+		list_add(&tseq->list, tlist);
+	}
+	return 0;
+}
+
+/*
+ * Parse compound
+ */
+static int parse_compound(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg,
+	  int (*fcn)(snd_use_case_mgr_t *, snd_config_t *, void *, void *),
+	  void *data1, void *data2)
+{
+	const char *id;
+	snd_config_iterator_t i, next;
+	snd_config_t *n;
+	int err;
+
+	if (snd_config_get_id(cfg, &id) < 0)
+		return -EINVAL;
+	
+	if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
+		uc_error("compound type expected for %s", id);
+		return -EINVAL;
+	}
+	/* parse compound */
+	snd_config_for_each(i, next, cfg) {
+		n = snd_config_iterator_entry(i);
+
+		if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
+			uc_error("compound type expected for %s, is %d", id, snd_config_get_type(cfg));
+			return -EINVAL;
+		}
+		
+		err = fcn(uc_mgr, n, data1, data2);
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+
+static int strip_legacy_dev_index(char *name)
+{
+	char *dot = strchr(name, '.');
+	if (!dot)
+		return 0;
+	if (dot[1] != '0' || dot[2] != '\0') {
+		uc_error("device name %s contains a '.',"
+			 " and is not legacy foo.0 format", name);
+		return -EINVAL;
+	}
+	*dot = '\0';
+	return 0;
+}
+
+/*
+ * Parse device list
+ */
+static int parse_device_list(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED,
+			     struct dev_list *dev_list,
+			     enum dev_list_type type,
+			     snd_config_t *cfg)
+{
+	struct dev_list_node *sdev;
+	const char *id;
+	snd_config_iterator_t i, next;
+	snd_config_t *n;
+	int err;
+
+	if (dev_list->type != DEVLIST_NONE) {
+		uc_error("error: multiple supported or"
+			" conflicting device lists");
+		return -EEXIST;
+	}
+
+	if (snd_config_get_id(cfg, &id) < 0)
+		return -EINVAL;
+
+	if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
+		uc_error("compound type expected for %s", id);
+		return -EINVAL;
+	}
+
+	snd_config_for_each(i, next, cfg) {
+		n = snd_config_iterator_entry(i);
+
+		if (snd_config_get_id(n, &id) < 0)
+			return -EINVAL;
+
+		sdev = calloc(1, sizeof(struct dev_list_node));
+		if (sdev == NULL)
+			return -ENOMEM;
+		err = parse_string(n, &sdev->name);
+		if (err < 0) {
+			free(sdev);
+			return err;
+		}
+		err = strip_legacy_dev_index(sdev->name);
+		if (err < 0) {
+			free(sdev->name);
+			free(sdev);
+			return err;
+		}
+		list_add(&sdev->list, &dev_list->list);
+	}
+
+	dev_list->type = type;
+
+	return 0;
+}
+
+/*
+ * Parse sequences.
+ *
+ * Sequence controls elements  are in the following form:-
+ *
+ * cdev "hw:0"
+ * cset "element_id_syntax value_syntax"
+ * usleep time
+ * exec "any unix command with arguments"
+ *
+ * e.g.
+ *	cset "name='Master Playback Switch' 0,0"
+ *      cset "iface=PCM,name='Disable HDMI',index=1 0"
+ */
+static int parse_sequence(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED,
+			  struct list_head *base,
+			  snd_config_t *cfg)
+{
+	struct sequence_element *curr;
+	snd_config_iterator_t i, next;
+	snd_config_t *n;
+	int err, idx = 0;
+	const char *cmd = NULL;
+
+	if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
+		uc_error("error: compound is expected for sequence definition");
+		return -EINVAL;
+	}
+
+	snd_config_for_each(i, next, cfg) {
+		const char *id;
+		idx ^= 1;
+		n = snd_config_iterator_entry(i);
+		err = snd_config_get_id(n, &id);
+		if (err < 0)
+			continue;
+		if (idx == 1) {
+			if (snd_config_get_type(n) != SND_CONFIG_TYPE_STRING) {
+				uc_error("error: string type is expected for sequence command");
+				return -EINVAL;
+			}
+			snd_config_get_string(n, &cmd);
+			continue;
+		}
+
+		/* alloc new sequence element */
+		curr = calloc(1, sizeof(struct sequence_element));
+		if (curr == NULL)
+			return -ENOMEM;
+		list_add_tail(&curr->list, base);
+
+		if (strcmp(cmd, "cdev") == 0) {
+			curr->type = SEQUENCE_ELEMENT_TYPE_CDEV;
+			err = parse_string(n, &curr->data.cdev);
+			if (err < 0) {
+				uc_error("error: cdev requires a string!");
+				return err;
+			}
+			continue;
+		}
+
+		if (strcmp(cmd, "cset") == 0) {
+			curr->type = SEQUENCE_ELEMENT_TYPE_CSET;
+			err = parse_string(n, &curr->data.cset);
+			if (err < 0) {
+				uc_error("error: cset requires a string!");
+				return err;
+			}
+			continue;
+		}
+
+		if (strcmp(cmd, "usleep") == 0) {
+			curr->type = SEQUENCE_ELEMENT_TYPE_SLEEP;
+			err = snd_config_get_integer(n, &curr->data.sleep);
+			if (err < 0) {
+				uc_error("error: usleep requires integer!");
+				return err;
+			}
+			continue;
+		}
+
+		if (strcmp(cmd, "msleep") == 0) {
+			curr->type = SEQUENCE_ELEMENT_TYPE_SLEEP;
+			err = snd_config_get_integer(n, &curr->data.sleep);
+			if (err < 0) {
+				uc_error("error: msleep requires integer!");
+				return err;
+			}
+			curr->data.sleep *= 1000L;
+			continue;
+		}
+
+		if (strcmp(cmd, "exec") == 0) {
+			curr->type = SEQUENCE_ELEMENT_TYPE_EXEC;
+			err = parse_string(n, &curr->data.exec);
+			if (err < 0) {
+				uc_error("error: exec requires a string!");
+				return err;
+			}
+			continue;
+		}
+		
+		list_del(&curr->list);
+		uc_mgr_free_sequence_element(curr);
+	}
+
+	return 0;
+}
+
+/*
+ * Parse values.
+ *
+ * Parse values describing PCM, control/mixer settings and stream parameters.
+ *
+ * Value {
+ *   TQ Voice
+ *   CapturePCM "hw:1"
+ *   PlaybackVolume "name='Master Playback Volume',index=2"
+ *   PlaybackSwitch "name='Master Playback Switch',index=2"
+ * }
+ */
+static int parse_value(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED,
+			  struct list_head *base,
+			  snd_config_t *cfg)
+{
+	struct ucm_value *curr;
+	snd_config_iterator_t i, next;
+	snd_config_t *n;
+	long l;
+	long long ll;
+	double d;
+	snd_config_type_t type;
+	int err;
+
+	if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
+		uc_error("error: compound is expected for value definition");
+		return -EINVAL;
+	}
+	snd_config_for_each(i, next, cfg) {
+		const char *id;
+		n = snd_config_iterator_entry(i);
+		err = snd_config_get_id(n, &id);
+		if (err < 0)
+			continue;
+
+		/* alloc new value */
+		curr = calloc(1, sizeof(struct ucm_value));
+		if (curr == NULL)
+			return -ENOMEM;
+		list_add_tail(&curr->list, base);
+		curr->name = strdup(id);
+		if (curr->name == NULL)
+			return -ENOMEM;
+		type = snd_config_get_type(n);
+		switch (type) {
+		case SND_CONFIG_TYPE_INTEGER:
+			curr->data = malloc(16);
+			if (curr->data == NULL)
+				return -ENOMEM;
+			snd_config_get_integer(n, &l);
+			sprintf(curr->data, "%li", l);
+			break;
+		case SND_CONFIG_TYPE_INTEGER64:
+			curr->data = malloc(32);
+			if (curr->data == NULL)
+				return -ENOMEM;
+			snd_config_get_integer64(n, &ll);
+			sprintf(curr->data, "%lli", ll);
+			break;
+		case SND_CONFIG_TYPE_REAL:
+			curr->data = malloc(64);
+			if (curr->data == NULL)
+				return -ENOMEM;
+			snd_config_get_real(n, &d);
+			sprintf(curr->data, "%-16g", d);
+			break;
+		case SND_CONFIG_TYPE_STRING:
+			err = parse_string(n, &curr->data);
+			if (err < 0) {
+				uc_error("error: unable to parse a string for id '%s'!", id);
+				return err;
+			}
+			break;
+		default:
+			uc_error("error: invalid type %i in Value compound", type);
+			return -EINVAL;
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * Parse Modifier Use cases
+ *
+ *	# Each modifier is described in new section. N modifiers are allowed
+ *	SectionModifier."Capture Voice" {
+ *
+ *		Comment "Record voice call"
+ *
+ *		SupportedDevice [
+ *			"x"
+ *			"y"
+ *		]
+ *
+ *		ConflictingDevice [
+ *			"x"
+ *			"y"
+ *		]
+ *
+ *		EnableSequence [
+ *			....
+ *		]
+ *
+ *		DisableSequence [
+ *			...
+ *		]
+ *
+ *              TransitionSequence."ToModifierName" [
+ *			...
+ *		]
+ *
+ *		# Optional TQ and ALSA PCMs
+ *		Value {
+ *			TQ Voice
+ *			CapturePCM "hw:1"
+ *			PlaybackVolume "name='Master Playback Volume',index=2"
+ *			PlaybackSwitch "name='Master Playback Switch',index=2"
+ *		}
+ *
+ *	 }
+ *
+ * SupportedDevice and ConflictingDevice cannot be specified together.
+ * Both are optional.
+ */
+static int parse_modifier(snd_use_case_mgr_t *uc_mgr,
+		snd_config_t *cfg,
+		void *data1,
+		void *data2)
+{
+	struct use_case_verb *verb = data1;
+	struct use_case_modifier *modifier;
+	const char *name;
+	snd_config_iterator_t i, next;
+	snd_config_t *n;
+	int err;
+
+	if (data2) {
+		name = data2;
+		if (!parse_is_name_safe(name))
+			return -EINVAL;
+	}
+	else {
+		if (parse_get_safe_id(cfg, &name) < 0)
+			return -EINVAL;
+	}
+
+	/* allocate modifier */
+	modifier = calloc(1, sizeof(*modifier));
+	if (modifier == NULL)
+		return -ENOMEM;
+	INIT_LIST_HEAD(&modifier->enable_list);
+	INIT_LIST_HEAD(&modifier->disable_list);
+	INIT_LIST_HEAD(&modifier->transition_list);
+	INIT_LIST_HEAD(&modifier->dev_list.list);
+	INIT_LIST_HEAD(&modifier->value_list);
+	list_add_tail(&modifier->list, &verb->modifier_list);
+	modifier->name = strdup(name);
+
+	snd_config_for_each(i, next, cfg) {
+		const char *id;
+		n = snd_config_iterator_entry(i);
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+
+		if (strcmp(id, "Comment") == 0) {
+			err = parse_string(n, &modifier->comment);
+			if (err < 0) {
+				uc_error("error: failed to get modifier comment");
+				return err;
+			}
+			continue;
+		}
+
+		if (strcmp(id, "SupportedDevice") == 0) {
+			err = parse_device_list(uc_mgr, &modifier->dev_list,
+						DEVLIST_SUPPORTED, n);
+			if (err < 0) {
+				uc_error("error: failed to parse supported"
+					" device list");
+				return err;
+			}
+		}
+
+		if (strcmp(id, "ConflictingDevice") == 0) {
+			err = parse_device_list(uc_mgr, &modifier->dev_list,
+						DEVLIST_CONFLICTING, n);
+			if (err < 0) {
+				uc_error("error: failed to parse conflicting"
+					" device list");
+				return err;
+			}
+		}
+
+		if (strcmp(id, "EnableSequence") == 0) {
+			err = parse_sequence(uc_mgr, &modifier->enable_list, n);
+			if (err < 0) {
+				uc_error("error: failed to parse modifier"
+					" enable sequence");
+				return err;
+			}
+			continue;
+		}
+
+		if (strcmp(id, "DisableSequence") == 0) {
+			err = parse_sequence(uc_mgr, &modifier->disable_list, n);
+			if (err < 0) {
+				uc_error("error: failed to parse modifier"
+					" disable sequence");
+				return err;
+			}
+			continue;
+		}
+
+		if (strcmp(id, "TransitionSequence") == 0) {
+			err = parse_transition(uc_mgr, &modifier->transition_list, n);
+			if (err < 0) {
+				uc_error("error: failed to parse transition"
+					" modifier");
+				return err;
+			}
+			continue;
+		}
+
+		if (strcmp(id, "Value") == 0) {
+			err = parse_value(uc_mgr, &modifier->value_list, n);
+			if (err < 0) {
+				uc_error("error: failed to parse Value");
+				return err;
+			}
+			continue;
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * Parse Device Use Cases
+ *
+ *# Each device is described in new section. N devices are allowed
+ *SectionDevice."Headphones" {
+ *	Comment "Headphones connected to 3.5mm jack"
+ *
+ *	upportedDevice [
+ *		"x"
+ *		"y"
+ *	]
+ *
+ *	ConflictingDevice [
+ *		"x"
+ *		"y"
+ *	]
+ *
+ *	EnableSequence [
+ *		....
+ *	]
+ *
+ *	DisableSequence [
+ *		...
+ *	]
+ *
+ *      TransitionSequence."ToDevice" [
+ *		...
+ *	]
+ *
+ *	Value {
+ *		PlaybackVolume "name='Master Playback Volume',index=2"
+ *		PlaybackSwitch "name='Master Playback Switch',index=2"
+ *	}
+ * }
+ */
+static int parse_device(snd_use_case_mgr_t *uc_mgr,
+			snd_config_t *cfg,
+			void *data1,
+			void *data2)
+{
+	struct use_case_verb *verb = data1;
+	const char *name;
+	struct use_case_device *device;
+	snd_config_iterator_t i, next;
+	snd_config_t *n;
+	int err;
+
+	if (data2) {
+		name = data2;
+		if (!parse_is_name_safe(name))
+			return -EINVAL;
+	}
+	else {
+		if (parse_get_safe_id(cfg, &name) < 0)
+			return -EINVAL;
+	}
+
+	device = calloc(1, sizeof(*device));
+	if (device == NULL)
+		return -ENOMEM;
+	INIT_LIST_HEAD(&device->enable_list);
+	INIT_LIST_HEAD(&device->disable_list);
+	INIT_LIST_HEAD(&device->transition_list);
+	INIT_LIST_HEAD(&device->dev_list.list);
+	INIT_LIST_HEAD(&device->value_list);
+	list_add_tail(&device->list, &verb->device_list);
+	device->name = strdup(name);
+
+	snd_config_for_each(i, next, cfg) {
+		const char *id;
+		n = snd_config_iterator_entry(i);
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+
+		if (strcmp(id, "Comment") == 0) {
+			err = parse_string(n, &device->comment);
+			if (err < 0) {
+				uc_error("error: failed to get device comment");
+				return err;
+			}
+			continue;
+		}
+
+		if (strcmp(id, "SupportedDevice") == 0) {
+			err = parse_device_list(uc_mgr, &device->dev_list,
+						DEVLIST_SUPPORTED, n);
+			if (err < 0) {
+				uc_error("error: failed to parse supported"
+					" device list");
+				return err;
+			}
+		}
+
+		if (strcmp(id, "ConflictingDevice") == 0) {
+			err = parse_device_list(uc_mgr, &device->dev_list,
+						DEVLIST_CONFLICTING, n);
+			if (err < 0) {
+				uc_error("error: failed to parse conflicting"
+					" device list");
+				return err;
+			}
+		}
+
+		if (strcmp(id, "EnableSequence") == 0) {
+			uc_dbg("EnableSequence");
+			err = parse_sequence(uc_mgr, &device->enable_list, n);
+			if (err < 0) {
+				uc_error("error: failed to parse device enable"
+					 " sequence");
+				return err;
+			}
+			continue;
+		}
+
+		if (strcmp(id, "DisableSequence") == 0) {
+			uc_dbg("DisableSequence");
+			err = parse_sequence(uc_mgr, &device->disable_list, n);
+			if (err < 0) {
+				uc_error("error: failed to parse device disable"
+					 " sequence");
+				return err;
+			}
+			continue;
+		}
+
+		if (strcmp(id, "TransitionSequence") == 0) {
+			uc_dbg("TransitionSequence");
+			err = parse_transition(uc_mgr, &device->transition_list, n);
+			if (err < 0) {
+				uc_error("error: failed to parse transition"
+					" device");
+				return err;
+			}
+			continue;
+		}
+
+		if (strcmp(id, "Value") == 0) {
+			err = parse_value(uc_mgr, &device->value_list, n);
+			if (err < 0) {
+				uc_error("error: failed to parse Value");
+				return err;
+			}
+			continue;
+		}
+	}
+	return 0;
+}
+
+static int parse_compound_check_legacy(snd_use_case_mgr_t *uc_mgr,
+	  snd_config_t *cfg,
+	  int (*fcn)(snd_use_case_mgr_t *, snd_config_t *, void *, void *),
+	  void *data1)
+{
+	const char *id, *idchild;
+	int child_ctr = 0, legacy_format = 1;
+	snd_config_iterator_t i, next;
+	snd_config_t *child;
+	int err;
+
+	err = snd_config_get_id(cfg, &id);
+	if (err < 0)
+		return err;
+
+	snd_config_for_each(i, next, cfg) {
+		child_ctr++;
+		if (child_ctr > 1) {
+			break;
+		}
+
+		child = snd_config_iterator_entry(i);
+
+		if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
+			legacy_format = 0;
+			break;
+		}
+
+		if (snd_config_get_id(child, &idchild) < 0)
+			return -EINVAL;
+
+		if (strcmp(idchild, "0")) {
+			legacy_format = 0;
+			break;
+		}
+	}
+	if (child_ctr != 1) {
+		legacy_format = 0;
+	}
+
+	if (legacy_format)
+		return parse_compound(uc_mgr, cfg, fcn, data1, (void *)id);
+	else
+		return fcn(uc_mgr, cfg, data1, NULL);
+}
+
+static int parse_device_name(snd_use_case_mgr_t *uc_mgr,
+			     snd_config_t *cfg,
+			     void *data1,
+			     void *data2 ATTRIBUTE_UNUSED)
+{
+	return parse_compound_check_legacy(uc_mgr, cfg, parse_device, data1);
+}
+
+static int parse_modifier_name(snd_use_case_mgr_t *uc_mgr,
+			     snd_config_t *cfg,
+			     void *data1,
+			     void *data2 ATTRIBUTE_UNUSED)
+{
+	return parse_compound_check_legacy(uc_mgr, cfg, parse_modifier, data1);
+}
+
+/*
+ * Parse Verb Section
+ *
+ * # Example Use case verb section for Voice call blah
+ * # By Joe Blogs <joe@blogs.com>
+ *
+ * SectionVerb {
+ *	# enable and disable sequences are compulsory
+ *	EnableSequence [
+ *		cset "name='Master Playback Switch',index=2 0,0"
+ *		cset "name='Master Playback Volume',index=2 25,25"
+ *		msleep 50
+ *		cset "name='Master Playback Switch',index=2 1,1"
+ *		cset "name='Master Playback Volume',index=2 50,50"
+ *	]
+ *
+ *	DisableSequence [
+ *		cset "name='Master Playback Switch',index=2 0,0"
+ *		cset "name='Master Playback Volume',index=2 25,25"
+ *		msleep 50
+ *		cset "name='Master Playback Switch',index=2 1,1"
+ *		cset "name='Master Playback Volume',index=2 50,50"
+ *	]
+ *
+ *      # Optional transition verb
+ *      TransitionSequence."ToCaseName" [
+ *		msleep 1
+ *      ]
+ *
+ *	# Optional TQ and ALSA PCMs
+ *	Value {
+ *		TQ HiFi
+ *		CapturePCM "hw:0"
+ *		PlaybackPCM "hw:0"
+ *	}
+ * }
+ */
+static int parse_verb(snd_use_case_mgr_t *uc_mgr,
+		      struct use_case_verb *verb,
+		      snd_config_t *cfg)
+{
+	snd_config_iterator_t i, next;
+	snd_config_t *n;
+	int err;
+	
+	/* parse verb section */
+	snd_config_for_each(i, next, cfg) {
+		const char *id;
+		n = snd_config_iterator_entry(i);
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+
+		if (strcmp(id, "EnableSequence") == 0) {
+			uc_dbg("Parse EnableSequence");
+			err = parse_sequence(uc_mgr, &verb->enable_list, n);
+			if (err < 0) {
+				uc_error("error: failed to parse verb enable sequence");
+				return err;
+			}
+			continue;
+		}
+
+		if (strcmp(id, "DisableSequence") == 0) {
+			uc_dbg("Parse DisableSequence");
+			err = parse_sequence(uc_mgr, &verb->disable_list, n);
+			if (err < 0) {
+				uc_error("error: failed to parse verb disable sequence");
+				return err;
+			}
+			continue;
+		}
+
+		if (strcmp(id, "TransitionSequence") == 0) {
+			uc_dbg("Parse TransitionSequence");
+			err = parse_transition(uc_mgr, &verb->transition_list, n);
+			if (err < 0) {
+				uc_error("error: failed to parse transition sequence");
+				return err;
+			}
+			continue;
+		}
+
+		if (strcmp(id, "Value") == 0) {
+			uc_dbg("Parse Value");
+			err = parse_value(uc_mgr, &verb->value_list, n);
+			if (err < 0)
+				return err;
+			continue;
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * Parse a Use case verb file.
+ *
+ * This file contains the following :-
+ *  o Verb enable and disable sequences.
+ *  o Supported Device enable and disable sequences for verb.
+ *  o Supported Modifier enable and disable sequences for verb
+ *  o Optional QoS for the verb and modifiers.
+ *  o Optional PCM device ID for verb and modifiers
+ *  o Alias kcontrols IDs for master and volumes and mutes.
+ */
+static int parse_verb_file(snd_use_case_mgr_t *uc_mgr,
+			   const char *use_case_name,
+			   const char *comment,
+			   const char *file)
+{
+	snd_config_iterator_t i, next;
+	snd_config_t *n;
+	struct use_case_verb *verb;
+	snd_config_t *cfg;
+	char filename[MAX_FILE];
+	char *env = getenv(ALSA_CONFIG_UCM_VAR);
+	int err;
+
+	/* allocate verb */
+	verb = calloc(1, sizeof(struct use_case_verb));
+	if (verb == NULL)
+		return -ENOMEM;
+	INIT_LIST_HEAD(&verb->enable_list);
+	INIT_LIST_HEAD(&verb->disable_list);
+	INIT_LIST_HEAD(&verb->transition_list);
+	INIT_LIST_HEAD(&verb->device_list);
+	INIT_LIST_HEAD(&verb->modifier_list);
+	INIT_LIST_HEAD(&verb->value_list);
+	list_add_tail(&verb->list, &uc_mgr->verb_list);
+	if (use_case_name == NULL)
+		return -EINVAL;
+	verb->name = strdup(use_case_name);
+	if (verb->name == NULL)
+		return -ENOMEM;
+
+	if (comment != NULL) {
+		verb->comment = strdup(comment);
+		if (verb->comment == NULL)
+			return -ENOMEM;
+	}
+
+	/* open Verb file for reading */
+	snprintf(filename, sizeof(filename), "%s/%s/%s",
+		env ? env : ALSA_USE_CASE_DIR,
+		uc_mgr->card_name, file);
+	filename[sizeof(filename)-1] = '\0';
+	
+	err = uc_mgr_config_load(filename, &cfg);
+	if (err < 0) {
+		uc_error("error: failed to open verb file %s : %d",
+			filename, -errno);
+		return err;
+	}
+
+	/* parse master config sections */
+	snd_config_for_each(i, next, cfg) {
+		const char *id;
+		n = snd_config_iterator_entry(i);
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+
+		/* find verb section and parse it */
+		if (strcmp(id, "SectionVerb") == 0) {
+			err = parse_verb(uc_mgr, verb, n);
+			if (err < 0) {
+				uc_error("error: %s failed to parse verb",
+						file);
+				return err;
+			}
+			continue;
+		}
+
+		/* find device sections and parse them */
+		if (strcmp(id, "SectionDevice") == 0) {
+			err = parse_compound(uc_mgr, n,
+						parse_device_name, verb, NULL);
+			if (err < 0) {
+				uc_error("error: %s failed to parse device",
+						file);
+				return err;
+			}
+			continue;
+		}
+
+		/* find modifier sections and parse them */
+		if (strcmp(id, "SectionModifier") == 0) {
+			err = parse_compound(uc_mgr, n,
+					     parse_modifier_name, verb, NULL);
+			if (err < 0) {
+				uc_error("error: %s failed to parse modifier",
+						file);
+				return err;
+			}
+			continue;
+		}
+	}
+
+	/* use case verb must have at least 1 device */
+	if (list_empty(&verb->device_list)) {
+		uc_error("error: no use case device defined", file);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+/*
+ * Parse master section for "Use Case" and "File" tags.
+ */
+static int parse_master_section(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg,
+				void *data1 ATTRIBUTE_UNUSED,
+				void *data2 ATTRIBUTE_UNUSED)
+{
+	snd_config_iterator_t i, next;
+	snd_config_t *n;
+	const char *use_case_name, *file = NULL, *comment = NULL;
+	int err;
+
+	if (snd_config_get_id(cfg, &use_case_name) < 0) {
+		uc_error("unable to get name for use case section");
+		return -EINVAL;
+	}
+
+	if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
+		uc_error("compound type expected for use case section");
+		return -EINVAL;
+	}
+	/* parse master config sections */
+	snd_config_for_each(i, next, cfg) {
+		const char *id;
+		n = snd_config_iterator_entry(i);
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+
+		/* get use case verb file name */
+		if (strcmp(id, "File") == 0) {
+			err = snd_config_get_string(n, &file);
+			if (err < 0) {
+				uc_error("failed to get File");
+				return err;
+			}
+			continue;
+		}
+
+		/* get optional use case comment */
+		if (strncmp(id, "Comment", 7) == 0) {
+			err = snd_config_get_string(n, &comment);
+			if (err < 0) {
+				uc_error("error: failed to get Comment");
+				return err;
+			}
+			continue;
+		}
+
+		uc_error("unknown field %s in master section");
+	}
+
+	uc_dbg("use_case_name %s file '%s'", use_case_name, file);
+
+	/* do we have both use case name and file ? */
+	if (!file) {
+		uc_error("error: use case missing file");
+		return -EINVAL;
+	}
+
+	/* parse verb file */
+	return parse_verb_file(uc_mgr, use_case_name, comment, file);
+}
+
+/*
+ * parse controls
+ */
+static int parse_controls(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg)
+{
+	int err;
+	
+	if (!list_empty(&uc_mgr->default_list)) {
+		uc_error("Default list is not empty");
+		return -EINVAL;
+	}
+	err = parse_sequence(uc_mgr, &uc_mgr->default_list, cfg);
+	if (err < 0) {
+		uc_error("Unable to parse SectionDefaults");
+		return err;
+	}
+	
+	return 0;
+}
+
+/*
+ * Each sound card has a master sound card file that lists all the supported
+ * use case verbs for that sound card. i.e.
+ *
+ * #Example master file for blah sound card
+ * #By Joe Blogs <joe@bloggs.org>
+ *
+ * Comment "Nice Abstracted Soundcard"
+ *
+ * # The file is divided into Use case sections. One section per use case verb.
+ *
+ * SectionUseCase."Voice Call" {
+ *	File "voice_call_blah"
+ *	Comment "Make a voice phone call."
+ * }
+ *
+ * SectionUseCase."HiFi" {
+ *	File "hifi_blah"
+ *	Comment "Play and record HiFi quality Music."
+ * }
+ *
+ * # Define Value defaults
+ *
+ * ValueDefaults {
+ *	PlaybackCTL "hw:CARD=0"
+ *	CaptureCTL "hw:CARD=0"
+ * }
+ *
+ * # This file also stores the default sound card state.
+ *
+ * SectionDefaults [
+ *	cset "name='Master Playback Switch',index=2 1,1"
+ *	cset "name='Master Playback Volume',index=2 25,25"
+ *	cset "name='Master Mono Playback',index=1 0"
+ *	cset "name='Master Mono Playback Volume',index=1 0"
+ *	cset "name='PCM Switch',index=2 1,1"
+ *      exec "some binary here"
+ *      msleep 50
+ *	........
+ * ]
+ *
+ * # End of example file.
+ */
+static int parse_master_file(snd_use_case_mgr_t *uc_mgr, snd_config_t *cfg)
+{
+	snd_config_iterator_t i, next;
+	snd_config_t *n;
+	const char *id;
+	int err;
+
+	if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) {
+		uc_error("compound type expected for master file");
+		return -EINVAL;
+	}
+
+	/* parse master config sections */
+	snd_config_for_each(i, next, cfg) {
+
+		n = snd_config_iterator_entry(i);
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+
+		if (strcmp(id, "Comment") == 0) {
+			err = parse_string(n, &uc_mgr->comment);
+			if (err < 0) {
+				uc_error("error: failed to get master comment");
+				return err;
+			}
+			continue;
+		}
+
+		/* find use case section and parse it */
+		if (strcmp(id, "SectionUseCase") == 0) {
+			err = parse_compound(uc_mgr, n,
+					     parse_master_section,
+					     NULL, NULL);
+			if (err < 0)
+				return err;
+			continue;
+		}
+
+		/* find default control values section and parse it */
+		if (strcmp(id, "SectionDefaults") == 0) {
+			err = parse_controls(uc_mgr, n);
+			if (err < 0)
+				return err;
+			continue;
+		}
+
+		/* get the default values */
+		if (strcmp(id, "ValueDefaults") == 0) {
+			err = parse_value(uc_mgr, &uc_mgr->value_list, n);
+			if (err < 0) {
+				uc_error("error: failed to parse ValueDefaults");
+				return err;
+			}
+			continue;
+		}
+
+		uc_error("uknown master file field %s", id);
+	}
+	return 0;
+}
+
+static int load_master_config(const char *card_name, snd_config_t **cfg)
+{
+	char filename[MAX_FILE];
+	char *env = getenv(ALSA_CONFIG_UCM_VAR);
+	int err;
+
+	snprintf(filename, sizeof(filename)-1,
+		"%s/%s/%s.conf", env ? env : ALSA_USE_CASE_DIR,
+		card_name, card_name);
+	filename[MAX_FILE-1] = '\0';
+
+	err = uc_mgr_config_load(filename, cfg);
+	if (err < 0) {
+		uc_error("error: could not parse configuration for card %s",
+				card_name);
+		return err;
+	}
+
+	return 0;
+}
+
+/* load master use case file for sound card */
+int uc_mgr_import_master_config(snd_use_case_mgr_t *uc_mgr)
+{
+	snd_config_t *cfg;
+	int err;
+
+	err = load_master_config(uc_mgr->card_name, &cfg);
+	if (err < 0)
+		return err;
+	err = parse_master_file(uc_mgr, cfg);
+	snd_config_delete(cfg);
+	if (err < 0)
+		uc_mgr_free_verb(uc_mgr);
+
+	return err;
+}
+
+static int filename_filter(const struct dirent *dirent)
+{
+	if (dirent == NULL)
+		return 0;
+	if (dirent->d_type == DT_DIR) {
+		if (dirent->d_name[0] == '.') {
+			if (dirent->d_name[1] == '\0')
+				return 0;
+			if (dirent->d_name[1] == '.' &&
+			    dirent->d_name[2] == '\0')
+				return 0;
+		}
+		return 1;
+	}
+	return 0;
+}
+
+/* scan all cards and comments */
+int uc_mgr_scan_master_configs(const char **_list[])
+{
+	char filename[MAX_FILE], dfl[MAX_FILE];
+	char *env = getenv(ALSA_CONFIG_UCM_VAR);
+	const char **list;
+	snd_config_t *cfg, *c;
+	int i, cnt, err;
+	ssize_t ss;
+	struct dirent **namelist;
+
+	snprintf(filename, sizeof(filename)-1,
+		"%s", env ? env : ALSA_USE_CASE_DIR);
+	filename[MAX_FILE-1] = '\0';
+
+	err = scandir(filename, &namelist, filename_filter, versionsort);
+	if (err < 0) {
+		err = -errno;
+		uc_error("error: could not scan directory %s: %s",
+				filename, strerror(-err));
+		return err;
+	}
+	cnt = err;
+
+	dfl[0] = '\0';
+	if (strlen(filename) + 8 < sizeof(filename)) {
+		strcat(filename, "/default");
+		ss = readlink(filename, dfl, sizeof(dfl)-1);
+		if (ss >= 0) {
+			dfl[ss] = '\0';
+			dfl[sizeof(dfl)-1] = '\0';
+			if (dfl[0] && dfl[strlen(dfl)-1] == '/')
+				dfl[strlen(dfl)-1] = '\0';
+		} else {
+			dfl[0] = '\0';
+		}
+	}
+
+	list = calloc(1, cnt * 2 * sizeof(char *));
+	if (list == NULL) {
+		err = -ENOMEM;
+		goto __err;
+	}
+
+	for (i = 0; i < cnt; i++) {
+		err = load_master_config(namelist[i]->d_name, &cfg);
+		if (err < 0)
+			goto __err;
+		err = snd_config_search(cfg, "Comment", &c);
+		if (err >= 0) {
+			err = parse_string(c, (char **)&list[i*2+1]);
+			if (err < 0) {
+				snd_config_delete(cfg);
+				goto __err;
+			}
+		}
+		snd_config_delete(cfg);
+		list[i * 2] = strdup(namelist[i]->d_name);
+		if (list[i * 2] == NULL) {
+			err = -ENOMEM;
+			goto __err;
+		}
+		if (strcmp(dfl, list[i * 2]) == 0) {
+			/* default to top */
+			const char *save1 = list[i * 2];
+			const char *save2 = list[i * 2 + 1];
+			memmove(list + 2, list, i * 2 * sizeof(char *));
+			list[0] = save1;
+			list[1] = save2;
+		}
+	}
+	err = cnt * 2;
+
+      __err:
+	for (i = 0; i < cnt; i++) {
+		free(namelist[i]);
+		if (err < 0) {
+			free((void *)list[i * 2]);
+			free((void *)list[i * 2 + 1]);
+		}
+	}
+	free(namelist);
+
+	if (err >= 0)
+		*_list = list;
+
+	return err;
+}
diff --git a/src/ucm/ucm_local.h b/src/ucm/ucm_local.h
new file mode 100644
index 0000000..03d3ace
--- /dev/null
+++ b/src/ucm/ucm_local.h
@@ -0,0 +1,221 @@
+/*
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software  
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ *  Support for the verb/device/modifier core logic and API,
+ *  command line tool and file parser was kindly sponsored by
+ *  Texas Instruments Inc.
+ *  Support for multiple active modifiers and devices,
+ *  transition sequences, multiple client access and user defined use
+ *  cases was kindly sponsored by Wolfson Microelectronics PLC.
+ *
+ *  Copyright (C) 2008-2010 SlimLogic Ltd
+ *  Copyright (C) 2010 Wolfson Microelectronics PLC
+ *  Copyright (C) 2010 Texas Instruments Inc.
+ *  Copyright (C) 2010 Red Hat Inc.
+ *  Authors: Liam Girdwood <lrg@slimlogic.co.uk>
+ *	         Stefan Schmidt <stefan@slimlogic.co.uk>
+ *	         Justin Xu <justinx@slimlogic.co.uk>
+ *               Jaroslav Kysela <perex@perex.cz>
+ */
+
+
+
+#if 0
+#define UC_MGR_DEBUG
+#endif
+
+#include "local.h"
+#include "use-case.h"
+
+#define MAX_FILE		256
+#define ALSA_USE_CASE_DIR	ALSA_CONFIG_DIR "/ucm"
+
+#define SEQUENCE_ELEMENT_TYPE_CDEV	1
+#define SEQUENCE_ELEMENT_TYPE_CSET	2
+#define SEQUENCE_ELEMENT_TYPE_SLEEP	3
+#define SEQUENCE_ELEMENT_TYPE_EXEC	4
+
+struct ucm_value {
+        struct list_head list;
+        char *name;
+        char *data;
+};
+
+struct sequence_element {
+	struct list_head list;
+	unsigned int type;
+	union {
+		long sleep; /* Sleep time in microseconds if sleep element, else 0 */
+		char *cdev;
+		char *cset;
+		char *exec;
+	} data;
+};
+
+/*
+ * Transition sequences. i.e. transition between one verb, device, mod to another
+ */
+struct transition_sequence {
+	struct list_head list;
+	char *name;
+	struct list_head transition_list;
+};
+
+/*
+ * Modifier Supported Devices.
+ */
+enum dev_list_type {
+	DEVLIST_NONE,
+	DEVLIST_SUPPORTED,
+	DEVLIST_CONFLICTING
+};
+
+struct dev_list_node {
+	struct list_head list;
+	char *name;
+};
+
+struct dev_list {
+	enum dev_list_type type;
+	struct list_head list;
+};
+
+/*
+ * Describes a Use Case Modifier and it's enable and disable sequences.
+ * A use case verb can have N modifiers.
+ */
+struct use_case_modifier {
+	struct list_head list;
+	struct list_head active_list;
+
+	char *name;
+	char *comment;
+
+	/* modifier enable and disable sequences */
+	struct list_head enable_list;
+	struct list_head disable_list;
+
+	/* modifier transition list */
+	struct list_head transition_list;
+
+	/* list of devices supported or conflicting */
+	struct dev_list dev_list;
+
+	/* values */
+	struct list_head value_list;
+};
+
+/*
+ * Describes a Use Case Device and it's enable and disable sequences.
+ * A use case verb can have N devices.
+ */
+struct use_case_device {
+	struct list_head list;
+	struct list_head active_list;
+
+	char *name;
+	char *comment;
+
+	/* device enable and disable sequences */
+	struct list_head enable_list;
+	struct list_head disable_list;
+
+	/* device transition list */
+	struct list_head transition_list;
+
+	/* list of devices supported or conflicting */
+	struct dev_list dev_list;
+
+	/* value list */
+	struct list_head value_list;
+};
+
+/*
+ * Describes a Use Case Verb and it's enable and disable sequences.
+ * A use case verb can have N devices and N modifiers.
+ */
+struct use_case_verb {
+	struct list_head list;
+
+	unsigned int active: 1;
+
+	char *name;
+	char *comment;
+
+	/* verb enable and disable sequences */
+	struct list_head enable_list;
+	struct list_head disable_list;
+
+	/* verb transition list */
+	struct list_head transition_list;
+
+	/* hardware devices that can be used with this use case */
+	struct list_head device_list;
+
+	/* modifiers that can be used with this use case */
+	struct list_head modifier_list;
+
+	/* value list */
+	struct list_head value_list;
+};
+
+/*
+ *  Manages a sound card and all its use cases.
+ */
+struct snd_use_case_mgr {
+	char *card_name;
+	char *comment;
+
+	/* use case verb, devices and modifier configs parsed from files */
+	struct list_head verb_list;
+
+	/* default settings - sequence */
+	struct list_head default_list;
+
+	/* default settings - value list */
+	struct list_head value_list;
+
+	/* current status */
+	struct use_case_verb *active_verb;
+	struct list_head active_devices;
+	struct list_head active_modifiers;
+
+	/* locking */
+	pthread_mutex_t mutex;
+
+	/* change to list of ctl handles */
+	snd_ctl_t *ctl;
+	char *ctl_dev;
+};
+
+#define uc_error SNDERR
+
+#ifdef UC_MGR_DEBUG
+#define uc_dbg SNDERR
+#else
+#define uc_dbg(fmt, arg...) do { } while (0)
+#endif
+
+void uc_mgr_error(const char *fmt, ...);
+void uc_mgr_stdout(const char *fmt, ...);
+
+int uc_mgr_config_load(const char *file, snd_config_t **cfg);
+int uc_mgr_import_master_config(snd_use_case_mgr_t *uc_mgr);
+int uc_mgr_scan_master_configs(const char **_list[]);
+
+void uc_mgr_free_sequence_element(struct sequence_element *seq);
+void uc_mgr_free_transition_element(struct transition_sequence *seq);
+void uc_mgr_free_verb(snd_use_case_mgr_t *uc_mgr);
+void uc_mgr_free(snd_use_case_mgr_t *uc_mgr);
diff --git a/src/ucm/utils.c b/src/ucm/utils.c
new file mode 100644
index 0000000..45307b0
--- /dev/null
+++ b/src/ucm/utils.c
@@ -0,0 +1,237 @@
+/*
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software  
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ *  Support for the verb/device/modifier core logic and API,
+ *  command line tool and file parser was kindly sponsored by
+ *  Texas Instruments Inc.
+ *  Support for multiple active modifiers and devices,
+ *  transition sequences, multiple client access and user defined use
+ *  cases was kindly sponsored by Wolfson Microelectronics PLC.
+ *
+ *  Copyright (C) 2008-2010 SlimLogic Ltd
+ *  Copyright (C) 2010 Wolfson Microelectronics PLC
+ *  Copyright (C) 2010 Texas Instruments Inc.
+ *  Copyright (C) 2010 Red Hat Inc.
+ *  Authors: Liam Girdwood <lrg@slimlogic.co.uk>
+ *	         Stefan Schmidt <stefan@slimlogic.co.uk>
+ *	         Justin Xu <justinx@slimlogic.co.uk>
+ *               Jaroslav Kysela <perex@perex.cz>
+ */
+
+#include "ucm_local.h"
+
+void uc_mgr_error(const char *fmt,...)
+{
+	va_list va;
+	va_start(va, fmt);
+	fprintf(stderr, "ucm: ");
+	vfprintf(stderr, fmt, va);
+	va_end(va);
+}
+
+void uc_mgr_stdout(const char *fmt,...)
+{
+	va_list va;
+	va_start(va, fmt);
+	vfprintf(stdout, fmt, va);
+	va_end(va);
+}
+
+int uc_mgr_config_load(const char *file, snd_config_t **cfg)
+{
+	FILE *fp;
+	snd_input_t *in;
+	snd_config_t *top;
+	int err;
+
+	fp = fopen(file, "r");
+	if (fp == NULL) {
+		err = -errno;
+		goto __err;
+	}
+	err = snd_input_stdio_attach(&in, fp, 1);
+	if (err < 0) {
+	      __err:
+		uc_error("could not open configuration file %s", file);
+		return err;
+	}
+	err = snd_config_top(&top);
+	if (err < 0)
+		return err;
+	err = snd_config_load(top, in);
+	if (err < 0) {
+		uc_error("could not load configuration file %s", file);
+		snd_config_delete(top);
+		return err;
+	}
+	err = snd_input_close(in);
+	if (err < 0) {
+		snd_config_delete(top);
+		return err;
+	}
+	*cfg = top;
+	return 0;
+}
+
+void uc_mgr_free_value(struct list_head *base)
+{
+	struct list_head *pos, *npos;
+	struct ucm_value *val;
+	
+	list_for_each_safe(pos, npos, base) {
+		val = list_entry(pos, struct ucm_value, list);
+		free(val->name);
+		free(val->data);
+		list_del(&val->list);
+		free(val);
+	}
+}
+
+void uc_mgr_free_dev_list(struct dev_list *dev_list)
+{
+	struct list_head *pos, *npos;
+	struct dev_list_node *dlist;
+	
+	list_for_each_safe(pos, npos, &dev_list->list) {
+		dlist = list_entry(pos, struct dev_list_node, list);
+		free(dlist->name);
+		list_del(&dlist->list);
+		free(dlist);
+	}
+}
+
+void uc_mgr_free_sequence_element(struct sequence_element *seq)
+{
+	if (seq == NULL)
+		return;
+	switch (seq->type) {
+	case SEQUENCE_ELEMENT_TYPE_CSET:
+	case SEQUENCE_ELEMENT_TYPE_EXEC:
+		free(seq->data.exec);
+		break;
+	default:
+		break;
+	}
+	free(seq);
+}
+
+void uc_mgr_free_sequence(struct list_head *base)
+{
+	struct list_head *pos, *npos;
+	struct sequence_element *seq;
+	
+	list_for_each_safe(pos, npos, base) {
+		seq = list_entry(pos, struct sequence_element, list);
+		list_del(&seq->list);
+		uc_mgr_free_sequence_element(seq);
+	}
+}
+
+void uc_mgr_free_transition_element(struct transition_sequence *tseq)
+{
+	free(tseq->name);
+	uc_mgr_free_sequence(&tseq->transition_list);
+	free(tseq);
+}
+
+void uc_mgr_free_transition(struct list_head *base)
+{
+	struct list_head *pos, *npos;
+	struct transition_sequence *tseq;
+	
+	list_for_each_safe(pos, npos, base) {
+		tseq = list_entry(pos, struct transition_sequence, list);
+		list_del(&tseq->list);
+		uc_mgr_free_transition_element(tseq);
+	}
+}
+
+void uc_mgr_free_modifier(struct list_head *base)
+{
+	struct list_head *pos, *npos;
+	struct use_case_modifier *mod;
+	
+	list_for_each_safe(pos, npos, base) {
+		mod = list_entry(pos, struct use_case_modifier, list);
+		free(mod->name);
+		free(mod->comment);
+		uc_mgr_free_sequence(&mod->enable_list);
+		uc_mgr_free_sequence(&mod->disable_list);
+		uc_mgr_free_transition(&mod->transition_list);
+		uc_mgr_free_dev_list(&mod->dev_list);
+		uc_mgr_free_value(&mod->value_list);
+		list_del(&mod->list);
+		free(mod);
+	}
+}
+
+void uc_mgr_free_device(struct list_head *base)
+{
+	struct list_head *pos, *npos;
+	struct use_case_device *dev;
+	
+	list_for_each_safe(pos, npos, base) {
+		dev = list_entry(pos, struct use_case_device, list);
+		free(dev->name);
+		free(dev->comment);
+		uc_mgr_free_sequence(&dev->enable_list);
+		uc_mgr_free_sequence(&dev->disable_list);
+		uc_mgr_free_transition(&dev->transition_list);
+		uc_mgr_free_dev_list(&dev->dev_list);
+		uc_mgr_free_value(&dev->value_list);
+		list_del(&dev->list);
+		free(dev);
+	}
+}
+
+void uc_mgr_free_verb(snd_use_case_mgr_t *uc_mgr)
+{
+	struct list_head *pos, *npos;
+	struct use_case_verb *verb;
+
+	list_for_each_safe(pos, npos, &uc_mgr->verb_list) {
+		verb = list_entry(pos, struct use_case_verb, list);
+		free(verb->name);
+		free(verb->comment);
+		uc_mgr_free_sequence(&verb->enable_list);
+		uc_mgr_free_sequence(&verb->disable_list);
+		uc_mgr_free_transition(&verb->transition_list);
+		uc_mgr_free_value(&verb->value_list);
+		uc_mgr_free_device(&verb->device_list);
+		uc_mgr_free_modifier(&verb->modifier_list);
+		list_del(&verb->list);
+		free(verb);
+	}
+	uc_mgr_free_sequence(&uc_mgr->default_list);
+	uc_mgr_free_value(&uc_mgr->value_list);
+	free(uc_mgr->comment);
+	uc_mgr->comment = NULL;
+	uc_mgr->active_verb = NULL;
+	INIT_LIST_HEAD(&uc_mgr->active_devices);
+	INIT_LIST_HEAD(&uc_mgr->active_modifiers);
+	if (uc_mgr->ctl != NULL) {
+		snd_ctl_close(uc_mgr->ctl);
+		uc_mgr->ctl = NULL;
+	}
+	free(uc_mgr->ctl_dev);
+	uc_mgr->ctl_dev = NULL;
+}
+
+void uc_mgr_free(snd_use_case_mgr_t *uc_mgr)
+{
+	uc_mgr_free_verb(uc_mgr);
+	free(uc_mgr->card_name);
+	free(uc_mgr);
+}
diff --git a/src/userfile.c b/src/userfile.c
new file mode 100644
index 0000000..3a73836
--- /dev/null
+++ b/src/userfile.c
@@ -0,0 +1,72 @@
+/*
+ *  Get full filename
+ *  Copyright (c) 2003 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *   This library is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as
+ *   published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+  
+#include <config.h>
+#include <string.h>
+#include <errno.h>
+
+/**
+ * \brief Get the full file name
+ * \param file The file name string to parse
+ * \param result The pointer to store the resultant file name
+ * \return 0 if successful, or a negative error code
+ *
+ * Parses the given file name with POSIX-Shell-like expansion and
+ * stores the first matchine one.  The returned string is strdup'ed.
+ */
+
+#ifdef HAVE_WORDEXP_H
+#include <wordexp.h>
+#include <assert.h>
+int snd_user_file(const char *file, char **result)
+{
+	wordexp_t we;
+	int err;
+	
+	assert(file && result);
+	err = wordexp(file, &we, WRDE_NOCMD);
+	switch (err) {
+	case WRDE_NOSPACE:
+		return -ENOMEM;
+	case 0:
+		if (we.we_wordc == 1)
+			break;
+		/* fall thru */
+	default:
+		wordfree(&we);
+		return -EINVAL;
+	}
+	*result = strdup(we.we_wordv[0]);
+	if (*result == NULL)
+		return -ENOMEM;
+	wordfree(&we);
+	return 0;
+}
+
+#else /* !HAVE_WORDEXP_H */
+/* just copy the string - would be nicer to expand by ourselves, though... */
+int snd_user_file(const char *file, char **result)
+{
+	*result = strdup(file);
+	if (! *result)
+		return -ENOMEM;
+	return 0;
+}
+#endif /* HAVE_WORDEXP_H */
diff --git a/test/Makefile.am b/test/Makefile.am
new file mode 100644
index 0000000..ae524a4
--- /dev/null
+++ b/test/Makefile.am
@@ -0,0 +1,25 @@
+SUBDIRS=. lsb
+
+check_PROGRAMS=control pcm pcm_min latency seq \
+	       playmidi1 timer rawmidi midiloop \
+	       oldapi queue_timer namehint client_event_filter
+
+control_LDADD=../src/libasound.la
+pcm_LDADD=../src/libasound.la
+pcm_min_LDADD=../src/libasound.la
+latency_LDADD=../src/libasound.la
+seq_LDADD=../src/libasound.la
+playmidi1_LDADD=../src/libasound.la
+timer_LDADD=../src/libasound.la
+rawmidi_LDADD=../src/libasound.la
+midiloop_LDADD=../src/libasound.la
+oldapi_LDADD=../src/libasound.la
+queue_timer_LDADD=../src/libasound.la
+namehint_LDADD=../src/libasound.la
+client_event_filter_LDADD=../src/libasound.la
+code_CFLAGS=-Wall -pipe -g -O2
+
+INCLUDES=-I$(top_srcdir)/include
+AM_CFLAGS=-Wall -pipe -g
+
+EXTRA_DIST=seq-decoder.c seq-sender.c midifile.h midifile.c midifile.3
diff --git a/test/client_event_filter.c b/test/client_event_filter.c
new file mode 100644
index 0000000..0650314
--- /dev/null
+++ b/test/client_event_filter.c
@@ -0,0 +1,46 @@
+#include <alsa/asoundlib.h>
+
+void dump_event_filter(snd_seq_client_info_t *client_info) {
+	int i, b;
+
+	for (i = 0; i <= 255;) {
+		b = snd_seq_client_info_event_filter_check(client_info, i);
+		i++;
+		printf("%c%s%s", (b ? 'X' : '.'),
+			(i % 8 == 0 ? " " : ""),
+			(i % 32 == 0 ? "\n" : ""));
+	}
+	printf("\n");
+}
+
+int main(void) {
+	snd_seq_client_info_t *client_info;
+
+	snd_seq_client_info_alloca(&client_info);
+
+	printf("first client_info_event_filter                   :\n");
+	dump_event_filter(client_info);
+
+	snd_seq_client_info_event_filter_add(client_info, SND_SEQ_EVENT_NOTEON);
+	printf("after snd_seq_client_info_event_filter_add(client_info, SND_SEQ_EVENT_NOTEON);\n");
+	dump_event_filter(client_info);
+
+	snd_seq_client_info_event_filter_add(client_info, SND_SEQ_EVENT_PGMCHANGE);
+	printf("after snd_seq_client_info_event_filter_add(client_info, SND_SEQ_EVENT_PGMCHANGE);\n");
+	dump_event_filter(client_info);
+
+	snd_seq_client_info_event_filter_del(client_info, SND_SEQ_EVENT_NOTEON);
+	printf("after snd_seq_client_info_event_filter_del(client_info, SND_SEQ_EVENT_NOTEON);\n");
+	dump_event_filter(client_info);
+
+	snd_seq_client_info_event_filter_clear(client_info);
+	printf("after snd_seq_client_info_event_filter_clear(client_info);\n");
+	dump_event_filter(client_info);
+
+	snd_seq_client_info_event_filter_add(client_info, SND_SEQ_EVENT_NOTEON);
+	printf("after snd_seq_client_info_event_filter_add(client_info, SND_SEQ_EVENT_NOTEON);\n");
+	dump_event_filter(client_info);
+
+	return 0;
+}
+
diff --git a/test/code.c b/test/code.c
new file mode 100644
index 0000000..e2032de
--- /dev/null
+++ b/test/code.c
@@ -0,0 +1,333 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sched.h>
+#include <sys/time.h>
+
+#ifndef __builtin_expect
+#include <linux/compiler.h>
+#endif
+
+#define rdtscll(val) \
+     __asm__ __volatile__("rdtsc" : "=A" (val))
+
+#define likely(x)       __builtin_expect((x),1)
+#define unlikely(x)     __builtin_expect((x),0)
+
+typedef short int s16;
+typedef int s32;
+
+#if 0
+#define CONFIG_SMP
+#endif
+
+#ifdef CONFIG_SMP
+#define LOCK_PREFIX "lock ; "
+#else
+#define LOCK_PREFIX ""
+#endif
+
+struct __xchg_dummy { unsigned long a[100]; };
+#define __xg(x) ((struct __xchg_dummy *)(x))
+
+static inline unsigned long __cmpxchg(volatile void *ptr, unsigned long old,
+				      unsigned long new, int size)
+{
+	unsigned long prev;
+	switch (size) {
+	case 1:
+		__asm__ __volatile__(LOCK_PREFIX "cmpxchgb %b1,%2"
+				     : "=a"(prev)
+				     : "q"(new), "m"(*__xg(ptr)), "0"(old)
+				     : "memory");
+		return prev;
+	case 2:
+		__asm__ __volatile__(LOCK_PREFIX "cmpxchgw %w1,%2"
+				     : "=a"(prev)
+				     : "q"(new), "m"(*__xg(ptr)), "0"(old)
+				     : "memory");
+		return prev;
+	case 4:
+		__asm__ __volatile__(LOCK_PREFIX "cmpxchgl %1,%2"
+				     : "=a"(prev)
+				     : "q"(new), "m"(*__xg(ptr)), "0"(old)
+				     : "memory");
+		return prev;
+	}
+	return old;
+}
+
+#define cmpxchg(ptr,o,n)\
+	((__typeof__(*(ptr)))__cmpxchg((ptr),(unsigned long)(o),\
+				       (unsigned long)(n),sizeof(*(ptr))))
+
+static inline void atomic_add(volatile int *dst, int v)
+{
+	__asm__ __volatile__(
+		LOCK_PREFIX "addl %1,%0"
+		:"=m" (*dst)
+		:"ir" (v), "m" (*dst));
+}
+
+static double detect_cpu_clock()
+{
+	struct timeval tm_begin, tm_end;
+	unsigned long long tsc_begin, tsc_end;
+
+	/* Warm cache */
+	gettimeofday(&tm_begin, 0);
+
+	rdtscll(tsc_begin);
+	gettimeofday(&tm_begin, 0);
+
+	usleep(1000000);
+
+	rdtscll(tsc_end);
+	gettimeofday(&tm_end, 0);
+
+	return (tsc_end - tsc_begin) / (tm_end.tv_sec - tm_begin.tv_sec + (tm_end.tv_usec - tm_begin.tv_usec) / 1e6);
+}
+
+void mix_areas_srv(unsigned int size,
+		   const s16 *src,
+		   volatile s32 *sum,
+		   unsigned int src_step, unsigned int sum_step)
+{
+	src_step /= sizeof(*src);
+	sum_step /= sizeof(*sum);
+        while (size-- > 0) {
+                atomic_add(sum, *src);
+                src += src_step;
+                sum += sum_step;
+        }
+}
+
+void saturate(unsigned int size,
+              s16 *dst, const s32 *sum,
+              unsigned int dst_step, unsigned int sum_step)
+{
+	dst_step /= sizeof(*dst);
+	sum_step /= sizeof(*sum);
+        while (size-- > 0) {
+                s32 sample = *sum;
+                if (unlikely(sample < -0x8000))
+                        *dst = -0x8000;
+                else if (unlikely(sample > 0x7fff))
+                        *dst = 0x7fff;
+                else
+                        *dst = sample;
+                dst += dst_step;
+                sum += sum_step;
+        }
+}
+
+void mix_areas0(unsigned int size,
+		volatile s16 *dst, s16 *src,
+		volatile s32 *sum,
+		unsigned int dst_step,
+		unsigned int src_step,
+		unsigned int sum_step)
+{
+	dst_step /= sizeof(*dst);
+	src_step /= sizeof(*src);
+	sum_step /= sizeof(*sum);
+	while (size-- > 0) {
+		s32 sample = *dst + *src;
+		if (unlikely(sample < -0x8000))
+			*dst = -0x8000;
+		else if (unlikely(sample > 0x7fff))
+			*dst = 0x7fff;
+		else
+			*dst = sample;
+		dst += dst_step;
+		src += src_step;
+		sum += sum_step;
+	}
+}
+
+#define MIX_AREAS1 mix_areas1
+#define MIX_AREAS1_MMX mix_areas1_mmx
+#include "../src/pcm/pcm_dmix_i386.h"
+#undef MIX_AREAS1
+#undef MIX_AREAS1_MMX
+
+void mix_areas2(unsigned int size,
+		volatile s16 *dst, const s16 *src,
+		volatile s32 *sum,
+		unsigned int dst_step,
+		unsigned int src_step)
+{
+	dst_step /= sizeof(*dst);
+	src_step /= sizeof(*src);
+	while (size-- > 0) {
+		s32 sample = *src;
+		s32 old_sample = *sum;
+		if (cmpxchg(dst, 0, 1) == 0)
+			sample -= old_sample;
+		atomic_add(sum, sample);
+		do {
+			sample = *sum;
+			if (unlikely(sample < -0x8000))
+				*dst = -0x8000;
+			else if (unlikely(sample > 0x7fff))
+				*dst = 0x7fff;
+			else
+				*dst = sample;
+		} while (unlikely(sample != *sum));
+		sum++;
+		dst += dst_step;
+		src += src_step;
+	}
+}
+
+void setscheduler(void)
+{
+	struct sched_param sched_param;
+
+	if (sched_getparam(0, &sched_param) < 0) {
+		printf("Scheduler getparam failed...\n");
+		return;
+	}
+	sched_param.sched_priority = sched_get_priority_max(SCHED_RR);
+	if (!sched_setscheduler(0, SCHED_RR, &sched_param)) {
+		printf("Scheduler set to Round Robin with priority %i...\n", sched_param.sched_priority);
+		fflush(stdout);
+		return;
+	}
+	printf("!!!Scheduler set to Round Robin with priority %i FAILED!!!\n", sched_param.sched_priority);
+}
+
+int cache_size = 1024*1024;
+
+void init(s16 *dst, s32 *sum, int size)
+{
+	int count;
+	char *a;
+	
+	for (count = size - 1; count >= 0; count--)
+		*sum++ = 0;
+	for (count = size - 1; count >= 0; count--)
+		*dst++ = 0;
+	a = malloc(cache_size);
+	for (count = cache_size - 1; count >= 0; count--) {
+		a[count] = count & 0xff;
+		a[count] ^= 0x55;
+		a[count] ^= 0xaa;
+	}
+	free(a);
+}
+
+int main(int argc, char **argv)
+{
+	int size = 2048, n = 4, max = 32267;
+	int LOOP = 100;
+	int i, t;
+	unsigned long long begin, end, diff, diffS, diff0, diff1, diff1_mmx, diff2;
+        double cpu_clock = detect_cpu_clock();
+	s16 *dst = malloc(sizeof(*dst) * size);
+	s32 *sum = calloc(size, sizeof(*sum));
+	s16 **srcs = malloc(sizeof(*srcs) * n);
+
+	setscheduler();
+#ifndef CONFIG_SMP
+        printf("CPU clock: %fMhz (UP)\n\n", cpu_clock / 10e5);
+#else
+        printf("CPU clock: %fMhz (SMP)\n\n", cpu_clock / 10e5);
+#endif
+	if (argc > 3) {
+		size = atoi(argv[1]);
+		n = atoi(argv[2]);
+		max = atoi(argv[3]);
+	}
+	if (argc > 4)
+		cache_size = atoi(argv[4]) * 1024;
+	for (i = 0; i < n; i++) {
+		int k;
+		s16 *s;
+		srcs[i] = s = malloc(sizeof(s16) * size);
+		for (k = 0; k < size; ++k, ++s) {
+			*s = (rand() % (max * 2)) - max;
+		}
+	}
+
+	for (t = 0, diffS = -1; t < LOOP; t++) {
+		init(dst, sum, size);
+		rdtscll(begin);
+		for (i = 0; i < n; i++) {
+			mix_areas_srv(size, srcs[i], sum, 2, 4);
+		}
+		saturate(size, dst, sum, 2, 4);
+		rdtscll(end);
+		diff = end - begin;
+		if (diff < diffS)
+			diffS = diff;
+		printf("mix_areas_srv : %lld               \r", diff); fflush(stdout);
+	}
+
+	for (t = 0, diff0 = -1; t < LOOP; t++) {
+		init(dst, sum, size);
+		rdtscll(begin);
+		for (i = 0; i < n; i++) {
+			mix_areas0(size, dst, srcs[i], sum, 2, 2, 4);
+		}
+		rdtscll(end);
+		diff = end - begin;
+		if (diff < diff0)
+			diff0 = diff;
+		printf("mix_areas0    : %lld               \r", diff); fflush(stdout);
+	}
+
+	for (t = 0, diff1 = -1; t < LOOP; t++) {
+		init(dst, sum, size);
+		rdtscll(begin);
+		for (i = 0; i < n; i++) {
+			mix_areas1(size, dst, srcs[i], sum, 2, 2, 4);
+		}
+		rdtscll(end);
+		diff = end - begin;
+		if (diff < diff1)
+			diff1 = diff;
+		printf("mix_areas1    : %lld              \r", diff); fflush(stdout);
+	}
+
+	for (t = 0, diff1_mmx = -1; t < LOOP; t++) {
+		init(dst, sum, size);
+		rdtscll(begin);
+		for (i = 0; i < n; i++) {
+			mix_areas1_mmx(size, dst, srcs[i], sum, 2, 2, 4);
+		}
+		rdtscll(end);
+		diff = end - begin;
+		if (diff < diff1_mmx)
+			diff1_mmx = diff;
+		printf("mix_areas1_mmx: %lld              \r", diff); fflush(stdout);
+	}
+
+	for (t = 0, diff2 = -1; t < LOOP; t++) {
+		init(dst, sum, size);
+		rdtscll(begin);
+		for (i = 0; i < n; i++) {
+			mix_areas2(size, dst, srcs[i], sum, 2, 2);
+		}
+		rdtscll(end);
+		diff = end - begin;
+		if (diff < diff2)
+			diff2 = diff;
+		printf("mix_areas2    : %lld              \r", diff); fflush(stdout);
+	}
+
+	printf("                                                                           \r");
+	printf("Summary (the best times):\n");
+	printf("mix_areas_srv : %lld %f%%\n", diffS, 100*2*44100.0*diffS/(size*n*cpu_clock));
+	printf("mix_areas0    : %lld %f%%\n", diff0, 100*2*44100.0*diff0/(size*n*cpu_clock));
+	printf("mix_areas1    : %lld %f%%\n", diff1, 100*2*44100.0*diff1/(size*n*cpu_clock));
+	printf("mix_areas1_mmx: %lld %f%%\n", diff1_mmx, 100*2*44100.0*diff1_mmx/(size*n*cpu_clock));
+	printf("mix_areas2    : %lld %f%%\n", diff2, 100*2*44100.0*diff2/(size*n*cpu_clock));
+
+	printf("\n");
+	printf("areas1/srv ratio     : %f\n", (double)diff1 / diffS);
+	printf("areas1_mmx/srv ratio : %f\n", (double)diff1_mmx / diffS);
+
+	return 0;
+}
diff --git a/test/control.c b/test/control.c
new file mode 100644
index 0000000..f4b437e
--- /dev/null
+++ b/test/control.c
@@ -0,0 +1,106 @@
+#include <stdio.h>
+#include <string.h>
+#include "../include/asoundlib.h"
+
+int main(void)
+{
+	int idx, dev, err;
+	snd_ctl_t *handle;
+	snd_ctl_card_info_t *info;
+	snd_pcm_info_t *pcminfo;
+	snd_rawmidi_info_t *rawmidiinfo;
+	char str[128];
+
+	snd_ctl_card_info_alloca(&info);
+	snd_pcm_info_alloca(&pcminfo);
+	snd_rawmidi_info_alloca(&rawmidiinfo);
+
+	idx = -1;
+	while (1) {
+		if ((err = snd_card_next(&idx)) < 0) {
+			printf("Card next error: %s\n", snd_strerror(err));
+			break;
+		}
+		if (idx < 0)
+			break;
+		sprintf(str, "hw:CARD=%i", idx);
+		if ((err = snd_ctl_open(&handle, str, 0)) < 0) {
+			printf("Open error: %s\n", snd_strerror(err));
+			continue;
+		}
+		if ((err = snd_ctl_card_info(handle, info)) < 0) {
+			printf("HW info error: %s\n", snd_strerror(err));
+			continue;
+		}
+		printf("Soundcard #%i:\n", idx + 1);
+		printf("  card - %i\n", snd_ctl_card_info_get_card(info));
+		printf("  id - '%s'\n", snd_ctl_card_info_get_id(info));
+		printf("  driver - '%s'\n", snd_ctl_card_info_get_driver(info));
+		printf("  name - '%s'\n", snd_ctl_card_info_get_name(info));
+		printf("  longname - '%s'\n", snd_ctl_card_info_get_longname(info));
+		printf("  mixername - '%s'\n", snd_ctl_card_info_get_mixername(info));
+		printf("  components - '%s'\n", snd_ctl_card_info_get_components(info));
+		dev = -1;
+		while (1) {
+			snd_pcm_sync_id_t sync;
+			if ((err = snd_ctl_pcm_next_device(handle, &dev)) < 0) {
+				printf("  PCM next device error: %s\n", snd_strerror(err));
+				break;
+			}
+			if (dev < 0)
+				break;
+			snd_pcm_info_set_device(pcminfo, dev);
+			snd_pcm_info_set_subdevice(pcminfo, 0);
+			snd_pcm_info_set_stream(pcminfo, SND_PCM_STREAM_PLAYBACK);
+			if ((err = snd_ctl_pcm_info(handle, pcminfo)) < 0) {
+				printf("  PCM info error: %s\n", snd_strerror(err));
+				continue;
+			}
+			printf("PCM info, device #%i:\n", dev);
+			printf("  device - %i\n", snd_pcm_info_get_device(pcminfo));
+			printf("  subdevice - %i\n", snd_pcm_info_get_subdevice(pcminfo));
+			printf("  stream - %i\n", snd_pcm_info_get_stream(pcminfo));
+			printf("  card - %i\n", snd_pcm_info_get_card(pcminfo));
+			printf("  id - '%s'\n", snd_pcm_info_get_id(pcminfo));
+			printf("  name - '%s'\n", snd_pcm_info_get_name(pcminfo));
+			printf("  subdevice name - '%s'\n", snd_pcm_info_get_subdevice_name(pcminfo));
+			printf("  class - 0x%x\n", snd_pcm_info_get_class(pcminfo));
+			printf("  subclass - 0x%x\n", snd_pcm_info_get_subclass(pcminfo));
+			printf("  subdevices count - %i\n", snd_pcm_info_get_subdevices_count(pcminfo));
+			printf("  subdevices avail - %i\n", snd_pcm_info_get_subdevices_avail(pcminfo));
+			sync = snd_pcm_info_get_sync(pcminfo);
+			printf("  sync - 0x%x,0x%x,0x%x,0x%x\n", sync.id32[0], sync.id32[1], sync.id32[2], sync.id32[3]);
+		}
+		dev = -1;
+		while (1) {
+			if ((err = snd_ctl_rawmidi_next_device(handle, &dev)) < 0) {
+				printf("  RAWMIDI next device error: %s\n", snd_strerror(err));
+				break;
+			}
+			if (dev < 0)
+				break;
+			snd_rawmidi_info_set_device(rawmidiinfo, dev);
+			snd_rawmidi_info_set_subdevice(rawmidiinfo, 0);
+			snd_rawmidi_info_set_stream(rawmidiinfo, SND_RAWMIDI_STREAM_OUTPUT);
+			if ((err = snd_ctl_rawmidi_info(handle, rawmidiinfo)) < 0) {
+				printf("  RAWMIDI info error: %s\n", snd_strerror(err));
+				continue;
+			}
+			printf("RAWMIDI info, device #%i:\n", dev);
+			printf("  device - %i\n", snd_rawmidi_info_get_device(rawmidiinfo));
+			printf("  subdevice - %i\n", snd_rawmidi_info_get_subdevice(rawmidiinfo));
+			printf("  stream - %i\n", snd_rawmidi_info_get_stream(rawmidiinfo));
+			printf("  card - %i\n", snd_rawmidi_info_get_card(rawmidiinfo));
+			printf("  flags - 0x%x\n", snd_rawmidi_info_get_flags(rawmidiinfo));
+			printf("  id - '%s'\n", snd_rawmidi_info_get_id(rawmidiinfo));
+			printf("  name - '%s'\n", snd_rawmidi_info_get_name(rawmidiinfo));
+			printf("  subname - '%s'\n", snd_rawmidi_info_get_subdevice_name(rawmidiinfo));
+			printf("  subdevices count - %i\n", snd_rawmidi_info_get_subdevices_count(rawmidiinfo));
+			printf("  subdevices avail - %i\n", snd_rawmidi_info_get_subdevices_avail(rawmidiinfo));
+		}
+		snd_ctl_close(handle);
+	}
+	
+	snd_config_update_free_global();
+	return 0;
+}
diff --git a/test/latency.c b/test/latency.c
new file mode 100644
index 0000000..e9bc6d8
--- /dev/null
+++ b/test/latency.c
@@ -0,0 +1,701 @@
+/*
+ *  Latency test program
+ *
+ *     Author: Jaroslav Kysela <perex@perex.cz>
+ *
+ *     Author of bandpass filter sweep effect:
+ *	       Maarten de Boer <mdeboer@iua.upf.es>
+ *
+ *  This small demo program can be used for measuring latency between
+ *  capture and playback. This latency is measured from driver (diff when
+ *  playback and capture was started). Scheduler is set to SCHED_RR.
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sched.h>
+#include <errno.h>
+#include <getopt.h>
+#include "../include/asoundlib.h"
+#include <sys/time.h>
+#include <math.h>
+
+char *pdevice = "hw:0,0";
+char *cdevice = "hw:0,0";
+snd_pcm_format_t format = SND_PCM_FORMAT_S16_LE;
+int rate = 22050;
+int channels = 2;
+int buffer_size = 0;		/* auto */
+int period_size = 0;		/* auto */
+int latency_min = 32;		/* in frames / 2 */
+int latency_max = 2048;		/* in frames / 2 */
+int loop_sec = 30;		/* seconds */
+int block = 0;			/* block mode */
+int use_poll = 0;
+int resample = 1;
+unsigned long loop_limit;
+
+snd_output_t *output = NULL;
+
+int setparams_stream(snd_pcm_t *handle,
+		     snd_pcm_hw_params_t *params,
+		     const char *id)
+{
+	int err;
+	unsigned int rrate;
+
+	err = snd_pcm_hw_params_any(handle, params);
+	if (err < 0) {
+		printf("Broken configuration for %s PCM: no configurations available: %s\n", snd_strerror(err), id);
+		return err;
+	}
+	err = snd_pcm_hw_params_set_rate_resample(handle, params, resample);
+	if (err < 0) {
+		printf("Resample setup failed for %s (val %i): %s\n", id, resample, snd_strerror(err));
+		return err;
+	}
+	err = snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
+	if (err < 0) {
+		printf("Access type not available for %s: %s\n", id, snd_strerror(err));
+		return err;
+	}
+	err = snd_pcm_hw_params_set_format(handle, params, format);
+	if (err < 0) {
+		printf("Sample format not available for %s: %s\n", id, snd_strerror(err));
+		return err;
+	}
+	err = snd_pcm_hw_params_set_channels(handle, params, channels);
+	if (err < 0) {
+		printf("Channels count (%i) not available for %s: %s\n", channels, id, snd_strerror(err));
+		return err;
+	}
+	rrate = rate;
+	err = snd_pcm_hw_params_set_rate_near(handle, params, &rrate, 0);
+	if (err < 0) {
+		printf("Rate %iHz not available for %s: %s\n", rate, id, snd_strerror(err));
+		return err;
+	}
+	if ((int)rrate != rate) {
+		printf("Rate doesn't match (requested %iHz, get %iHz)\n", rate, err);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+int setparams_bufsize(snd_pcm_t *handle,
+		      snd_pcm_hw_params_t *params,
+		      snd_pcm_hw_params_t *tparams,
+		      snd_pcm_uframes_t bufsize,
+		      const char *id)
+{
+	int err;
+	snd_pcm_uframes_t periodsize;
+
+	snd_pcm_hw_params_copy(params, tparams);
+	periodsize = bufsize * 2;
+	err = snd_pcm_hw_params_set_buffer_size_near(handle, params, &periodsize);
+	if (err < 0) {
+		printf("Unable to set buffer size %li for %s: %s\n", bufsize * 2, id, snd_strerror(err));
+		return err;
+	}
+	if (period_size > 0)
+		periodsize = period_size;
+	else
+		periodsize /= 2;
+	err = snd_pcm_hw_params_set_period_size_near(handle, params, &periodsize, 0);
+	if (err < 0) {
+		printf("Unable to set period size %li for %s: %s\n", periodsize, id, snd_strerror(err));
+		return err;
+	}
+	return 0;
+}
+
+int setparams_set(snd_pcm_t *handle,
+		  snd_pcm_hw_params_t *params,
+		  snd_pcm_sw_params_t *swparams,
+		  const char *id)
+{
+	int err;
+	snd_pcm_uframes_t val;
+
+	err = snd_pcm_hw_params(handle, params);
+	if (err < 0) {
+		printf("Unable to set hw params for %s: %s\n", id, snd_strerror(err));
+		return err;
+	}
+	err = snd_pcm_sw_params_current(handle, swparams);
+	if (err < 0) {
+		printf("Unable to determine current swparams for %s: %s\n", id, snd_strerror(err));
+		return err;
+	}
+	err = snd_pcm_sw_params_set_start_threshold(handle, swparams, 0x7fffffff);
+	if (err < 0) {
+		printf("Unable to set start threshold mode for %s: %s\n", id, snd_strerror(err));
+		return err;
+	}
+	if (!block)
+		val = 4;
+	else
+		snd_pcm_hw_params_get_period_size(params, &val, NULL);
+	err = snd_pcm_sw_params_set_avail_min(handle, swparams, val);
+	if (err < 0) {
+		printf("Unable to set avail min for %s: %s\n", id, snd_strerror(err));
+		return err;
+	}
+	err = snd_pcm_sw_params(handle, swparams);
+	if (err < 0) {
+		printf("Unable to set sw params for %s: %s\n", id, snd_strerror(err));
+		return err;
+	}
+	return 0;
+}
+
+int setparams(snd_pcm_t *phandle, snd_pcm_t *chandle, int *bufsize)
+{
+	int err, last_bufsize = *bufsize;
+	snd_pcm_hw_params_t *pt_params, *ct_params;	/* templates with rate, format and channels */
+	snd_pcm_hw_params_t *p_params, *c_params;
+	snd_pcm_sw_params_t *p_swparams, *c_swparams;
+	snd_pcm_uframes_t p_size, c_size, p_psize, c_psize;
+	unsigned int p_time, c_time;
+	unsigned int val;
+
+	snd_pcm_hw_params_alloca(&p_params);
+	snd_pcm_hw_params_alloca(&c_params);
+	snd_pcm_hw_params_alloca(&pt_params);
+	snd_pcm_hw_params_alloca(&ct_params);
+	snd_pcm_sw_params_alloca(&p_swparams);
+	snd_pcm_sw_params_alloca(&c_swparams);
+	if ((err = setparams_stream(phandle, pt_params, "playback")) < 0) {
+		printf("Unable to set parameters for playback stream: %s\n", snd_strerror(err));
+		exit(0);
+	}
+	if ((err = setparams_stream(chandle, ct_params, "capture")) < 0) {
+		printf("Unable to set parameters for playback stream: %s\n", snd_strerror(err));
+		exit(0);
+	}
+
+	if (buffer_size > 0) {
+		*bufsize = buffer_size;
+		goto __set_it;
+	}
+
+      __again:
+      	if (buffer_size > 0)
+      		return -1;
+      	if (last_bufsize == *bufsize)
+		*bufsize += 4;
+	last_bufsize = *bufsize;
+	if (*bufsize > latency_max)
+		return -1;
+      __set_it:
+	if ((err = setparams_bufsize(phandle, p_params, pt_params, *bufsize, "playback")) < 0) {
+		printf("Unable to set sw parameters for playback stream: %s\n", snd_strerror(err));
+		exit(0);
+	}
+	if ((err = setparams_bufsize(chandle, c_params, ct_params, *bufsize, "capture")) < 0) {
+		printf("Unable to set sw parameters for playback stream: %s\n", snd_strerror(err));
+		exit(0);
+	}
+
+	snd_pcm_hw_params_get_period_size(p_params, &p_psize, NULL);
+	if (p_psize > (unsigned int)*bufsize)
+		*bufsize = p_psize;
+	snd_pcm_hw_params_get_period_size(c_params, &c_psize, NULL);
+	if (c_psize > (unsigned int)*bufsize)
+		*bufsize = c_psize;
+	snd_pcm_hw_params_get_period_time(p_params, &p_time, NULL);
+	snd_pcm_hw_params_get_period_time(c_params, &c_time, NULL);
+	if (p_time != c_time)
+		goto __again;
+
+	snd_pcm_hw_params_get_buffer_size(p_params, &p_size);
+	if (p_psize * 2 < p_size) {
+                snd_pcm_hw_params_get_periods_min(p_params, &val, NULL);
+                if (val > 2) {
+			printf("playback device does not support 2 periods per buffer\n");
+			exit(0);
+		}
+		goto __again;
+	}
+	snd_pcm_hw_params_get_buffer_size(c_params, &c_size);
+	if (c_psize * 2 < c_size) {
+                snd_pcm_hw_params_get_periods_min(c_params, &val, NULL);
+		if (val > 2 ) {
+			printf("capture device does not support 2 periods per buffer\n");
+			exit(0);
+		}
+		goto __again;
+	}
+	if ((err = setparams_set(phandle, p_params, p_swparams, "playback")) < 0) {
+		printf("Unable to set sw parameters for playback stream: %s\n", snd_strerror(err));
+		exit(0);
+	}
+	if ((err = setparams_set(chandle, c_params, c_swparams, "capture")) < 0) {
+		printf("Unable to set sw parameters for playback stream: %s\n", snd_strerror(err));
+		exit(0);
+	}
+
+	if ((err = snd_pcm_prepare(phandle)) < 0) {
+		printf("Prepare error: %s\n", snd_strerror(err));
+		exit(0);
+	}
+
+	snd_pcm_dump(phandle, output);
+	snd_pcm_dump(chandle, output);
+	fflush(stdout);
+	return 0;
+}
+
+void showstat(snd_pcm_t *handle, size_t frames)
+{
+	int err;
+	snd_pcm_status_t *status;
+
+	snd_pcm_status_alloca(&status);
+	if ((err = snd_pcm_status(handle, status)) < 0) {
+		printf("Stream status error: %s\n", snd_strerror(err));
+		exit(0);
+	}
+	printf("*** frames = %li ***\n", (long)frames);
+	snd_pcm_status_dump(status, output);
+}
+
+void showlatency(size_t latency)
+{
+	double d;
+	latency *= 2;
+	d = (double)latency / (double)rate;
+	printf("Trying latency %li frames, %.3fus, %.6fms (%.4fHz)\n", (long)latency, d * 1000000, d * 1000, (double)1 / d);
+}
+
+void showinmax(size_t in_max)
+{
+	double d;
+
+	printf("Maximum read: %li frames\n", (long)in_max);
+	d = (double)in_max / (double)rate;
+	printf("Maximum read latency: %.3fus, %.6fms (%.4fHz)\n", d * 1000000, d * 1000, (double)1 / d);
+}
+
+void gettimestamp(snd_pcm_t *handle, snd_timestamp_t *timestamp)
+{
+	int err;
+	snd_pcm_status_t *status;
+
+	snd_pcm_status_alloca(&status);
+	if ((err = snd_pcm_status(handle, status)) < 0) {
+		printf("Stream status error: %s\n", snd_strerror(err));
+		exit(0);
+	}
+	snd_pcm_status_get_trigger_tstamp(status, timestamp);
+}
+
+void setscheduler(void)
+{
+	struct sched_param sched_param;
+
+	if (sched_getparam(0, &sched_param) < 0) {
+		printf("Scheduler getparam failed...\n");
+		return;
+	}
+	sched_param.sched_priority = sched_get_priority_max(SCHED_RR);
+	if (!sched_setscheduler(0, SCHED_RR, &sched_param)) {
+		printf("Scheduler set to Round Robin with priority %i...\n", sched_param.sched_priority);
+		fflush(stdout);
+		return;
+	}
+	printf("!!!Scheduler set to Round Robin with priority %i FAILED!!!\n", sched_param.sched_priority);
+}
+
+long timediff(snd_timestamp_t t1, snd_timestamp_t t2)
+{
+	signed long l;
+
+	t1.tv_sec -= t2.tv_sec;
+	l = (signed long) t1.tv_usec - (signed long) t2.tv_usec;
+	if (l < 0) {
+		t1.tv_sec--;
+		l = 1000000 + l;
+		l %= 1000000;
+	}
+	return (t1.tv_sec * 1000000) + l;
+}
+
+long readbuf(snd_pcm_t *handle, char *buf, long len, size_t *frames, size_t *max)
+{
+	long r;
+
+	if (!block) {
+		do {
+			r = snd_pcm_readi(handle, buf, len);
+		} while (r == -EAGAIN);
+		if (r > 0) {
+			*frames += r;
+			if ((long)*max < r)
+				*max = r;
+		}
+		// printf("read = %li\n", r);
+	} else {
+		int frame_bytes = (snd_pcm_format_width(format) / 8) * channels;
+		do {
+			r = snd_pcm_readi(handle, buf, len);
+			if (r > 0) {
+				buf += r * frame_bytes;
+				len -= r;
+				*frames += r;
+				if ((long)*max < r)
+					*max = r;
+			}
+			// printf("r = %li, len = %li\n", r, len);
+		} while (r >= 1 && len > 0);
+	}
+	// showstat(handle, 0);
+	return r;
+}
+
+long writebuf(snd_pcm_t *handle, char *buf, long len, size_t *frames)
+{
+	long r;
+
+	while (len > 0) {
+		r = snd_pcm_writei(handle, buf, len);
+		if (r == -EAGAIN)
+			continue;
+		// printf("write = %li\n", r);
+		if (r < 0)
+			return r;
+		// showstat(handle, 0);
+		buf += r * 4;
+		len -= r;
+		*frames += r;
+	}
+	return 0;
+}
+			
+#define FILTERSWEEP_LFO_CENTER 2000.
+#define FILTERSWEEP_LFO_DEPTH 1800.
+#define FILTERSWEEP_LFO_FREQ 0.2
+#define FILTER_BANDWIDTH 50
+
+/* filter the sweep variables */
+float lfo,dlfo,fs,fc,BW,C,D,a0,a1,a2,b1,b2,*x[3],*y[3];
+
+void applyeffect(char* buffer,int r)
+{
+	short* samples = (short*) buffer;
+	int i;
+	for (i=0;i<r;i++)
+	{
+		int chn;
+
+		fc = sin(lfo)*FILTERSWEEP_LFO_DEPTH+FILTERSWEEP_LFO_CENTER;
+		lfo += dlfo;
+		if (lfo>2.*M_PI) lfo -= 2.*M_PI;
+		C = 1./tan(M_PI*BW/fs);
+		D = 2.*cos(2*M_PI*fc/fs);
+		a0 = 1./(1.+C);
+		a1 = 0;
+		a2 = -a0;
+		b1 = -C*D*a0;
+		b2 = (C-1)*a0;
+
+		for (chn=0;chn<channels;chn++)
+		{
+			x[chn][2] = x[chn][1];
+			x[chn][1] = x[chn][0];
+
+			y[chn][2] = y[chn][1];
+			y[chn][1] = y[chn][0];
+
+			x[chn][0] = samples[i*channels+chn];
+			y[chn][0] = a0*x[chn][0] + a1*x[chn][1] + a2*x[chn][2] 
+				- b1*y[chn][1] - b2*y[chn][2];
+			samples[i*channels+chn] = y[chn][0];
+		}
+	}
+}
+
+void help(void)
+{
+	int k;
+	printf(
+"Usage: latency [OPTION]... [FILE]...\n"
+"-h,--help      help\n"
+"-P,--pdevice   playback device\n"
+"-C,--cdevice   capture device\n"
+"-m,--min       minimum latency in frames\n"
+"-M,--max       maximum latency in frames\n"
+"-F,--frames    frames to transfer\n"
+"-f,--format    sample format\n"
+"-c,--channels  channels\n"
+"-r,--rate      rate\n"
+"-B,--buffer    buffer size in frames\n"
+"-E,--period    period size in frames\n"
+"-s,--seconds   duration of test in seconds\n"
+"-b,--block     block mode\n"
+"-p,--poll      use poll (wait for event - reduces CPU usage)\n"
+"-e,--effect    apply an effect (bandpass filter sweep)\n"
+);
+        printf("Recognized sample formats are:");
+        for (k = 0; k < SND_PCM_FORMAT_LAST; ++k) {
+                const char *s = snd_pcm_format_name(k);
+                if (s)
+                        printf(" %s", s);
+        }
+        printf("\n\n");
+        printf(
+"Tip #1 (usable latency with large periods, non-blocking mode, good CPU usage,\n"
+"        superb xrun prevention):\n"
+"  latency -m 8192 -M 8192 -t 1 -p\n"
+"Tip #2 (superb latency, non-blocking mode, but heavy CPU usage):\n"
+"  latency -m 128 -M 128\n"
+);
+}
+
+int main(int argc, char *argv[])
+{
+	struct option long_option[] =
+	{
+		{"help", 0, NULL, 'h'},
+		{"pdevice", 1, NULL, 'P'},
+		{"cdevice", 1, NULL, 'C'},
+		{"min", 1, NULL, 'm'},
+		{"max", 1, NULL, 'M'},
+		{"frames", 1, NULL, 'F'},
+		{"format", 1, NULL, 'f'},
+		{"channels", 1, NULL, 'c'},
+		{"rate", 1, NULL, 'r'},
+		{"buffer", 1, NULL, 'B'},
+		{"period", 1, NULL, 'E'},
+		{"seconds", 1, NULL, 's'},
+		{"block", 0, NULL, 'b'},
+		{"poll", 0, NULL, 'p'},
+		{"effect", 0, NULL, 'e'},
+		{NULL, 0, NULL, 0},
+	};
+	snd_pcm_t *phandle, *chandle;
+	char *buffer;
+	int err, latency, morehelp;
+	int ok;
+	snd_timestamp_t p_tstamp, c_tstamp;
+	ssize_t r;
+	size_t frames_in, frames_out, in_max;
+	int effect = 0;
+	morehelp = 0;
+	while (1) {
+		int c;
+		if ((c = getopt_long(argc, argv, "hP:C:m:M:F:f:c:r:s:bpen", long_option, NULL)) < 0)
+			break;
+		switch (c) {
+		case 'h':
+			morehelp++;
+			break;
+		case 'P':
+			pdevice = strdup(optarg);
+			break;
+		case 'C':
+			cdevice = strdup(optarg);
+			break;
+		case 'm':
+			err = atoi(optarg) / 2;
+			latency_min = err >= 4 ? err : 4;
+			if (latency_max < latency_min)
+				latency_max = latency_min;
+			break;
+		case 'M':
+			err = atoi(optarg) / 2;
+			latency_max = latency_min > err ? latency_min : err;
+			break;
+		case 'f':
+			format = snd_pcm_format_value(optarg);
+			if (format == SND_PCM_FORMAT_UNKNOWN) {
+				printf("Unknown format, setting to default S16_LE\n");
+				format = SND_PCM_FORMAT_S16_LE;
+			}
+			break;
+		case 'c':
+			err = atoi(optarg);
+			channels = err >= 1 && err < 1024 ? err : 1;
+			break;
+		case 'r':
+			err = atoi(optarg);
+			rate = err >= 4000 && err < 200000 ? err : 44100;
+			break;
+		case 'B':
+			err = atoi(optarg);
+			buffer_size = err >= 32 && err < 200000 ? err : 0;
+			break;
+		case 'E':
+			err = atoi(optarg);
+			period_size = err >= 32 && err < 200000 ? err : 0;
+			break;
+		case 's':
+			err = atoi(optarg);
+			loop_sec = err >= 1 && err <= 100000 ? err : 30;
+			break;
+		case 'b':
+			block = 1;
+			break;
+		case 'p':
+			use_poll = 1;
+			break;
+		case 'e':
+			effect = 1;
+			break;
+		case 'n':
+			resample = 0;
+			break;
+		}
+	}
+
+	if (morehelp) {
+		help();
+		return 0;
+	}
+	err = snd_output_stdio_attach(&output, stdout, 0);
+	if (err < 0) {
+		printf("Output failed: %s\n", snd_strerror(err));
+		return 0;
+	}
+
+	loop_limit = loop_sec * rate;
+	latency = latency_min - 4;
+	buffer = malloc((latency_max * snd_pcm_format_width(format) / 8) * 2);
+
+	setscheduler();
+
+	printf("Playback device is %s\n", pdevice);
+	printf("Capture device is %s\n", cdevice);
+	printf("Parameters are %iHz, %s, %i channels, %s mode\n", rate, snd_pcm_format_name(format), channels, block ? "blocking" : "non-blocking");
+	printf("Poll mode: %s\n", use_poll ? "yes" : "no");
+	printf("Loop limit is %li frames, minimum latency = %i, maximum latency = %i\n", loop_limit, latency_min * 2, latency_max * 2);
+
+	if ((err = snd_pcm_open(&phandle, pdevice, SND_PCM_STREAM_PLAYBACK, block ? 0 : SND_PCM_NONBLOCK)) < 0) {
+		printf("Playback open error: %s\n", snd_strerror(err));
+		return 0;
+	}
+	if ((err = snd_pcm_open(&chandle, cdevice, SND_PCM_STREAM_CAPTURE, block ? 0 : SND_PCM_NONBLOCK)) < 0) {
+		printf("Record open error: %s\n", snd_strerror(err));
+		return 0;
+	}
+
+	/* initialize the filter sweep variables */
+	if (effect) {
+		fs = (float) rate;
+		BW = FILTER_BANDWIDTH;
+
+		lfo = 0;
+		dlfo = 2.*M_PI*FILTERSWEEP_LFO_FREQ/fs;
+
+		x[0] = (float*) malloc(channels*sizeof(float));		
+		x[1] = (float*) malloc(channels*sizeof(float));		
+		x[2] = (float*) malloc(channels*sizeof(float));		
+		y[0] = (float*) malloc(channels*sizeof(float));		
+		y[1] = (float*) malloc(channels*sizeof(float));		
+		y[2] = (float*) malloc(channels*sizeof(float));		
+	}
+			  
+	while (1) {
+		frames_in = frames_out = 0;
+		if (setparams(phandle, chandle, &latency) < 0)
+			break;
+		showlatency(latency);
+		if ((err = snd_pcm_link(chandle, phandle)) < 0) {
+			printf("Streams link error: %s\n", snd_strerror(err));
+			exit(0);
+		}
+		if (snd_pcm_format_set_silence(format, buffer, latency*channels) < 0) {
+			fprintf(stderr, "silence error\n");
+			break;
+		}
+		if (writebuf(phandle, buffer, latency, &frames_out) < 0) {
+			fprintf(stderr, "write error\n");
+			break;
+		}
+		if (writebuf(phandle, buffer, latency, &frames_out) < 0) {
+			fprintf(stderr, "write error\n");
+			break;
+		}
+
+		if ((err = snd_pcm_start(chandle)) < 0) {
+			printf("Go error: %s\n", snd_strerror(err));
+			exit(0);
+		}
+		gettimestamp(phandle, &p_tstamp);
+		gettimestamp(chandle, &c_tstamp);
+#if 0
+		printf("Playback:\n");
+		showstat(phandle, frames_out);
+		printf("Capture:\n");
+		showstat(chandle, frames_in);
+#endif
+
+		ok = 1;
+		in_max = 0;
+		while (ok && frames_in < loop_limit) {
+			if (use_poll) {
+				/* use poll to wait for next event */
+				snd_pcm_wait(chandle, 1000);
+			}
+			if ((r = readbuf(chandle, buffer, latency, &frames_in, &in_max)) < 0)
+				ok = 0;
+			else {
+				if (effect)
+					applyeffect(buffer,r);
+			 	if (writebuf(phandle, buffer, r, &frames_out) < 0)
+					ok = 0;
+			}
+		}
+		if (ok)
+			printf("Success\n");
+		else
+			printf("Failure\n");
+		printf("Playback:\n");
+		showstat(phandle, frames_out);
+		printf("Capture:\n");
+		showstat(chandle, frames_in);
+		showinmax(in_max);
+		if (p_tstamp.tv_sec == p_tstamp.tv_sec &&
+		    p_tstamp.tv_usec == c_tstamp.tv_usec)
+			printf("Hardware sync\n");
+		snd_pcm_drop(chandle);
+		snd_pcm_nonblock(phandle, 0);
+		snd_pcm_drain(phandle);
+		snd_pcm_nonblock(phandle, !block ? 1 : 0);
+		if (ok) {
+#if 1
+			printf("Playback time = %li.%i, Record time = %li.%i, diff = %li\n",
+			       p_tstamp.tv_sec,
+			       (int)p_tstamp.tv_usec,
+			       c_tstamp.tv_sec,
+			       (int)c_tstamp.tv_usec,
+			       timediff(p_tstamp, c_tstamp));
+#endif
+			break;
+		}
+		snd_pcm_unlink(chandle);
+		snd_pcm_hw_free(phandle);
+		snd_pcm_hw_free(chandle);
+	}
+	snd_pcm_close(phandle);
+	snd_pcm_close(chandle);
+	return 0;
+}
diff --git a/test/lsb/Makefile.am b/test/lsb/Makefile.am
new file mode 100644
index 0000000..ceb4d71
--- /dev/null
+++ b/test/lsb/Makefile.am
@@ -0,0 +1,7 @@
+TESTS  = config
+TESTS += midi_event
+check_PROGRAMS = $(TESTS)
+noinst_HEADERS = test.h
+
+AM_CFLAGS = -Wall -pipe
+LDADD = ../../src/libasound.la
diff --git a/test/lsb/config.c b/test/lsb/config.c
new file mode 100644
index 0000000..3503798
--- /dev/null
+++ b/test/lsb/config.c
@@ -0,0 +1,582 @@
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include "test.h"
+
+static int configs_equal(snd_config_t *c1, snd_config_t *c2);
+
+/* checks if all children of c1 also occur in c2 */
+static int subset_of(snd_config_t *c1, snd_config_t *c2)
+{
+	snd_config_iterator_t i, next;
+	snd_config_t *e1, *e2;
+	const char *id;
+
+	snd_config_for_each(i, next, c1) {
+		e1 = snd_config_iterator_entry(i);
+		if (snd_config_get_id(e1, &id) < 0 || !id)
+			return 0;
+		if (snd_config_search(c2, id, &e2) < 0)
+			return 0;
+		if (!configs_equal(e1, e2))
+			return 0;
+	}
+	return 1;
+}
+
+/* checks if two configuration nodes are equal */
+static int configs_equal(snd_config_t *c1, snd_config_t *c2)
+{
+	long i1, i2;
+	long long i641, i642;
+	const char *s1, *s2;
+
+	if (snd_config_get_type(c1) != snd_config_get_type(c2))
+		return 0;
+	switch (snd_config_get_type(c1)) {
+	case SND_CONFIG_TYPE_INTEGER:
+		return snd_config_get_integer(c1, &i1) >= 0 &&
+			snd_config_get_integer(c2, &i2) >= 0 &&
+			i1 == i2;
+	case SND_CONFIG_TYPE_INTEGER64:
+		return snd_config_get_integer64(c1, &i641) >= 0 &&
+			snd_config_get_integer64(c2, &i642) >= 0 &&
+			i641 == i642;
+	case SND_CONFIG_TYPE_STRING:
+		return snd_config_get_string(c1, &s1) >= 0 &&
+			snd_config_get_string(c2, &s2) >= 0 &&
+			!s1 == !s2 &&
+			(!s1 || !strcmp(s1, s2));
+	case SND_CONFIG_TYPE_COMPOUND:
+		return subset_of(c1, c2) && subset_of(c2, c1);
+	default:
+		fprintf(stderr, "unknown configuration node type %d\n",
+			(int)snd_config_get_type(c1));
+		return 0;
+	}
+}
+
+static void test_top(void)
+{
+	snd_config_t *top;
+	const char *id;
+
+	if (ALSA_CHECK(snd_config_top(&top)) < 0)
+		return;
+
+	TEST_CHECK(snd_config_get_type(top) == SND_CONFIG_TYPE_COMPOUND);
+	TEST_CHECK(snd_config_iterator_first(top) == snd_config_iterator_end(top));
+	TEST_CHECK(snd_config_get_id(top, &id) >= 0 && id == NULL);
+
+	ALSA_CHECK(snd_config_delete(top));
+}
+
+static void test_load(void)
+{
+	const char *config_text1 = "s='world';";
+	const char *config_text2 = "c.elem 0";
+	snd_config_t *loaded, *made, *c, *c2;
+	snd_input_t *input;
+
+	ALSA_CHECK(snd_config_top(&loaded));
+	ALSA_CHECK(snd_config_imake_integer(&c, "i", 42));
+	ALSA_CHECK(snd_config_add(loaded, c));
+	ALSA_CHECK(snd_config_imake_string(&c, "s", "hello"));
+	ALSA_CHECK(snd_config_add(loaded, c));
+
+	ALSA_CHECK(snd_config_top(&made));
+	ALSA_CHECK(snd_config_imake_string(&c, "s", "world"));
+	ALSA_CHECK(snd_config_add(made, c));
+	ALSA_CHECK(snd_config_imake_integer(&c, "i", 42));
+	ALSA_CHECK(snd_config_add(made, c));
+
+	ALSA_CHECK(snd_input_buffer_open(&input, config_text1, strlen(config_text1)));
+	ALSA_CHECK(snd_config_load(loaded, input));
+	ALSA_CHECK(snd_input_close(input));
+	TEST_CHECK(configs_equal(loaded, made));
+
+	ALSA_CHECK(snd_config_make_compound(&c, "c", 0));
+	ALSA_CHECK(snd_config_add(made, c));
+	ALSA_CHECK(snd_config_imake_integer(&c2, "elem", 0));
+	ALSA_CHECK(snd_config_add(c, c2));
+
+	ALSA_CHECK(snd_input_buffer_open(&input, config_text2, strlen(config_text2)));
+	ALSA_CHECK(snd_config_load(loaded, input));
+	ALSA_CHECK(snd_input_close(input));
+	TEST_CHECK(configs_equal(loaded, made));
+
+	ALSA_CHECK(snd_config_delete(loaded));
+	ALSA_CHECK(snd_config_delete(made));
+}
+
+static void test_save(void)
+{
+	const char *text =
+		"a.b.c 'x.y.z'\n"
+		"xxx = yyy;\n"
+		"q { qq=qqq }\n"
+		"a [ 1 2 3 4 5 '...' ]\n";
+	snd_config_t *orig, *saved;
+	snd_input_t *input;
+	snd_output_t *output;
+	char *buf;
+	size_t buf_size;
+
+	ALSA_CHECK(snd_input_buffer_open(&input, text, strlen(text)));
+	ALSA_CHECK(snd_config_top(&orig));
+	ALSA_CHECK(snd_config_load(orig, input));
+	ALSA_CHECK(snd_input_close(input));
+	ALSA_CHECK(snd_output_buffer_open(&output));
+	ALSA_CHECK(snd_config_save(orig, output));
+	buf_size = snd_output_buffer_string(output, &buf);
+	ALSA_CHECK(snd_input_buffer_open(&input, buf, buf_size));
+	ALSA_CHECK(snd_config_top(&saved));
+	ALSA_CHECK(snd_config_load(saved, input));
+	ALSA_CHECK(snd_input_close(input));
+	ALSA_CHECK(snd_output_close(output));
+	TEST_CHECK(configs_equal(orig, saved));
+	ALSA_CHECK(snd_config_delete(orig));
+	ALSA_CHECK(snd_config_delete(saved));
+}
+
+static void test_update(void)
+{
+	ALSA_CHECK(snd_config_update_free_global());
+	TEST_CHECK(snd_config == NULL);
+	ALSA_CHECK(snd_config_update());
+	TEST_CHECK(snd_config_get_type(snd_config) == SND_CONFIG_TYPE_COMPOUND);
+	ALSA_CHECK(snd_config_update());
+	TEST_CHECK(snd_config_get_type(snd_config) == SND_CONFIG_TYPE_COMPOUND);
+	ALSA_CHECK(snd_config_update_free_global());
+	TEST_CHECK(snd_config == NULL);
+}
+
+static void test_search(void)
+{
+	const char *text =
+		"a 42\n"
+		"b {\n"
+		"    c cee\n"
+		"    d {\n"
+		"        e 2.71828\n"
+		"    }\n"
+		"}\n";
+	snd_input_t *input;
+	snd_config_t *top, *c;
+	const char *id;
+
+	ALSA_CHECK(snd_input_buffer_open(&input, text, strlen(text)));
+	ALSA_CHECK(snd_config_top(&top));
+	ALSA_CHECK(snd_config_load(top, input));
+	ALSA_CHECK(snd_input_close(input));
+
+	ALSA_CHECK(snd_config_search(top, "a", &c));
+	ALSA_CHECK(snd_config_get_id(c, &id));
+	TEST_CHECK(!strcmp(id, "a"));
+	ALSA_CHECK(snd_config_search(top, "b.d.e", &c));
+	ALSA_CHECK(snd_config_get_id(c, &id));
+	TEST_CHECK(!strcmp(id, "e"));
+	ALSA_CHECK(snd_config_search(top, "b.c", NULL));
+	TEST_CHECK(snd_config_search(top, "x", NULL) == -ENOENT);
+	TEST_CHECK(snd_config_search(top, "b.y", &c) == -ENOENT);
+	TEST_CHECK(snd_config_search(top, "a.z", &c) == -ENOENT);
+
+	ALSA_CHECK(snd_config_delete(top));
+}
+
+static void test_searchv(void)
+{
+	const char *text =
+		"a 42\n"
+		"b {\n"
+		"    c cee\n"
+		"    d {\n"
+		"        e 2.71828\n"
+		"    }\n"
+		"}\n";
+	snd_input_t *input;
+	snd_config_t *top, *c;
+	const char *id;
+
+	ALSA_CHECK(snd_input_buffer_open(&input, text, strlen(text)));
+	ALSA_CHECK(snd_config_top(&top));
+	ALSA_CHECK(snd_config_load(top, input));
+	ALSA_CHECK(snd_input_close(input));
+
+	ALSA_CHECK(snd_config_searchv(top, &c, "a", NULL));
+	ALSA_CHECK(snd_config_get_id(c, &id));
+	TEST_CHECK(!strcmp(id, "a"));
+	ALSA_CHECK(snd_config_searchv(top, &c, "b", "d.e", NULL));
+	ALSA_CHECK(snd_config_get_id(c, &id));
+	TEST_CHECK(!strcmp(id, "e"));
+	ALSA_CHECK(snd_config_searchv(top, NULL, "b.c", NULL));
+	TEST_CHECK(snd_config_searchv(top, NULL, "x", NULL) == -ENOENT);
+	TEST_CHECK(snd_config_searchv(top, &c, "b.y", NULL) == -ENOENT);
+	TEST_CHECK(snd_config_searchv(top, &c, "a", "z", NULL) == -ENOENT);
+
+	ALSA_CHECK(snd_config_delete(top));
+}
+
+static void test_add(void)
+{
+	snd_config_t *c1, *c2, *c3, *c4, *c5;
+	snd_config_iterator_t i;
+	unsigned int count = 0;
+
+	ALSA_CHECK(snd_config_top(&c1));
+	ALSA_CHECK(snd_config_imake_integer(&c2, "c2", 0xc2));
+	ALSA_CHECK(snd_config_add(c1, c2));
+	ALSA_CHECK(snd_config_imake_string(&c3, "c3", "c3"));
+	ALSA_CHECK(snd_config_add(c1, c3));
+	for (i = snd_config_iterator_first(c1);
+	     i != snd_config_iterator_end(c1);
+	     i = snd_config_iterator_next(i))
+		++count;
+	TEST_CHECK(count == 2);
+	ALSA_CHECK(snd_config_search(c1, "c2", &c2));
+	ALSA_CHECK(snd_config_search(c1, "c3", &c3));
+	ALSA_CHECK(snd_config_top(&c4));
+	TEST_CHECK(snd_config_add(c1, c4) == -EINVAL);
+	ALSA_CHECK(snd_config_imake_integer(&c5, "c5", 5));
+	ALSA_CHECK(snd_config_add(c4, c5));
+	TEST_CHECK(snd_config_add(c1, c5) == -EINVAL);
+	ALSA_CHECK(snd_config_delete(c4));
+	ALSA_CHECK(snd_config_imake_integer(&c3, "c3", 333));
+	TEST_CHECK(snd_config_add(c1, c3) == -EEXIST);
+	ALSA_CHECK(snd_config_delete(c3));
+	ALSA_CHECK(snd_config_delete(c1));
+}
+
+static void test_delete(void)
+{
+	snd_config_t *c;
+
+	ALSA_CHECK(snd_config_top(&c));
+	ALSA_CHECK(snd_config_delete(c));
+	ALSA_CHECK(snd_config_imake_string(&c, "s", "..."));
+	ALSA_CHECK(snd_config_delete(c));
+}
+
+static void test_copy(void)
+{
+	snd_config_t *c1, *c2, *c3;
+	long value;
+
+	ALSA_CHECK(snd_config_imake_integer(&c1, "c1", 123));
+	ALSA_CHECK(snd_config_copy(&c2, c1));
+	ALSA_CHECK(snd_config_set_integer(c1, 456));
+	TEST_CHECK(snd_config_get_type(c2) == SND_CONFIG_TYPE_INTEGER);
+	ALSA_CHECK(snd_config_get_integer(c2, &value));
+	TEST_CHECK(value == 123);
+	ALSA_CHECK(snd_config_delete(c1));
+	ALSA_CHECK(snd_config_delete(c2));
+	ALSA_CHECK(snd_config_top(&c1));
+	ALSA_CHECK(snd_config_imake_integer(&c2, "a", 1));
+	ALSA_CHECK(snd_config_add(c1, c2));
+	ALSA_CHECK(snd_config_copy(&c3, c1));
+	ALSA_CHECK(snd_config_set_integer(c2, 2));
+	TEST_CHECK(!configs_equal(c1, c3));
+	ALSA_CHECK(snd_config_search(c3, "a", &c2));
+	ALSA_CHECK(snd_config_set_integer(c2, 2));
+	TEST_CHECK(configs_equal(c1, c3));
+	ALSA_CHECK(snd_config_delete(c1));
+	ALSA_CHECK(snd_config_delete(c3));
+}
+
+static void test_make_integer(void)
+{
+	snd_config_t *c;
+	const char *id;
+	long value;
+
+	ALSA_CHECK(snd_config_make_integer(&c, "i"));
+	TEST_CHECK(snd_config_get_type(c) == SND_CONFIG_TYPE_INTEGER);
+	ALSA_CHECK(snd_config_get_id(c, &id));
+	TEST_CHECK(!strcmp(id, "i"));
+	ALSA_CHECK(snd_config_get_integer(c, &value));
+	TEST_CHECK(value == 0);
+	ALSA_CHECK(snd_config_delete(c));
+}
+
+static void test_make_integer64(void)
+{
+	snd_config_t *c;
+	const char *id;
+	long long value;
+
+	ALSA_CHECK(snd_config_make_integer64(&c, "i"));
+	TEST_CHECK(snd_config_get_type(c) == SND_CONFIG_TYPE_INTEGER64);
+	ALSA_CHECK(snd_config_get_id(c, &id));
+	TEST_CHECK(!strcmp(id, "i"));
+	ALSA_CHECK(snd_config_get_integer64(c, &value));
+	TEST_CHECK(value == 0);
+	ALSA_CHECK(snd_config_delete(c));
+}
+
+static void test_make_string(void)
+{
+	snd_config_t *c;
+	const char *id;
+	const char *value;
+
+	ALSA_CHECK(snd_config_make_string(&c, "s"));
+	TEST_CHECK(snd_config_get_type(c) == SND_CONFIG_TYPE_STRING);
+	ALSA_CHECK(snd_config_get_id(c, &id));
+	TEST_CHECK(!strcmp(id, "s"));
+	ALSA_CHECK(snd_config_get_string(c, &value));
+	TEST_CHECK(value == NULL);
+	ALSA_CHECK(snd_config_delete(c));
+}
+
+static void test_make_compound(void)
+{
+	snd_config_t *c;
+	const char *id;
+
+	ALSA_CHECK(snd_config_make_compound(&c, "c", 0));
+	TEST_CHECK(snd_config_get_type(c) == SND_CONFIG_TYPE_COMPOUND);
+	ALSA_CHECK(snd_config_get_id(c, &id));
+	TEST_CHECK(!strcmp(id, "c"));
+	TEST_CHECK(snd_config_iterator_first(c) == snd_config_iterator_end(c));
+	ALSA_CHECK(snd_config_delete(c));
+}
+
+static void test_imake_integer(void)
+{
+	snd_config_t *c;
+	const char *id;
+	long value;
+
+	ALSA_CHECK(snd_config_imake_integer(&c, "i", 123));
+	TEST_CHECK(snd_config_get_type(c) == SND_CONFIG_TYPE_INTEGER);
+	ALSA_CHECK(snd_config_get_id(c, &id));
+	TEST_CHECK(!strcmp(id, "i"));
+	ALSA_CHECK(snd_config_get_integer(c, &value));
+	TEST_CHECK(value == 123);
+	ALSA_CHECK(snd_config_delete(c));
+}
+
+static void test_imake_integer64(void)
+{
+	snd_config_t *c;
+	const char *id;
+	long long value;
+
+	ALSA_CHECK(snd_config_imake_integer64(&c, "i", 123456789012345LL));
+	TEST_CHECK(snd_config_get_type(c) == SND_CONFIG_TYPE_INTEGER64);
+	ALSA_CHECK(snd_config_get_id(c, &id));
+	TEST_CHECK(!strcmp(id, "i"));
+	ALSA_CHECK(snd_config_get_integer64(c, &value));
+	TEST_CHECK(value == 123456789012345LL);
+	ALSA_CHECK(snd_config_delete(c));
+}
+
+static void test_imake_string(void)
+{
+	snd_config_t *c;
+	const char *id;
+	const char *value;
+
+	ALSA_CHECK(snd_config_imake_string(&c, "s", "xyzzy"));
+	TEST_CHECK(snd_config_get_type(c) == SND_CONFIG_TYPE_STRING);
+	ALSA_CHECK(snd_config_get_id(c, &id));
+	TEST_CHECK(!strcmp(id, "s"));
+	ALSA_CHECK(snd_config_get_string(c, &value));
+	TEST_CHECK(!strcmp(value, "xyzzy"));
+	ALSA_CHECK(snd_config_delete(c));
+}
+
+static void test_get_type(void)
+{
+	snd_config_t *c;
+
+	ALSA_CHECK(snd_config_top(&c));
+	TEST_CHECK(snd_config_get_type(c) == SND_CONFIG_TYPE_COMPOUND);
+	ALSA_CHECK(snd_config_delete(c));
+	ALSA_CHECK(snd_config_make_integer(&c, "i"));
+	TEST_CHECK(snd_config_get_type(c) == SND_CONFIG_TYPE_INTEGER);
+	ALSA_CHECK(snd_config_delete(c));
+	ALSA_CHECK(snd_config_make_string(&c, "s"));
+	TEST_CHECK(snd_config_get_type(c) == SND_CONFIG_TYPE_STRING);
+	ALSA_CHECK(snd_config_delete(c));
+}
+
+static void test_set_integer(void)
+{
+	snd_config_t *c;
+	long value;
+
+	ALSA_CHECK(snd_config_make_integer(&c, "i"));
+	ALSA_CHECK(snd_config_set_integer(c, 123));
+	ALSA_CHECK(snd_config_get_integer(c, &value));
+	TEST_CHECK(value == 123);
+	ALSA_CHECK(snd_config_delete(c));
+	ALSA_CHECK(snd_config_make_string(&c, "s"));
+	TEST_CHECK(snd_config_set_integer(c, 123) == -EINVAL);
+	ALSA_CHECK(snd_config_delete(c));
+}
+
+static void test_set_integer64(void)
+{
+	snd_config_t *c;
+	long long value;
+
+	ALSA_CHECK(snd_config_make_integer64(&c, "i"));
+	ALSA_CHECK(snd_config_set_integer64(c, 123456789012345LL));
+	ALSA_CHECK(snd_config_get_integer64(c, &value));
+	TEST_CHECK(value == 123456789012345LL);
+	ALSA_CHECK(snd_config_delete(c));
+	ALSA_CHECK(snd_config_make_string(&c, "s"));
+	TEST_CHECK(snd_config_set_integer64(c, 123) == -EINVAL);
+	ALSA_CHECK(snd_config_delete(c));
+}
+
+static void test_set_string(void)
+{
+	snd_config_t *c;
+	const char *value;
+
+	ALSA_CHECK(snd_config_make_string(&c, "s"));
+	ALSA_CHECK(snd_config_set_string(c, "string"));
+	ALSA_CHECK(snd_config_get_string(c, &value));
+	TEST_CHECK(!strcmp(value, "string"));
+	ALSA_CHECK(snd_config_set_string(c, NULL));
+	ALSA_CHECK(snd_config_get_string(c, &value));
+	TEST_CHECK(value == NULL);
+	ALSA_CHECK(snd_config_delete(c));
+	ALSA_CHECK(snd_config_make_integer(&c, "i"));
+	TEST_CHECK(snd_config_set_string(c, "") == -EINVAL);
+	ALSA_CHECK(snd_config_delete(c));
+}
+
+static void test_set_ascii(void)
+{
+	snd_config_t *c;
+	const char *s;
+	long i;
+
+	ALSA_CHECK(snd_config_make_string(&c, "s"));
+	ALSA_CHECK(snd_config_set_ascii(c, "foo"));
+	ALSA_CHECK(snd_config_get_string(c, &s));
+	TEST_CHECK(!strcmp(s, "foo"));
+	ALSA_CHECK(snd_config_delete(c));
+	ALSA_CHECK(snd_config_make_integer(&c, "i"));
+	ALSA_CHECK(snd_config_set_ascii(c, "23"));
+	ALSA_CHECK(snd_config_get_integer(c, &i));
+	TEST_CHECK(i == 23);
+	TEST_CHECK(snd_config_set_ascii(c, "half blue") == -EINVAL);
+	ALSA_CHECK(snd_config_delete(c));
+	ALSA_CHECK(snd_config_top(&c));
+	TEST_CHECK(snd_config_set_ascii(c, "0") == -EINVAL);
+	ALSA_CHECK(snd_config_delete(c));
+}
+
+static void test_get_id(void)
+{
+	snd_config_t *c;
+	const char *id;
+
+	ALSA_CHECK(snd_config_make_integer(&c, "my_id"));
+	ALSA_CHECK(snd_config_get_id(c, &id));
+	TEST_CHECK(!strcmp(id, "my_id"));
+	ALSA_CHECK(snd_config_delete(c));
+}
+
+#define test_get_integer test_set_integer
+#define test_get_integer64 test_set_integer64
+#define test_get_string test_set_string
+
+static void test_get_ascii(void)
+{
+	snd_config_t *c;
+	char *value;
+
+	ALSA_CHECK(snd_config_imake_integer(&c, "i", 123));
+	ALSA_CHECK(snd_config_get_ascii(c, &value));
+	TEST_CHECK(!strcmp(value, "123"));
+	free(value);
+	ALSA_CHECK(snd_config_delete(c));
+	ALSA_CHECK(snd_config_imake_string(&c, "s", "bar"));
+	ALSA_CHECK(snd_config_get_ascii(c, &value));
+	TEST_CHECK(!strcmp(value, "bar"));
+	free(value);
+	ALSA_CHECK(snd_config_delete(c));
+	ALSA_CHECK(snd_config_top(&c));
+	TEST_CHECK(snd_config_get_ascii(c, &value) == -EINVAL);
+	ALSA_CHECK(snd_config_delete(c));
+}
+
+static void test_iterators(void)
+{
+	snd_config_t *c, *c2;
+	snd_config_iterator_t i;
+	long v;
+
+	ALSA_CHECK(snd_config_top(&c));
+	i = snd_config_iterator_first(c);
+	TEST_CHECK(i == snd_config_iterator_end(c));
+	ALSA_CHECK(snd_config_imake_integer(&c2, "one", 1));
+	ALSA_CHECK(snd_config_add(c, c2));
+	i = snd_config_iterator_first(c);
+	TEST_CHECK(i != snd_config_iterator_end(c));
+	c2 = snd_config_iterator_entry(i);
+	ALSA_CHECK(snd_config_get_integer(c2, &v));
+	TEST_CHECK(v == 1);
+	i = snd_config_iterator_next(i);
+	TEST_CHECK(i == snd_config_iterator_end(c));
+	ALSA_CHECK(snd_config_delete(c));
+}
+
+static void test_for_each(void)
+{
+	snd_config_t *c, *c2;
+	snd_config_iterator_t i, next;
+	long v;
+	unsigned int count = 0;
+
+	ALSA_CHECK(snd_config_top(&c));
+	ALSA_CHECK(snd_config_imake_integer(&c2, "one", 1));
+	ALSA_CHECK(snd_config_add(c, c2));
+	snd_config_for_each(i, next, c) {
+		TEST_CHECK(i != snd_config_iterator_end(c));
+		c2 = snd_config_iterator_entry(i);
+		ALSA_CHECK(snd_config_get_integer(c2, &v));
+		TEST_CHECK(v == 1);
+		++count;
+	}
+	TEST_CHECK(count == 1);
+	ALSA_CHECK(snd_config_delete(c));
+}
+
+int main(void)
+{
+	test_top();
+	test_load();
+	test_save();
+	test_update();
+	test_search();
+	test_searchv();
+	test_add();
+	test_delete();
+	test_copy();
+	test_make_integer();
+	test_make_integer64();
+	test_make_string();
+	test_make_compound();
+	test_imake_integer();
+	test_imake_integer64();
+	test_imake_string();
+	test_get_type();
+	test_set_integer();
+	test_set_integer64();
+	test_set_string();
+	test_set_ascii();
+	test_get_id();
+	test_get_integer();
+	test_get_integer64();
+	test_get_string();
+	test_get_ascii();
+	test_iterators();
+	test_for_each();
+	return TEST_EXIT_CODE();
+}
diff --git a/test/lsb/midi_event.c b/test/lsb/midi_event.c
new file mode 100644
index 0000000..2ae90a7
--- /dev/null
+++ b/test/lsb/midi_event.c
@@ -0,0 +1,371 @@
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <regex.h>
+#include "test.h"
+
+/*
+ * Checks whether the regular expression matches the entire MIDI data, printed
+ * as hex.
+ */
+static int midi_matches_regex(unsigned char *midi, int count, const char *regex)
+{
+	char *text;
+	regex_t re;
+	regmatch_t match;
+	int i;
+
+	text = malloc(2 * count + 1);
+	if (!text)
+		return 0;
+	for (i = 0; i < count; ++i)
+		sprintf(text + 2 * i, "%02x", midi[i]);
+	if (regcomp(&re, regex, REG_EXTENDED) != 0) {
+		free(text);
+		return 0;
+	}
+	i = regexec(&re, text, 1, &match, 0);
+	i = i == 0 && match.rm_so == 0 && match.rm_eo == strlen(text);
+	regfree(&re);
+	free(text);
+	return i;
+}
+
+static void test_decode(void)
+{
+	snd_midi_event_t *midi_event;
+	snd_seq_event_t ev;
+	unsigned char buf[50];
+	int count;
+
+	if (ALSA_CHECK(snd_midi_event_new(256 /* ? */, &midi_event)) < 0)
+		return;
+
+#define DECODE() snd_midi_event_decode(midi_event, buf, sizeof(buf), &ev)
+#define BUF_MATCHES(str) midi_matches_regex(buf, count, str)
+#define DECODES_TO(str) ((count = DECODE()), BUF_MATCHES(str))
+
+	snd_seq_ev_clear(&ev);
+
+	snd_seq_ev_set_fixed(&ev);
+	ev.type = SND_SEQ_EVENT_NONE;
+	TEST_CHECK(DECODE() == -ENOENT);
+
+	snd_seq_ev_set_noteoff(&ev, 1, 2, 3);
+	TEST_CHECK(DECODES_TO("810203"));
+
+	snd_seq_ev_set_noteon(&ev, 4, 5, 6);
+	TEST_CHECK(DECODES_TO("940506"));
+
+	snd_seq_ev_set_keypress(&ev, 7, 8, 9);
+	TEST_CHECK(DECODES_TO("a70809"));
+
+	snd_seq_ev_set_controller(&ev, 10, 11, 12);
+	TEST_CHECK(DECODES_TO("ba0b0c"));
+
+	snd_seq_ev_set_pgmchange(&ev, 13, 14);
+	TEST_CHECK(DECODES_TO("cd0e"));
+
+	snd_seq_ev_set_chanpress(&ev, 15, 16);
+	TEST_CHECK(DECODES_TO("df10"));
+
+	snd_seq_ev_set_pitchbend(&ev, 1, 0x222);
+	TEST_CHECK(DECODES_TO("e12244"));
+
+	snd_seq_ev_set_sysex(&ev, 6, "\xf0\x7e\x7f\x06\x01\xf7");
+	TEST_CHECK(DECODES_TO("f07e7f0601f7"));
+
+	snd_seq_ev_set_fixed(&ev);
+	ev.type = SND_SEQ_EVENT_QFRAME;
+	ev.data.control.value = 3;
+	TEST_CHECK(DECODES_TO("f103"));
+
+	ev.type = SND_SEQ_EVENT_SONGPOS;
+	ev.data.control.value = 0x444;
+	TEST_CHECK(DECODES_TO("f24408"));
+
+	ev.type = SND_SEQ_EVENT_SONGSEL;
+	ev.data.control.value = 5;
+	TEST_CHECK(DECODES_TO("f305"));
+
+	ev.type = SND_SEQ_EVENT_TUNE_REQUEST;
+	TEST_CHECK(DECODES_TO("f6"));
+
+	ev.type = SND_SEQ_EVENT_CLOCK;
+	TEST_CHECK(DECODES_TO("f8"));
+
+	ev.type = SND_SEQ_EVENT_START;
+	TEST_CHECK(DECODES_TO("fa"));
+
+	ev.type = SND_SEQ_EVENT_CONTINUE;
+	TEST_CHECK(DECODES_TO("fb"));
+
+	ev.type = SND_SEQ_EVENT_STOP;
+	TEST_CHECK(DECODES_TO("fc"));
+
+	ev.type = SND_SEQ_EVENT_SENSING;
+	TEST_CHECK(DECODES_TO("fe"));
+
+	ev.type = SND_SEQ_EVENT_RESET;
+	TEST_CHECK(DECODES_TO("ff"));
+
+	ev.type = SND_SEQ_EVENT_CONTROL14;
+	ev.data.control.channel = 6;
+	ev.data.control.param = 7;
+	ev.data.control.value = 0x888;
+	/*
+	 * This regular expression catches all allowed combinations of LSB/MSB
+	 * order and running status.
+	 */
+	TEST_CHECK(DECODES_TO("b6(0711(b6)?2708|2708(b6)?0711)"));
+
+	ev.type = SND_SEQ_EVENT_NONREGPARAM;
+	ev.data.control.channel = 9;
+	ev.data.control.param = 0xaaa;
+	ev.data.control.value = 0xbbb;
+	TEST_CHECK(DECODES_TO("b9(622a(b9)?6315|6315(b9)?622a)(b9)?(0617(b9)?263b|263b(b9)?0617)"));
+
+	ev.type = SND_SEQ_EVENT_REGPARAM;
+	ev.data.control.channel = 12;
+	ev.data.control.param = 0xddd;
+	ev.data.control.value = 0xeee;
+	TEST_CHECK(DECODES_TO("bc(645d(bc)?651b|651b(bc)?645d)(bc)?(061d(bc)?266e|266e(bc)?061d)"));
+
+	/* no running status after SysEx */
+	snd_seq_ev_set_pgmchange(&ev, 0, 0x11);
+	TEST_CHECK(DECODES_TO("c011"));
+	snd_seq_ev_set_sysex(&ev, 6, "\xf0\x7e\x7f\x09\x02\xf7");
+	TEST_CHECK(DECODES_TO("f07e7f0902f7"));
+	snd_seq_ev_set_pgmchange(&ev, 0, 0x11);
+	TEST_CHECK(DECODES_TO("c011"));
+
+	/* no running status for non-realtime common messages */
+	ev.type = SND_SEQ_EVENT_QFRAME;
+	ev.data.control.value = 0x11;
+	TEST_CHECK(DECODES_TO("f111"));
+	TEST_CHECK(DECODES_TO("f111"));
+
+	/* buffer overflow */
+	TEST_CHECK(snd_midi_event_decode(midi_event, buf, 1, &ev) == -ENOMEM);
+
+	snd_midi_event_free(midi_event);
+}
+
+static void test_reset_decode(void)
+{
+	snd_midi_event_t *midi_event;
+	snd_seq_event_t ev;
+	unsigned char buf[50];
+	int count;
+
+	if (ALSA_CHECK(snd_midi_event_new(256 /* ? */, &midi_event)) < 0)
+		return;
+
+	snd_seq_ev_clear(&ev);
+
+	snd_seq_ev_set_noteon(&ev, 1, 2, 3);
+	TEST_CHECK(DECODES_TO("910203"));
+
+	snd_midi_event_reset_decode(midi_event);
+
+	TEST_CHECK(DECODES_TO("910203"));
+
+	snd_midi_event_free(midi_event);
+}
+
+static void test_encode(void)
+{
+	snd_midi_event_t *midi_event;
+	snd_seq_event_t ev;
+
+	if (ALSA_CHECK(snd_midi_event_new(256, &midi_event)) < 0)
+		return;
+
+#define ENCODE(str) snd_midi_event_encode(midi_event, \
+					  (const unsigned char *)str, \
+					  sizeof(str) - 1, &ev)
+	TEST_CHECK(ENCODE("\x81\x02\x03") == 3);
+	TEST_CHECK(ev.type == SND_SEQ_EVENT_NOTEOFF);
+	TEST_CHECK((ev.flags & SND_SEQ_EVENT_LENGTH_MASK) == SND_SEQ_EVENT_LENGTH_FIXED);
+	TEST_CHECK(ev.data.note.channel == 1);
+	TEST_CHECK(ev.data.note.note == 2);
+	TEST_CHECK(ev.data.note.velocity == 3);
+
+	TEST_CHECK(ENCODE("\x94\x05\x06") == 3);
+	TEST_CHECK(ev.type == SND_SEQ_EVENT_NOTEON);
+	TEST_CHECK(ev.data.note.channel == 4);
+	TEST_CHECK(ev.data.note.note == 5);
+	TEST_CHECK(ev.data.note.velocity == 6);
+
+	TEST_CHECK(ENCODE("\xa7\x08\x09") == 3);
+	TEST_CHECK(ev.type == SND_SEQ_EVENT_KEYPRESS);
+	TEST_CHECK(ev.data.note.channel == 7);
+	TEST_CHECK(ev.data.note.note == 8);
+	TEST_CHECK(ev.data.note.velocity == 9);
+
+	TEST_CHECK(ENCODE("\xba\x0b\x0c") == 3);
+	TEST_CHECK(ev.type == SND_SEQ_EVENT_CONTROLLER);
+	TEST_CHECK(ev.data.control.channel == 10);
+	TEST_CHECK(ev.data.control.param == 11);
+	TEST_CHECK(ev.data.control.value == 12);
+
+	TEST_CHECK(ENCODE("\xcd\x0e") == 2);
+	TEST_CHECK(ev.type == SND_SEQ_EVENT_PGMCHANGE);
+	TEST_CHECK(ev.data.control.channel == 13);
+	TEST_CHECK(ev.data.control.value == 14);
+
+	TEST_CHECK(ENCODE("\xdf\x10") == 2);
+	TEST_CHECK(ev.type == SND_SEQ_EVENT_CHANPRESS);
+	TEST_CHECK(ev.data.control.channel == 15);
+	TEST_CHECK(ev.data.control.value == 16);
+
+	TEST_CHECK(ENCODE("\xe1\x22\x33") == 3);
+	TEST_CHECK(ev.type == SND_SEQ_EVENT_PITCHBEND);
+	TEST_CHECK(ev.data.control.channel == 1);
+	TEST_CHECK(ev.data.control.value == -1630);
+
+	TEST_CHECK(ENCODE("\xf0\x7f\x7f\x04\x01\x7f\x7f\xf7") == 8);
+	TEST_CHECK(ev.type == SND_SEQ_EVENT_SYSEX);
+	TEST_CHECK((ev.flags & SND_SEQ_EVENT_LENGTH_MASK) == SND_SEQ_EVENT_LENGTH_VARIABLE);
+	TEST_CHECK(ev.data.ext.len == 8);
+	TEST_CHECK(!memcmp(ev.data.ext.ptr, "\xf0\x7f\x7f\x04\x01\x7f\x7f\xf7", 8));
+
+	TEST_CHECK(ENCODE("\xf1\x04") == 2);
+	TEST_CHECK(ev.type == SND_SEQ_EVENT_QFRAME);
+	TEST_CHECK(ev.data.control.value == 4);
+
+	TEST_CHECK(ENCODE("\xf2\x55\x66") == 3);
+	TEST_CHECK(ev.type == SND_SEQ_EVENT_SONGPOS);
+	TEST_CHECK(ev.data.control.value == 13141);
+
+	TEST_CHECK(ENCODE("\xf3\x07") == 2);
+	TEST_CHECK(ev.type == SND_SEQ_EVENT_SONGSEL);
+	TEST_CHECK(ev.data.control.value == 7);
+
+	TEST_CHECK(ENCODE("\xf6") == 1);
+	TEST_CHECK(ev.type == SND_SEQ_EVENT_TUNE_REQUEST);
+
+	TEST_CHECK(ENCODE("\xf8") == 1);
+	TEST_CHECK(ev.type == SND_SEQ_EVENT_CLOCK);
+
+	TEST_CHECK(ENCODE("\xfa") == 1);
+	TEST_CHECK(ev.type == SND_SEQ_EVENT_START);
+
+	TEST_CHECK(ENCODE("\xfb") == 1);
+	TEST_CHECK(ev.type == SND_SEQ_EVENT_CONTINUE);
+
+	TEST_CHECK(ENCODE("\xfc") == 1);
+	TEST_CHECK(ev.type == SND_SEQ_EVENT_STOP);
+
+	TEST_CHECK(ENCODE("\xfe") == 1);
+	TEST_CHECK(ev.type == SND_SEQ_EVENT_SENSING);
+
+	TEST_CHECK(ENCODE("\xff") == 1);
+	TEST_CHECK(ev.type == SND_SEQ_EVENT_RESET);
+
+	TEST_CHECK(ENCODE("\xc1\xf8") == 2);
+	TEST_CHECK(ev.type == SND_SEQ_EVENT_CLOCK);
+	TEST_CHECK(ENCODE("\x22") == 1);
+	TEST_CHECK(ev.type == SND_SEQ_EVENT_PGMCHANGE);
+	TEST_CHECK(ev.data.control.channel == 1);
+	TEST_CHECK(ev.data.control.value == 0x22);
+	TEST_CHECK(ENCODE("\xf8") == 1);
+	TEST_CHECK(ev.type == SND_SEQ_EVENT_CLOCK);
+	TEST_CHECK(ENCODE("\x33") == 1);
+	TEST_CHECK(ev.type == SND_SEQ_EVENT_PGMCHANGE);
+	TEST_CHECK(ev.data.control.channel == 1);
+	TEST_CHECK(ev.data.control.value == 0x33);
+
+	TEST_CHECK(ENCODE("\xc1\xf6") == 2);
+	TEST_CHECK(ev.type == SND_SEQ_EVENT_TUNE_REQUEST);
+	TEST_CHECK(ENCODE("\x44\x44") == 2);
+	TEST_CHECK(ev.type == SND_SEQ_EVENT_NONE);
+
+	snd_midi_event_free(midi_event);
+}
+
+static void test_reset_encode(void)
+{
+	snd_midi_event_t *midi_event;
+	snd_seq_event_t ev;
+
+	if (ALSA_CHECK(snd_midi_event_new(256, &midi_event)) < 0)
+		return;
+
+	TEST_CHECK(ENCODE("\x91\x02") == 2);
+	TEST_CHECK(ev.type == SND_SEQ_EVENT_NONE);
+
+	snd_midi_event_reset_encode(midi_event);
+
+	TEST_CHECK(ENCODE("\x03") == 1);
+	TEST_CHECK(ev.type == SND_SEQ_EVENT_NONE);
+
+	snd_midi_event_free(midi_event);
+}
+
+static void test_init(void)
+{
+	snd_midi_event_t *midi_event;
+	snd_seq_event_t ev;
+	unsigned char buf[50];
+	int count;
+
+	if (ALSA_CHECK(snd_midi_event_new(256, &midi_event)) < 0)
+		return;
+
+	snd_seq_ev_set_noteon(&ev, 1, 2, 3);
+	TEST_CHECK(DECODES_TO("910203"));
+
+	TEST_CHECK(ENCODE("\x94\x05") == 2);
+	TEST_CHECK(ev.type == SND_SEQ_EVENT_NONE);
+
+	snd_midi_event_init(midi_event);
+
+	snd_seq_ev_set_noteon(&ev, 1, 2, 3);
+	TEST_CHECK(DECODES_TO("910203"));
+
+	TEST_CHECK(ENCODE("\x06") == 1);
+	TEST_CHECK(ev.type == SND_SEQ_EVENT_NONE);
+
+	snd_midi_event_free(midi_event);
+}
+
+static void test_encode_byte(void)
+{
+	snd_midi_event_t *midi_event;
+	snd_seq_event_t ev;
+
+	if (ALSA_CHECK(snd_midi_event_new(256, &midi_event)) < 0)
+		return;
+
+#define ENCODE_BYTE(c) snd_midi_event_encode_byte(midi_event, c, &ev)
+	TEST_CHECK(ENCODE_BYTE(0x81) == 0);
+	TEST_CHECK(ENCODE_BYTE(0x02) == 0);
+	TEST_CHECK(ENCODE_BYTE(0x03) == 1);
+	TEST_CHECK(ev.type == SND_SEQ_EVENT_NOTEOFF);
+	TEST_CHECK((ev.flags & SND_SEQ_EVENT_LENGTH_MASK) == SND_SEQ_EVENT_LENGTH_FIXED);
+	TEST_CHECK(ev.data.note.channel == 1);
+	TEST_CHECK(ev.data.note.note == 2);
+	TEST_CHECK(ev.data.note.velocity == 3);
+	TEST_CHECK(ENCODE_BYTE(0x04) == 0);
+	TEST_CHECK(ENCODE_BYTE(0xf8) == 1);
+	TEST_CHECK(ev.type == SND_SEQ_EVENT_CLOCK);
+	TEST_CHECK(ENCODE_BYTE(0x05) == 1);
+	TEST_CHECK(ev.type == SND_SEQ_EVENT_NOTEOFF);
+	TEST_CHECK(ev.data.note.channel == 1);
+	TEST_CHECK(ev.data.note.note == 4);
+	TEST_CHECK(ev.data.note.velocity == 5);
+
+	snd_midi_event_free(midi_event);
+}
+
+int main(void)
+{
+	test_decode();
+	test_reset_decode();
+	test_encode();
+	test_reset_encode();
+	test_encode_byte();
+	test_init();
+	return TEST_EXIT_CODE();
+}
diff --git a/test/lsb/test.h b/test/lsb/test.h
new file mode 100644
index 0000000..ff697c5
--- /dev/null
+++ b/test/lsb/test.h
@@ -0,0 +1,29 @@
+#ifndef TEST_H_INCLUDED
+#define TEST_H_INCLUDED
+
+#include <stdio.h>
+#include <alsa/asoundlib.h>
+
+/* XXX this variable definition does not belong in a header file */
+static int any_test_failed;
+
+#define TEST_CHECK(cond) do \
+		if (!(cond)) { \
+			fprintf(stderr, "%s:%d: test failed: %s\n", __FILE__, __LINE__, #cond); \
+			any_test_failed = 1; \
+		} \
+	while (0)
+
+#define ALSA_CHECK(fn) ({ \
+		int err = fn; \
+		if (err < 0) { \
+			fprintf(stderr, "%s:%d: ALSA function call failed (%s): %s\n", \
+				__FILE__, __LINE__, snd_strerror(err), #fn); \
+			any_test_failed = 1; \
+		} \
+		err; \
+	})
+
+#define TEST_EXIT_CODE() any_test_failed
+
+#endif
diff --git a/test/midifile.3 b/test/midifile.3
new file mode 100644
index 0000000..3aadb6d
--- /dev/null
+++ b/test/midifile.3
@@ -0,0 +1,336 @@
+.TH MIDIFILE 3
+.SH NAME
+mfread,mfwrite \- read and write a standard MIDI file
+.SH SYNOPSIS
+\fC#include "mfread.h"
+
+mfread ()
+
+.nf
+int (*Mf_getc) ();
+int (*Mf_putc) ();
+int (*Mf_error) (char *msg);
+int (*Mf_header) (int format, int ntrks, int division);
+int (*Mf_trackstart) ();
+int (*Mf_trackend) ();
+int (*Mf_noteon) (int chan, int pitch, int vol);
+int (*Mf_noteoff) (int chan, int pitch, int vol);
+int (*Mf_pressure) (int chan, int pitch, int pressure);
+int (*Mf_parameter) (int chan, int control, int value);
+int (*Mf_pitchbend) (int chan, int msb, int lsb);
+int (*Mf_program) (int chan, int program);
+int (*Mf_chanpressure) (int chan, int pressure);
+int (*Mf_sysex) (int leng, char *msg);
+int (*Mf_metamisc) (int type, int leng, int msg);
+int (*Mf_seqspecific) (int type, int leng, int msg);
+int (*Mf_seqnum) (int num);
+int (*Mf_text) (int type, int leng, int msg);
+int (*Mf_eot) ();
+int (*Mf_timesig) (int numer, int denom, int clocks, int qnotes);
+int (*Mf_smpte) (int hour, int min, int sec, int frame, int fract);
+int (*Mf_tempo) (int microsecs);
+int (*Mf_keysig) (int sharpflat, int minor);
+int (*Mf_arbitrary) (int leng, int msg);
+int Mf_nomerge;
+long Mf_currtime;
+.fi
+.sp 1
+mfwrite(int format, int ntracks, int division, FILE *fp)
+.sp 1
+.nf
+int (*Mf_writetrack)(int track);
+int (*Mf_writetempotrack)();
+
+void mf_write_midi_event(delta, type, chan, data, size)
+unsigned long delta;
+unsigned int type,chan,size;
+char *data;
+
+void mf_write_meta_event(delta, type, data, size)
+unsigned long delta;
+unsigned int type,chan,size;
+char *data;
+
+void mf_write_tempo(tempo)
+unsigned long tempo;
+
+unsigned long mf_sec2ticks(float seconds, int division, int tempo)
+float seconds;
+int division;
+unsigned int tempo;
+
+float mf_ticks2sec(ticks, division, tempo)
+unsigned long ticks;
+int division;
+unsigned int tempo;
+.fi
+
+.SH DESCRIPTION
+The \fCmfread\fR function reads and interprets a standard MIDI file.
+To use it you need to understand the general form of a
+MIDI file and the type of information it contains, but you don't
+need to know much, if anything, about the detailed format of the file
+and the mechanics of reading it reliably and portably.
+
+The \fCmfwrite\fR function writes a standard MIDI file making
+use of user-defined functions that access the program's
+data structure.  To use it you need to define your own Mf_writetrack
+routine and then make use of the write_* family of routines to
+write out the MIDI data.  The \fCmfwrite\fR routine takes
+care of the file format and writing the file and track chunk headers. 
+
+.SH READING STANDARD MIDI FILES
+A single call to \fCmfread\fR will read an entire MIDI file.
+The interface to \fCmfread\fR is a set of external variables
+named \fCMf_*\fR, most of which are function pointers to be called
+from within \fCmfread\fR during the process of parsing the MIDI file.
+Before calling \fCmfread\fR, the only
+requirement is that you assign a value
+to \fCMf_getc\fR - a pointer to a function that will return
+characters from the MIDI file, using \-1 to indicate EOF.
+All the rest of the function
+pointers are initialized to NULL, and the default action for each
+is to do nothing.  The following is a complete program using \fCmfread\fR
+that could serve as a 'syntax checker' for MIDI files:
+
+.in +1i
+.ft C
+.nf
+#include <stdio.h>
+#include "midifile.h"
+
+mygetc()
+{
+	/* use standard input */
+	return(getchar());
+}
+
+main()
+{
+	Mf_getc = mygetc;
+	mfread();
+	exit(0);
+}
+.fi
+.ft R
+.in -1i
+
+This takes advantage of the default action when an error is detected, which
+is to exit silently with a return code of 1.  An error function of your
+own can be used by giving a value to \fCMf_error\fR; the function will be
+called with the error message as an argument.
+The other \fCMf_* variables can similarly be used to call arbitrary
+functions while parsing the MIDI file.  The descriptions below
+of the information passed to these functions is sparse; refer to
+the MIDI file standard for the complete descriptions.
+
+\fCMf_header\fR is the first function to be called, and its arguments
+contain information from the MIDI file's header; the format (0,1, or 2),
+the number of tracks, and the division of a quarter-note that defines
+the times units.
+\fCMf_trackstart\fR and
+\fCMf_trackend\fR are called at the beginning and end of each track.
+
+Once inside a track, each separate message causes a function to be called.
+For example, each note-on message causes \fCMf_noteon\fR to be called
+with the channel, pitch, and volume as arguments.  The time at which
+the message occurred is stored in \fCMf_currtime\fR - one of the few
+external variables that isn't a function pointer.  The other channel messages
+are handled in a similar and obvious fashion -
+\fCMf_noteoff\fR,
+\fCMf_pressure\fR,
+\fCMf_parameter\fR,
+\fCMf_pitchbend\fR,
+\fCMf_program\fR,
+and \fCMf_chanpressure\fR.  See the declarations above for the arguments
+that are passed to each.
+
+System exclusive messages are handled by calling \fCMf_sysex\fR, passing
+as arguments the message length and a pointer to a static buffer containing
+the entire message.
+The buffer is expanded when necessary; memory availability is the only limit
+to its size.  Normally, 'continued' system exclusives are automatically
+merged, and \fCMf_sysex\fR is only called once.  It you want to disable this
+you can set \fCMf_nomerge\fR to 1, causing \fCMf_sysex\fR to be called
+once for each part of the message.
+
+\fCMf_seqnum\fR is called by the \fImeta\fR message that provides
+a sequence number,
+which if present must appear at the beginning of a track.
+The tempo \fImeta\fR message causes \fCMf_tempo\fR to be called; its
+argument is the number of microseconds per MIDI quarter-note (24 MIDI clocks).
+The end-of-track \fImeta\fR message causes \fCMf_eot\fR to be called.
+The key signature \fImeta\fR message causes \fCMf_keysig\fR to be called;
+the first argument conveys the number of sharps or flats, the second
+argument is 1 if the key is minor.
+
+The \fCMf_timesig\fR and \fCMf_smpte\fR functions are called when the
+corresponding \fImeta\fR messages are seen.  See the MIDI file standard
+for a description of their arguments.
+
+The \fItext\fR messages in the MIDI file standard are of the following
+types:
+
+.in +1i
+.nf
+0x01		Text Event
+0x02		Copyright
+0x03		Sequence/Track Name
+0x04		Instrument
+0x05		Lyric
+0x06		Marker
+0x07		Cue Point
+0x08-0x0F	Reserved but Undefined
+.fi
+.in -1i
+
+\fCMf_text\fR is called for each of these; the arguments are
+the type number, the message length, and a pointer to the message buffer.
+
+Miscellaneous \fImeta\fR messages are handled by \fCMf_metamisc\fR,
+sequencer-specific messages are handled by \fCMf_seqspecific\fR, and
+arbitrary "escape" messages (started with 0xF7) are handled by
+\fCMf_arbitrary\fR.
+.SH READING EXAMPLE
+The following is a \fCstrings\fR-like program for MIDI files:
+
+.in +1i
+.ft C
+.nf
+#include <stdio.h>
+#include <ctype.h>
+#include "midifile.h"
+
+FILE *F;
+
+mygetc() { return(getc(F)); }
+
+mytext(type,leng,msg)
+char *msg;
+{
+	char *p;
+	char *ep = msg + leng;
+
+	for ( p=msg; p<ep ; p++ )
+		putchar( isprint(*p) ? *p : '?' );
+	putchar('\n');
+}
+
+main(argc,argv)
+char **argv;
+{
+	if ( argc > 1 )
+		F = fopen(argv[1],"r");
+	else
+		F = stdin;
+
+	Mf_getc = mygetc;
+	Mf_text = mytext;
+
+	mfread();
+
+	exit(0);
+}
+.fi
+.ft R
+.in -1i
+.sp
+.SH WRITING STANDARD MIDI FILES
+A single call to \fCmfwrite\fR will write an entire MIDI file.  Before
+calling \fCmfwrite\fR, you must assign values to function pointers
+\fCMf_writetrack\fR and \fCMf_putc\fR.  The first is a routine to
+access your MIDI data structure, which can make use of other library
+routines to write the actual MIDI data.  The routine
+\fCMf_writetrack\fR will be passed a single parameter which is the
+number of the track to be written.  The pointer \fCMf_putc\fR should be
+set to point to a routine that accepts a character as input, writes that
+character to a file, and returns the value that was written.  In the
+case of a format 1 file, a routine has to be written to write a tempo
+map, and assigned to the function pointer \fCMf_writetempotrack\fR.
+This is because format 1 files assume the first track written is a
+tempo track.
+
+\fCmf_write_midi_event\fR and \fCmf_write_meta_event\fR are routines
+that should be called from your \fCMf_writetrack\fR routine to write
+out MIDI events.  The delta time param is the number of ticks since the
+last event.  The int "type" is the type of MIDI message. The int "chan"
+is the MIDI channel, which can be between 1 and 16.  The char pointer
+"data" points to an array containing the data bytes, if any exist. The
+int "size" is the number of data bytes.
+
+\fCmf_sec2ticks\fR and \fCmf_ticks2sec\fR are utility routines
+to help you convert between the MIDI file parameter of ticks
+and the more standard seconds. The int "division" is the same
+division parameter from the file header, and tempo is expressed
+in microseconds per MIDI quarter-note, or "24ths of a microsecond
+per MIDI clock". The division has two meanings, depending on
+whether bit 15 is set or not.  If bit 15 of division is zero,
+bits 14 through 0 represent the number of delta-time "ticks"
+which make up a quarter note.  If bit 15 of division is a one,
+delta-times in a file correspond to subdivisions of a second
+similar to SMPTE and MIDI time code. In this format bits
+14 through 8 contain one of four values \-24, \-25, \-29, or \-30,
+corresponding to the four standard SMPTE and MIDI time code
+frame per second formats, where \-29 represents 30 drop frame.
+The second byte consisting of bits 7 through 0 corresponds
+the the resolution within a frame.  Refer the Standard MIDI Files 
+1.0 spec for more details.
+
+.SH WRITING EXAMPLE
+The following is a simple program to demonstrate writing MIDI files.
+The track would consist of a series of quarter notes from lowest to
+highest in pitch at constant velocity, each separated by a quarter-note
+rest.
+.sp
+.in +1i
+.ft C
+.nf
+#include <stdio.h>
+#include <ctype.h>
+#include "midifile.h"
+
+FILE *fp;
+myputc(c) { return(putc(c,fp));}
+
+int mywritetrack(track)
+int track;
+{
+    int i;
+    char data[2];
+
+    /* 120 beats/per/second */
+    mf_write_tempo((long)500000); 
+
+    for(i = 1 ; i < 128; i++){
+       data[0] = i; /* note number */
+       data[1] = 64; /* velocity */
+       if(!mf_write_midi_event(480,note_on,1,data,2)) 
+	   return(\-1);
+       if(!mf_write_midi_event(480,note_off,1,data,2)) 
+           return(\-1);
+    }
+
+    return(1);
+} /* end of write_track() */
+
+main(argc,argv)
+char **argv;
+{
+    if((fp = fopen(argv[1],"w")) == 0L)
+	exit(1);
+
+    Mf_putc = myputc;
+    Mf_writetrack = mywritetrack;
+
+    /* write a single track */
+    mfwrite(0,1,480,fp);
+}
+.sp
+.fi
+.ft R
+.in -1i
+.sp
+.SH AUTHOR
+Tim Thompson (att!twitch!glimmer!tjt)
+.SH CONTRIBUTORS
+Michael Czeiszperger (mike@pan.com)
diff --git a/test/midifile.c b/test/midifile.c
new file mode 100644
index 0000000..8d6ba90
--- /dev/null
+++ b/test/midifile.c
@@ -0,0 +1,1173 @@
+/*
+ * midifile 1.11
+ *
+ * Read and write a MIDI file.  Externally-assigned function pointers are
+ * called upon recognizing things in the file.
+ *
+ * Original release ?
+ * June 1989 - Added writing capability, M. Czeiszperger.
+ *
+ *          The file format implemented here is called
+ *          Standard MIDI Files, and is part of the Musical
+ *          instrument Digital Interface specification.
+ *          The spec is available from:
+ *
+ *               International MIDI Association
+ *               5316 West 57th Street
+ *               Los Angeles, CA 90056
+ *
+ *          An in-depth description of the spec can also be found
+ *          in the article "Introducing Standard MIDI Files", published
+ *          in Electronic Musician magazine, April, 1989.
+ *
+ * February 1993 - Minor adjustments, Greg Lee:
+ *	(1) can now set the global variable Mf_interactive to 1 to prevent the
+ *	    reading functions from looking for file and track headers
+ *	(2) can now write system exclusive data with
+ *		mf_write_midi_event(delta_time, system_exclusive, 0, data, size)
+ *	(3) changed definition of 'sequencer_specific' in midifile.h to 0x7f
+ *	(4) changed mf_write_tempo to take additional delta_time as first argument
+ *	    (since delta need not be zero)
+ *	(5) added function mf_write_seqnum(unsigned long delta_time, unsigned seqnum)
+ *	(6) changed mf_write_midi_event to use running status
+ *	(7) removed the code to write an end of track meta event automatically
+ *		-- this must now be done by the user of the library (I changed
+ *		it because I need to be able to control the time delta of this
+ *		 meta event)
+ *	(8) added global variables Mf_division, Mf_currtempo, Mf_realtime, which
+ *		are updated by the reading functions.  Mf_realtime is useful,
+ *		because Mf_currtime does not really measure time at all, since
+ *		its units change value at every tempo change.  Mf_realtime is
+ *		the midi-time elapsed in units of 1/16 of a centisecond (but it
+ *		does not handle SMPTE times)
+ *	(9) maintains a history of tempo settings to update Mf_currtempo,
+ *		to handle tempo tracks.
+ *	(10) if there is an Mf_error function, the error routine no longer
+ *		exits, leaving it to the application to do this.
+ *	(11) chanmessage skips over invalid c1 command bytes > 127 and
+ *		adjusts invalid c2 argument byte > 127 to 127.
+ *	(12) readmt returns EOF when it encounters a 0 or 0x1a byte instead of an expected
+ *		header string (some midi files have padding at end).
+ */
+#define NO_LC_DEFINES
+#include "midifile.h"
+#ifdef NO_LC_DEFINES
+#define system_exclusive      	0xf0
+#define	meta_event		0xFF
+#define	set_tempo		0x51
+#define lowerbyte(x) ((unsigned char)(x & 0xff))
+#define upperbyte(x) ((unsigned char)((x & 0xff00)>>8))
+#endif
+
+#define NULLFUNC 0
+#if 0
+#define NULL 0
+#endif
+
+#define THINK
+
+#ifdef THINK
+#include <stdlib.h>
+#endif
+
+#include <stdio.h>
+#include <values.h>
+
+#include <string.h>
+/*void exit(), free();*/
+
+/* public stuff */
+
+/* Functions to be called while processing the MIDI file. */
+int (*Mf_getc) () = NULLFUNC;
+void (*Mf_error) () = NULLFUNC;
+void (*Mf_header) () = NULLFUNC;
+void (*Mf_trackstart) () = NULLFUNC;
+void (*Mf_trackend) () = NULLFUNC;
+void (*Mf_noteon) () = NULLFUNC;
+void (*Mf_noteoff) () = NULLFUNC;
+void (*Mf_pressure) () = NULLFUNC;
+void (*Mf_parameter) () = NULLFUNC;
+void (*Mf_pitchbend) () = NULLFUNC;
+void (*Mf_program) () = NULLFUNC;
+void (*Mf_chanpressure) () = NULLFUNC;
+void (*Mf_sysex) () = NULLFUNC;
+void (*Mf_arbitrary) () = NULLFUNC;
+void (*Mf_metamisc) () = NULLFUNC;
+void (*Mf_seqnum) () = NULLFUNC;
+void (*Mf_eot) () = NULLFUNC;
+void (*Mf_smpte) () = NULLFUNC;
+void (*Mf_tempo) () = NULLFUNC;
+void (*Mf_timesig) () = NULLFUNC;
+void (*Mf_keysig) () = NULLFUNC;
+void (*Mf_seqspecific) () = NULLFUNC;
+void (*Mf_text) () = NULLFUNC;
+
+/* Functions to implement in order to write a MIDI file */
+int (*Mf_putc) () = NULLFUNC;
+int (*Mf_writetrack) () = NULLFUNC;
+int (*Mf_writetempotrack) () = NULLFUNC;
+
+int Mf_nomerge = 0;		/* 1 => continue'ed system exclusives are */
+ /* not collapsed. */
+int Mf_interactive = 0;		/* 1 => file and track headers are not required */
+unsigned long Mf_currtime = 0L;	/* current time in delta-time units */
+unsigned long Mf_realtime = 0L;	/* current time in 1/16 centisecond-time units */
+static double Mf_f_realtime = 0;/* as above, floating */
+static double old_f_realtime = 0;
+int Mf_division = 96;
+unsigned long Mf_currtempo = 500000;
+static unsigned long old_currtempo = 500000;
+static unsigned long old_realtime = 0;
+static unsigned long old_currtime = 0;
+static unsigned long revised_time = 0;
+static unsigned long tempo_change_time = 0;
+
+#define MAX_HISTORY 512
+static unsigned long tempo_history[MAX_HISTORY];
+static unsigned long tempo_history_time[MAX_HISTORY];
+static int tempo_history_count = 0;
+
+/* private stuff */
+static long Mf_toberead = 0L;
+static long Mf_numbyteswritten = 0L;
+
+static long readvarinum ();
+static long read32bit ();
+static long to32bit ();
+static int read16bit ();
+static int to16bit ();
+static char *msg ();
+static void readheader ();
+static int readtrack ();
+static void badbyte ();
+static void metaevent ();
+static void sysex ();
+static void chanmessage ();
+static void msginit ();
+static int msgleng ();
+static void msgadd ();
+static void biggermsg ();
+static int eputc (unsigned char c);
+
+double mf_ticks2sec (unsigned long ticks, int division, unsigned long tempo);
+int mf_write_meta_event ();
+void mf_write_tempo ();
+void mf_write_seqnum ();
+void WriteVarLen ();
+
+#ifdef READ_MODS
+#include "mp_mod.c"
+static int mod_file_flag = 0;
+#endif /* READ_MODS */
+static int force_exit;
+
+void
+mfread ()
+{
+  force_exit = 0;
+  if (Mf_getc == NULLFUNC)
+    mferror ("mfread() called without setting Mf_getc");
+
+  readheader ();
+#ifdef READ_MODS
+  if (mod_file_flag)
+    do_module();
+  else
+#endif
+    while (readtrack () && !force_exit)
+      ;
+}
+
+/* for backward compatibility with the original lib */
+void
+midifile ()
+{
+  mfread ();
+}
+
+static
+int 
+readmt (s)			/* read through the "MThd" or "MTrk" header string */
+     char *s;
+{
+  int n = 0;
+  char *p = s;
+  int c;
+
+  while (n++ < 4 && (c = (*Mf_getc) ()) != EOF)
+    {
+      if (c != *p++)
+	{
+	  char buff[32];
+	  if (!c) return(EOF);
+	  if (c == 0x1a) return(EOF);
+	  (void) strcpy (buff, "expecting ");
+	  (void) strcat (buff, s);
+	  mferror (buff);
+	  break;
+	}
+    }
+  return (c);
+}
+
+static
+int
+egetc ()			/* read a single character and abort on EOF */
+{
+  int c = (*Mf_getc) ();
+
+  if (c == EOF) {
+    mferror ("premature EOF");
+    force_exit = 1;
+  }
+  Mf_toberead--;
+  return (c);
+}
+
+static
+void 
+readheader ()			/* read a header chunk */
+{
+  int format, ntrks, division;
+
+
+  Mf_division = 96;
+  Mf_currtempo = 500000;
+  old_currtempo = 500000;
+  tempo_history_count = 0;
+  tempo_history[tempo_history_count] = Mf_currtempo;
+  tempo_history_time[tempo_history_count] = 0;
+
+  if (Mf_interactive)
+    {
+      Mf_toberead = 0;
+      format = 0;
+      ntrks = 1;
+      division = 96;
+    }
+  else
+#ifdef READ_MODS
+    if (!strncmp(Mf_file_contents, "MThd", 4))
+#endif
+    {
+      if (readmt ("MThd") == EOF)
+	return;
+
+      Mf_toberead = read32bit ();
+      format = read16bit ();
+      ntrks = read16bit ();
+      Mf_division = division = read16bit ();
+    }
+#ifdef READ_MODS
+  else
+    {
+      format = 0;
+      ntrks = 1;
+      division = Mf_division;
+      Mf_toberead = 0;
+      mod_file_flag = 1;
+    }
+#endif
+
+  if (Mf_header)
+    (*Mf_header) (format, ntrks, division);
+
+  /* flush any extra stuff, in case the length of header is not 6 */
+  while (Mf_toberead > 0 && !force_exit)
+    (void) egetc ();
+}
+
+
+/*#define DEBUG_TIMES*/
+static
+unsigned long
+find_tempo()
+{
+  int i;
+  unsigned long old_tempo = Mf_currtempo;
+  unsigned long new_tempo = Mf_currtempo;
+
+  for (i = 0; i <= tempo_history_count; i++) {
+    if (tempo_history_time[i] <= Mf_currtime) old_tempo = tempo_history[i];
+    new_tempo = tempo_history[i];
+    if (tempo_history_time[i] > revised_time) break;
+  }
+  if (i > tempo_history_count || tempo_history_time[i] > Mf_currtime) {
+#ifdef DEBUG_TIMES
+printf("[past %d, old_tempo %d]\n", tempo_history_time[i], old_tempo);
+#endif
+    revised_time = Mf_currtime;
+    return(old_tempo);
+  }
+  tempo_change_time = revised_time = tempo_history_time[i];
+#ifdef DEBUG_TIMES
+printf("[revised_time %d, new_tempo %d]\n", revised_time, new_tempo);
+#endif
+  return(new_tempo);
+}
+
+static
+int 
+readtrack ()			/* read a track chunk */
+{
+  /* This array is indexed by the high half of a status byte.  It's */
+  /* value is either the number of bytes needed (1 or 2) for a channel */
+  /* message, or 0 (meaning it's not  a channel message). */
+  static int chantype[] =
+  {
+    0, 0, 0, 0, 0, 0, 0, 0,	/* 0x00 through 0x70 */
+    2, 2, 2, 2, 1, 1, 2, 0	/* 0x80 through 0xf0 */
+  };
+  long lookfor;
+  int c, c1, type;
+  int sysexcontinue = 0;	/* 1 if last message was an unfinished sysex */
+  int running = 0;		/* 1 when running status used */
+  int status = 0;		/* status value (e.g. 0x90==note-on) */
+  int needed;
+
+  if (Mf_interactive)
+    {
+      Mf_toberead = MAXINT;
+    }
+  else
+    {
+      if (readmt ("MTrk") == EOF)
+	return (0);
+
+      Mf_toberead = read32bit ();
+    }
+  Mf_currtime = Mf_realtime = 0;
+  Mf_f_realtime = old_f_realtime = 0;
+  old_currtime = old_realtime = 0;
+  Mf_currtempo = find_tempo();
+
+  if (Mf_trackstart)
+    (*Mf_trackstart) ();
+
+  while (!force_exit && (Mf_interactive || Mf_toberead > 0))
+    {
+
+      if (Mf_interactive)
+	Mf_currtime += 1;
+      else
+	{
+	  double delta_secs;
+	  unsigned long delta_ticks = readvarinum ();
+	  revised_time = Mf_currtime;
+	  Mf_currtime += delta_ticks;	/* delta time */
+
+/*
+ * Step through each tempo change from old_currtime up to now,
+ * revising Mf_realtime after each change.
+ */
+
+	  while (revised_time < Mf_currtime) {
+	    unsigned long save_time = revised_time;
+	    unsigned long save_tempo = Mf_currtempo;
+	    Mf_currtempo = find_tempo();
+
+	    if (Mf_currtempo != old_currtempo) {
+	      old_currtempo = Mf_currtempo;
+	      old_realtime = Mf_realtime;
+	      if (revised_time != tempo_change_time) {
+	        old_f_realtime = Mf_f_realtime;
+	        old_currtime = save_time;
+	      }
+	    delta_secs = mf_ticks2sec (revised_time-old_currtime, Mf_division, save_tempo);
+#ifdef DEBUG_TIMES
+printf("d(rev %d - old %d, div %d, tempo %d) = %.3f\n",
+revised_time, old_currtime, Mf_division, save_tempo, delta_secs * 1600.0);
+#endif
+	    Mf_f_realtime = old_f_realtime + delta_secs * 1600.0;
+	    Mf_realtime = (unsigned long)(0.5 + Mf_f_realtime);
+#ifdef DEBUG_TIMES
+printf("\tt=%d ticks ( = %d csec/16 < old %.2f + %.2f)\n", Mf_currtime, Mf_realtime, 
+old_f_realtime, delta_secs * 1600.0);
+#endif
+	      if (revised_time == tempo_change_time) {
+		old_currtime = revised_time;
+	      old_f_realtime = Mf_f_realtime;
+	      }
+	    }
+	    else {
+	    delta_secs = mf_ticks2sec (revised_time-old_currtime, Mf_division, Mf_currtempo);
+#ifdef DEBUG_TIMES
+printf("d(rev %d - old %d, div %d, tempo %d) = %.3f\n",
+revised_time, old_currtime, Mf_division, Mf_currtempo, delta_secs * 1600.0);
+#endif
+	    Mf_f_realtime = old_f_realtime + delta_secs * 1600.0;
+	    Mf_realtime = (unsigned long)(0.5 + Mf_f_realtime);
+#ifdef DEBUG_TIMES
+printf("\tt=%d ticks ( = %d csec/16 < old %.2f + %.2f)\n", Mf_currtime, Mf_realtime, 
+old_f_realtime, delta_secs * 1600.0);
+#endif
+	    }
+
+
+	  }
+	}
+
+      c = egetc ();
+
+      if (sysexcontinue && c != 0xf7)
+	mferror ("didn't find expected continuation of a sysex");
+
+      if ((c & 0x80) == 0)
+	{			/* running status? */
+	  if (status == 0)
+	    mferror ("unexpected running status");
+	  running = 1;
+	}
+      else
+	{
+	  status = c;
+	  running = 0;
+	}
+
+      needed = chantype[(status >> 4) & 0xf];
+
+      if (needed)
+	{			/* ie. is it a channel message? */
+
+	  if (running)
+	    c1 = c;
+	  else
+	    c1 = egetc ();
+	  chanmessage (status, c1, (needed > 1) ? egetc () : 0);
+	  continue;;
+	}
+
+      switch (c)
+	{
+
+	case 0xff:		/* meta event */
+
+	  type = egetc ();
+	  lookfor = Mf_toberead - readvarinum ();
+	  msginit ();
+
+	  while (Mf_toberead > lookfor)
+	    msgadd (egetc ());
+
+	  metaevent (type);
+	  break;
+
+	case 0xf0:		/* start of system exclusive */
+
+	  lookfor = Mf_toberead - readvarinum ();
+	  msginit ();
+	  msgadd (0xf0);
+
+	  while (Mf_toberead > lookfor)
+	    msgadd (c = egetc ());
+
+	  if (c == 0xf7 || Mf_nomerge == 0)
+	    sysex ();
+	  else
+	    sysexcontinue = 1;	/* merge into next msg */
+	  break;
+
+	case 0xf7:		/* sysex continuation or arbitrary stuff */
+
+	  lookfor = Mf_toberead - readvarinum ();
+
+	  if (!sysexcontinue)
+	    msginit ();
+
+	  while (Mf_toberead > lookfor)
+	    msgadd (c = egetc ());
+
+	  if (!sysexcontinue)
+	    {
+	      if (Mf_arbitrary)
+		(*Mf_arbitrary) (msgleng (), msg ());
+	    }
+	  else if (c == 0xf7)
+	    {
+	      sysex ();
+	      sysexcontinue = 0;
+	    }
+	  break;
+	default:
+	  badbyte (c);
+	  break;
+	}
+    }
+  if (Mf_trackend)
+    (*Mf_trackend) ();
+  return (1);
+}
+
+static
+void 
+badbyte (c)
+     int c;
+{
+  char buff[32];
+
+  (void) sprintf (buff, "unexpected byte: 0x%02x", c);
+  mferror (buff);
+}
+
+static
+void 
+metaevent (int type)
+{
+  int leng = msgleng ();
+  char *m = msg ();
+
+  switch (type)
+    {
+    case 0x00:
+      if (Mf_seqnum)
+	(*Mf_seqnum) (to16bit (m[0], m[1]));
+      break;
+    case 0x01:			/* Text event */
+    case 0x02:			/* Copyright notice */
+    case 0x03:			/* Sequence/Track name */
+    case 0x04:			/* Instrument name */
+    case 0x05:			/* Lyric */
+    case 0x06:			/* Marker */
+    case 0x07:			/* Cue point */
+    case 0x08:
+    case 0x09:
+    case 0x0a:
+    case 0x0b:
+    case 0x0c:
+    case 0x0d:
+    case 0x0e:
+    case 0x0f:
+      /* These are all text events */
+      if (Mf_text)
+	(*Mf_text) (type, leng, m);
+      break;
+    case 0x2f:			/* End of Track */
+      if (Mf_eot)
+	(*Mf_eot) ();
+      break;
+    case 0x51:			/* Set tempo */
+      if (Mf_tempo)
+	(*Mf_tempo) (Mf_currtempo = to32bit (0, m[0], m[1], m[2]));
+      if (tempo_history[tempo_history_count] == Mf_currtempo) break;
+      if (tempo_history_time[tempo_history_count] > Mf_currtime) break;
+      if (tempo_history_count < MAX_HISTORY - 1) tempo_history_count++;
+      tempo_history[tempo_history_count] = Mf_currtempo;
+      tempo_history_time[tempo_history_count] = Mf_currtime;
+      break;
+    case 0x54:
+      if (Mf_smpte)
+	(*Mf_smpte) (m[0], m[1], m[2], m[3], m[4]);
+      break;
+    case 0x58:
+      if (Mf_timesig)
+	(*Mf_timesig) (m[0], m[1], m[2], m[3]);
+      break;
+    case 0x59:
+      if (Mf_keysig)
+	(*Mf_keysig) (m[0], m[1]);
+      break;
+    case 0x7f:
+      if (Mf_seqspecific)
+	(*Mf_seqspecific) (leng, m);
+      break;
+    default:
+      if (Mf_metamisc)
+	(*Mf_metamisc) (type, leng, m);
+    }
+}
+
+static
+void 
+sysex ()
+{
+  if (Mf_sysex)
+    (*Mf_sysex) (msgleng (), msg ());
+}
+
+static
+void 
+chanmessage (status, c1, c2)
+     int status;
+     int c1, c2;
+{
+  int chan = status & 0xf;
+
+  /* I found a midi file with Mod Wheel values 128. --gl */
+
+  if (c1 > 127) /*mferror("chanmessage: bad c1") ??*/ return;
+  if (c2 > 127) c2 = 127;
+
+  switch (status & 0xf0)
+    {
+    case 0x80:
+      if (Mf_noteoff)
+	(*Mf_noteoff) (chan, c1, c2);
+      break;
+    case 0x90:
+      if (Mf_noteon)
+	(*Mf_noteon) (chan, c1, c2);
+      break;
+    case 0xa0:
+      if (Mf_pressure)
+	(*Mf_pressure) (chan, c1, c2);
+      break;
+    case 0xb0:
+      if (Mf_parameter)
+	(*Mf_parameter) (chan, c1, c2);
+      break;
+    case 0xe0:
+      if (Mf_pitchbend)
+	(*Mf_pitchbend) (chan, c1, c2);
+      break;
+    case 0xc0:
+      if (Mf_program)
+	(*Mf_program) (chan, c1);
+      break;
+    case 0xd0:
+      if (Mf_chanpressure)
+	(*Mf_chanpressure) (chan, c1);
+      break;
+    }
+}
+
+/* readvarinum - read a varying-length number, and return the */
+/* number of characters it took. */
+
+static long
+readvarinum ()
+{
+  long value;
+  int c;
+
+  c = egetc ();
+  value = c;
+  if (c & 0x80)
+    {
+      value &= 0x7f;
+      do
+	{
+	  c = egetc ();
+	  value = (value << 7) + (c & 0x7f);
+	}
+      while (c & 0x80);
+    }
+  return (value);
+}
+
+static long
+to32bit (int c1, int c2, int c3, int c4)
+{
+  long value = 0L;
+
+  value = (c1 & 0xff);
+  value = (value << 8) + (c2 & 0xff);
+  value = (value << 8) + (c3 & 0xff);
+  value = (value << 8) + (c4 & 0xff);
+  return (value);
+}
+
+static int
+to16bit (c1, c2)
+     int c1, c2;
+{
+  return ((c1 & 0xff) << 8) + (c2 & 0xff);
+}
+
+static long
+read32bit ()
+{
+  int c1, c2, c3, c4;
+
+  c1 = egetc ();
+  c2 = egetc ();
+  c3 = egetc ();
+  c4 = egetc ();
+  return to32bit (c1, c2, c3, c4);
+}
+
+static int
+read16bit ()
+{
+  int c1, c2;
+  c1 = egetc ();
+  c2 = egetc ();
+  return to16bit (c1, c2);
+}
+
+/* static */
+void
+mferror (s)
+     char *s;
+{
+  if (Mf_error)
+    (*Mf_error) (s);
+  else exit (1);
+}
+
+/* The code below allows collection of a system exclusive message of */
+/* arbitrary length.  The Msgbuff is expanded as necessary.  The only */
+/* visible data/routines are msginit(), msgadd(), msg(), msgleng(). */
+
+#define MSGINCREMENT 128
+static char *Msgbuff = NULL;	/* message buffer */
+static int Msgsize = 0;		/* Size of currently allocated Msg */
+static int Msgindex = 0;	/* index of next available location in Msg */
+
+static
+void 
+msginit ()
+{
+  Msgindex = 0;
+}
+
+static char *
+msg ()
+{
+  return (Msgbuff);
+}
+
+static
+int 
+msgleng ()
+{
+  return (Msgindex);
+}
+
+static
+void 
+msgadd (c)
+     int c;
+{
+  /* If necessary, allocate larger message buffer. */
+  if (Msgindex >= Msgsize)
+    biggermsg ();
+  Msgbuff[Msgindex++] = c;
+}
+
+static
+void 
+biggermsg ()
+{
+/* 	char *malloc(); */
+  char *newmess;
+  char *oldmess = Msgbuff;
+  int oldleng = Msgsize;
+
+  Msgsize += MSGINCREMENT;
+  newmess = (char *) malloc ((unsigned) (sizeof (char) * Msgsize));
+
+  if (newmess == NULL)
+    mferror ("malloc error!");
+
+  /* copy old message into larger new one */
+  if (oldmess != NULL)
+    {
+      register char *p = newmess;
+      register char *q = oldmess;
+      register char *endq = &oldmess[oldleng];
+
+      for (; q != endq; p++, q++)
+	*p = *q;
+      free (oldmess);
+    }
+  Msgbuff = newmess;
+}
+
+static int laststatus = 0;
+
+/*
+ * mfwrite() - The only function you'll need to call to write out
+ *             a midi file.
+ *
+ * format      0 - Single multi-channel track
+ *             1 - Multiple simultaneous tracks
+ *             2 - One or more sequentially independent
+ *                 single track patterns
+ * ntracks     The number of tracks in the file.
+ * division    This is kind of tricky, it can represent two
+ *             things, depending on whether it is positive or negative
+ *             (bit 15 set or not).  If  bit  15  of division  is zero,
+ *             bits 14 through 0 represent the number of delta-time
+ *             "ticks" which make up a quarter note.  If bit  15 of
+ *             division  is  a one, delta-times in a file correspond to
+ *             subdivisions of a second similar to  SMPTE  and  MIDI
+ *             time code.  In  this format bits 14 through 8 contain
+ *             one of four values - 24, -25, -29, or -30,
+ *             corresponding  to  the  four standard  SMPTE and MIDI
+ *             time code frame per second formats, where  -29
+ *             represents  30  drop  frame.   The  second  byte
+ *             consisting  of  bits 7 through 0 corresponds the the
+ *             resolution within a frame.  Refer the Standard MIDI
+ *             Files 1.0 spec for more details.
+ * fp          This should be the open file pointer to the file you
+ *             want to write.  It will have be a global in order
+ *             to work with Mf_putc.
+ */
+void
+mfwrite (format, ntracks, division, fp)
+     int format, ntracks, division;
+     FILE *fp;
+{
+  int i;
+  void mf_write_track_chunk (), mf_write_header_chunk ();
+
+  if (Mf_putc == NULLFUNC)
+    mferror ("mfmf_write() called without setting Mf_putc");
+
+  if (Mf_writetrack == NULLFUNC)
+    mferror ("mfmf_write() called without setting Mf_mf_writetrack");
+
+  laststatus = 0;
+
+  /* every MIDI file starts with a header */
+  mf_write_header_chunk (format, ntracks, division);
+
+  laststatus = 0;
+
+  /* In format 1 files, the first track is a tempo map */
+  if (format == 1 && (Mf_writetempotrack))
+    {
+      (*Mf_writetempotrack) ();
+    }
+
+  /* The rest of the file is a series of tracks */
+  for (i = 0; i < ntracks; i++)
+    mf_write_track_chunk (i, fp);
+}
+
+void
+mf_write_track_chunk (which_track, fp)
+     int which_track;
+     FILE *fp;
+{
+  unsigned long trkhdr, trklength;
+  long offset, place_marker;
+  void write16bit (), write32bit ();
+
+
+  laststatus = 0;
+
+  trkhdr = MTrk;
+  trklength = 0;
+
+  /* Remember where the length was written, because we don't
+	   know how long it will be until we've finished writing */
+  offset = ftell (fp);
+
+#ifdef DEBUG
+  printf ("offset = %d\n", (int) offset);
+#endif
+
+  /* Write the track chunk header */
+  write32bit (trkhdr);
+  write32bit (trklength);
+
+  Mf_numbyteswritten = 0L;	/* the header's length doesn't count */
+
+  if (Mf_writetrack)
+    {
+      (*Mf_writetrack) (which_track);
+    }
+
+  /* mf_write End of track meta event */
+/* but this does not necessarily have a delta of 0, so
+ * I don't want to do it -- leave it up to the user of the
+ * library functions to do
+ *	--gl
+	eputc(0);
+	eputc(laststatus = meta_event);
+	eputc(end_of_track);
+
+ 	eputc(0);
+ */
+
+  /* It's impossible to know how long the track chunk will be beforehand,
+           so the position of the track length data is kept so that it can
+           be written after the chunk has been generated */
+  place_marker = ftell (fp);
+
+  /* This method turned out not to be portable because the
+           parameter returned from ftell is not guaranteed to be
+           in bytes on every machine */
+  /* track.length = place_marker - offset - (long) sizeof(track); */
+
+#ifdef DEBUG
+  printf ("length = %d\n", (int) trklength);
+#endif
+
+  if (fseek (fp, offset, 0) < 0)
+    mferror ("error seeking during final stage of write");
+
+  trklength = Mf_numbyteswritten;
+
+  /* Re-mf_write the track chunk header with right length */
+  write32bit (trkhdr);
+  write32bit (trklength);
+
+  fseek (fp, place_marker, 0);
+}				/* End gen_track_chunk() */
+
+
+void
+mf_write_header_chunk (format, ntracks, division)
+     int format, ntracks, division;
+{
+  unsigned long ident, length;
+  void write16bit (), write32bit ();
+
+  ident = MThd;			/* Head chunk identifier                    */
+  length = 6;			/* Chunk length                             */
+
+  /* individual bytes of the header must be written separately
+       to preserve byte order across cpu types :-( */
+  write32bit (ident);
+  write32bit (length);
+  write16bit (format);
+  write16bit (ntracks);
+  write16bit (division);
+}				/* end gen_header_chunk() */
+
+
+/*
+ * mf_write_midi_event()
+ *
+ * Library routine to mf_write a single MIDI track event in the standard MIDI
+ * file format. The format is:
+ *
+ *                    <delta-time><event>
+ *
+ * In this case, event can be any multi-byte midi message, such as
+ * "note on", "note off", etc.
+ *
+ * delta_time - the time in ticks since the last event.
+ * type - the type of meta event.
+ * chan - The midi channel.
+ * data - A pointer to a block of chars containing the META EVENT,
+ *        data.
+ * size - The length of the meta-event data.
+ */
+int
+mf_write_midi_event (delta_time, type, chan, data, size)
+     unsigned long delta_time;
+     int chan, type;
+     unsigned long size;
+     char *data;
+{
+  int i;
+  unsigned char c;
+
+  WriteVarLen (delta_time);
+
+  /* all MIDI events start with the type in the first four bits,
+       and the channel in the lower four bits */
+  if (type == system_exclusive || type == 0xf7)
+    {
+      c = type;
+      laststatus = 0;
+    }
+  else
+    c = type | chan;
+
+  if (chan > 15)
+    perror ("error: MIDI channel greater than 16\n");
+
+  if (laststatus != c)
+    eputc (laststatus = c);
+
+  if (type == system_exclusive || type == 0xf7)
+    WriteVarLen (size);
+
+  /* write out the data bytes */
+  for (i = 0; i < (int)size; i++)
+    eputc (data[i]);
+
+  return (size);
+}				/* end mf_write MIDI event */
+
+/*
+ * mf_write_meta_event()
+ *
+ * Library routine to mf_write a single meta event in the standard MIDI
+ * file format. The format of a meta event is:
+ *
+ *          <delta-time><FF><type><length><bytes>
+ *
+ * delta_time - the time in ticks since the last event.
+ * type - the type of meta event.
+ * data - A pointer to a block of chars containing the META EVENT,
+ *        data.
+ * size - The length of the meta-event data.
+ */
+int 
+mf_write_meta_event (delta_time, type, data, size)
+     unsigned long delta_time;
+     unsigned char *data, type;
+     unsigned long size;
+{
+  int i;
+
+  WriteVarLen (delta_time);
+
+  /* This marks the fact we're writing a meta-event */
+  eputc (laststatus = meta_event);
+
+  /* The type of meta event */
+  eputc (type);
+
+  /* The length of the data bytes to follow */
+  WriteVarLen (size);
+
+  for (i = 0; i < (int)size; i++)
+    {
+      if (eputc (data[i]) != data[i])
+	return (-1);
+    }
+  return (size);
+}				/* end mf_write_meta_event */
+
+void
+mf_write_tempo (delta_time, tempo)
+     unsigned long delta_time;
+     unsigned long tempo;
+{
+  /* Write tempo */
+  /* all tempos are written as 120 beats/minute, */
+  /* expressed in microseconds/quarter note     */
+
+  WriteVarLen (delta_time);
+  eputc (laststatus = meta_event);
+  eputc (set_tempo);
+
+  eputc (3);
+  eputc ((unsigned) (0xff & (tempo >> 16)));
+  eputc ((unsigned) (0xff & (tempo >> 8)));
+  eputc ((unsigned) (0xff & tempo));
+}
+
+void
+mf_write_seqnum (delta_time, seqnum)
+     unsigned long delta_time;
+     unsigned seqnum;
+{
+
+  WriteVarLen (delta_time);
+  eputc (laststatus = meta_event);
+  eputc (0);
+
+  eputc ((unsigned) (0xff & (seqnum >> 8)));
+  eputc ((unsigned) (0xff & seqnum));
+}
+
+unsigned long
+mf_sec2ticks (secs, division, tempo)
+     int division;
+     unsigned long tempo;
+     double secs;
+{
+  return (unsigned long) (((secs * 1000.0) / 4.0 * division) / tempo);
+}
+
+/*
+ * Write multi-length bytes to MIDI format files
+ */
+void
+WriteVarLen (value)
+     unsigned long value;
+{
+  unsigned long buffer;
+
+  buffer = value & 0x7f;
+  while ((value >>= 7) > 0)
+    {
+      buffer <<= 8;
+      buffer |= 0x80;
+      buffer += (value & 0x7f);
+    }
+  while (1)
+    {
+      eputc ((unsigned) (buffer & 0xff));
+
+      if (buffer & 0x80)
+	buffer >>= 8;
+      else
+	return;
+    }
+}				/* end of WriteVarLen */
+
+/*
+ * This routine converts delta times in ticks into seconds. The
+ * else statement is needed because the formula is different for tracks
+ * based on notes and tracks based on SMPTE times.
+ *
+ */
+double
+mf_ticks2sec (ticks, division, tempo)
+     int division;
+     unsigned long tempo;
+     unsigned long ticks;
+{
+  double smpte_format, smpte_resolution;
+
+  if (division > 0)
+    return ((double) (((double) (ticks) * (double) (tempo)) / ((double) (division) * 1000000.0)));
+  else
+    {
+      smpte_format = upperbyte (division);
+      smpte_resolution = lowerbyte (division);
+      return (double) ((double) ticks / (smpte_format * smpte_resolution * 1000000.0));
+    }
+}				/* end of ticks2sec() */
+
+
+/*
+ * write32bit()
+ * write16bit()
+ *
+ * These routines are used to make sure that the byte order of
+ * the various data types remains constant between machines. This
+ * helps make sure that the code will be portable from one system
+ * to the next.  It is slightly dangerous that it assumes that longs
+ * have at least 32 bits and ints have at least 16 bits, but this
+ * has been true at least on PCs, UNIX machines, and Macintosh's.
+ *
+ */
+void
+write32bit (data)
+     unsigned long data;
+{
+  eputc ((unsigned) ((data >> 24) & 0xff));
+  eputc ((unsigned) ((data >> 16) & 0xff));
+  eputc ((unsigned) ((data >> 8) & 0xff));
+  eputc ((unsigned) (data & 0xff));
+}
+
+void
+write16bit (data)
+     int data;
+{
+  eputc ((unsigned) ((data & 0xff00) >> 8));
+  eputc ((unsigned) (data & 0xff));
+}
+
+/* write a single character and abort on error */
+static int
+eputc (c)
+     unsigned char c;
+{
+  int return_val;
+
+  if ((Mf_putc) == NULLFUNC)
+    {
+      mferror ("Mf_putc undefined");
+      return (-1);
+    }
+
+  return_val = (*Mf_putc) (c);
+
+  if (return_val == EOF)
+    mferror ("error writing");
+
+  Mf_numbyteswritten++;
+  return (return_val);
+}
diff --git a/test/midifile.h b/test/midifile.h
new file mode 100644
index 0000000..7dd4626
--- /dev/null
+++ b/test/midifile.h
@@ -0,0 +1,132 @@
+/* definitions for MIDI file parsing code */
+extern int (*Mf_getc)();
+extern void (*Mf_header)();
+extern void (*Mf_trackstart)();
+extern void (*Mf_trackend)();
+extern void (*Mf_noteon)();
+extern void (*Mf_noteoff)();
+extern void (*Mf_pressure)();
+extern void (*Mf_parameter)();
+extern void (*Mf_pitchbend)();
+extern void (*Mf_program)();
+extern void (*Mf_chanpressure)();
+extern void (*Mf_sysex)();
+extern void (*Mf_metamisc)();
+extern void (*Mf_seqspecific)();
+extern void (*Mf_seqnum)();
+extern void (*Mf_text)();
+extern void (*Mf_eot)();
+extern void (*Mf_timesig)();
+extern void (*Mf_smpte)();
+extern void (*Mf_tempo)();
+extern void (*Mf_keysig)();
+extern void (*Mf_arbitrary)();
+extern void (*Mf_error)();
+extern unsigned long Mf_currtime;
+extern unsigned long Mf_realtime;
+extern unsigned long Mf_currtempo;
+extern int Mf_division;
+extern int Mf_nomerge;
+#ifdef READ_MODS
+extern unsigned char *Mf_file_contents;
+extern int Mf_file_size;
+#endif
+
+/* definitions for MIDI file writing code */
+extern int (*Mf_putc)();
+extern int (*Mf_writetrack)();
+extern int (*Mf_writetempotrack)();
+
+extern void midifile();
+extern unsigned long mf_sec2ticks();
+extern void mfwrite();
+extern int mf_write_meta_event();
+extern int mf_write_midi_event(unsigned long delta_time, int type,
+	int chan, char *data, unsigned long size);
+extern double mf_ticks2sec(unsigned long ticks,int division,unsigned long tempo);
+extern void mf_write_tempo();
+extern void mf_write_seqnum();
+extern void mfread();
+extern void mferror(char *s);
+
+#ifndef NO_LC_DEFINES
+/* MIDI status commands most significant bit is 1 */
+#define note_off         	0x80
+#define note_on          	0x90
+#define poly_aftertouch  	0xa0
+#define control_change    	0xb0
+#define program_chng     	0xc0
+#define channel_aftertouch      0xd0
+#define pitch_wheel      	0xe0
+#define system_exclusive      	0xf0
+#define delay_packet	 	(1111)
+
+/* 7 bit controllers */
+#define damper_pedal            0x40
+#define portamento	        0x41 	
+#define sustenuto	        0x42
+#define soft_pedal	        0x43
+#define general_4               0x44
+#define	hold_2		        0x45
+#define	general_5	        0x50
+#define	general_6	        0x51
+#define general_7	        0x52
+#define general_8	        0x53
+#ifndef PLAYMIDI
+#define tremolo_depth	        0x5c
+#define ctrl_chorus_depth       0x5d
+#define	detune		        0x5e
+#define phaser_depth	        0x5f
+#endif
+
+/* parameter values */
+#define data_inc	        0x60
+#define data_dec	        0x61
+
+/* parameter selection */
+#define non_reg_lsb	        0x62
+#define non_reg_msb	        0x63
+#define reg_lsb		        0x64
+#define reg_msb		        0x65
+
+/* Standard MIDI Files meta event definitions */
+#define	meta_event		0xFF
+#define	sequence_number 	0x00
+#define	text_event		0x01
+#define copyright_notice 	0x02
+#define sequence_name    	0x03
+#define instrument_name 	0x04
+#define lyric	        	0x05
+#define marker			0x06
+#define	cue_point		0x07
+#define channel_prefix		0x20
+#define	end_of_track		0x2f
+#define	set_tempo		0x51
+#define	smpte_offset		0x54
+#define	time_signature		0x58
+#define	key_signature		0x59
+#define	sequencer_specific	0x74
+
+/* Manufacturer's ID number */
+#define Seq_Circuits (0x01) /* Sequential Circuits Inc. */
+#define Big_Briar    (0x02) /* Big Briar Inc.           */
+#define Octave       (0x03) /* Octave/Plateau           */
+#define Moog         (0x04) /* Moog Music               */
+#define Passport     (0x05) /* Passport Designs         */
+#define Lexicon      (0x06) /* Lexicon 			*/
+#define Tempi        (0x20) /* Bon Tempi                */
+#define Siel         (0x21) /* S.I.E.L.                 */
+#define Kawai        (0x41) 
+#define Roland       (0x42)
+#define Korg         (0x42)
+#define Yamaha       (0x43)
+#endif
+
+/* miscellaneous definitions */
+#define MThd 0x4d546864
+#define MTrk 0x4d54726b
+
+#ifndef NO_LC_DEFINES
+#define lowerbyte(x) ((unsigned char)(x & 0xff))
+#define upperbyte(x) ((unsigned char)((x & 0xff00)>>8))
+#endif
diff --git a/test/midiloop.c b/test/midiloop.c
new file mode 100644
index 0000000..ee2e563
--- /dev/null
+++ b/test/midiloop.c
@@ -0,0 +1,190 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <sys/time.h>
+#include "../include/asoundlib.h"
+#include <string.h>
+#include <signal.h>
+
+static void usage(void)
+{
+	fprintf(stderr, "Usage: midiloop [options]\n");
+	fprintf(stderr, "  options:\n");
+	fprintf(stderr, "    -v: verbose mode\n");
+	fprintf(stderr, "    -i <rawmidi device> : test input device\n");
+	fprintf(stderr, "    -o <rawmidi device> : test output device\n");
+}
+
+int stop = 0;
+
+void sighandler(int dummy ATTRIBUTE_UNUSED)
+{
+	stop=1;
+}
+
+long long timediff(struct timeval t1, struct timeval t2)
+{
+	signed long l;
+
+	t1.tv_sec -= t2.tv_sec;
+	l = (signed long) t1.tv_usec - (signed long) t2.tv_usec;
+	if (l < 0) {
+		t1.tv_sec--;
+		l = -l;
+		l %= 1000000;
+	}
+	return ((long long)t1.tv_sec * (long long)1000000) + (long long)l;
+}
+
+int writepattern(snd_rawmidi_t *handle_out, unsigned char *obuf)
+{
+	int patsize, i;
+
+	patsize = 0;
+	for (i = 0; i < 15; i++) {
+		obuf[patsize++] = 0x90 + i;
+		obuf[patsize++] = 0x40;
+		obuf[patsize++] = 0x3f;
+		obuf[patsize++] = 0xb0 + i;
+		obuf[patsize++] = 0x2e;
+		obuf[patsize++] = 0x7a;
+		obuf[patsize++] = 0x80 + i;
+		obuf[patsize++] = 0x23;
+		obuf[patsize++] = 0x24;
+		obuf[patsize++] = 0xf0;
+		obuf[patsize++] = i;
+		obuf[patsize++] = 0xf7;
+	}
+	i = snd_rawmidi_write(handle_out, obuf, patsize);
+	if (i != patsize) {
+		printf("Written only %i bytes from %i bytes\n", i, patsize);
+		exit(EXIT_FAILURE);
+	}
+	return patsize;
+}
+
+int main(int argc, char** argv)
+{
+	int i, j, k, opos, ipos, patsize;
+	int err;
+	int verbose = 0;
+	snd_rawmidi_t *handle_in = NULL, *handle_out = NULL;
+	unsigned char ibuf[512], obuf[512];
+	char *iname = "hw:0,0", *oname = "hw:0,0";
+	struct timeval start, end;
+	long long diff;
+	snd_rawmidi_status_t *istat, *ostat;
+	
+	for (i = 1 ; i<argc ; i++) {
+		if (argv[i][0]=='-') {
+			if (!strcmp(argv[i], "--help")) {
+				usage();
+				return 0;
+			}
+			switch (argv[i][1]) {
+				case 'h':
+					usage();
+					return 0;
+				case 'v':
+					verbose = 1;
+					break;
+				case 'i':
+					if (i + 1 < argc)
+						iname = argv[++i];
+					break;
+				case 'o':
+					if (i + 1 < argc)
+						oname = argv[++i];
+					break;
+			}			
+		}
+	}
+
+	if (iname == NULL)
+		iname = oname;
+	if (oname == NULL)
+		oname = iname;
+
+	if (verbose) {
+		fprintf(stderr, "Using: \n");
+		fprintf(stderr, "  Input: %s  Output: %s\n", iname, oname);
+	}
+	
+	err = snd_rawmidi_open(&handle_in, NULL, iname, SND_RAWMIDI_NONBLOCK);
+	if (err) {
+		fprintf(stderr,"snd_rawmidi_open %s failed: %d\n",iname,err);
+		exit(EXIT_FAILURE);
+	}
+
+	err = snd_rawmidi_open(NULL, &handle_out, oname, 0);
+	if (err) {
+		fprintf(stderr,"snd_rawmidi_open %s failed: %d\n",oname,err);
+		exit(EXIT_FAILURE);
+	}
+
+	signal(SIGINT, sighandler);
+
+	i = snd_rawmidi_read(handle_in, ibuf, sizeof(ibuf));
+	if (i > 0) {
+		printf("Read ahead: %i\n", i);
+		for (j = 0; j < i; j++)
+			printf("%02x:", ibuf[j]);
+		printf("\n");
+		exit(EXIT_FAILURE);
+	}
+
+	snd_rawmidi_nonblock(handle_in, 0);
+
+	patsize = writepattern(handle_out, obuf);
+	gettimeofday(&start, NULL);
+	patsize = writepattern(handle_out, obuf);
+
+	k = ipos = opos = err = 0;
+	while (!stop) {
+		i = snd_rawmidi_read(handle_in, ibuf, sizeof(ibuf));
+		for (j = 0; j < i; j++, ipos++)
+			if (obuf[k] != ibuf[j]) {
+				printf("ipos = %i, i[0x%x] != o[0x%x]\n", ipos, ibuf[j], obuf[k]);
+				if (opos > 0)
+					stop = 1;
+			} else {
+				printf("match success: ipos = %i, opos = %i [%i:0x%x]\n", ipos, opos, k, obuf[k]);
+				k++; opos++;
+				if (k >= patsize) {
+					patsize = writepattern(handle_out, obuf);
+					k = 0;
+				}
+			}
+	}
+
+	gettimeofday(&end, NULL);
+
+	printf("End...\n");
+
+	snd_rawmidi_status_alloca(&istat);
+	snd_rawmidi_status_alloca(&ostat);
+	err = snd_rawmidi_status(handle_in, istat);
+	if (err < 0)
+		fprintf(stderr, "input stream status error: %d\n", err);
+	err = snd_rawmidi_status(handle_out, ostat);
+	if (err < 0)
+		fprintf(stderr, "output stream status error: %d\n", err);
+	printf("input.status.avail = %zi\n", snd_rawmidi_status_get_avail(istat));
+	printf("input.status.xruns = %zi\n", snd_rawmidi_status_get_xruns(istat));
+	printf("output.status.avail = %zi\n", snd_rawmidi_status_get_avail(ostat));
+	printf("output.status.xruns = %zi\n", snd_rawmidi_status_get_xruns(ostat));
+
+	diff = timediff(end, start);
+	printf("Time diff: %Liusec (%Li bytes/sec)\n", diff, ((long long)opos * 1000000) / diff);
+
+	if (verbose) {
+		fprintf(stderr,"Closing\n");
+	}
+	
+	snd_rawmidi_drain(handle_in); 
+	snd_rawmidi_close(handle_in);	
+	snd_rawmidi_drain(handle_out); 
+	snd_rawmidi_close(handle_out);	
+
+	return 0;
+}
diff --git a/test/namehint.c b/test/namehint.c
new file mode 100644
index 0000000..e978d5c
--- /dev/null
+++ b/test/namehint.c
@@ -0,0 +1,22 @@
+#include "../include/asoundlib.h"
+#include <err.h>
+
+int main(int argc, char *argv[])
+{
+	const char *iface = "pcm";
+	char **hints, **n;
+	int err;
+
+	if (argc > 1)
+		iface = argv[1];
+	err = snd_device_name_hint(-1, iface, &hints);
+	if (err < 0)
+		errx(1, "snd_device_name_hint error: %s", snd_strerror(err));
+	n = hints;
+	while (*n != NULL) {
+		printf("%s\n", *n);
+		n++;
+	}
+	snd_device_name_free_hint(hints);
+	return 0;
+}
diff --git a/test/oldapi.c b/test/oldapi.c
new file mode 100644
index 0000000..e325f4c
--- /dev/null
+++ b/test/oldapi.c
@@ -0,0 +1,42 @@
+/*
+ *  Old PCM API compilation test
+ *
+ *     Author: Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sched.h>
+#include <errno.h>
+#include <getopt.h>
+#define ALSA_PCM_OLD_HW_PARAMS_API
+#define ALSA_PCM_OLD_SW_PARAMS_API
+#include "../include/asoundlib.h"
+#include <sys/time.h>
+
+typedef void (myfcn)(void *);
+
+int main(int argc ATTRIBUTE_UNUSED, char *argv[] ATTRIBUTE_UNUSED)
+{
+	myfcn *fcn;
+	snd_pcm_hw_params_get_access(NULL);
+	fcn = &snd_pcm_hw_params_get_access;
+	return 0;
+}
diff --git a/test/omixer.c b/test/omixer.c
new file mode 100644
index 0000000..119fa33
--- /dev/null
+++ b/test/omixer.c
@@ -0,0 +1,92 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sched.h>
+#include <errno.h>
+#include <getopt.h>
+#include "../include/mixer_ordinary.h"
+#include <sys/time.h>
+#include <math.h>
+
+static void help(void)
+{
+	printf(
+"Usage: omixer [OPTION]...\n\n"
+"-h,--help      help\n"
+"-P,--pname     playback PCM device\n"
+"-C,--cname     capture PCM device\n"
+);
+}
+
+int main(int argc, char *argv[])
+{
+	struct option long_option[] =
+	{
+		{"help", 0, NULL, 'h'},
+		{"pname", 1, NULL, 'P'},
+		{"cname", 1, NULL, 'C'},
+		{NULL, 0, NULL, 0},
+	};
+	int err, morehelp, result = EXIT_SUCCESS;
+	char *pname = "default", *cname = "default";
+	snd_pcm_t *phandle = NULL, *chandle = NULL;
+	sndo_mixer_t *handle;
+
+	morehelp = 0;
+	while (1) {
+		int c;
+		if ((c = getopt_long(argc, argv, "hP:C:", long_option, NULL)) < 0)
+			break;
+		switch (c) {
+		case 'h':
+			morehelp++;
+			break;
+		case 'P':
+			pname = strdup(optarg);
+			break;
+		case 'C':
+			cname = strdup(optarg);
+			break;
+		}
+	}
+
+	if (morehelp) {
+		help();
+		return 0;
+	}
+
+	if (strcmp(pname, "-")) { 
+		err = snd_pcm_open(&phandle, pname, SND_PCM_STREAM_PLAYBACK, 0);
+		if (err < 0) {
+			fprintf(stderr, "Playback PCM open error: %s\n", snd_strerror(err));
+			result = EXIT_FAILURE;
+			goto __end;
+		}
+	}
+
+	if (strcmp(cname, "-")) {
+		err = snd_pcm_open(&chandle, cname, SND_PCM_STREAM_CAPTURE, 0);
+		if (err < 0) {
+			if (phandle)
+				snd_pcm_close(phandle);
+			fprintf(stderr, "Capture PCM open error: %s\n", snd_strerror(err));
+			result = EXIT_FAILURE;
+			goto __end;
+		}
+	}
+
+	err = sndo_mixer_open_pcm(&handle, phandle, chandle, NULL);
+	if (err < 0) {
+		fprintf(stderr, "mixer open error: %s\n", snd_strerror(err));
+		result = EXIT_FAILURE;
+	} else {
+		sndo_mixer_close(handle);
+	}
+      __end:
+	if (chandle)
+		snd_pcm_close(chandle);
+	if (phandle)
+		snd_pcm_close(phandle);
+	snd_config_update_free_global(); /* to keep valgrind happy */
+	return result;
+}
diff --git a/test/pcm.c b/test/pcm.c
new file mode 100644
index 0000000..abb83e4
--- /dev/null
+++ b/test/pcm.c
@@ -0,0 +1,925 @@
+/*
+ *  This small demo sends a simple sinusoidal wave to your speakers.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sched.h>
+#include <errno.h>
+#include <getopt.h>
+#include "../include/asoundlib.h"
+#include <sys/time.h>
+#include <math.h>
+
+static char *device = "plughw:0,0";			/* playback device */
+static snd_pcm_format_t format = SND_PCM_FORMAT_S16;	/* sample format */
+static unsigned int rate = 44100;			/* stream rate */
+static unsigned int channels = 1;			/* count of channels */
+static unsigned int buffer_time = 500000;		/* ring buffer length in us */
+static unsigned int period_time = 100000;		/* period time in us */
+static double freq = 440;				/* sinusoidal wave frequency in Hz */
+static int verbose = 0;					/* verbose flag */
+static int resample = 1;				/* enable alsa-lib resampling */
+static int period_event = 0;				/* produce poll event after each period */
+
+static snd_pcm_sframes_t buffer_size;
+static snd_pcm_sframes_t period_size;
+static snd_output_t *output = NULL;
+
+static void generate_sine(const snd_pcm_channel_area_t *areas, 
+			  snd_pcm_uframes_t offset,
+			  int count, double *_phase)
+{
+	static double max_phase = 2. * M_PI;
+	double phase = *_phase;
+	double step = max_phase*freq/(double)rate;
+	unsigned char *samples[channels];
+	int steps[channels];
+	unsigned int chn;
+	int format_bits = snd_pcm_format_width(format);
+	unsigned int maxval = (1 << (format_bits - 1)) - 1;
+	int bps = format_bits / 8;  /* bytes per sample */
+	int phys_bps = snd_pcm_format_physical_width(format) / 8;
+	int big_endian = snd_pcm_format_big_endian(format) == 1;
+	int to_unsigned = snd_pcm_format_unsigned(format) == 1;
+	int is_float = (format == SND_PCM_FORMAT_FLOAT_LE ||
+			format == SND_PCM_FORMAT_FLOAT_BE);
+
+	/* verify and prepare the contents of areas */
+	for (chn = 0; chn < channels; chn++) {
+		if ((areas[chn].first % 8) != 0) {
+			printf("areas[%i].first == %i, aborting...\n", chn, areas[chn].first);
+			exit(EXIT_FAILURE);
+		}
+		samples[chn] = /*(signed short *)*/(((unsigned char *)areas[chn].addr) + (areas[chn].first / 8));
+		if ((areas[chn].step % 16) != 0) {
+			printf("areas[%i].step == %i, aborting...\n", chn, areas[chn].step);
+			exit(EXIT_FAILURE);
+		}
+		steps[chn] = areas[chn].step / 8;
+		samples[chn] += offset * steps[chn];
+	}
+	/* fill the channel areas */
+	while (count-- > 0) {
+		union {
+			float f;
+			int i;
+		} fval;
+		int res, i;
+		if (is_float) {
+			fval.f = sin(phase) * maxval;
+			res = fval.i;
+		} else
+			res = sin(phase) * maxval;
+		if (to_unsigned)
+			res ^= 1U << (format_bits - 1);
+		for (chn = 0; chn < channels; chn++) {
+			/* Generate data in native endian format */
+			if (big_endian) {
+				for (i = 0; i < bps; i++)
+					*(samples[chn] + phys_bps - 1 - i) = (res >> i * 8) & 0xff;
+			} else {
+				for (i = 0; i < bps; i++)
+					*(samples[chn] + i) = (res >>  i * 8) & 0xff;
+			}
+			samples[chn] += steps[chn];
+		}
+		phase += step;
+		if (phase >= max_phase)
+			phase -= max_phase;
+	}
+	*_phase = phase;
+}
+
+static int set_hwparams(snd_pcm_t *handle,
+			snd_pcm_hw_params_t *params,
+			snd_pcm_access_t access)
+{
+	unsigned int rrate;
+	snd_pcm_uframes_t size;
+	int err, dir;
+
+	/* choose all parameters */
+	err = snd_pcm_hw_params_any(handle, params);
+	if (err < 0) {
+		printf("Broken configuration for playback: no configurations available: %s\n", snd_strerror(err));
+		return err;
+	}
+	/* set hardware resampling */
+	err = snd_pcm_hw_params_set_rate_resample(handle, params, resample);
+	if (err < 0) {
+		printf("Resampling setup failed for playback: %s\n", snd_strerror(err));
+		return err;
+	}
+	/* set the interleaved read/write format */
+	err = snd_pcm_hw_params_set_access(handle, params, access);
+	if (err < 0) {
+		printf("Access type not available for playback: %s\n", snd_strerror(err));
+		return err;
+	}
+	/* set the sample format */
+	err = snd_pcm_hw_params_set_format(handle, params, format);
+	if (err < 0) {
+		printf("Sample format not available for playback: %s\n", snd_strerror(err));
+		return err;
+	}
+	/* set the count of channels */
+	err = snd_pcm_hw_params_set_channels(handle, params, channels);
+	if (err < 0) {
+		printf("Channels count (%i) not available for playbacks: %s\n", channels, snd_strerror(err));
+		return err;
+	}
+	/* set the stream rate */
+	rrate = rate;
+	err = snd_pcm_hw_params_set_rate_near(handle, params, &rrate, 0);
+	if (err < 0) {
+		printf("Rate %iHz not available for playback: %s\n", rate, snd_strerror(err));
+		return err;
+	}
+	if (rrate != rate) {
+		printf("Rate doesn't match (requested %iHz, get %iHz)\n", rate, err);
+		return -EINVAL;
+	}
+	/* set the buffer time */
+	err = snd_pcm_hw_params_set_buffer_time_near(handle, params, &buffer_time, &dir);
+	if (err < 0) {
+		printf("Unable to set buffer time %i for playback: %s\n", buffer_time, snd_strerror(err));
+		return err;
+	}
+	err = snd_pcm_hw_params_get_buffer_size(params, &size);
+	if (err < 0) {
+		printf("Unable to get buffer size for playback: %s\n", snd_strerror(err));
+		return err;
+	}
+	buffer_size = size;
+	/* set the period time */
+	err = snd_pcm_hw_params_set_period_time_near(handle, params, &period_time, &dir);
+	if (err < 0) {
+		printf("Unable to set period time %i for playback: %s\n", period_time, snd_strerror(err));
+		return err;
+	}
+	err = snd_pcm_hw_params_get_period_size(params, &size, &dir);
+	if (err < 0) {
+		printf("Unable to get period size for playback: %s\n", snd_strerror(err));
+		return err;
+	}
+	period_size = size;
+	/* write the parameters to device */
+	err = snd_pcm_hw_params(handle, params);
+	if (err < 0) {
+		printf("Unable to set hw params for playback: %s\n", snd_strerror(err));
+		return err;
+	}
+	return 0;
+}
+
+static int set_swparams(snd_pcm_t *handle, snd_pcm_sw_params_t *swparams)
+{
+	int err;
+
+	/* get the current swparams */
+	err = snd_pcm_sw_params_current(handle, swparams);
+	if (err < 0) {
+		printf("Unable to determine current swparams for playback: %s\n", snd_strerror(err));
+		return err;
+	}
+	/* start the transfer when the buffer is almost full: */
+	/* (buffer_size / avail_min) * avail_min */
+	err = snd_pcm_sw_params_set_start_threshold(handle, swparams, (buffer_size / period_size) * period_size);
+	if (err < 0) {
+		printf("Unable to set start threshold mode for playback: %s\n", snd_strerror(err));
+		return err;
+	}
+	/* allow the transfer when at least period_size samples can be processed */
+	/* or disable this mechanism when period event is enabled (aka interrupt like style processing) */
+	err = snd_pcm_sw_params_set_avail_min(handle, swparams, period_event ? buffer_size : period_size);
+	if (err < 0) {
+		printf("Unable to set avail min for playback: %s\n", snd_strerror(err));
+		return err;
+	}
+	/* enable period events when requested */
+	if (period_event) {
+		err = snd_pcm_sw_params_set_period_event(handle, swparams, 1);
+		if (err < 0) {
+			printf("Unable to set period event: %s\n", snd_strerror(err));
+			return err;
+		}
+	}
+	/* write the parameters to the playback device */
+	err = snd_pcm_sw_params(handle, swparams);
+	if (err < 0) {
+		printf("Unable to set sw params for playback: %s\n", snd_strerror(err));
+		return err;
+	}
+	return 0;
+}
+
+/*
+ *   Underrun and suspend recovery
+ */
+ 
+static int xrun_recovery(snd_pcm_t *handle, int err)
+{
+	if (verbose)
+		printf("stream recovery\n");
+	if (err == -EPIPE) {	/* under-run */
+		err = snd_pcm_prepare(handle);
+		if (err < 0)
+			printf("Can't recovery from underrun, prepare failed: %s\n", snd_strerror(err));
+		return 0;
+	} else if (err == -ESTRPIPE) {
+		while ((err = snd_pcm_resume(handle)) == -EAGAIN)
+			sleep(1);	/* wait until the suspend flag is released */
+		if (err < 0) {
+			err = snd_pcm_prepare(handle);
+			if (err < 0)
+				printf("Can't recovery from suspend, prepare failed: %s\n", snd_strerror(err));
+		}
+		return 0;
+	}
+	return err;
+}
+
+/*
+ *   Transfer method - write only
+ */
+
+static int write_loop(snd_pcm_t *handle,
+		      signed short *samples,
+		      snd_pcm_channel_area_t *areas)
+{
+	double phase = 0;
+	signed short *ptr;
+	int err, cptr;
+
+	while (1) {
+		generate_sine(areas, 0, period_size, &phase);
+		ptr = samples;
+		cptr = period_size;
+		while (cptr > 0) {
+			err = snd_pcm_writei(handle, ptr, cptr);
+			if (err == -EAGAIN)
+				continue;
+			if (err < 0) {
+				if (xrun_recovery(handle, err) < 0) {
+					printf("Write error: %s\n", snd_strerror(err));
+					exit(EXIT_FAILURE);
+				}
+				break;	/* skip one period */
+			}
+			ptr += err * channels;
+			cptr -= err;
+		}
+	}
+}
+ 
+/*
+ *   Transfer method - write and wait for room in buffer using poll
+ */
+
+static int wait_for_poll(snd_pcm_t *handle, struct pollfd *ufds, unsigned int count)
+{
+	unsigned short revents;
+
+	while (1) {
+		poll(ufds, count, -1);
+		snd_pcm_poll_descriptors_revents(handle, ufds, count, &revents);
+		if (revents & POLLERR)
+			return -EIO;
+		if (revents & POLLOUT)
+			return 0;
+	}
+}
+
+static int write_and_poll_loop(snd_pcm_t *handle,
+			       signed short *samples,
+			       snd_pcm_channel_area_t *areas)
+{
+	struct pollfd *ufds;
+	double phase = 0;
+	signed short *ptr;
+	int err, count, cptr, init;
+
+	count = snd_pcm_poll_descriptors_count (handle);
+	if (count <= 0) {
+		printf("Invalid poll descriptors count\n");
+		return count;
+	}
+
+	ufds = malloc(sizeof(struct pollfd) * count);
+	if (ufds == NULL) {
+		printf("No enough memory\n");
+		return -ENOMEM;
+	}
+	if ((err = snd_pcm_poll_descriptors(handle, ufds, count)) < 0) {
+		printf("Unable to obtain poll descriptors for playback: %s\n", snd_strerror(err));
+		return err;
+	}
+
+	init = 1;
+	while (1) {
+		if (!init) {
+			err = wait_for_poll(handle, ufds, count);
+			if (err < 0) {
+				if (snd_pcm_state(handle) == SND_PCM_STATE_XRUN ||
+				    snd_pcm_state(handle) == SND_PCM_STATE_SUSPENDED) {
+					err = snd_pcm_state(handle) == SND_PCM_STATE_XRUN ? -EPIPE : -ESTRPIPE;
+					if (xrun_recovery(handle, err) < 0) {
+						printf("Write error: %s\n", snd_strerror(err));
+						exit(EXIT_FAILURE);
+					}
+					init = 1;
+				} else {
+					printf("Wait for poll failed\n");
+					return err;
+				}
+			}
+		}
+
+		generate_sine(areas, 0, period_size, &phase);
+		ptr = samples;
+		cptr = period_size;
+		while (cptr > 0) {
+			err = snd_pcm_writei(handle, ptr, cptr);
+			if (err < 0) {
+				if (xrun_recovery(handle, err) < 0) {
+					printf("Write error: %s\n", snd_strerror(err));
+					exit(EXIT_FAILURE);
+				}
+				init = 1;
+				break;	/* skip one period */
+			}
+			if (snd_pcm_state(handle) == SND_PCM_STATE_RUNNING)
+				init = 0;
+			ptr += err * channels;
+			cptr -= err;
+			if (cptr == 0)
+				break;
+			/* it is possible, that the initial buffer cannot store */
+			/* all data from the last period, so wait awhile */
+			err = wait_for_poll(handle, ufds, count);
+			if (err < 0) {
+				if (snd_pcm_state(handle) == SND_PCM_STATE_XRUN ||
+				    snd_pcm_state(handle) == SND_PCM_STATE_SUSPENDED) {
+					err = snd_pcm_state(handle) == SND_PCM_STATE_XRUN ? -EPIPE : -ESTRPIPE;
+					if (xrun_recovery(handle, err) < 0) {
+						printf("Write error: %s\n", snd_strerror(err));
+						exit(EXIT_FAILURE);
+					}
+					init = 1;
+				} else {
+					printf("Wait for poll failed\n");
+					return err;
+				}
+			}
+		}
+	}
+}
+
+/*
+ *   Transfer method - asynchronous notification
+ */
+
+struct async_private_data {
+	signed short *samples;
+	snd_pcm_channel_area_t *areas;
+	double phase;
+};
+
+static void async_callback(snd_async_handler_t *ahandler)
+{
+	snd_pcm_t *handle = snd_async_handler_get_pcm(ahandler);
+	struct async_private_data *data = snd_async_handler_get_callback_private(ahandler);
+	signed short *samples = data->samples;
+	snd_pcm_channel_area_t *areas = data->areas;
+	snd_pcm_sframes_t avail;
+	int err;
+	
+	avail = snd_pcm_avail_update(handle);
+	while (avail >= period_size) {
+		generate_sine(areas, 0, period_size, &data->phase);
+		err = snd_pcm_writei(handle, samples, period_size);
+		if (err < 0) {
+			printf("Write error: %s\n", snd_strerror(err));
+			exit(EXIT_FAILURE);
+		}
+		if (err != period_size) {
+			printf("Write error: written %i expected %li\n", err, period_size);
+			exit(EXIT_FAILURE);
+		}
+		avail = snd_pcm_avail_update(handle);
+	}
+}
+
+static int async_loop(snd_pcm_t *handle,
+		      signed short *samples,
+		      snd_pcm_channel_area_t *areas)
+{
+	struct async_private_data data;
+	snd_async_handler_t *ahandler;
+	int err, count;
+
+	data.samples = samples;
+	data.areas = areas;
+	data.phase = 0;
+	err = snd_async_add_pcm_handler(&ahandler, handle, async_callback, &data);
+	if (err < 0) {
+		printf("Unable to register async handler\n");
+		exit(EXIT_FAILURE);
+	}
+	for (count = 0; count < 2; count++) {
+		generate_sine(areas, 0, period_size, &data.phase);
+		err = snd_pcm_writei(handle, samples, period_size);
+		if (err < 0) {
+			printf("Initial write error: %s\n", snd_strerror(err));
+			exit(EXIT_FAILURE);
+		}
+		if (err != period_size) {
+			printf("Initial write error: written %i expected %li\n", err, period_size);
+			exit(EXIT_FAILURE);
+		}
+	}
+	if (snd_pcm_state(handle) == SND_PCM_STATE_PREPARED) {
+		err = snd_pcm_start(handle);
+		if (err < 0) {
+			printf("Start error: %s\n", snd_strerror(err));
+			exit(EXIT_FAILURE);
+		}
+	}
+
+	/* because all other work is done in the signal handler,
+	   suspend the process */
+	while (1) {
+		sleep(1);
+	}
+}
+
+/*
+ *   Transfer method - asynchronous notification + direct write
+ */
+
+static void async_direct_callback(snd_async_handler_t *ahandler)
+{
+	snd_pcm_t *handle = snd_async_handler_get_pcm(ahandler);
+	struct async_private_data *data = snd_async_handler_get_callback_private(ahandler);
+	const snd_pcm_channel_area_t *my_areas;
+	snd_pcm_uframes_t offset, frames, size;
+	snd_pcm_sframes_t avail, commitres;
+	snd_pcm_state_t state;
+	int first = 0, err;
+	
+	while (1) {
+		state = snd_pcm_state(handle);
+		if (state == SND_PCM_STATE_XRUN) {
+			err = xrun_recovery(handle, -EPIPE);
+			if (err < 0) {
+				printf("XRUN recovery failed: %s\n", snd_strerror(err));
+				exit(EXIT_FAILURE);
+			}
+			first = 1;
+		} else if (state == SND_PCM_STATE_SUSPENDED) {
+			err = xrun_recovery(handle, -ESTRPIPE);
+			if (err < 0) {
+				printf("SUSPEND recovery failed: %s\n", snd_strerror(err));
+				exit(EXIT_FAILURE);
+			}
+		}
+		avail = snd_pcm_avail_update(handle);
+		if (avail < 0) {
+			err = xrun_recovery(handle, avail);
+			if (err < 0) {
+				printf("avail update failed: %s\n", snd_strerror(err));
+				exit(EXIT_FAILURE);
+			}
+			first = 1;
+			continue;
+		}
+		if (avail < period_size) {
+			if (first) {
+				first = 0;
+				err = snd_pcm_start(handle);
+				if (err < 0) {
+					printf("Start error: %s\n", snd_strerror(err));
+					exit(EXIT_FAILURE);
+				}
+			} else {
+				break;
+			}
+			continue;
+		}
+		size = period_size;
+		while (size > 0) {
+			frames = size;
+			err = snd_pcm_mmap_begin(handle, &my_areas, &offset, &frames);
+			if (err < 0) {
+				if ((err = xrun_recovery(handle, err)) < 0) {
+					printf("MMAP begin avail error: %s\n", snd_strerror(err));
+					exit(EXIT_FAILURE);
+				}
+				first = 1;
+			}
+			generate_sine(my_areas, offset, frames, &data->phase);
+			commitres = snd_pcm_mmap_commit(handle, offset, frames);
+			if (commitres < 0 || (snd_pcm_uframes_t)commitres != frames) {
+				if ((err = xrun_recovery(handle, commitres >= 0 ? -EPIPE : commitres)) < 0) {
+					printf("MMAP commit error: %s\n", snd_strerror(err));
+					exit(EXIT_FAILURE);
+				}
+				first = 1;
+			}
+			size -= frames;
+		}
+	}
+}
+
+static int async_direct_loop(snd_pcm_t *handle,
+			     signed short *samples ATTRIBUTE_UNUSED,
+			     snd_pcm_channel_area_t *areas ATTRIBUTE_UNUSED)
+{
+	struct async_private_data data;
+	snd_async_handler_t *ahandler;
+	const snd_pcm_channel_area_t *my_areas;
+	snd_pcm_uframes_t offset, frames, size;
+	snd_pcm_sframes_t commitres;
+	int err, count;
+
+	data.samples = NULL;	/* we do not require the global sample area for direct write */
+	data.areas = NULL;	/* we do not require the global areas for direct write */
+	data.phase = 0;
+	err = snd_async_add_pcm_handler(&ahandler, handle, async_direct_callback, &data);
+	if (err < 0) {
+		printf("Unable to register async handler\n");
+		exit(EXIT_FAILURE);
+	}
+	for (count = 0; count < 2; count++) {
+		size = period_size;
+		while (size > 0) {
+			frames = size;
+			err = snd_pcm_mmap_begin(handle, &my_areas, &offset, &frames);
+			if (err < 0) {
+				if ((err = xrun_recovery(handle, err)) < 0) {
+					printf("MMAP begin avail error: %s\n", snd_strerror(err));
+					exit(EXIT_FAILURE);
+				}
+			}
+			generate_sine(my_areas, offset, frames, &data.phase);
+			commitres = snd_pcm_mmap_commit(handle, offset, frames);
+			if (commitres < 0 || (snd_pcm_uframes_t)commitres != frames) {
+				if ((err = xrun_recovery(handle, commitres >= 0 ? -EPIPE : commitres)) < 0) {
+					printf("MMAP commit error: %s\n", snd_strerror(err));
+					exit(EXIT_FAILURE);
+				}
+			}
+			size -= frames;
+		}
+	}
+	err = snd_pcm_start(handle);
+	if (err < 0) {
+		printf("Start error: %s\n", snd_strerror(err));
+		exit(EXIT_FAILURE);
+	}
+
+	/* because all other work is done in the signal handler,
+	   suspend the process */
+	while (1) {
+		sleep(1);
+	}
+}
+
+/*
+ *   Transfer method - direct write only
+ */
+
+static int direct_loop(snd_pcm_t *handle,
+		       signed short *samples ATTRIBUTE_UNUSED,
+		       snd_pcm_channel_area_t *areas ATTRIBUTE_UNUSED)
+{
+	double phase = 0;
+	const snd_pcm_channel_area_t *my_areas;
+	snd_pcm_uframes_t offset, frames, size;
+	snd_pcm_sframes_t avail, commitres;
+	snd_pcm_state_t state;
+	int err, first = 1;
+
+	while (1) {
+		state = snd_pcm_state(handle);
+		if (state == SND_PCM_STATE_XRUN) {
+			err = xrun_recovery(handle, -EPIPE);
+			if (err < 0) {
+				printf("XRUN recovery failed: %s\n", snd_strerror(err));
+				return err;
+			}
+			first = 1;
+		} else if (state == SND_PCM_STATE_SUSPENDED) {
+			err = xrun_recovery(handle, -ESTRPIPE);
+			if (err < 0) {
+				printf("SUSPEND recovery failed: %s\n", snd_strerror(err));
+				return err;
+			}
+		}
+		avail = snd_pcm_avail_update(handle);
+		if (avail < 0) {
+			err = xrun_recovery(handle, avail);
+			if (err < 0) {
+				printf("avail update failed: %s\n", snd_strerror(err));
+				return err;
+			}
+			first = 1;
+			continue;
+		}
+		if (avail < period_size) {
+			if (first) {
+				first = 0;
+				err = snd_pcm_start(handle);
+				if (err < 0) {
+					printf("Start error: %s\n", snd_strerror(err));
+					exit(EXIT_FAILURE);
+				}
+			} else {
+				err = snd_pcm_wait(handle, -1);
+				if (err < 0) {
+					if ((err = xrun_recovery(handle, err)) < 0) {
+						printf("snd_pcm_wait error: %s\n", snd_strerror(err));
+						exit(EXIT_FAILURE);
+					}
+					first = 1;
+				}
+			}
+			continue;
+		}
+		size = period_size;
+		while (size > 0) {
+			frames = size;
+			err = snd_pcm_mmap_begin(handle, &my_areas, &offset, &frames);
+			if (err < 0) {
+				if ((err = xrun_recovery(handle, err)) < 0) {
+					printf("MMAP begin avail error: %s\n", snd_strerror(err));
+					exit(EXIT_FAILURE);
+				}
+				first = 1;
+			}
+			generate_sine(my_areas, offset, frames, &phase);
+			commitres = snd_pcm_mmap_commit(handle, offset, frames);
+			if (commitres < 0 || (snd_pcm_uframes_t)commitres != frames) {
+				if ((err = xrun_recovery(handle, commitres >= 0 ? -EPIPE : commitres)) < 0) {
+					printf("MMAP commit error: %s\n", snd_strerror(err));
+					exit(EXIT_FAILURE);
+				}
+				first = 1;
+			}
+			size -= frames;
+		}
+	}
+}
+ 
+/*
+ *   Transfer method - direct write only using mmap_write functions
+ */
+
+static int direct_write_loop(snd_pcm_t *handle,
+			     signed short *samples,
+			     snd_pcm_channel_area_t *areas)
+{
+	double phase = 0;
+	signed short *ptr;
+	int err, cptr;
+
+	while (1) {
+		generate_sine(areas, 0, period_size, &phase);
+		ptr = samples;
+		cptr = period_size;
+		while (cptr > 0) {
+			err = snd_pcm_mmap_writei(handle, ptr, cptr);
+			if (err == -EAGAIN)
+				continue;
+			if (err < 0) {
+				if (xrun_recovery(handle, err) < 0) {
+					printf("Write error: %s\n", snd_strerror(err));
+					exit(EXIT_FAILURE);
+				}
+				break;	/* skip one period */
+			}
+			ptr += err * channels;
+			cptr -= err;
+		}
+	}
+}
+ 
+/*
+ *
+ */
+
+struct transfer_method {
+	const char *name;
+	snd_pcm_access_t access;
+	int (*transfer_loop)(snd_pcm_t *handle,
+			     signed short *samples,
+			     snd_pcm_channel_area_t *areas);
+};
+
+static struct transfer_method transfer_methods[] = {
+	{ "write", SND_PCM_ACCESS_RW_INTERLEAVED, write_loop },
+	{ "write_and_poll", SND_PCM_ACCESS_RW_INTERLEAVED, write_and_poll_loop },
+	{ "async", SND_PCM_ACCESS_RW_INTERLEAVED, async_loop },
+	{ "async_direct", SND_PCM_ACCESS_MMAP_INTERLEAVED, async_direct_loop },
+	{ "direct_interleaved", SND_PCM_ACCESS_MMAP_INTERLEAVED, direct_loop },
+	{ "direct_noninterleaved", SND_PCM_ACCESS_MMAP_NONINTERLEAVED, direct_loop },
+	{ "direct_write", SND_PCM_ACCESS_MMAP_INTERLEAVED, direct_write_loop },
+	{ NULL, SND_PCM_ACCESS_RW_INTERLEAVED, NULL }
+};
+
+static void help(void)
+{
+	int k;
+	printf(
+"Usage: pcm [OPTION]... [FILE]...\n"
+"-h,--help	help\n"
+"-D,--device	playback device\n"
+"-r,--rate	stream rate in Hz\n"
+"-c,--channels	count of channels in stream\n"
+"-f,--frequency	sine wave frequency in Hz\n"
+"-b,--buffer	ring buffer size in us\n"
+"-p,--period	period size in us\n"
+"-m,--method	transfer method\n"
+"-o,--format	sample format\n"
+"-v,--verbose   show the PCM setup parameters\n"
+"-n,--noresample  do not resample\n"
+"-e,--pevent    enable poll event after each period\n"
+"\n");
+        printf("Recognized sample formats are:");
+        for (k = 0; k < SND_PCM_FORMAT_LAST; ++k) {
+                const char *s = snd_pcm_format_name(k);
+                if (s)
+                        printf(" %s", s);
+        }
+        printf("\n");
+        printf("Recognized transfer methods are:");
+        for (k = 0; transfer_methods[k].name; k++)
+        	printf(" %s", transfer_methods[k].name);
+	printf("\n");
+}
+
+int main(int argc, char *argv[])
+{
+	struct option long_option[] =
+	{
+		{"help", 0, NULL, 'h'},
+		{"device", 1, NULL, 'D'},
+		{"rate", 1, NULL, 'r'},
+		{"channels", 1, NULL, 'c'},
+		{"frequency", 1, NULL, 'f'},
+		{"buffer", 1, NULL, 'b'},
+		{"period", 1, NULL, 'p'},
+		{"method", 1, NULL, 'm'},
+		{"format", 1, NULL, 'o'},
+		{"verbose", 1, NULL, 'v'},
+		{"noresample", 1, NULL, 'n'},
+		{"pevent", 1, NULL, 'e'},
+		{NULL, 0, NULL, 0},
+	};
+	snd_pcm_t *handle;
+	int err, morehelp;
+	snd_pcm_hw_params_t *hwparams;
+	snd_pcm_sw_params_t *swparams;
+	int method = 0;
+	signed short *samples;
+	unsigned int chn;
+	snd_pcm_channel_area_t *areas;
+
+	snd_pcm_hw_params_alloca(&hwparams);
+	snd_pcm_sw_params_alloca(&swparams);
+
+	morehelp = 0;
+	while (1) {
+		int c;
+		if ((c = getopt_long(argc, argv, "hD:r:c:f:b:p:m:o:vne", long_option, NULL)) < 0)
+			break;
+		switch (c) {
+		case 'h':
+			morehelp++;
+			break;
+		case 'D':
+			device = strdup(optarg);
+			break;
+		case 'r':
+			rate = atoi(optarg);
+			rate = rate < 4000 ? 4000 : rate;
+			rate = rate > 196000 ? 196000 : rate;
+			break;
+		case 'c':
+			channels = atoi(optarg);
+			channels = channels < 1 ? 1 : channels;
+			channels = channels > 1024 ? 1024 : channels;
+			break;
+		case 'f':
+			freq = atoi(optarg);
+			freq = freq < 50 ? 50 : freq;
+			freq = freq > 5000 ? 5000 : freq;
+			break;
+		case 'b':
+			buffer_time = atoi(optarg);
+			buffer_time = buffer_time < 1000 ? 1000 : buffer_time;
+			buffer_time = buffer_time > 1000000 ? 1000000 : buffer_time;
+			break;
+		case 'p':
+			period_time = atoi(optarg);
+			period_time = period_time < 1000 ? 1000 : period_time;
+			period_time = period_time > 1000000 ? 1000000 : period_time;
+			break;
+		case 'm':
+			for (method = 0; transfer_methods[method].name; method++)
+					if (!strcasecmp(transfer_methods[method].name, optarg))
+					break;
+			if (transfer_methods[method].name == NULL)
+				method = 0;
+			break;
+		case 'o':
+			for (format = 0; format < SND_PCM_FORMAT_LAST; format++) {
+				const char *format_name = snd_pcm_format_name(format);
+				if (format_name)
+					if (!strcasecmp(format_name, optarg))
+					break;
+			}
+			if (format == SND_PCM_FORMAT_LAST)
+				format = SND_PCM_FORMAT_S16;
+			if (!snd_pcm_format_linear(format) &&
+			    !(format == SND_PCM_FORMAT_FLOAT_LE ||
+			      format == SND_PCM_FORMAT_FLOAT_BE)) {
+				printf("Invalid (non-linear/float) format %s\n",
+				       optarg);
+				return 1;
+			}
+			break;
+		case 'v':
+			verbose = 1;
+			break;
+		case 'n':
+			resample = 0;
+			break;
+		case 'e':
+			period_event = 1;
+			break;
+		}
+	}
+
+	if (morehelp) {
+		help();
+		return 0;
+	}
+
+	err = snd_output_stdio_attach(&output, stdout, 0);
+	if (err < 0) {
+		printf("Output failed: %s\n", snd_strerror(err));
+		return 0;
+	}
+
+	printf("Playback device is %s\n", device);
+	printf("Stream parameters are %iHz, %s, %i channels\n", rate, snd_pcm_format_name(format), channels);
+	printf("Sine wave rate is %.4fHz\n", freq);
+	printf("Using transfer method: %s\n", transfer_methods[method].name);
+
+	if ((err = snd_pcm_open(&handle, device, SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
+		printf("Playback open error: %s\n", snd_strerror(err));
+		return 0;
+	}
+	
+	if ((err = set_hwparams(handle, hwparams, transfer_methods[method].access)) < 0) {
+		printf("Setting of hwparams failed: %s\n", snd_strerror(err));
+		exit(EXIT_FAILURE);
+	}
+	if ((err = set_swparams(handle, swparams)) < 0) {
+		printf("Setting of swparams failed: %s\n", snd_strerror(err));
+		exit(EXIT_FAILURE);
+	}
+
+	if (verbose > 0)
+		snd_pcm_dump(handle, output);
+
+	samples = malloc((period_size * channels * snd_pcm_format_physical_width(format)) / 8);
+	if (samples == NULL) {
+		printf("No enough memory\n");
+		exit(EXIT_FAILURE);
+	}
+	
+	areas = calloc(channels, sizeof(snd_pcm_channel_area_t));
+	if (areas == NULL) {
+		printf("No enough memory\n");
+		exit(EXIT_FAILURE);
+	}
+	for (chn = 0; chn < channels; chn++) {
+		areas[chn].addr = samples;
+		areas[chn].first = chn * snd_pcm_format_physical_width(format);
+		areas[chn].step = channels * snd_pcm_format_physical_width(format);
+	}
+
+	err = transfer_methods[method].transfer_loop(handle, samples, areas);
+	if (err < 0)
+		printf("Transfer failed: %s\n", snd_strerror(err));
+
+	free(areas);
+	free(samples);
+	snd_pcm_close(handle);
+	return 0;
+}
+
diff --git a/test/pcm_min.c b/test/pcm_min.c
new file mode 100644
index 0000000..e971405
--- /dev/null
+++ b/test/pcm_min.c
@@ -0,0 +1,51 @@
+/*
+ *  This extra small demo sends a random samples to your speakers.
+ */
+
+#include "../include/asoundlib.h"
+
+static char *device = "default";			/* playback device */
+
+snd_output_t *output = NULL;
+unsigned char buffer[16*1024];				/* some random data */
+
+int main(void)
+{
+        int err;
+        unsigned int i;
+        snd_pcm_t *handle;
+        snd_pcm_sframes_t frames;
+
+        for (i = 0; i < sizeof(buffer); i++)
+                buffer[i] = random() & 0xff;
+
+	if ((err = snd_pcm_open(&handle, device, SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
+		printf("Playback open error: %s\n", snd_strerror(err));
+		exit(EXIT_FAILURE);
+	}
+	if ((err = snd_pcm_set_params(handle,
+	                              SND_PCM_FORMAT_U8,
+	                              SND_PCM_ACCESS_RW_INTERLEAVED,
+	                              1,
+	                              48000,
+	                              1,
+	                              500000)) < 0) {	/* 0.5sec */
+		printf("Playback open error: %s\n", snd_strerror(err));
+		exit(EXIT_FAILURE);
+	}
+
+        for (i = 0; i < 16; i++) {
+                frames = snd_pcm_writei(handle, buffer, sizeof(buffer));
+                if (frames < 0)
+                        frames = snd_pcm_recover(handle, frames, 0);
+                if (frames < 0) {
+                        printf("snd_pcm_writei failed: %s\n", snd_strerror(err));
+                        break;
+                }
+                if (frames > 0 && frames < (long)sizeof(buffer))
+                        printf("Short write (expected %li, wrote %li)\n", (long)sizeof(buffer), frames);
+        }
+
+	snd_pcm_close(handle);
+	return 0;
+}
diff --git a/test/playmidi1.c b/test/playmidi1.c
new file mode 100644
index 0000000..a7f3a63
--- /dev/null
+++ b/test/playmidi1.c
@@ -0,0 +1,617 @@
+/*
+ *   MIDI file player for ALSA sequencer 
+ *   (type 0 only!, the library that is used doesn't support merging of tracks)
+ *
+ *   Copyright (c) 1998 by Frank van de Pol <F.K.W.van.de.Pol@inter.nl.net>
+ *
+ *   Modified so that this uses alsa-lib
+ *   1999 Jan. by Isaku Yamahata <yamahata@kusm.kyoto-u.ac.jp>
+ *
+ *   19990604	Takashi Iwai <iwai@ww.uni-erlangen.de>
+ *	- use blocking mode
+ *	- fix tempo event bug
+ *	- add command line options
+ *
+ *   19990827	Takashi Iwai <iwai@ww.uni-erlangen.de>
+ *	- use snd_seq_alloc_queue()
+ *
+ *   19990916	Takashi Iwai <iwai@ww.uni-erlangen.de>
+ *	- use middle-level sequencer routines and macros
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+
+#include "midifile.h"		/* SMF library header */
+#include "midifile.c"		/* SMF library code */
+
+#include "../include/asoundlib.h"
+
+/* send the real-time time stamps (instead of midi ticks) to the ALSA sequencer */
+static int use_realtime = 0;
+
+/* control the event buffering by using a blocking mode */
+static int use_blocking_mode = 1;
+
+/* default destination queue, client and port numbers */
+#define DEST_CLIENT_NUMBER	65
+#define DEST_PORT_NUMBER	0
+
+/* event pool size */
+#define WRITE_POOL_SIZE		200
+#define WRITE_POOL_SPACE	10
+#define READ_POOL_SIZE		10	/* we need to read the pool only for echoing */
+
+static FILE *F;
+static snd_seq_t *seq_handle = NULL;
+static int ppq = 96;
+static int slave_ppq = 96;
+
+static double local_secs = 0;
+static int local_ticks = 0;
+static int local_tempo = 500000;
+
+static int dest_queue = -1;
+static int shared_queue = 0;
+static int tick_offset = 0;
+static int dest_client = DEST_CLIENT_NUMBER;
+static int dest_port = DEST_PORT_NUMBER;
+static int my_port = 0;
+
+static int verbose = 0;
+static int slave   = 0;		/* allow external sync */
+
+#define VERB_INFO	1
+#define VERB_MUCH	2
+#define VERB_EVENT	3
+
+static void alsa_start_timer(void);
+static void alsa_stop_timer(void);
+static void wait_start(void);
+
+
+static inline double tick2time_dbl(int tick)
+{
+	return local_secs + ((double) (tick - local_ticks) * (double) local_tempo * 1.0E-6 / (double) ppq);
+}
+
+static void tick2time(snd_seq_real_time_t * tm, int tick)
+{
+	double secs = tick2time_dbl(tick);
+	tm->tv_sec = secs;
+	tm->tv_nsec = (secs - tm->tv_sec) * 1.0E9;
+}
+
+static void write_ev(snd_seq_event_t *ev)
+{
+	int rc;
+
+	if (use_blocking_mode) {
+		rc = snd_seq_event_output(seq_handle, ev);
+		if (rc < 0) {
+			printf("written = %i (%s)\n", rc, snd_strerror(rc));
+			exit(1);
+		}
+		return;
+	}
+	while ((rc = snd_seq_event_output(seq_handle, ev)) < 0) {
+		int npfds = snd_seq_poll_descriptors_count(seq_handle, POLLOUT);
+		struct pollfd *pfds = alloca(sizeof(*pfds) * npfds);
+		snd_seq_poll_descriptors(seq_handle, pfds, npfds, POLLOUT);
+		if ((rc = poll(pfds, npfds, -1)) < 0) {
+			printf("poll error = %i (%s)\n", rc, snd_strerror(errno));
+			exit(1);
+		}
+	}
+}
+
+/* read the byte */
+static int mygetc(void)
+{
+	return getc(F);
+}
+
+/* print out the text */
+static void mytext(int type ATTRIBUTE_UNUSED, int leng, char *msg)
+{
+	char *p;
+	char *ep = msg + leng;
+
+	if (verbose >= VERB_INFO) {
+		for (p = msg; p < ep; p++)
+			putchar(isprint(*p) ? *p : '?');
+		putchar('\n');
+	}
+}
+
+static void do_header(int format, int ntracks, int division)
+{
+	snd_seq_queue_tempo_t *tempo;
+
+	if (verbose >= VERB_INFO)
+		printf("smf format %d, %d tracks, %d ppq\n", format, ntracks, division);
+	ppq = division;
+
+	if (format != 0 || ntracks != 1) {
+		printf("This player does not support merging of tracks.\n");
+		if (! shared_queue)
+			alsa_stop_timer();
+		exit(1);
+	}
+	/* set the ppq */
+	snd_seq_queue_tempo_alloca(&tempo);
+	/* ppq must be set before starting the timer */
+	if (snd_seq_get_queue_tempo(seq_handle, dest_queue, tempo) < 0) {
+    		perror("get_queue_tempo");
+    		exit(1);
+	}
+	if ((slave_ppq = snd_seq_queue_tempo_get_ppq(tempo)) != ppq) {
+		snd_seq_queue_tempo_set_ppq(tempo, ppq);
+		if (snd_seq_set_queue_tempo(seq_handle, dest_queue, tempo) < 0) {
+    			perror("set_queue_tempo");
+    			if (!slave && !shared_queue)
+    				exit(1);
+			else
+				printf("different PPQ %d in SMF from queue PPQ %d\n", ppq, slave_ppq);
+		} else
+			slave_ppq = ppq;
+		if (verbose >= VERB_INFO)
+			printf("ALSA Timer updated, PPQ = %d\n", snd_seq_queue_tempo_get_ppq(tempo));
+	}
+
+	/* start playing... */
+	if (slave) {
+		if (verbose >= VERB_INFO)
+			printf("Wait till timer starts...\n");	
+		wait_start();
+		if (verbose >= VERB_INFO)
+			printf("Go!\n");	
+	} else if (shared_queue) {
+		snd_seq_queue_status_t *stat;
+		snd_seq_queue_status_alloca(&stat);
+		snd_seq_get_queue_status(seq_handle, dest_queue, stat);
+		tick_offset = snd_seq_queue_status_get_tick_time(stat);
+		fprintf(stderr, "tick offset = %d\n", tick_offset);
+	} else {
+		alsa_start_timer();
+		tick_offset = 0;
+	}
+}
+
+/* fill the event time */
+static void set_event_time(snd_seq_event_t *ev, unsigned int currtime)
+{
+	if (use_realtime) {
+		snd_seq_real_time_t rtime;
+		if (ppq != slave_ppq)
+			currtime = (currtime * slave_ppq) / ppq;
+		tick2time(&rtime, currtime);
+		snd_seq_ev_schedule_real(ev, dest_queue, 0, &rtime);
+	} else {
+		if (ppq != slave_ppq)
+			currtime = (currtime * slave_ppq) / ppq;
+		currtime += tick_offset;
+		snd_seq_ev_schedule_tick(ev, dest_queue, 0, currtime);
+	}
+}
+
+/* fill the normal event header */
+static void set_event_header(snd_seq_event_t *ev)
+{
+	snd_seq_ev_clear(ev);
+	snd_seq_ev_set_dest(ev, dest_client, dest_port);
+	snd_seq_ev_set_source(ev, my_port);
+	set_event_time(ev, Mf_currtime);
+}
+
+/* start the timer */
+static void alsa_start_timer(void)
+{
+	snd_seq_start_queue(seq_handle, dest_queue, NULL);
+}
+
+/* stop the timer */
+static void alsa_stop_timer(void)
+{
+	snd_seq_event_t ev;
+	set_event_header(&ev);
+	snd_seq_stop_queue(seq_handle, dest_queue, &ev);
+}
+
+/* change the tempo */
+static void do_tempo(int us)
+{
+	snd_seq_event_t ev;
+
+	if (verbose >= VERB_MUCH) {
+		double bpm;
+		bpm = 60.0E6 / (double) us;
+		printf("Tempo %d us/beat, %.2f bpm\n", us, bpm);
+	}
+
+	/* store the new tempo and timestamp of the tempo change */
+	local_secs = tick2time_dbl(Mf_currtime);
+	local_ticks = Mf_currtime;
+	local_tempo = us;
+
+	set_event_header(&ev);
+	if (!slave)
+		snd_seq_change_queue_tempo(seq_handle, dest_queue, us, &ev);
+}
+
+static void do_noteon(int chan, int pitch, int vol)
+{
+	snd_seq_event_t ev;
+
+	if (verbose >= VERB_EVENT)
+		printf("%ld: NoteOn (%d) %d %d\n", Mf_currtime, chan, pitch, vol);
+	set_event_header(&ev);
+	snd_seq_ev_set_noteon(&ev, chan, pitch, vol);
+	write_ev(&ev);
+}
+
+
+static void do_noteoff(int chan, int pitch, int vol)
+{
+	snd_seq_event_t ev;
+
+	if (verbose >= VERB_EVENT)
+		printf("%ld: NoteOff (%d) %d %d\n", Mf_currtime, chan, pitch, vol);
+	set_event_header(&ev);
+	snd_seq_ev_set_noteoff(&ev, chan, pitch, vol);
+	write_ev(&ev);
+}
+
+
+static void do_program(int chan, int program)
+{
+	snd_seq_event_t ev;
+
+	if (verbose >= VERB_EVENT)
+		printf("%ld: Program (%d) %d\n", Mf_currtime, chan, program);
+	set_event_header(&ev);
+	snd_seq_ev_set_pgmchange(&ev, chan, program);
+	write_ev(&ev);
+}
+
+
+static void do_parameter(int chan, int control, int value)
+{
+	snd_seq_event_t ev;
+
+	if (verbose >= VERB_EVENT)
+		printf("%ld: Control (%d) %d %d\n", Mf_currtime, chan, control, value);
+	set_event_header(&ev);
+	snd_seq_ev_set_controller(&ev, chan, control, value);
+	write_ev(&ev);
+}
+
+
+static void do_pitchbend(int chan, int lsb, int msb)
+{	/* !@#$% lsb & msb are in the wrong order in docs */
+	snd_seq_event_t ev;
+
+	if (verbose >= VERB_EVENT)
+		printf("%ld: Pitchbend (%d) %d %d\n", Mf_currtime, chan, lsb, msb);
+	set_event_header(&ev);
+	snd_seq_ev_set_pitchbend(&ev, chan, (lsb + (msb << 7)) - 8192);
+	write_ev(&ev);
+}
+
+static void do_pressure(int chan, int pitch, int pressure)
+{
+	snd_seq_event_t ev;
+
+	if (verbose >= VERB_EVENT)
+		printf("%ld: KeyPress (%d) %d %d\n", Mf_currtime, chan, pitch, pressure);
+	set_event_header(&ev);
+	snd_seq_ev_set_keypress(&ev, chan, pitch, pressure);
+	write_ev(&ev);
+}
+
+static void do_chanpressure(int chan, int pressure)
+{
+	snd_seq_event_t ev;
+
+	if (verbose >= VERB_EVENT)
+		printf("%ld: ChanPress (%d) %d\n", Mf_currtime, chan, pressure);
+	set_event_header(&ev);
+	snd_seq_ev_set_chanpress(&ev, chan, pressure);
+	write_ev(&ev);
+}
+
+static void do_sysex(int len, char *msg)
+{
+	snd_seq_event_t ev;
+
+	if (verbose >= VERB_MUCH) {
+		int c;
+		printf("%ld: Sysex, len=%d\n", Mf_currtime, len);
+		for (c = 0; c < len; c++) {
+			printf(" %02x", (unsigned char)msg[c]);
+			if (c % 16 == 15)
+				putchar('\n');
+		}
+		if (c % 16 != 15)
+			putchar('\n');
+	}
+
+	set_event_header(&ev);
+	snd_seq_ev_set_sysex(&ev, len, msg);
+	write_ev(&ev);
+}
+
+static snd_seq_event_t *wait_for_event(void)
+{
+	int left;
+	snd_seq_event_t *input_event;
+  
+	if (use_blocking_mode) {
+		/* read the event - blocked until any event is read */
+		left = snd_seq_event_input(seq_handle, &input_event);
+	} else {
+		/* read the event - using select syscall */
+		while ((left = snd_seq_event_input(seq_handle, &input_event)) >= 0 &&
+		       input_event == NULL) {
+			int npfds = snd_seq_poll_descriptors_count(seq_handle, POLLIN);
+			struct pollfd *pfds = alloca(sizeof(*pfds) * npfds);
+			snd_seq_poll_descriptors(seq_handle, pfds, npfds, POLLIN);
+			if ((left = poll(pfds, npfds, -1)) < 0) {
+				printf("poll error = %i (%s)\n", errno, snd_strerror(errno));
+				exit(1);
+			}
+		}
+	}
+
+	if (left < 0) {
+		printf("alsa_sync error!:%s\n", snd_strerror(left));
+		return NULL;
+	}
+
+	return input_event;
+}
+
+/* synchronize to the end of the event */
+static void alsa_sync(void)
+{
+	/* send the echo event to the self client. */
+	if (verbose >= VERB_MUCH)
+		printf("alsa_sync syncing...\n");
+	/* dump the buffer */
+	snd_seq_drain_output(seq_handle);
+	snd_seq_sync_output_queue(seq_handle);
+	if (verbose >= VERB_MUCH)
+		printf("alsa_sync synced\n");
+	sleep(1); /* give a time for note releasing.. */
+}
+
+
+/* wait for the start of the queue */
+static void wait_start(void)
+{
+	snd_seq_event_t *input_event;
+
+	/* wait for the start event from the system timer */
+	for (;;) {
+		input_event = wait_for_event();
+		if (input_event) {
+			if (verbose >= VERB_MUCH)
+				printf("wait_start got event. type=%d, flags=%d\n",
+				       input_event->type, input_event->flags);
+			if (input_event->type == SND_SEQ_EVENT_START &&
+			    input_event->data.queue.queue == dest_queue) {
+				snd_seq_free_event(input_event);
+				break;
+			}
+			snd_seq_free_event(input_event);
+		}
+	}
+	if (verbose >= VERB_MUCH)
+		printf("start received\n");
+}
+
+
+/* print the usage */
+static void usage(void)
+{
+	fprintf(stderr, "usage: playmidi1 [options] [file]\n");
+	fprintf(stderr, "  options:\n");
+	fprintf(stderr, "  -v: verbose mode\n");
+	fprintf(stderr, "  -a client:port : set destination address (default=%d:%d)\n",
+		DEST_CLIENT_NUMBER, DEST_PORT_NUMBER);
+	fprintf(stderr, "  -q queue: use the specified queue\n");
+	fprintf(stderr, "  -s queue: slave mode (allow external clock synchronization)\n");
+	fprintf(stderr, "  -r : play on real-time mode\n");
+	fprintf(stderr, "  -b : play on non-blocking mode\n");
+}
+
+int main(int argc, char *argv[])
+{
+	int tmp;
+	int c;
+	snd_seq_addr_t dest_addr;
+	const char *addr = "65:0";
+
+	while ((c = getopt(argc, argv, "s:a:p:q:vrb")) != -1) {
+		switch (c) {
+		case 'v':
+			verbose++;
+			break;
+		case 'a':
+		case 'p':
+			addr = optarg;
+			break;
+		case 'q':
+			dest_queue = atoi(optarg);
+			if (dest_queue < 0) {
+				fprintf(stderr, "invalid queue number %d\n", dest_queue);
+				exit(1);
+			}
+			break;
+		case 's':
+			slave = 1;
+			dest_queue = atoi(optarg);
+			if (dest_queue < 0) {
+				fprintf(stderr, "invalid queue number %d\n", dest_queue);
+				exit(1);
+			}
+			break;
+		case 'r':
+			use_realtime = 1;
+			break;
+		case 'b':
+			use_blocking_mode = 0;
+			break;
+		default:
+			usage();
+			exit(1);
+		}
+	}
+
+	if (verbose >= VERB_INFO) {
+		if (use_realtime)
+			printf("ALSA MIDI Player, feeding events to real-time queue\n");
+		else
+			printf("ALSA MIDI Player, feeding events to song queue\n");
+	}
+
+	/* open the sequencer device */
+	/* Here we open the device in read/write for slave mode. */
+	tmp = snd_seq_open(&seq_handle, "hw", slave ? SND_SEQ_OPEN_DUPLEX : SND_SEQ_OPEN_OUTPUT, 0);
+	if (tmp < 0) {
+		perror("open /dev/snd/seq");
+		exit(1);
+	}
+	
+	tmp = snd_seq_nonblock(seq_handle, !use_blocking_mode);
+	if (tmp < 0) {
+		perror("block_mode");
+		exit(1);
+	}
+			
+	/* set the name */
+	/* set the event filter to receive only the echo event */
+	/* if running in slave mode, also listen for a START event */
+	if (slave)
+		snd_seq_set_client_event_filter(seq_handle, SND_SEQ_EVENT_START);
+	snd_seq_set_client_name(seq_handle, "MIDI file player");
+
+	/* create the port */
+	my_port = snd_seq_create_simple_port(seq_handle, "Port 0",
+					     SND_SEQ_PORT_CAP_WRITE |
+					     SND_SEQ_PORT_CAP_READ,
+					     SND_SEQ_PORT_TYPE_MIDI_GENERIC);
+	if (my_port < 0) {
+		perror("create port");
+		exit(1);
+	}
+	
+	if (snd_seq_parse_address(seq_handle, &dest_addr, addr) < 0) {
+		perror("invalid destination address");
+		exit(1);
+	}
+	dest_client = dest_addr.client;
+	dest_port = dest_addr.port;
+
+	/* set the queue */
+	if (dest_queue >= 0) {
+		shared_queue = 1;
+		if (snd_seq_set_queue_usage(seq_handle, dest_queue, 1) < 0) {
+			perror("use queue");
+			exit(1);
+		}
+	} else {
+		shared_queue = 0;
+		dest_queue = snd_seq_alloc_queue(seq_handle);
+		if (dest_queue < 0) {
+			perror("alloc queue");
+			exit(1);
+		}
+	}
+
+	/* set the subscriber */
+	tmp = snd_seq_connect_to(seq_handle, my_port, dest_client, dest_port);
+	if (tmp < 0) {
+		perror("subscribe");
+		exit(1);
+	}
+
+	/* subscribe for the timer START event */	
+	if (slave) {	
+		tmp = snd_seq_connect_from(seq_handle, my_port,
+					   SND_SEQ_CLIENT_SYSTEM,
+					   dest_queue + 16 /*snd_seq_queue_sync_port(dest_queue)*/);
+		if (tmp < 0) {
+			perror("subscribe");
+			exit(1);
+		}	
+	}
+	
+	/* change the pool size */
+	if (snd_seq_set_client_pool_output(seq_handle, WRITE_POOL_SIZE) < 0 ||
+	    snd_seq_set_client_pool_input(seq_handle, READ_POOL_SIZE) < 0 ||
+	    snd_seq_set_client_pool_output_room(seq_handle, WRITE_POOL_SPACE) < 0) {
+		perror("pool");
+		exit(1);
+	}
+	
+	if (optind < argc) {
+		F = fopen(argv[optind], "r");
+		if (F == NULL) {
+			fprintf(stderr, "playmidi1: can't open file %s\n", argv[optind]);
+			exit(1);
+		}
+	} else
+		F = stdin;
+
+	Mf_header = do_header;
+	Mf_tempo = do_tempo;
+	Mf_getc = mygetc;
+	Mf_text = mytext;
+
+	Mf_noteon = do_noteon;
+	Mf_noteoff = do_noteoff;
+	Mf_program = do_program;
+	Mf_parameter = do_parameter;
+	Mf_pitchbend = do_pitchbend;
+	Mf_pressure = do_pressure;
+	Mf_chanpressure = do_chanpressure;
+	Mf_sysex = do_sysex;
+
+	/* go.. go.. go.. */
+	mfread();
+
+	alsa_sync();
+	if (! shared_queue)
+		alsa_stop_timer();
+
+	snd_seq_close(seq_handle);
+
+	if (verbose >= VERB_INFO) {
+		printf("Stopping at %f s,  tick %f\n",
+		       tick2time_dbl(Mf_currtime + 1), (double) (Mf_currtime + 1));
+	}
+
+	exit(0);
+}
diff --git a/test/queue_timer.c b/test/queue_timer.c
new file mode 100644
index 0000000..c4ffb19
--- /dev/null
+++ b/test/queue_timer.c
@@ -0,0 +1,128 @@
+#include <unistd.h>
+#include <sys/time.h>
+#include <alsa/asoundlib.h>
+#include <alsa/seq.h>
+
+void normalize(struct timeval *tv)
+{
+    if (tv->tv_sec == 0) {
+	while (tv->tv_usec <= -1000000) { tv->tv_usec += 1000000; --tv->tv_sec; }
+	while (tv->tv_usec >=  1000000) { tv->tv_usec -= 1000000; ++tv->tv_sec; }
+    } else if (tv->tv_sec < 0) {
+	while (tv->tv_usec <= -1000000) { tv->tv_usec += 1000000; --tv->tv_sec; }
+	while (tv->tv_usec > 0) { tv->tv_usec -= 1000000; ++tv->tv_sec; }
+    } else { 
+	while (tv->tv_usec >= 1000000) { tv->tv_usec -= 1000000; ++tv->tv_sec; }
+	while (tv->tv_usec < 0) { tv->tv_usec += 1000000; --tv->tv_sec; }
+    }
+}
+
+int
+main(int argc ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED)
+{
+    snd_seq_t *handle;
+    int portid;
+    /* int npfd;
+       struct pollfd *pfd;
+    */
+    int queue;
+    /* int i;
+       int rval;'
+    */
+    struct timeval starttv, prevdiff;
+    int countdown = -1;
+    /* snd_seq_queue_timer_t *timer;
+       snd_timer_id_t *timerid;
+    */
+
+    if (snd_seq_open(&handle, "hw", SND_SEQ_OPEN_DUPLEX, 0) < 0) {
+	fprintf(stderr, "failed to open ALSA sequencer interface\n");
+	return 1;
+    }
+
+    snd_seq_set_client_name(handle, "generator");
+
+    if ((portid = snd_seq_create_simple_port
+	 (handle, "generator",
+	  SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ, 0)) < 0) {
+	fprintf(stderr, "failed to create ALSA sequencer port\n");
+	return 1;
+    }
+
+    if ((queue = snd_seq_alloc_queue(handle)) < 0) {
+	fprintf(stderr, "failed to create ALSA sequencer queue\n");
+	return 1;
+    }
+/*
+    snd_seq_queue_timer_alloca(&timer);
+    snd_seq_get_queue_timer(handle, queue, timer);
+    snd_timer_id_alloca(&timerid);
+    snd_timer_id_set_class(timerid, SND_TIMER_CLASS_PCM);
+    snd_timer_id_set_sclass(timerid, SND_TIMER_SCLASS_NONE);
+    snd_timer_id_set_card(timerid, 0);
+    snd_timer_id_set_device(timerid, 0);
+    snd_timer_id_set_subdevice(timerid, 0);
+    snd_seq_queue_timer_set_id(timer, timerid);
+    snd_seq_set_queue_timer(handle, queue, timer);
+*/
+    snd_seq_start_queue(handle, queue, 0);
+    snd_seq_drain_output(handle);
+
+    gettimeofday(&starttv, 0);
+    prevdiff.tv_sec = 0;
+    prevdiff.tv_usec = 0;
+
+    while (countdown != 0) {
+
+	snd_seq_queue_status_t *status;
+	const snd_seq_real_time_t *rtime;
+	struct timeval tv, diff, diffdiff;
+	struct timespec ts;
+
+	snd_seq_queue_status_alloca(&status);
+
+	snd_seq_get_queue_status(handle, queue, status);
+	rtime = snd_seq_queue_status_get_real_time(status);
+
+	gettimeofday(&tv, 0);
+
+	tv.tv_sec -= starttv.tv_sec;
+	tv.tv_usec -= starttv.tv_usec;
+	normalize(&tv);
+
+	diff.tv_sec = tv.tv_sec - rtime->tv_sec;
+	diff.tv_usec = tv.tv_usec - rtime->tv_nsec / 1000;
+	normalize(&diff);
+
+	diffdiff.tv_sec = diff.tv_sec - prevdiff.tv_sec;
+	diffdiff.tv_usec = diff.tv_usec - prevdiff.tv_usec;
+	normalize(&diffdiff);
+	prevdiff = diff;
+
+	fprintf(stderr, " real time: %12ld sec %8ld usec\nqueue time: %12ld sec %8ld usec\n      diff: %12ld sec %8ld usec\n  diffdiff: %12ld sec %8ld usec\n",
+		tv.tv_sec, tv.tv_usec,
+		(long)rtime->tv_sec, (long)rtime->tv_nsec / 1000,
+		diff.tv_sec, diff.tv_usec,
+		(long)diffdiff.tv_sec, (long)diffdiff.tv_usec);
+
+	if (diffdiff.tv_usec >  5000 ||
+	    diffdiff.tv_usec < -5000) {
+	    fprintf(stderr, "oops! queue slipped\n");
+	    if (tv.tv_sec < 5) {
+		fprintf(stderr, "(ignoring in first few seconds)\n");
+	    } else {
+		countdown = 2;
+	    }
+	} else {
+	    if (countdown > 0) --countdown;
+	}
+
+	fprintf(stderr, "\n");
+//	sleep(1);
+	ts.tv_sec = 0;
+	ts.tv_nsec = 500000000;
+	nanosleep(&ts, 0);
+    }
+    return EXIT_SUCCESS;
+}
+
diff --git a/test/rawmidi.c b/test/rawmidi.c
new file mode 100644
index 0000000..67f585b
--- /dev/null
+++ b/test/rawmidi.c
@@ -0,0 +1,241 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include "../include/asoundlib.h"
+#include <signal.h>
+
+static void usage(void)
+{
+	fprintf(stderr, "usage: rawmidi [options]\n");
+	fprintf(stderr, "  options:\n");
+	fprintf(stderr, "    -v: verbose mode\n");
+	fprintf(stderr, "    -i device-id : test ALSA input device\n");
+	fprintf(stderr, "    -o device-id : test ALSA output device\n");
+	fprintf(stderr, "    -I node      : test input node\n");
+	fprintf(stderr, "    -O node      : test output node\n");
+	fprintf(stderr, "    -t: test midi thru\n");
+	fprintf(stderr, "  example:\n");
+	fprintf(stderr, "    rawmidi -i hw:0,0 -O /dev/midi1\n");
+	fprintf(stderr, "    tests input for card 0, device 0, using snd_rawmidi API\n");
+	fprintf(stderr, "    and /dev/midi1 using file descriptors\n");
+}
+
+int stop=0;
+
+void sighandler(int dum)
+{
+	stop=1;
+}
+
+int main(int argc,char** argv)
+{
+	int i;
+	int err;
+	int thru=0;
+	int verbose = 0;
+	char *device_in = NULL;
+	char *device_out = NULL;
+	char *node_in = NULL;
+	char *node_out = NULL;
+	
+	int fd_in = -1,fd_out = -1;
+	snd_rawmidi_t *handle_in = 0,*handle_out = 0;
+	
+	if (argc==1) {
+		usage();
+		exit(0);
+	}
+	
+	for (i = 1 ; i<argc ; i++) {
+		if (argv[i][0]=='-') {
+			switch (argv[i][1]) {
+				case 'h':
+					usage();
+					break;
+				case 'v':
+					verbose = 1;
+					break;
+				case 't':
+					thru = 1;
+					break;
+				case 'i':
+					if (i + 1 < argc)
+						device_in = argv[++i];
+					break;
+				case 'I':
+					if (i + 1 < argc)
+						node_in = argv[++i];
+					break;
+				case 'o':
+					if (i + 1 < argc)
+						device_out = argv[++i];
+					break;
+				case 'O':
+					if (i + 1 < argc)
+						node_out = argv[++i];
+					break;
+			}			
+		}
+	}
+
+	if (verbose) {
+		fprintf(stderr,"Using: \n");
+		fprintf(stderr,"Input: ");
+		if (device_in) {
+			fprintf(stderr,"device %s\n",device_in);
+		}else if (node_in){
+			fprintf(stderr,"%s\n",node_in);	
+		}else{
+			fprintf(stderr,"NONE\n");
+		}
+		fprintf(stderr,"Output: ");
+		if (device_out) {
+			fprintf(stderr,"device %s\n",device_out);
+		}else if (node_out){
+			fprintf(stderr,"%s\n",node_out);		
+		}else{
+			fprintf(stderr,"NONE\n");
+		}
+	}
+	
+	if (device_in) {
+		err = snd_rawmidi_open(&handle_in,NULL,device_in,0);	
+		if (err) {
+			fprintf(stderr,"snd_rawmidi_open %s failed: %d\n",device_in,err);
+		}
+	}
+	if (node_in && (!node_out || strcmp(node_out,node_in))) {
+		fd_in = open(node_in,O_RDONLY);
+		if (fd_in<0) {
+			fprintf(stderr,"open %s for input failed\n",node_in);
+		}	
+	}
+
+	signal(SIGINT,sighandler);
+
+	if (device_out) {
+		err = snd_rawmidi_open(NULL,&handle_out,device_out,0);
+		if (err) {
+			fprintf(stderr,"snd_rawmidi_open %s failed: %d\n",device_out,err);
+		}
+	}
+	if (node_out && (!node_in || strcmp(node_out,node_in))) {
+		fd_out = open(node_out,O_WRONLY);		
+		if (fd_out<0) {
+			fprintf(stderr,"open %s for output failed\n",node_out);
+		}	
+	}
+
+	if (node_in && node_out && strcmp(node_out,node_in)==0) {
+		fd_in = fd_out = open(node_out,O_RDWR);		
+		if (fd_out<0) {
+			fprintf(stderr,"open %s for input and output failed\n",node_out);
+		}		
+	}
+
+
+
+	if (!thru) {
+		if (handle_in || fd_in!=-1) {
+			fprintf(stderr,"Read midi in\n");
+			fprintf(stderr,"Press ctrl-c to stop\n");
+		}
+
+		if (handle_in) {
+			unsigned char ch;
+			while (!stop) {
+				snd_rawmidi_read(handle_in,&ch,1);
+				if (verbose) {
+					fprintf(stderr,"read %02x\n",ch);
+				}
+			}
+		}
+		if (fd_in!=-1) {
+			unsigned char ch;
+			while (!stop) {
+				read(fd_in,&ch,1);
+				if (verbose) {
+					fprintf(stderr,"read %02x\n",ch);
+				}
+			}	
+		}
+
+		if (handle_out || fd_out!=-1) {
+			fprintf(stderr,"Writing note on / note off\n");
+		}
+
+		if (handle_out) {
+			unsigned char ch;
+			ch=0x90; snd_rawmidi_write(handle_out,&ch,1);
+			ch=60;   snd_rawmidi_write(handle_out,&ch,1);
+			ch=100;  snd_rawmidi_write(handle_out,&ch,1);
+			snd_rawmidi_drain(handle_out);
+			sleep(1);
+			ch=0x90; snd_rawmidi_write(handle_out,&ch,1);
+			ch=60;   snd_rawmidi_write(handle_out,&ch,1);
+			ch=0;    snd_rawmidi_write(handle_out,&ch,1);
+			snd_rawmidi_drain(handle_out); 
+		}
+		if (fd_out!=-1) {
+			unsigned char ch;
+			ch=0x90; write(fd_out,&ch,1);
+			ch=60;   write(fd_out,&ch,1);
+			ch=100;  write(fd_out,&ch,1);
+			sleep(1);
+			ch=0x90; write(fd_out,&ch,1);
+			ch=60;   write(fd_out,&ch,1);
+			ch=0;    write(fd_out,&ch,1);
+		}
+	} else {
+		if ((handle_in || fd_in!=-1) && (handle_out || fd_out!=-1)) {
+			if (verbose) {
+				fprintf(stderr,"Testing midi thru in\n");
+			}
+			while (!stop) {
+				unsigned char ch;
+			
+				if (handle_in) {
+					snd_rawmidi_read(handle_in,&ch,1);
+				}
+				if (fd_in!=-1) {
+					read(fd_in,&ch,1);
+				}	
+				if (verbose) {
+					fprintf(stderr,"thru: %02x\n",ch);
+				}
+
+				if (handle_out) {
+					snd_rawmidi_write(handle_out,&ch,1);
+					snd_rawmidi_drain(handle_out); 
+				}
+				if (fd_out!=-1) {
+					write(fd_out,&ch,1);
+				}
+			}
+		}else{
+				fprintf(stderr,"Testing midi thru needs both input and output\n");		
+				exit(-1);
+		}
+	}
+
+	if (verbose) {
+		fprintf(stderr,"Closing\n");
+	}
+	
+	if (handle_in) {
+		snd_rawmidi_drain(handle_in); 
+		snd_rawmidi_close(handle_in);	
+	}
+	if (handle_out) {
+		snd_rawmidi_drain(handle_out); 
+		snd_rawmidi_close(handle_out);	
+	}
+	if (fd_in!=-1) {
+		close(fd_in);
+	}
+	if (fd_out!=-1) {
+		close(fd_out);
+	}
+
+	return 0;
+}
diff --git a/test/seq-decoder.c b/test/seq-decoder.c
new file mode 100644
index 0000000..b110e98
--- /dev/null
+++ b/test/seq-decoder.c
@@ -0,0 +1,353 @@
+/*
+ *  Simple event decoder
+ */
+
+static char *event_names[256] = {
+	[SND_SEQ_EVENT_SYSTEM]=	"System",
+	[SND_SEQ_EVENT_RESULT]=	"Result",
+	[SND_SEQ_EVENT_NOTE]=	"Note",
+	[SND_SEQ_EVENT_NOTEON]=	"Note On",
+	[SND_SEQ_EVENT_NOTEOFF]=	"Note Off",
+	[SND_SEQ_EVENT_KEYPRESS]=	"Key Pressure",
+	[SND_SEQ_EVENT_CONTROLLER]=	"Controller",
+	[SND_SEQ_EVENT_PGMCHANGE]=	"Program Change",
+	[SND_SEQ_EVENT_CHANPRESS]=	"Channel Pressure",
+	[SND_SEQ_EVENT_PITCHBEND]=	"Pitchbend",
+	[SND_SEQ_EVENT_CONTROL14]=	"Control14",
+	[SND_SEQ_EVENT_NONREGPARAM]=	"Nonregparam",
+	[SND_SEQ_EVENT_REGPARAM]=		"Regparam",
+	[SND_SEQ_EVENT_SONGPOS]=	"Song Position",
+	[SND_SEQ_EVENT_SONGSEL]=	"Song Select",
+	[SND_SEQ_EVENT_QFRAME]=	"Qframe",
+	[SND_SEQ_EVENT_TIMESIGN]=	"SMF Time Signature",
+	[SND_SEQ_EVENT_KEYSIGN]=	"SMF Key Signature",
+	[SND_SEQ_EVENT_START]=	"Start",
+	[SND_SEQ_EVENT_CONTINUE]=	"Continue",
+	[SND_SEQ_EVENT_STOP]=	"Stop",
+	[SND_SEQ_EVENT_SETPOS_TICK]=	"Set Position Tick",
+	[SND_SEQ_EVENT_SETPOS_TIME]=	"Set Position Time",
+	[SND_SEQ_EVENT_TEMPO]=	"Tempo",
+	[SND_SEQ_EVENT_CLOCK]=	"Clock",
+	[SND_SEQ_EVENT_TICK]=	"Tick",
+	[SND_SEQ_EVENT_TUNE_REQUEST]=	"Tune Request",
+	[SND_SEQ_EVENT_RESET]=	"Reset",
+	[SND_SEQ_EVENT_SENSING]=	"Active Sensing",
+	[SND_SEQ_EVENT_ECHO]=	"Echo",
+	[SND_SEQ_EVENT_OSS]=	"OSS",
+	[SND_SEQ_EVENT_CLIENT_START]=	"Client Start",
+	[SND_SEQ_EVENT_CLIENT_EXIT]=	"Client Exit",
+	[SND_SEQ_EVENT_CLIENT_CHANGE]=	"Client Change",
+	[SND_SEQ_EVENT_PORT_START]=	"Port Start",
+	[SND_SEQ_EVENT_PORT_EXIT]=	"Port Exit",
+	[SND_SEQ_EVENT_PORT_CHANGE]=	"Port Change",
+	[SND_SEQ_EVENT_PORT_SUBSCRIBED]=	"Port Subscribed",
+	[SND_SEQ_EVENT_PORT_UNSUBSCRIBED]=	"Port Unsubscribed",
+#if 0
+	[SND_SEQ_EVENT_SAMPLE]=	"Sample",
+	[SND_SEQ_EVENT_SAMPLE_CLUSTER]=	"Sample Cluster",
+	[SND_SEQ_EVENT_SAMPLE_START]=	"Sample Start",
+	[SND_SEQ_EVENT_SAMPLE_STOP]=	"Sample Stop",
+	[SND_SEQ_EVENT_SAMPLE_FREQ]=	"Sample Freq",
+	[SND_SEQ_EVENT_SAMPLE_VOLUME]=	"Sample Volume",
+	[SND_SEQ_EVENT_SAMPLE_LOOP]=	"Sample Loop",
+	[SND_SEQ_EVENT_SAMPLE_POSITION]=	"Sample Position",
+	[SND_SEQ_EVENT_SAMPLE_PRIVATE1]=	"Sample Private1",
+#endif
+	[SND_SEQ_EVENT_USR0]=	"User 0",
+	[SND_SEQ_EVENT_USR1]=	"User 1",
+	[SND_SEQ_EVENT_USR2]=	"User 2",
+	[SND_SEQ_EVENT_USR3]=	"User 3",
+	[SND_SEQ_EVENT_USR4]=	"User 4",
+	[SND_SEQ_EVENT_USR5]=	"User 5",
+	[SND_SEQ_EVENT_USR6]=	"User 6",
+	[SND_SEQ_EVENT_USR7]=	"User 7",
+	[SND_SEQ_EVENT_USR8]=	"User 8",
+	[SND_SEQ_EVENT_USR9]=	"User 9",
+#if 0
+	[SND_SEQ_EVENT_INSTR_BEGIN]=	"Instr Begin",
+	[SND_SEQ_EVENT_INSTR_END]=	"Instr End",
+	[SND_SEQ_EVENT_INSTR_INFO]=	"Instr Info",
+	[SND_SEQ_EVENT_INSTR_INFO_RESULT]=	"Instr Info Result",
+	[SND_SEQ_EVENT_INSTR_FINFO]=	"Instr Font Info",
+	[SND_SEQ_EVENT_INSTR_FINFO_RESULT]=	"Instr Font Info Result",
+	[SND_SEQ_EVENT_INSTR_RESET]=	"Instr Reset",
+	[SND_SEQ_EVENT_INSTR_STATUS]=	"Instr Status",
+	[SND_SEQ_EVENT_INSTR_STATUS_RESULT]=	"Instr Status Result",
+	[SND_SEQ_EVENT_INSTR_PUT]=	"Instr Put",
+	[SND_SEQ_EVENT_INSTR_GET]=	"Instr Get",
+	[SND_SEQ_EVENT_INSTR_GET_RESULT]=	"Instr Get Result",
+	[SND_SEQ_EVENT_INSTR_FREE]=	"Instr Free",
+	[SND_SEQ_EVENT_INSTR_LIST]=	"Instr List",
+	[SND_SEQ_EVENT_INSTR_LIST_RESULT]=	"Instr List Result",
+	[SND_SEQ_EVENT_INSTR_CLUSTER]=	"Instr Cluster",
+	[SND_SEQ_EVENT_INSTR_CLUSTER_GET]=	"Instr Cluster Get",
+	[SND_SEQ_EVENT_INSTR_CLUSTER_RESULT]=	"Instr Cluster Result",
+	[SND_SEQ_EVENT_INSTR_CHANGE]=	"Instr Change",
+#endif
+	[SND_SEQ_EVENT_SYSEX]=	"Sysex",
+	[SND_SEQ_EVENT_BOUNCE]=	"Bounce",
+	[SND_SEQ_EVENT_USR_VAR0]=	"User Var0",
+	[SND_SEQ_EVENT_USR_VAR1]=	"User Var1",
+	[SND_SEQ_EVENT_USR_VAR2]=	"User Var2",
+	[SND_SEQ_EVENT_USR_VAR3]=	"User Var3",
+	[SND_SEQ_EVENT_USR_VAR4]=	"User Var4",
+#if 0
+	[SND_SEQ_EVENT_IPCSHM]=	"IPC Shm",
+	[SND_SEQ_EVENT_USR_VARIPC0]=	"User IPC0",
+	[SND_SEQ_EVENT_USR_VARIPC1]=	"User IPC1",
+	[SND_SEQ_EVENT_USR_VARIPC2]=	"User IPC2",
+	[SND_SEQ_EVENT_USR_VARIPC3]=	"User IPC3",
+	[SND_SEQ_EVENT_USR_VARIPC4]=	"User IPC4",
+#endif
+	[SND_SEQ_EVENT_NONE]=	"None",
+};
+
+int decode_event(snd_seq_event_t * ev)
+{
+	char *space = "         ";
+
+	printf("EVENT>>> Type = %d, flags = 0x%x", ev->type, ev->flags);
+	switch (ev->flags & SND_SEQ_TIME_STAMP_MASK) {
+	case SND_SEQ_TIME_STAMP_TICK:
+		printf(", time = %d ticks",
+		       ev->time.tick);
+		break;
+	case SND_SEQ_TIME_STAMP_REAL:
+		printf(", time = %d.%09d",
+		       (int)ev->time.time.tv_sec,
+		       (int)ev->time.time.tv_nsec);
+		break;
+	}
+	printf("\n%sSource = %d.%d, dest = %d.%d, queue = %d\n",
+	       space,
+	       ev->source.client,
+	       ev->source.port,
+	       ev->dest.client,
+	       ev->dest.port,
+	       ev->queue);
+
+	if (event_names[ev->type])
+		printf("%sEvent = %s", space, event_names[ev->type]);
+	else
+		printf("%sEvent = Reserved %d\n", space, ev->type);
+	/* decode the actual event data... */
+	switch (ev->type) {
+	case SND_SEQ_EVENT_NOTE:
+		printf("; ch=%d, note=%d, velocity=%d, off_velocity=%d, duration=%d\n",
+		       ev->data.note.channel,
+		       ev->data.note.note,
+		       ev->data.note.velocity,
+		       ev->data.note.off_velocity,
+		       ev->data.note.duration);
+		break;
+
+	case SND_SEQ_EVENT_NOTEON:
+	case SND_SEQ_EVENT_NOTEOFF:
+	case SND_SEQ_EVENT_KEYPRESS:
+		printf("; ch=%d, note=%d, velocity=%d\n",
+		       ev->data.note.channel,
+		       ev->data.note.note,
+		       ev->data.note.velocity);
+		break;
+		
+	case SND_SEQ_EVENT_CONTROLLER:
+		printf("; ch=%d, param=%i, value=%i\n",
+		       ev->data.control.channel,
+		       ev->data.control.param,
+		       ev->data.control.value);
+		break;
+
+	case SND_SEQ_EVENT_PGMCHANGE:
+		printf("; ch=%d, program=%i\n",
+		       ev->data.control.channel,
+		       ev->data.control.value);
+		break;
+			
+	case SND_SEQ_EVENT_CHANPRESS:
+	case SND_SEQ_EVENT_PITCHBEND:
+		printf("; ch=%d, value=%i\n",
+		       ev->data.control.channel,
+		       ev->data.control.value);
+		break;
+			
+	case SND_SEQ_EVENT_SYSEX:
+		{
+			unsigned char *sysex = (unsigned char *) ev + sizeof(snd_seq_event_t);
+			unsigned int c;
+			
+			printf("; len=%d [", ev->data.ext.len);
+			
+			for (c = 0; c < ev->data.ext.len; c++) {
+				printf("%02x%s", sysex[c], c < ev->data.ext.len - 1 ? ":" : "");
+			}
+			printf("]\n");
+		}
+		break;
+			
+	case SND_SEQ_EVENT_QFRAME:
+		printf("; frame=0x%02x\n", ev->data.control.value);
+		break;
+		
+	case SND_SEQ_EVENT_CLOCK:
+	case SND_SEQ_EVENT_START:
+	case SND_SEQ_EVENT_CONTINUE:
+	case SND_SEQ_EVENT_STOP:
+		printf("; queue = %i\n", ev->data.queue.queue);
+		break;
+
+	case SND_SEQ_EVENT_SENSING:
+		printf("\n");
+		break;
+
+	case SND_SEQ_EVENT_ECHO:
+		{
+			int i;
+				
+			printf("; ");
+			for (i = 0; i < 8; i++) {
+				printf("%02i%s", ev->data.raw8.d[i], i < 7 ? ":" : "\n");
+			}
+		}
+		break;
+			
+	case SND_SEQ_EVENT_CLIENT_START:
+	case SND_SEQ_EVENT_CLIENT_EXIT:
+	case SND_SEQ_EVENT_CLIENT_CHANGE:
+		printf("; client=%i\n", ev->data.addr.client);
+		break;
+
+	case SND_SEQ_EVENT_PORT_START:
+	case SND_SEQ_EVENT_PORT_EXIT:
+	case SND_SEQ_EVENT_PORT_CHANGE:
+		printf("; client=%i, port = %i\n", ev->data.addr.client, ev->data.addr.port);
+		break;
+
+	case SND_SEQ_EVENT_PORT_SUBSCRIBED:
+	case SND_SEQ_EVENT_PORT_UNSUBSCRIBED:
+		printf("; %i:%i -> %i:%i\n",
+		       ev->data.connect.sender.client, ev->data.connect.sender.port,
+		       ev->data.connect.dest.client, ev->data.connect.dest.port);
+		break;
+
+	default:
+		printf("; not implemented\n");
+	}
+
+
+	switch (ev->flags & SND_SEQ_EVENT_LENGTH_MASK) {
+	case SND_SEQ_EVENT_LENGTH_FIXED:
+		return sizeof(snd_seq_event_t);
+
+	case SND_SEQ_EVENT_LENGTH_VARIABLE:
+		return sizeof(snd_seq_event_t) + ev->data.ext.len;
+	}
+
+	return 0;
+}
+
+void event_decoder_start_timer(snd_seq_t *handle, int queue,
+			       int client ATTRIBUTE_UNUSED,
+			       int port ATTRIBUTE_UNUSED)
+{
+	int err;
+
+	if ((err = snd_seq_start_queue(handle, queue, NULL))<0)
+		fprintf(stderr, "Timer event output error: %s\n", snd_strerror(err));
+	while (snd_seq_drain_output(handle)>0)
+		sleep(1);
+}
+
+void event_decoder(snd_seq_t *handle, int argc, char *argv[])
+{
+	snd_seq_event_t *ev;
+	snd_seq_port_info_t *pinfo;
+	snd_seq_port_subscribe_t *sub;
+	snd_seq_addr_t addr;
+	int client, port, queue, max, err, v1, v2;
+	char *ptr;
+	struct pollfd *pfds;
+
+	if ((client = snd_seq_client_id(handle))<0) {
+		fprintf(stderr, "Cannot determine client number: %s\n", snd_strerror(client));
+		return;
+	}
+	printf("Client ID = %i\n", client);
+	if ((queue = snd_seq_alloc_queue(handle))<0) {
+		fprintf(stderr, "Cannot allocate queue: %s\n", snd_strerror(queue));
+		return;
+	}
+	printf("Queue ID = %i\n", queue);
+	if ((err = snd_seq_nonblock(handle, 1))<0)
+		fprintf(stderr, "Cannot set nonblock mode: %s\n", snd_strerror(err));
+	snd_seq_port_info_alloca(&pinfo);
+	snd_seq_port_info_set_name(pinfo, "Input");
+	snd_seq_port_info_set_type(pinfo, SND_SEQ_PORT_TYPE_MIDI_GENERIC);
+	snd_seq_port_info_set_capability(pinfo, SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_WRITE);
+	if ((err = snd_seq_create_port(handle, pinfo)) < 0) {
+		fprintf(stderr, "Cannot create input port: %s\n", snd_strerror(err));
+		return;
+	}
+	port = snd_seq_port_info_get_port(pinfo);
+	event_decoder_start_timer(handle, queue, client, port);
+
+	snd_seq_port_subscribe_alloca(&sub);
+	addr.client = SND_SEQ_CLIENT_SYSTEM;
+	addr.port = SND_SEQ_PORT_SYSTEM_ANNOUNCE;
+	snd_seq_port_subscribe_set_sender(sub, &addr);
+	addr.client = client;
+	addr.port = port;
+	snd_seq_port_subscribe_set_dest(sub, &addr);
+	snd_seq_port_subscribe_set_queue(sub, queue);
+	snd_seq_port_subscribe_set_time_update(sub, 1);
+	snd_seq_port_subscribe_set_time_real(sub, 1);
+	if ((err = snd_seq_subscribe_port(handle, sub))<0) {
+		fprintf(stderr, "Cannot subscribe announce port: %s\n", snd_strerror(err));
+		return;
+	}
+
+	addr.client = SND_SEQ_CLIENT_SYSTEM;
+	addr.port = SND_SEQ_PORT_SYSTEM_TIMER;
+	snd_seq_port_subscribe_set_sender(sub, &addr);
+	if ((err = snd_seq_subscribe_port(handle, sub))<0) {
+		fprintf(stderr, "Cannot subscribe timer port: %s\n", snd_strerror(err));
+		return;
+	}
+
+	for (max = 0; max < argc; max++) {
+		ptr = argv[max];
+		if (!ptr)
+			continue;
+		snd_seq_port_subscribe_set_time_real(sub, 0);
+		if (tolower(*ptr) == 'r') {
+			snd_seq_port_subscribe_set_time_real(sub, 1);
+			ptr++;
+		}
+		if (sscanf(ptr, "%i.%i", &v1, &v2) != 2) {
+			fprintf(stderr, "Wrong argument '%s'...\n", argv[max]);
+			return;
+		}
+		addr.client = v1;
+		addr.port = v2;
+		snd_seq_port_subscribe_set_sender(sub, &addr);
+		if ((err = snd_seq_subscribe_port(handle, sub))<0) {
+			fprintf(stderr, "Cannot subscribe port %i from client %i: %s\n", v2, v1, snd_strerror(err));
+			return;
+		}
+	}
+	
+	max = snd_seq_poll_descriptors_count(handle, POLLIN);
+	pfds = alloca(sizeof(*pfds) * max);
+	while (1) {
+		snd_seq_poll_descriptors(handle, pfds, max, POLLIN);
+		if (poll(pfds, max, -1) < 0)
+			break;
+		do {
+			if ((err = snd_seq_event_input(handle, &ev))<0)
+				break;
+			if (!ev)
+				continue;
+			decode_event(ev);
+			snd_seq_free_event(ev);
+		} while (err > 0);
+	}
+}
diff --git a/test/seq-sender.c b/test/seq-sender.c
new file mode 100644
index 0000000..5d8ac92
--- /dev/null
+++ b/test/seq-sender.c
@@ -0,0 +1,272 @@
+
+#ifdef USE_PCM // XXX not yet
+/*
+ *  PCM timer layer
+ */
+
+int pcard = 0;
+int pdevice = 0;
+int period_size = 1024;
+
+void set_hwparams(snd_pcm_t *phandle)
+{
+	int err;
+	snd_pcm_hw_params_t *params;
+
+	err = snd_output_stdio_attach(&log, stderr, 0);
+	if (err < 0) {
+		fprintf(stderr, "cannot attach output stdio\n");
+		exit(0);
+	}
+
+	snd_pcm_hw_params_alloca(&params);
+	err = snd_pcm_hw_params_any(phandle, params);
+	if (err < 0) {
+		fprintf(stderr, "Broken configuration for this PCM: no configurations available\n");
+		exit(0);
+	}
+
+	err = snd_pcm_hw_params_set_access(phandle, params,
+					   SND_PCM_ACCESS_RW_INTERLEAVED);
+	if (err < 0) {
+		fprintf(stderr, "Access type not available\n");
+		exit(0);
+	}
+	err = snd_pcm_hw_params_set_format(phandle, params, SND_PCM_FORMAT_S16_LE);
+	if (err < 0) {
+		fprintf(stderr, "cannot set format\n");
+		exit(0);
+	}
+	err = snd_pcm_hw_params_set_channels(phandle, params, 2);
+	if (err < 0) {
+		fprintf(stderr, "cannot set channels 2\n");
+		exit(0);
+	}
+	err = snd_pcm_hw_params_set_rate_near(phandle, params, 44100, 0);
+	if (err < 0) {
+		fprintf(stderr, "cannot set rate\n");
+		exit(0);
+	}
+	err = snd_pcm_hw_params_set_period_size_near(phandle, params, period_size);
+	if (err < 0) {
+		fprintf(stderr, "cannot set period size\n");
+		exit(0);
+	}
+	err = snd_pcm_hw_params(phandle, params);
+	if (err < 0) {
+		fprintf(stderr, "Unable to install hw params:\n");
+		exit(0);
+	}
+	snd_pcm_hw_params_dump(params, log);
+}
+
+#endif
+/*
+ *  Simple event sender
+ */
+
+void event_sender_start_timer(snd_seq_t *handle,
+			      int client ATTRIBUTE_UNUSED,
+			      int queue,
+			      snd_pcm_t *phandle ATTRIBUTE_UNUSED)
+{
+	int err;
+	
+#ifdef USE_PCM
+	if (phandle) {
+		snd_pcm_playback_info_t pinfo;
+		snd_seq_queue_timer_t qtimer;
+
+		if ((err = snd_pcm_playback_info(phandle, &pinfo)) < 0) {
+			fprintf(stderr, "Playback info error: %s\n", snd_strerror(err));
+			exit(0);
+		}
+		bzero(&qtimer, sizeof(qtimer));
+		qtimer.type = SND_SEQ_TIMER_MASTER;
+		/* note: last bit from the subdevices specifies playback */
+		/* or capture direction for the timer specification */
+		qtimer.number = SND_TIMER_PCM(pcard, pdevice, pinfo.subdevice << 1);
+		if ((err = snd_seq_set_queue_timer(handle, queue, &qtimer)) < 0) {
+			fprintf(stderr, "Sequencer PCM timer setup failed: %s\n", snd_strerror(err));
+			exit(0);
+		}
+	}	
+#endif
+	if ((err = snd_seq_start_queue(handle, queue, NULL))<0)
+		fprintf(stderr, "Timer event output error: %s\n", snd_strerror(err));
+	snd_seq_drain_output(handle);
+}
+
+void event_sender_filter(snd_seq_t *handle)
+{
+	int err;
+
+	if ((err = snd_seq_set_client_event_filter(handle, SND_SEQ_EVENT_ECHO)) < 0) {
+		fprintf(stderr, "Unable to set client info: %s\n", snd_strerror(err));
+		return;
+	}
+}
+
+void send_event(snd_seq_t *handle, int queue, int client, int port,
+                snd_seq_addr_t *dest, int *time)
+{
+	int err;
+	snd_seq_event_t ev;
+	
+	bzero(&ev, sizeof(ev));
+	ev.queue = queue;
+	ev.source.client = ev.dest.client = client;
+	ev.source.port = ev.dest.port = port;
+	ev.flags = SND_SEQ_TIME_STAMP_REAL | SND_SEQ_TIME_MODE_ABS;
+	ev.time.time.tv_sec = *time; (*time)++;
+	ev.type = SND_SEQ_EVENT_ECHO;
+	if ((err = snd_seq_event_output(handle, &ev))<0)
+		fprintf(stderr, "Event output error: %s\n", snd_strerror(err));
+	ev.dest = *dest;
+	ev.type = SND_SEQ_EVENT_PGMCHANGE;
+	ev.data.control.channel = 0;
+	ev.data.control.value = 16;
+	if ((err = snd_seq_event_output(handle, &ev))<0)
+		fprintf(stderr, "Event output error: %s\n", snd_strerror(err));
+	ev.type = SND_SEQ_EVENT_NOTE;
+	ev.data.note.channel = 0;
+	ev.data.note.note = 64 + (queue*2);
+	ev.data.note.velocity = 127;
+	ev.data.note.off_velocity = 127;
+	ev.data.note.duration = 500;	/* 0.5sec */
+	if ((err = snd_seq_event_output(handle, &ev))<0)
+		fprintf(stderr, "Event output error: %s\n", snd_strerror(err));
+	if ((err = snd_seq_drain_output(handle))<0)
+		fprintf(stderr, "Event drain error: %s\n", snd_strerror(err));
+}
+
+void event_sender(snd_seq_t *handle, int argc, char *argv[])
+{
+	snd_seq_event_t *ev;
+	snd_seq_port_info_t *pinfo;
+	snd_seq_port_subscribe_t *sub;
+	snd_seq_addr_t addr;
+	struct pollfd *pfds;
+	int client, port, queue, max, err, v1, v2, time = 0, pcm_flag = 0;
+	char *ptr;
+	snd_pcm_t *phandle = NULL;
+
+	if (argc < 1) {
+		fprintf(stderr, "Invalid destination...\n");
+		return;
+	}
+
+	if ((client = snd_seq_client_id(handle))<0) {
+		fprintf(stderr, "Cannot determine client number: %s\n", snd_strerror(client));
+		return;
+	}
+	printf("Client ID = %i\n", client);
+	if ((queue = snd_seq_alloc_queue(handle))<0) {
+		fprintf(stderr, "Cannot allocate queue: %s\n", snd_strerror(queue));
+		return;
+	}
+	printf("Queue ID = %i\n", queue);
+	event_sender_filter(handle);
+	if ((err = snd_seq_nonblock(handle, 1))<0)
+		fprintf(stderr, "Cannot set nonblock mode: %s\n", snd_strerror(err));
+
+	snd_seq_port_info_alloca(&pinfo);
+	snd_seq_port_info_set_capability(pinfo, SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_READ);
+	snd_seq_port_info_set_name(pinfo, "Output");
+	if ((err = snd_seq_create_port(handle, pinfo)) < 0) {
+		fprintf(stderr, "Cannot create output port: %s\n", snd_strerror(err));
+		return;
+	}
+	port = snd_seq_port_info_get_port(pinfo);
+
+	snd_seq_port_subscribe_alloca(&sub);
+	addr.client = client;
+	addr.port = port;
+	snd_seq_port_subscribe_set_sender(sub, &addr);
+
+	for (max = 0; max < argc; max++) {
+		ptr = argv[max];
+		if (!ptr)
+			continue;
+		if (!strcmp(ptr, "pcm")) {
+			pcm_flag = 1;
+			continue;
+		}
+		if (sscanf(ptr, "%i.%i", &v1, &v2) != 2) {
+			fprintf(stderr, "Wrong argument '%s'...\n", argv[max]);
+			return;
+		}
+		addr.client = v1;
+		addr.port = v2;
+		snd_seq_port_subscribe_set_dest(sub, &addr);
+		if ((err = snd_seq_subscribe_port(handle, sub))<0) {
+			fprintf(stderr, "Cannot subscribe port %i from client %i: %s\n", v2, v1, snd_strerror(err));
+			return;
+		}
+	}
+
+	printf("Destination client = %i, port = %i\n", addr.client, addr.port);
+
+#ifdef USE_PCM
+	if (pcm_flag) {
+		if ((err = snd_pcm_open(&phandle, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
+			fprintf(stderr, "Playback open error: %s\n", snd_strerror(err));
+			exit(0);
+		}
+		set_hwparams(phandle);
+		pbuf = calloc(1, period_size * 4);
+		if (pbuf == NULL) {
+			fprintf(stderr, "No enough memory...\n");
+			exit(0);
+		}
+	}
+#endif
+	event_sender_start_timer(handle, client, queue, phandle);
+	
+	/* send the first event */
+	send_event(handle, queue, client, port, &addr, &time);
+#ifdef USE_PCM
+	if (phandle)
+		max += snd_pcm_poll_descriptors_count(phandle);
+#endif
+	pfds = alloca(sizeof(*pfds) * max);
+	while (1) {
+		int nseqs = snd_seq_poll_descriptors_count(handle, POLLOUT|POLLIN);
+		if (snd_seq_event_output_pending(handle))
+			snd_seq_poll_descriptors(handle, pfds, nseqs, POLLOUT|POLLIN);
+		else
+			snd_seq_poll_descriptors(handle, pfds, nseqs, POLLIN);
+		max = nseqs;
+#ifdef USE_PCM
+		if (phandle) {
+			int pmax = snd_pcm_poll_descriptors_count(phandle);
+			snd_seq_poll_descriptors(phandle, pfds + max, pmax);
+			max += pmax;
+		}
+#endif
+		if (poll(pfds, max, -1) < 0)
+			break;
+#ifdef USE_PCM
+		if (phandle && (pfds[nseqs].revents & POLLOUT)) {
+			if (snd_pcm_writei(phandle, pbuf, period_size) != period_size) {
+				fprintf(stderr, "Playback write error!!\n");
+				exit(0);
+			}
+		}
+#endif
+		if (pfds[0].revents & POLLOUT)
+			snd_seq_drain_output(handle);
+		if (pfds[0].revents & POLLIN) {
+			do {
+				if ((err = snd_seq_event_input(handle, &ev))<0)
+					break;
+				if (!ev)
+					continue;
+				if (ev->type == SND_SEQ_EVENT_ECHO)
+					send_event(handle, queue, client, port, &addr, &time);
+				decode_event(ev);
+				snd_seq_free_event(ev);
+			} while (err > 0);
+		}
+	}
+}
diff --git a/test/seq.c b/test/seq.c
new file mode 100644
index 0000000..34b000f
--- /dev/null
+++ b/test/seq.c
@@ -0,0 +1,233 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <getopt.h>
+#include "../include/asoundlib.h"
+
+#include "seq-decoder.c"
+#include "seq-sender.c"
+
+#define SEQ_VERSION "0.0.1"
+
+#define HELPID_HELP             1000
+#define HELPID_DEBUG            1001
+#define HELPID_VERBOSE		1002
+#define HELPID_VERSION          1003
+
+int max_clients;
+int max_ports;
+int max_queues;
+int debug = 0;
+int verbose = 0;
+
+void set_name(snd_seq_t *handle)
+{
+	int err;
+	char name[64];
+	
+	sprintf(name, "SeqUtil - %i", getpid());
+	if ((err = snd_seq_set_client_name(handle, name)) < 0) {
+		fprintf(stderr, "Set client info error: %s\n", snd_strerror(err));
+		exit(0);
+	}
+}
+
+void system_info(snd_seq_t *handle)
+{
+	int err;
+	snd_seq_system_info_t *sysinfo;
+	
+	snd_seq_system_info_alloca(&sysinfo);
+	if ((err = snd_seq_system_info(handle, sysinfo))<0) {
+		fprintf(stderr, "System info error: %s\n", snd_strerror(err));
+		exit(0);
+	}
+	max_clients = snd_seq_system_info_get_clients(sysinfo);
+	max_ports = snd_seq_system_info_get_ports(sysinfo);
+	max_queues = snd_seq_system_info_get_ports(sysinfo);
+}
+
+void show_system_info(snd_seq_t *handle ATTRIBUTE_UNUSED)
+{
+	printf("System info\n");
+	printf("  Max queues    : %i\n", max_queues);
+	printf("  Max clients   : %i\n", max_clients);
+	printf("  Max ports     : %i\n", max_ports);
+}
+
+void show_queue_status(snd_seq_t *handle, int queue)
+{
+	int err, idx, min, max;
+	snd_seq_queue_status_t *status;
+
+	snd_seq_queue_status_alloca(&status);
+	min = queue < 0 ? 0 : queue;
+	max = queue < 0 ? max_queues : queue + 1;
+	for (idx = min; idx < max; idx++) {
+		if ((err = snd_seq_get_queue_status(handle, idx, status))<0) {
+			if (err == -ENOENT)
+				continue;
+			fprintf(stderr, "Client %i info error: %s\n", idx, snd_strerror(err));
+			exit(0);
+		}
+		printf("Queue %i info\n", snd_seq_queue_status_get_queue(status));
+		printf("  Tick          : %u\n", snd_seq_queue_status_get_tick_time(status)); 
+		printf("  Realtime      : %i.%i\n",
+		       snd_seq_queue_status_get_real_time(status)->tv_sec,
+		       snd_seq_queue_status_get_real_time(status)->tv_nsec);
+		printf("  Flags         : 0x%x\n", snd_seq_queue_status_get_status(status));
+	}
+}
+
+void show_port_info(snd_seq_t *handle, int client, int port)
+{
+	int err, idx, min, max;
+	snd_seq_port_info_t *info;
+
+	snd_seq_port_info_alloca(&info);
+	min = port < 0 ? 0 : port;
+	max = port < 0 ? max_ports : port + 1;
+	for (idx = min; idx < max; idx++) {
+		if ((err = snd_seq_get_any_port_info(handle, client, idx, info))<0) {
+			if (err == -ENOENT)
+				continue;
+			fprintf(stderr, "Port %i/%i info error: %s\n", client, idx, snd_strerror(err));
+			exit(0);
+		}
+		printf("  Port %i info\n", idx);
+		printf("    Client        : %i\n", snd_seq_port_info_get_client(info));
+		printf("    Port          : %i\n", snd_seq_port_info_get_port(info));
+		printf("    Name          : %s\n", snd_seq_port_info_get_name(info));
+		printf("    Capability    : 0x%x\n", snd_seq_port_info_get_capability(info));
+		printf("    Type          : 0x%x\n", snd_seq_port_info_get_type(info));
+		//printf("    Midi channels : %i\n", info.midi_channels);
+		//printf("    Synth voices  : %i\n", info.synth_voices);
+		printf("    Output subs   : %i\n", snd_seq_port_info_get_write_use(info));
+		printf("    Input subs    : %i\n", snd_seq_port_info_get_read_use(info));
+	}
+}
+
+void show_client_info(snd_seq_t *handle, int client)
+{
+	int err, idx, min, max;
+	snd_seq_client_info_t *info;
+
+	snd_seq_client_info_alloca(&info);
+	min = client < 0 ? 0 : client;
+	max = client < 0 ? max_clients : client + 1;
+	for (idx = min; idx < max; idx++) {
+		if ((err = snd_seq_get_any_client_info(handle, idx, info))<0) {
+			if (err == -ENOENT)
+				continue;
+			fprintf(stderr, "Client %i info error: %s\n", idx, snd_strerror(err));
+			exit(0);
+		}
+		printf("Client %i info\n", idx);
+		if (verbose)
+			printf("  Client        : %i\n", snd_seq_client_info_get_client(info));
+		printf("  Type          : %s\n", snd_seq_client_info_get_type(info) == SND_SEQ_KERNEL_CLIENT ? "kernel" : "user");
+		printf("  Name          : %s\n", snd_seq_client_info_get_name(info));
+	}
+}
+
+static void help(void)
+{
+	printf("Usage: seq <options> command\n");
+	printf("\nAvailable options:\n");
+	printf("  -h,--help       this help\n");
+	printf("  -d,--debug      debug mode\n");
+	printf("  -v,--verbose    verbose mode\n");
+	printf("  -V,--version    print version of this program\n");
+	printf("\nAvailable commands:\n");
+	printf("  system          show basic sequencer info\n");
+	printf("  queue [#]       show all queues or specified queue\n");
+	printf("  client [#]      show all clients or specified client\n");
+	printf("  port <client> [#]  show all ports or specified port for specified client\n");
+	printf("  decoder         event decoder\n");
+	printf("  sender <client.port> [<client.port>] ...   event sender\n");
+}
+
+int main(int argc, char *argv[])
+{
+	int morehelp, err, arg, arg1;
+	snd_seq_t *handle;
+	static struct option long_option[] =
+	{
+		{"help", 0, NULL, HELPID_HELP},
+		{"debug", 0, NULL, HELPID_DEBUG},
+		{"verbose", 0, NULL, HELPID_VERBOSE},
+		{"version", 0, NULL, HELPID_VERSION},
+		{NULL, 0, NULL, 0},
+        };
+        
+        morehelp = 0;
+	
+	while (1) {
+		int c;
+
+		if ((c = getopt_long(argc, argv, "hdvV", long_option, NULL)) < 0)
+			break;
+		switch (c) {
+		case 'h':
+		case HELPID_HELP:
+			morehelp++;
+			break;
+		case 'd':
+		case HELPID_DEBUG:
+			debug = 1;
+			break;
+		case 'v':
+		case HELPID_VERBOSE:
+			verbose = 1;
+			break;
+		case 'V':
+		case HELPID_VERSION:
+			printf("alsactl version " SEQ_VERSION "\n");
+			return 1;
+		default:
+			fprintf(stderr, "\07Invalid switch or option needs an argument.\n");
+			morehelp++;
+		}
+	}
+        if (morehelp) {
+                help();
+                return 1;
+        }
+	if (argc - optind <= 0) {
+		fprintf(stderr, "seq: Specify command...\n");
+		return 0;
+	}
+	if ((err = snd_seq_open(&handle, "hw", SND_SEQ_OPEN_DUPLEX, 0))<0) {
+		fprintf(stderr, "Open error: %s\n", snd_strerror(err));
+		exit(0);
+	}
+	set_name(handle);
+	system_info(handle);
+
+        if (!strcmp(argv[optind], "system")) {
+		show_system_info(handle);
+	} else if (!strcmp(argv[optind], "queue")) {
+		arg = argc - optind > 1 ? atoi(argv[optind + 1]) : -1;
+		show_queue_status(handle, arg);
+	} else if (!strcmp(argv[optind], "client")) {
+		arg = argc - optind > 1 ? atoi(argv[optind + 1]) : -1;
+		show_client_info(handle, arg);
+	} else if (!strcmp(argv[optind], "port")) {
+		arg = argc - optind > 1 ? atoi(argv[optind + 1]) : -1;
+		if (arg < 0) {
+			fprintf(stderr, "Specify port...\n");
+			exit(0);
+		}
+		arg1 = argc - optind > 2 ? atoi(argv[optind + 2]) : -1;
+		show_port_info(handle, arg, arg1);
+	} else if (!strcmp(argv[optind], "decoder")) {
+		event_decoder(handle, argc - optind - 1, argv + optind + 1);
+	} else if (!strcmp(argv[optind], "sender")) {
+		event_sender(handle, argc - optind - 1, argv + optind + 1);
+	} else {
+		help();
+	}
+	exit(1);
+}
diff --git a/test/timer.c b/test/timer.c
new file mode 100644
index 0000000..b05eb2f
--- /dev/null
+++ b/test/timer.c
@@ -0,0 +1,193 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include "../include/asoundlib.h"
+
+void show_status(void *handle)
+{
+	int err;
+	snd_timer_status_t *status;
+	
+	snd_timer_status_alloca(&status);
+	if ((err = snd_timer_status(handle, status)) < 0) {
+		fprintf(stderr, "timer status %i (%s)\n", err, snd_strerror(err));
+		return;
+	}
+	printf("STATUS:\n");
+	printf("  resolution = %li\n", snd_timer_status_get_resolution(status));
+	printf("  lost = %li\n", snd_timer_status_get_lost(status));
+	printf("  overrun = %li\n", snd_timer_status_get_overrun(status));
+	printf("  queue = %li\n", snd_timer_status_get_queue(status));
+}
+
+void read_loop(void *handle, int master_ticks, int timeout)
+{
+	int count, err;
+	struct pollfd *fds;
+	snd_timer_read_t tr;
+	
+	count = snd_timer_poll_descriptors_count(handle);
+	fds = calloc(count, sizeof(struct pollfd));
+	if (fds == NULL) {
+		fprintf(stderr, "malloc error\n");
+		exit(EXIT_FAILURE);
+	}
+	while (master_ticks-- > 0) {
+		if ((err = snd_timer_poll_descriptors(handle, fds, count)) < 0) {
+			fprintf(stderr, "snd_timer_poll_descriptors error: %s\n", snd_strerror(err));
+			exit(EXIT_FAILURE);
+		}
+		if ((err = poll(fds, count, timeout)) < 0) {
+			fprintf(stderr, "poll error %i (%s)\n", err, strerror(err));
+			exit(EXIT_FAILURE);
+		}
+		if (err == 0) {
+			fprintf(stderr, "timer time out!!\n");
+			exit(EXIT_FAILURE);
+		}
+		while (snd_timer_read(handle, &tr, sizeof(tr)) == sizeof(tr)) {
+			printf("TIMER: resolution = %uns, ticks = %u\n",
+				tr.resolution, tr.ticks);
+		}
+	}
+	free(fds);
+}
+
+static void async_callback(snd_async_handler_t *ahandler)
+{
+	snd_timer_t *handle = snd_async_handler_get_timer(ahandler);
+	int *acount = snd_async_handler_get_callback_private(ahandler);
+	snd_timer_read_t tr;
+	
+	while (snd_timer_read(handle, &tr, sizeof(tr)) == sizeof(tr)) {
+		printf("TIMER: resolution = %uns, ticks = %u\n",
+			tr.resolution, tr.ticks);
+	}
+	(*acount)++;
+}
+
+int main(int argc, char *argv[])
+{
+	int idx, err;
+	int class = SND_TIMER_CLASS_GLOBAL;
+	int sclass = SND_TIMER_CLASS_NONE;
+	int card = 0;
+	int device = SND_TIMER_GLOBAL_SYSTEM;
+	int subdevice = 0;
+	int list = 0;
+	int async = 0;
+	int acount = 0;
+	snd_timer_t *handle;
+	snd_timer_id_t *id;
+	snd_timer_info_t *info;
+	snd_timer_params_t *params;
+	char timername[64];
+	snd_async_handler_t *ahandler;
+
+	snd_timer_id_alloca(&id);
+	snd_timer_info_alloca(&info);
+	snd_timer_params_alloca(&params);
+
+	idx = 1;
+	while (idx < argc) {
+		if (!strncmp(argv[idx], "class=", 5)) {
+			class = atoi(argv[idx]+6);
+		} else if (!strncmp(argv[idx], "sclass=", 6)) {
+			sclass = atoi(argv[idx]+7);
+		} else if (!strncmp(argv[idx], "card=", 5)) {
+			card = atoi(argv[idx]+5);
+		} else if (!strncmp(argv[idx], "device=", 7)) {
+			device = atoi(argv[idx]+7);
+		} else if (!strncmp(argv[idx], "subdevice=", 10)) {
+			subdevice = atoi(argv[idx]+10);
+		} else if (!strcmp(argv[idx], "list")) {
+			list = 1;
+		} else if (!strcmp(argv[idx], "async")) {
+			async = 1;
+		}
+		idx++;
+	}
+	if (class == SND_TIMER_CLASS_SLAVE && sclass == SND_TIMER_SCLASS_NONE) {
+		fprintf(stderr, "slave class is not set\n");
+		exit(EXIT_FAILURE);
+	}
+	if (list) {
+		snd_timer_query_t *qhandle;
+		if ((err = snd_timer_query_open(&qhandle, "hw", 0)) < 0) {
+			fprintf(stderr, "snd_timer_query_open error: %s\n", snd_strerror(err));
+			exit(EXIT_FAILURE);
+		}
+		snd_timer_id_set_class(id, SND_TIMER_CLASS_NONE);
+		while (1) {
+			if ((err = snd_timer_query_next_device(qhandle, id)) < 0) {
+				fprintf(stderr, "timer next device error: %s\n", snd_strerror(err));
+				break;
+			}
+			if (snd_timer_id_get_class(id) < 0)
+				break;
+			printf("Timer device: class %i, sclass %i, card %i, device %i, subdevice %i\n",
+					snd_timer_id_get_class(id),
+					snd_timer_id_get_sclass(id),
+					snd_timer_id_get_card(id),
+					snd_timer_id_get_device(id),
+					snd_timer_id_get_subdevice(id));
+		}
+		snd_timer_query_close(qhandle);
+		exit(EXIT_SUCCESS);
+	}
+	sprintf(timername, "hw:CLASS=%i,SCLASS=%i,CARD=%i,DEV=%i,SUBDEV=%i", class, sclass, card, device, subdevice);
+	if ((err = snd_timer_open(&handle, timername, SND_TIMER_OPEN_NONBLOCK))<0) {
+		fprintf(stderr, "timer open %i (%s)\n", err, snd_strerror(err));
+		exit(EXIT_FAILURE);
+	}
+	printf("Using timer class %i, slave class %i, card %i, device %i, subdevice %i\n", class, sclass, card, device, subdevice);
+	if ((err = snd_timer_info(handle, info)) < 0) {
+		fprintf(stderr, "timer info %i (%s)\n", err, snd_strerror(err));
+		exit(0);
+	}
+	printf("Timer info:\n");
+	printf("  slave = %s\n", snd_timer_info_is_slave(info) ? "yes" : "no");
+	printf("  card = %i\n", snd_timer_info_get_card(info));
+	printf("  id = '%s'\n", snd_timer_info_get_id(info));
+	printf("  name = '%s'\n", snd_timer_info_get_name(info));
+	printf("  average resolution = %li\n", snd_timer_info_get_resolution(info));
+	snd_timer_params_set_auto_start(params, 1);
+	if (!snd_timer_info_is_slave(info)) {
+		snd_timer_params_set_ticks(params, (1000000000 / snd_timer_info_get_resolution(info)) / 50); /* 50Hz */
+		if (snd_timer_params_get_ticks(params) < 1)
+			snd_timer_params_set_ticks(params, 1);
+		printf("Using %li tick(s)\n", snd_timer_params_get_ticks(params));
+	} else {
+		snd_timer_params_set_ticks(params, 1);
+	}
+	if ((err = snd_timer_params(handle, params)) < 0) {
+		fprintf(stderr, "timer params %i (%s)\n", err, snd_strerror(err));
+		exit(0);
+	}
+	show_status(handle);
+	if (async) {
+		err = snd_async_add_timer_handler(&ahandler, handle, async_callback, &acount);
+		if (err < 0) {
+			fprintf(stderr, "unable to add async handler %i (%s)\n", err, snd_strerror(err));
+			exit(EXIT_FAILURE);
+		}
+	}
+	if ((err = snd_timer_start(handle)) < 0) {
+		fprintf(stderr, "timer start %i (%s)\n", err, snd_strerror(err));
+		exit(EXIT_FAILURE);
+	}
+	if (async) {
+		/* because all other work is done in the signal handler,
+		   suspend the process */
+		while (acount < 25)
+			sleep(1);
+		snd_timer_stop(handle);
+	} else {
+		read_loop(handle, 25, snd_timer_info_is_slave(info) ? 10000 : 25);
+	}
+	show_status(handle);
+	snd_timer_close(handle);
+	printf("Done\n");
+	return EXIT_SUCCESS;
+}
diff --git a/test/ucm/TestHDA/Case1.conf b/test/ucm/TestHDA/Case1.conf
new file mode 100644
index 0000000..81b2445
--- /dev/null
+++ b/test/ucm/TestHDA/Case1.conf
@@ -0,0 +1,25 @@
+SectionVerb {
+	EnableSequence [
+		exec "Case1 enable seq"
+		exec "Case1 enable seq 2"
+	]
+	DisableSequence [
+		exec "Case2 disable seq"
+	]
+	TransitionVerb."Case2" [
+		exec "Case1->Case2 transition seq"
+	]
+	Value {
+		TestValue1 "123"
+	}
+}
+
+SectionDevice."Device1".0 {
+	
+}
+
+SectionModifier."Modifier1".0 {
+	SupportedDevice [
+		"Device1"
+	]
+}
diff --git a/test/ucm/TestHDA/TestHDA.conf b/test/ucm/TestHDA/TestHDA.conf
new file mode 100644
index 0000000..41dd74c
--- /dev/null
+++ b/test/ucm/TestHDA/TestHDA.conf
@@ -0,0 +1,13 @@
+Comment "A test HDA card"
+
+SectionUseCase."Case1" {
+	File "Case1.conf"
+	Comment "Case1 Comment"
+}
+
+SectionDefaults [
+	exec "my prg"
+	msleep 1
+	cdev "hw:0"
+	cset "name='PCM Playback Volume' 50%"
+]
diff --git a/test/ucm/anothercard/anothercard.conf b/test/ucm/anothercard/anothercard.conf
new file mode 100644
index 0000000..3d9ed7d
--- /dev/null
+++ b/test/ucm/anothercard/anothercard.conf
@@ -0,0 +1 @@
+Comment "Another Card"
diff --git a/test/ucm/testcard1/testcard1.conf b/test/ucm/testcard1/testcard1.conf
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/ucm/testcard1/testcard1.conf
diff --git a/usr/bin/aserver b/usr/bin/aserver
deleted file mode 100755
index 6c2e968..0000000
--- a/usr/bin/aserver
+++ /dev/null
Binary files differ
diff --git a/usr/lib/alsa-lib/smixer/smixer-ac97.so b/usr/lib/alsa-lib/smixer/smixer-ac97.so
deleted file mode 100755
index 83504d0..0000000
--- a/usr/lib/alsa-lib/smixer/smixer-ac97.so
+++ /dev/null
Binary files differ
diff --git a/usr/lib/alsa-lib/smixer/smixer-hda.so b/usr/lib/alsa-lib/smixer/smixer-hda.so
deleted file mode 100755
index 987624c..0000000
--- a/usr/lib/alsa-lib/smixer/smixer-hda.so
+++ /dev/null
Binary files differ
diff --git a/usr/lib/alsa-lib/smixer/smixer-sbase.so b/usr/lib/alsa-lib/smixer/smixer-sbase.so
deleted file mode 100755
index ce2ceac..0000000
--- a/usr/lib/alsa-lib/smixer/smixer-sbase.so
+++ /dev/null
Binary files differ
diff --git a/usr/lib/libasound.so b/usr/lib/libasound.so
deleted file mode 120000
index 42dfc99..0000000
--- a/usr/lib/libasound.so
+++ /dev/null
@@ -1 +0,0 @@
-libasound.so.2.0.0
\ No newline at end of file
diff --git a/usr/lib/libasound.so.2 b/usr/lib/libasound.so.2
deleted file mode 120000
index 42dfc99..0000000
--- a/usr/lib/libasound.so.2
+++ /dev/null
@@ -1 +0,0 @@
-libasound.so.2.0.0
\ No newline at end of file
diff --git a/usr/lib/libasound.so.2.0.0 b/usr/lib/libasound.so.2.0.0
deleted file mode 100755
index a397918..0000000
--- a/usr/lib/libasound.so.2.0.0
+++ /dev/null
Binary files differ
diff --git a/utils/Makefile.am b/utils/Makefile.am
new file mode 100644
index 0000000..447d525
--- /dev/null
+++ b/utils/Makefile.am
@@ -0,0 +1,14 @@
+if INSTALL_M4
+aclocaldir=$(datadir)/aclocal
+aclocal_DATA=alsa.m4
+endif
+EXTRA_DIST=alsa.m4 buildrpm alsa.pc.in
+
+alsapkgconfdir = @ALSA_PKGCONF_DIR@
+pkgconfigdir = $(alsapkgconfdir)
+pkgconfig_DATA = alsa.pc
+
+rpm: buildrpm alsa-lib.spec
+	VERSION=$(VERSION) $(srcdir)/buildrpm
+
+INCLUDES=-I$(top_srcdir)/include
diff --git a/utils/alsa-lib.spec.in b/utils/alsa-lib.spec.in
new file mode 100644
index 0000000..021cc75
--- /dev/null
+++ b/utils/alsa-lib.spec.in
@@ -0,0 +1,75 @@
+%define ver      @SND_LIB_VERSION@
+%define rel      1
+
+Summary: Advanced Linux Sound Architecture (ALSA) - Library
+Name: alsa-lib
+Version: %ver
+Release: %rel
+License: LGPL
+Group: System/Libraries
+Source: ftp://ftp.alsa-project.org/pub/lib/alsa-lib-%{ver}.tar.bz2
+BuildRoot: %{_tmppath}/%{name}-%{version}-root
+URL: http://www.alsa-project.org
+
+%description
+
+Advanced Linux Sound Architecture (ALSA) - Library
+
+%package -n alsa-lib-devel
+Summary: ALSA Libraries Development Files
+Group: Development/Libraries
+Requires: %{name} = %{version}-%{release}
+
+%description -n alsa-lib-devel
+Development files for building applications which use the ALSA libraries.
+
+%changelog
+* Sat Feb 22 2003 Ronny V. Vindenes <sublett@dc-s.com>
+- split alsa-lib into alsa-lib and alsa-lib-devel
+- changed which files are installed (now includes alsa.pc)
+- use standard rpm macros for build and install
+- changed BuildRoot from /var/tmp to %{_tmppath}
+
+* Tue Nov 20 2001 Jaroslav Kysela <perex@perex.cz>
+
+- changed BuildRoot from /tmp to /var/tmp
+- use the standard RPM macros for prefix and paths
+- added DESTDIR for make install
+
+* Sun Nov 11 2001 Miroslav Benes <mbenes@tenez.cz>
+
+- dangerous command "rpm -rf $RPM_BUILD_ROOT" checks $RPM_BUILD_ROOT variable
+- unset key "Docdir" - on some new systems are documentation in /usr/share/doc
+
+* Mon May 28 1998 Helge Jensen <slog@slog.dk>
+
+- Made SPEC file
+
+%prep
+%setup
+%build
+%configure
+make
+
+%install
+[ "$RPM_BUILD_ROOT" != "/" ] && rm -rf $RPM_BUILD_ROOT
+%makeinstall
+
+%clean
+[ "$RPM_BUILD_ROOT" != "/" ] && rm -rf $RPM_BUILD_ROOT
+
+%files
+%defattr(-, root, root)
+%{_bindir}/*
+%{_libdir}/*.so.*
+%{_datadir}/alsa
+%doc doc/*.txt
+
+%files -n alsa-lib-devel
+%defattr(-,root,root)
+%{_includedir}/alsa
+%{_includedir}/sys/*
+%{_libdir}/*.la
+%{_libdir}/*.so
+%{_datadir}/aclocal/*
+%{_libdir}/pkgconfig/*
diff --git a/utils/alsa.m4 b/utils/alsa.m4
new file mode 100644
index 0000000..79a24ef
--- /dev/null
+++ b/utils/alsa.m4
@@ -0,0 +1,141 @@
+dnl Configure Paths for Alsa
+dnl Some modifications by Richard Boulton <richard-alsa@tartarus.org>
+dnl Christopher Lansdown <lansdoct@cs.alfred.edu>
+dnl Jaroslav Kysela <perex@perex.cz>
+dnl Last modification: $Id: alsa.m4,v 1.24 2004/09/15 18:48:07 tiwai Exp $
+dnl AM_PATH_ALSA([MINIMUM-VERSION [, ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]])
+dnl Test for libasound, and define ALSA_CFLAGS and ALSA_LIBS as appropriate.
+dnl enables arguments --with-alsa-prefix=
+dnl                   --with-alsa-enc-prefix=
+dnl                   --disable-alsatest
+dnl
+dnl For backwards compatibility, if ACTION_IF_NOT_FOUND is not specified,
+dnl and the alsa libraries are not found, a fatal AC_MSG_ERROR() will result.
+dnl
+AC_DEFUN([AM_PATH_ALSA],
+[dnl Save the original CFLAGS, LDFLAGS, and LIBS
+alsa_save_CFLAGS="$CFLAGS"
+alsa_save_LDFLAGS="$LDFLAGS"
+alsa_save_LIBS="$LIBS"
+alsa_found=yes
+
+dnl
+dnl Get the cflags and libraries for alsa
+dnl
+AC_ARG_WITH(alsa-prefix,
+[  --with-alsa-prefix=PFX  Prefix where Alsa library is installed(optional)],
+[alsa_prefix="$withval"], [alsa_prefix=""])
+
+AC_ARG_WITH(alsa-inc-prefix,
+[  --with-alsa-inc-prefix=PFX  Prefix where include libraries are (optional)],
+[alsa_inc_prefix="$withval"], [alsa_inc_prefix=""])
+
+dnl FIXME: this is not yet implemented
+AC_ARG_ENABLE(alsatest,
+[  --disable-alsatest      Do not try to compile and run a test Alsa program],
+[enable_alsatest="$enableval"],
+[enable_alsatest=yes])
+
+dnl Add any special include directories
+AC_MSG_CHECKING(for ALSA CFLAGS)
+if test "$alsa_inc_prefix" != "" ; then
+	ALSA_CFLAGS="$ALSA_CFLAGS -I$alsa_inc_prefix"
+	CFLAGS="$CFLAGS -I$alsa_inc_prefix"
+fi
+AC_MSG_RESULT($ALSA_CFLAGS)
+
+dnl add any special lib dirs
+AC_MSG_CHECKING(for ALSA LDFLAGS)
+if test "$alsa_prefix" != "" ; then
+	ALSA_LIBS="$ALSA_LIBS -L$alsa_prefix"
+	LDFLAGS="$LDFLAGS $ALSA_LIBS"
+fi
+
+dnl add the alsa library
+ALSA_LIBS="$ALSA_LIBS -lasound -lm -ldl -lpthread"
+LIBS="$ALSA_LIBS $LIBS"
+AC_MSG_RESULT($ALSA_LIBS)
+
+dnl Check for a working version of libasound that is of the right version.
+min_alsa_version=ifelse([$1], ,0.1.1,$1)
+AC_MSG_CHECKING(for libasound headers version >= $min_alsa_version)
+no_alsa=""
+    alsa_min_major_version=`echo $min_alsa_version | \
+           sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\1/'`
+    alsa_min_minor_version=`echo $min_alsa_version | \
+           sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\2/'`
+    alsa_min_micro_version=`echo $min_alsa_version | \
+           sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\3/'`
+
+AC_LANG_SAVE
+AC_LANG_C
+AC_TRY_COMPILE([
+#include <alsa/asoundlib.h>
+], [
+/* ensure backward compatibility */
+#if !defined(SND_LIB_MAJOR) && defined(SOUNDLIB_VERSION_MAJOR)
+#define SND_LIB_MAJOR SOUNDLIB_VERSION_MAJOR
+#endif
+#if !defined(SND_LIB_MINOR) && defined(SOUNDLIB_VERSION_MINOR)
+#define SND_LIB_MINOR SOUNDLIB_VERSION_MINOR
+#endif
+#if !defined(SND_LIB_SUBMINOR) && defined(SOUNDLIB_VERSION_SUBMINOR)
+#define SND_LIB_SUBMINOR SOUNDLIB_VERSION_SUBMINOR
+#endif
+
+#  if(SND_LIB_MAJOR > $alsa_min_major_version)
+  exit(0);
+#  else
+#    if(SND_LIB_MAJOR < $alsa_min_major_version)
+#       error not present
+#    endif
+
+#   if(SND_LIB_MINOR > $alsa_min_minor_version)
+  exit(0);
+#   else
+#     if(SND_LIB_MINOR < $alsa_min_minor_version)
+#          error not present
+#      endif
+
+#      if(SND_LIB_SUBMINOR < $alsa_min_micro_version)
+#        error not present
+#      endif
+#    endif
+#  endif
+exit(0);
+],
+  [AC_MSG_RESULT(found.)],
+  [AC_MSG_RESULT(not present.)
+   ifelse([$3], , [AC_MSG_ERROR(Sufficiently new version of libasound not found.)])
+   alsa_found=no]
+)
+AC_LANG_RESTORE
+
+dnl Now that we know that we have the right version, let's see if we have the library and not just the headers.
+if test "x$enable_alsatest" = "xyes"; then
+AC_CHECK_LIB([asound], [snd_ctl_open],,
+	[ifelse([$3], , [AC_MSG_ERROR(No linkable libasound was found.)])
+	 alsa_found=no]
+)
+fi
+
+if test "x$alsa_found" = "xyes" ; then
+   ifelse([$2], , :, [$2])
+   LIBS=`echo $LIBS | sed 's/-lasound//g'`
+   LIBS=`echo $LIBS | sed 's/  //'`
+   LIBS="-lasound $LIBS"
+fi
+if test "x$alsa_found" = "xno" ; then
+   ifelse([$3], , :, [$3])
+   CFLAGS="$alsa_save_CFLAGS"
+   LDFLAGS="$alsa_save_LDFLAGS"
+   LIBS="$alsa_save_LIBS"
+   ALSA_CFLAGS=""
+   ALSA_LIBS=""
+fi
+
+dnl That should be it.  Now just export out symbols:
+AC_SUBST(ALSA_CFLAGS)
+AC_SUBST(ALSA_LIBS)
+])
+
diff --git a/utils/alsa.pc.in b/utils/alsa.pc.in
new file mode 100644
index 0000000..8de9859
--- /dev/null
+++ b/utils/alsa.pc.in
@@ -0,0 +1,14 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: alsa
+Description: Advanced Linux Sound Architecture (ALSA) - Library
+Version: @VERSION@
+Requires: 
+Libs: -L${libdir} -lasound
+Libs.private: @ALSA_DEPLIBS@
+# -I${includedir}/alsa below is just for backward compatibility
+# (it was set so mistakely in the older version)
+Cflags: -I${includedir} -I${includedir}/alsa
diff --git a/utils/buildrpm b/utils/buildrpm
new file mode 100755
index 0000000..e6f44ba
--- /dev/null
+++ b/utils/buildrpm
@@ -0,0 +1,46 @@
+#!/bin/bash
+
+source=.
+version=`cat $source/../version`
+package=$source/../../alsa-lib-$version.tar.bz2
+packagedir=/usr/src/redhat
+xrpmbuild=rpm
+rpmbuild --usage 2> /dev/null > /dev/null && xrpmbuild=rpmbuild
+
+if [ -d /usr/src/packages ]; then
+  packagedir=/usr/src/packages
+fi
+
+make -C .. clean
+make -C .. dist
+
+if [ ! -r $package ]; then
+  package=$source/../alsa-lib-$version.tar.bz2
+  if [ ! -r $package ]; then
+    echo "Error: wrong package: $package"
+    exit 1
+  fi
+fi
+
+cp -fv $package ${packagedir}/SOURCES
+
+if [ ! -r $source/buildrpm ]; then
+  echo "Error: invalid directory: $source"
+  exit 1
+fi
+
+if [ ! -d ${packagedir} ]; then
+  echo "Error: ${packagedir} directory not found"
+  exit 1
+fi
+
+if [ ! -r $source/alsa-lib.spec ]; then
+  cd $source/..
+  ./configure
+  cd utils
+fi
+
+cp -fv $source/alsa-lib.spec ${packagedir}/SPECS
+cd ${packagedir}/SPECS
+$xrpmbuild -ba alsa-lib.spec
+cd ${packagedir}
diff --git a/var/lib/alsa/asound.state b/var/lib/alsa/asound.state
deleted file mode 100644
index 87b3a3c..0000000
--- a/var/lib/alsa/asound.state
+++ /dev/null
@@ -1,17 +0,0 @@
-#Sound Card State
-state.A5SEVK {
-	control.25 {
-		iface MIXER
-		name 'Input Mux'
-		value 'Both Mic'
-		comment {
-			access 'read write'
-			type ENUMERATED
-			count 1
-			item.0 Line-in
-			item.1 'Both Mic'
-			item.2 Unknown
-		}
-	}
-}
-