OSDN Git Service

big simple mixer update
authorJaroslav Kysela <perex@perex.cz>
Fri, 3 Jun 2005 13:33:04 +0000 (13:33 +0000)
committerJaroslav Kysela <perex@perex.cz>
Fri, 3 Jun 2005 13:33:04 +0000 (13:33 +0000)
- exported all necessary functions to create a mixer module outside alsa-lib
- separated simple mixer API from the simple mixer implementation
  (using callbacks as usuall)
  - src/mixer/simple.c is the core
  - src/mixer/simple_none.c is the current (no-abstraction) implementation
    based on control names; note that this module does not depend on
    internal ALSA structures now
  - src/mixer/simple_abst.c is the ongoing abstraction which will use
    external dynamic modules; src/conf/smixer.conf will describe which
    modules will be used depending on the components from the driver

12 files changed:
include/Makefile.am
include/mixer.h
include/mixer_abst.h [new file with mode: 0644]
src/Versions
src/conf/smixer.conf [new file with mode: 0644]
src/mixer/Makefile.am
src/mixer/mixer.c
src/mixer/mixer_local.h
src/mixer/mixer_simple.h [new file with mode: 0644]
src/mixer/simple.c
src/mixer/simple_abst.c [new file with mode: 0644]
src/mixer/simple_none.c [new file with mode: 0644]

index b0ebc8c..29b041c 100644 (file)
@@ -6,7 +6,7 @@ alsaincludedir = ${includedir}/alsa
 alsainclude_HEADERS = asoundlib.h asoundef.h \
                      version.h global.h input.h output.h error.h \
                      conf.h pcm.h pcm_old.h pcm_plugin.h rawmidi.h timer.h \
-                     hwdep.h control.h mixer.h \
+                     hwdep.h control.h mixer.h mixer_abst.h \
                      seq_event.h seq.h seqmid.h seq_midi_event.h \
                      conv.h instr.h iatomic.h \
                      alisp.h pcm_external.h pcm_ioplug.h pcm_extplug.h
index 0f0db30..ac2db33 100644 (file)
@@ -74,6 +74,18 @@ typedef int (*snd_mixer_elem_callback_t)(snd_mixer_elem_t *elem,
 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 (legacy) mixer elements */
@@ -88,6 +100,7 @@ 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_detach(snd_mixer_t *mixer, const char *name);
+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);
@@ -108,6 +121,41 @@ 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_add_elem(snd_mixer_t *mixer, snd_mixer_elem_t *elem);
+int snd_mixer_remove_elem(snd_mixer_t *mixer, snd_mixer_elem_t *elem);
+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) do { assert(ptr); *ptr = (snd_mixer_selem_id_t *) alloca(snd_mixer_class_sizeof()); memset(*ptr, 0, snd_mixer_class_sizeof()); } while (0)
+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
@@ -126,14 +174,20 @@ typedef enum _snd_mixer_selem_channel_id {
        SND_MIXER_SCHN_FRONT_LEFT = 0,
        /** Front right */
        SND_MIXER_SCHN_FRONT_RIGHT,
-       /** Front center */
-       SND_MIXER_SCHN_FRONT_CENTER,
        /** 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
@@ -153,9 +207,11 @@ struct snd_mixer_selem_regopt {
        int ver;
        /** v1: abstract layer selection */
        enum snd_mixer_selem_regopt_abstract abstract;
-       /** v1: playback PCM connected to mixer device */
+       /** 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: playback PCM connected to mixer device */
+       /** v1: capture PCM connected to mixer device (NULL == none) */
        snd_pcm_t *capture_pcm;
 };
 
@@ -204,8 +260,8 @@ int snd_mixer_selem_set_playback_dB(snd_mixer_elem_t *elem, snd_mixer_selem_chan
 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_volume_dB(snd_mixer_elem_t *elem, long value, int dir);
-int snd_mixer_selem_set_capture_volume_dB(snd_mixer_elem_t *elem, long value, int dir);
+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);
diff --git a/include/mixer_abst.h b/include/mixer_abst.h
new file mode 100644 (file)
index 0000000..90acdb6
--- /dev/null
@@ -0,0 +1,100 @@
+/**
+ * \file include/mixer_abst.h
+ * \brief Mixer abstract implementation interface library for the ALSA library
+ * \author Jaroslav Kysela <perex@suse.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_ENUM            (1<<12)
+/* 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;
+
+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 (*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 (*set_volume_all)(snd_mixer_elem_t *elem, int dir, long value);
+       int (*set_dB_all)(snd_mixer_elem_t *elem, int dir, 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 (*set_switch_all)(snd_mixer_elem_t *elem, int dir, 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);
+
+/** \} */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __ALSA_MIXER_ABST_H */
+
index 37aecaf..33bc6c8 100644 (file)
@@ -218,3 +218,39 @@ ALSA_1.0.9 {
     snd_timer_ginfo_get_clients;
 
 } ALSA_1.0.8;
+
+ALSA_1.0.10 {
+  global:
+
+    snd_mixer_get_hctl;
+    snd_mixer_elem_get_private;
+
+    snd_mixer_class_register;
+    snd_mixer_add_elem;
+    snd_mixer_remove_elem;
+    snd_mixer_elem_new;
+    snd_mixer_elem_add;
+    snd_mixer_elem_remove;
+    snd_mixer_elem_free;
+    snd_mixer_elem_info;
+    snd_mixer_elem_value;
+    snd_mixer_elem_attach;
+    snd_mixer_elem_detach;
+    snd_mixer_elem_empty;
+
+    snd_mixer_class_malloc;
+    snd_mixer_class_free;
+    snd_mixer_class_copy;
+    snd_mixer_class_get_mixer;
+    snd_mixer_class_get_event;
+    snd_mixer_class_get_private;
+    snd_mixer_class_get_compare;
+    snd_mixer_class_set_event;
+    snd_mixer_class_set_private;
+    snd_mixer_class_set_private_free;
+    snd_mixer_class_set_compare;
+
+    snd_mixer_selem_set_playback_dB_all;
+    snd_mixer_selem_set_capture_dB_all;
+
+} ALSA_1.0.9;
diff --git a/src/conf/smixer.conf b/src/conf/smixer.conf
new file mode 100644 (file)
index 0000000..908de4a
--- /dev/null
@@ -0,0 +1,8 @@
+usb {
+       searchl "USB"
+       lib smixer_usb.so
+}
+ac97 {
+       searchl "AC97a:"
+       lib smixer_ac97.so
+}
index 115c407..10c0385 100644 (file)
@@ -1,6 +1,6 @@
 EXTRA_LTLIBRARIES=libmixer.la
 
-libmixer_la_SOURCES = bag.c mixer.c simple.c
+libmixer_la_SOURCES = bag.c mixer.c simple.c simple_none.c simple_abst.c
 
 noinst_HEADERS = mixer_local.h
 
index ba7c68c..2a06c75 100644 (file)
@@ -45,7 +45,6 @@ This is an abstraction layer over the hcontrol layer.
 #include <string.h>
 #include <fcntl.h>
 #include <sys/ioctl.h>
-#include <pthread.h>
 #include "mixer_local.h"
 
 #ifndef DOC_HIDDEN
@@ -244,6 +243,27 @@ int snd_mixer_detach(snd_mixer_t *mixer, const char *name)
        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)
 {
@@ -285,6 +305,47 @@ static int _snd_mixer_find_elem(snd_mixer_t *mixer, snd_mixer_elem_t *elem, int
 }
 
 /**
+ * \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
@@ -353,9 +414,7 @@ int snd_mixer_elem_remove(snd_mixer_elem_t *elem)
        }
        err = snd_mixer_elem_throw_event(elem, SND_CTL_EVENT_MASK_REMOVE);
        list_del(&elem->list);
-       if (elem->private_free)
-               elem->private_free(elem);
-       free(elem);
+       snd_mixer_elem_free(elem);
        mixer->count--;
        m = mixer->count - idx;
        if (m > 0)
@@ -366,6 +425,20 @@ int snd_mixer_elem_remove(snd_mixer_elem_t *elem)
 }
 
 /**
+ * \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
@@ -521,26 +594,22 @@ static int snd_mixer_compare_default(const snd_mixer_elem_t *c1,
        return c1->class->compare(c1, c2);
 }
 
-static snd_mixer_t *compare_mixer;
-static int mixer_compare(const void *a, const void *b) {
-       return compare_mixer->compare(*(const snd_mixer_elem_t * const *) a,
-                                     *(const snd_mixer_elem_t * const *) b);
+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);
 }
 
+typedef int (*qsort_func)(const void *, const void *);
 static int snd_mixer_sort(snd_mixer_t *mixer)
 {
        unsigned int k;
-       static pthread_mutex_t sync_lock = PTHREAD_MUTEX_INITIALIZER;
-
        assert(mixer);
        assert(mixer->compare);
        INIT_LIST_HEAD(&mixer->elems);
-
-       pthread_mutex_lock(&sync_lock);
-       compare_mixer = mixer;
-       qsort(mixer->pelems, mixer->count, sizeof(snd_mixer_elem_t*), mixer_compare);
-       pthread_mutex_unlock(&sync_lock);
-
+       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;
@@ -744,8 +813,8 @@ int snd_mixer_handle_events(snd_mixer_t *mixer)
 
 /**
  * \brief Set callback function for a mixer
- * \param obj mixer handle
- * \param val callback function
+ * \param mixer mixer handle
+ * \param callback callback function
  */
 void snd_mixer_set_callback(snd_mixer_t *obj, snd_mixer_callback_t val)
 {
@@ -755,78 +824,217 @@ void snd_mixer_set_callback(snd_mixer_t *obj, snd_mixer_callback_t val)
 
 /**
  * \brief Set callback private value for a mixer
- * \param obj mixer handle
- * \param val callback private value
+ * \param mixer mixer handle
+ * \param callback_private callback private value
  */
-void snd_mixer_set_callback_private(snd_mixer_t *obj, void * val)
+void snd_mixer_set_callback_private(snd_mixer_t *mixer, void * val)
 {
-       assert(obj);
-       obj->callback_private = val;
+       assert(mixer);
+       mixer->callback_private = val;
 }
 
 /**
  * \brief Get callback private value for a mixer
- * \param obj mixer handle
+ * \param mixer mixer handle
  * \return callback private value
  */
-void * snd_mixer_get_callback_private(const snd_mixer_t *obj)
+void * snd_mixer_get_callback_private(const snd_mixer_t *mixer)
 {
-       assert(obj);
-       return obj->callback_private;
+       assert(mixer);
+       return mixer->callback_private;
 }
 
 /**
  * \brief Get elements count for a mixer
- * \param obj mixer handle
+ * \param mixer mixer handle
  * \return elements count
  */
-unsigned int snd_mixer_get_count(const snd_mixer_t *obj)
+unsigned int snd_mixer_get_count(const snd_mixer_t *mixer)
 {
-       assert(obj);
-       return obj->count;
+       assert(mixer);
+       return mixer->count;
 }
 
 /**
  * \brief Set callback function for a mixer element
- * \param obj mixer element
+ * \param mixer mixer element
  * \param val callback function
  */
-void snd_mixer_elem_set_callback(snd_mixer_elem_t *obj, snd_mixer_elem_callback_t val)
+void snd_mixer_elem_set_callback(snd_mixer_elem_t *mixer, snd_mixer_elem_callback_t val)
 {
-       assert(obj);
-       obj->callback = val;
+       assert(mixer);
+       mixer->callback = val;
 }
 
 /**
  * \brief Set callback private value for a mixer element
- * \param obj mixer element
+ * \param mixer mixer element
  * \param val callback private value
  */
-void snd_mixer_elem_set_callback_private(snd_mixer_elem_t *obj, void * val)
+void snd_mixer_elem_set_callback_private(snd_mixer_elem_t *mixer, void * val)
 {
-       assert(obj);
-       obj->callback_private = val;
+       assert(mixer);
+       mixer->callback_private = val;
 }
 
 /**
  * \brief Get callback private value for a mixer element
- * \param obj mixer element
+ * \param mixer mixer element
  * \return callback private value
  */
-void * snd_mixer_elem_get_callback_private(const snd_mixer_elem_t *obj)
+void * snd_mixer_elem_get_callback_private(const snd_mixer_elem_t *mixer)
 {
-       assert(obj);
-       return obj->callback_private;
+       assert(mixer);
+       return mixer->callback_private;
 }
 
 /**
  * \brief Get type for a mixer element
- * \param obj 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 *obj)
+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 pointer to object to free
+ */
+void snd_mixer_class_free(snd_mixer_class_t *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->type;
+       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
+ * \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;
+}
index 24c1a48..0746093 100644 (file)
@@ -42,15 +42,10 @@ typedef struct list_head *bag_iterator_t;
 #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)
 
-#define MIXER_COMPARE_WEIGHT_SIMPLE_BASE       0
-#define MIXER_COMPARE_WEIGHT_NEXT_BASE         10000000
-#define MIXER_COMPARE_WEIGHT_NOT_FOUND         1000000000
-
 struct _snd_mixer_class {
        struct list_head list;
        snd_mixer_t *mixer;
-       int (*event)(snd_mixer_class_t *class, unsigned int mask,
-                    snd_hctl_elem_t *helem, snd_mixer_elem_t *melem);
+       snd_mixer_event_t event;
        void *private_data;             
        void (*private_free)(snd_mixer_class_t *class);
        snd_mixer_compare_t compare;
@@ -85,16 +80,3 @@ struct _snd_mixer_selem_id {
        unsigned char name[60];
        unsigned int index;
 };
-
-int snd_mixer_class_register(snd_mixer_class_t *class, snd_mixer_t *mixer);
-int snd_mixer_add_elem(snd_mixer_t *mixer, snd_mixer_elem_t *elem);
-int snd_mixer_remove_elem(snd_mixer_t *mixer, 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);
-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);
diff --git a/src/mixer/mixer_simple.h b/src/mixer/mixer_simple.h
new file mode 100644 (file)
index 0000000..9584bcf
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ *  Mixer Simple Interface - local header file
+ *  Copyright (c) 2005 by Jaroslav Kysela <perex@suse.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"
+
+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);
index 8b45ae8..5041a48 100644 (file)
@@ -3,13 +3,13 @@
  * \brief Mixer Simple Element Class Interface
  * \author Jaroslav Kysela <perex@suse.cz>
  * \author Abramo Bagnara <abramo@alsa-project.org>
- * \date 2001
+ * \date 2001-2004
  *
  * Mixer simple element class interface.
  */
 /*
  *  Mixer Interface - simple controls
- *  Copyright (c) 2000 by Jaroslav Kysela <perex@suse.cz>
+ *  Copyright (c) 2000,2004 by Jaroslav Kysela <perex@suse.cz>
  *  Copyright (c) 2001 by Abramo Bagnara <abramo@alsa-project.org>
  *
  *
 #include <sys/ioctl.h>
 #include <math.h>
 #include "mixer_local.h"
-
-#ifndef DOC_HIDDEN
-
-#define CAP_GVOLUME            (1<<1)
-#define CAP_GSWITCH            (1<<2)
-#define CAP_PVOLUME            (1<<3)
-#define CAP_PVOLUME_JOIN       (1<<4)
-#define CAP_PSWITCH            (1<<5)
-#define CAP_PSWITCH_JOIN       (1<<6)
-#define CAP_CVOLUME            (1<<7)
-#define CAP_CVOLUME_JOIN       (1<<8)
-#define CAP_CSWITCH            (1<<9)
-#define CAP_CSWITCH_JOIN       (1<<10)
-#define CAP_CSWITCH_EXCL       (1<<11)
-#define CAP_ENUM               (1<<12)
-
-typedef struct _mixer_simple mixer_simple_t;
-
-#define PLAY 0
-#define CAPT 1
-
-typedef enum _selem_ctl_type {
-       CTL_SINGLE,
-       CTL_ENUMLIST,
-       CTL_GLOBAL_SWITCH,
-       CTL_GLOBAL_VOLUME,
-       CTL_GLOBAL_ROUTE,
-       CTL_PLAYBACK_SWITCH,
-       CTL_PLAYBACK_VOLUME,
-       CTL_PLAYBACK_ROUTE,
-       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 access;
-       unsigned int values;
-       long min, max;
-} selem_ctl_t;
-
-typedef struct _selem {
-       snd_mixer_selem_id_t id;
-       selem_ctl_t ctls[CTL_LAST + 1];
-       unsigned int capture_item;
-       unsigned int capture_group;
-       unsigned int caps;
-       struct {
-               unsigned int range: 1;  /* Forced range */
-               long min, max;
-               unsigned int channels;
-               long vol[32];
-               unsigned int sw;
-       } str[2];
-} selem_t;
-
-static 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)
-{
-       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 *names[] = {
-               "Master",
-               "Headphone",
-               "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",
-               "Aux",
-               "Mono",
-               "Playback",
-               "Capture",
-               "Mix",
-               NULL
-       };
-       static const char *names1[] = {
-               "-",
-               NULL,
-       };
-       static const char *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_t *s, int dir, selem_ctl_t *c, long value)
-{
-       if (c->max == c->min) {
-               return s->str[dir].min;
-       } else {
-               int64_t 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_t *s, int dir, selem_ctl_t *c, long value)
-{
-       if (s->str[dir].max == s->str[dir].min) {
-               return c->min;
-       } else {
-               int64_t 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_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];
-       memset(&ctl, 0, sizeof(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, ctl.value.integer.value[idx1]);
-       }
-       return 0;
-}
-
-static int elem_read_switch(selem_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];
-       memset(&ctl, 0, sizeof(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 (!ctl.value.integer.value[idx1])
-                       s->str[dir].sw &= ~(1 << idx);
-       }
-       return 0;
-}
-
-static int elem_read_route(selem_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];
-       memset(&ctl, 0, sizeof(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 (!ctl.value.integer.value[idx1 * c->values + idx1])
-                       s->str[dir].sw &= ~(1 << idx);
-       }
-       return 0;
-}
-
-static int elem_read_enum(selem_t *s)
-{
-       snd_ctl_elem_value_t ctl;
-       unsigned int idx;
-       int err;
-       selem_ctl_t *c = &s->ctls[CTL_ENUMLIST];
-       memset(&ctl, 0, sizeof(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] = ctl.value.enumerated.item[idx1];
-       }
-       return 0;
-}
-
-static int selem_read(snd_mixer_elem_t *elem)
-{
-       selem_t *s;
-       unsigned int idx;
-       int err = 0;
-       long pvol[32], cvol[32];
-       unsigned int psw, csw;
-
-       assert(elem->type == SND_MIXER_ELEM_SIMPLE);
-       s = elem->private_data;
-
-       memcpy(pvol, s->str[PLAY].vol, sizeof(pvol));
-       memset(&s->str[PLAY].vol, 0, sizeof(s->str[PLAY].vol));
-       psw = s->str[PLAY].sw;
-       s->str[PLAY].sw = ~0U;
-       memcpy(cvol, s->str[CAPT].vol, sizeof(cvol));
-       memset(&s->str[CAPT].vol, 0, sizeof(s->str[CAPT].vol));
-       csw = s->str[CAPT].sw;
-       s->str[CAPT].sw = ~0U;
-
-       if (s->ctls[CTL_ENUMLIST].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, PLAY, CTL_PLAYBACK_VOLUME);
-       else if (s->ctls[CTL_GLOBAL_VOLUME].elem)
-               err = elem_read_volume(s, 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, PLAY, CTL_SINGLE);
-       if (err < 0)
-               return err;
-
-       if ((s->caps & (CAP_GSWITCH|CAP_PSWITCH)) == 0) {
-               s->str[PLAY].sw = 0;
-               goto __skip_pswitch;
-       }
-       if (s->ctls[CTL_PLAYBACK_SWITCH].elem) {
-               err = elem_read_switch(s, PLAY, CTL_PLAYBACK_SWITCH);
-               if (err < 0)
-                       return err;
-       }
-       if (s->ctls[CTL_GLOBAL_SWITCH].elem) {
-               err = elem_read_switch(s, 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, PLAY, CTL_SINGLE);
-               if (err < 0)
-                       return err;
-       }
-       if (s->ctls[CTL_PLAYBACK_ROUTE].elem) {
-               err = elem_read_route(s, PLAY, CTL_PLAYBACK_ROUTE);
-               if (err < 0)
-                       return err;
-       }
-       if (s->ctls[CTL_GLOBAL_ROUTE].elem) {
-               err = elem_read_route(s, PLAY, CTL_GLOBAL_ROUTE);
-               if (err < 0)
-                       return err;
-       }
-      __skip_pswitch:
-
-       if (s->ctls[CTL_CAPTURE_VOLUME].elem)
-               err = elem_read_volume(s, CAPT, CTL_CAPTURE_VOLUME);
-       else if (s->ctls[CTL_GLOBAL_VOLUME].elem)
-               err = elem_read_volume(s, 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, CAPT, CTL_SINGLE);
-       if (err < 0)
-               return err;
-
-       if ((s->caps & (CAP_GSWITCH|CAP_CSWITCH)) == 0) {
-               s->str[CAPT].sw = 0;
-               goto __skip_cswitch;
-       }
-       if (s->ctls[CTL_CAPTURE_SWITCH].elem) {
-               err = elem_read_switch(s, CAPT, CTL_CAPTURE_SWITCH);
-               if (err < 0)
-                       return err;
-       }
-       if (s->ctls[CTL_GLOBAL_SWITCH].elem) {
-               err = elem_read_switch(s, 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, CAPT, CTL_SINGLE);
-               if (err < 0)
-                       return err;
-       }
-       if (s->ctls[CTL_CAPTURE_ROUTE].elem) {
-               err = elem_read_route(s, CAPT, CTL_CAPTURE_ROUTE);
-               if (err < 0)
-                       return err;
-       }
-       if (s->ctls[CTL_GLOBAL_ROUTE].elem) {
-               err = elem_read_route(s, 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];
-               memset(&ctl, 0, sizeof(ctl));
-               err = snd_hctl_elem_read(c->elem, &ctl);
-               if (err < 0)
-                       return err;
-               for (idx = 0; idx < s->str[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[CAPT].sw &= ~(1 << idx);
-               }
-       }
-      __skip_cswitch:
-
-       if (memcmp(pvol, s->str[PLAY].vol, sizeof(pvol)) ||
-           psw != s->str[PLAY].sw ||
-           memcmp(cvol, s->str[CAPT].vol, sizeof(cvol)) ||
-           csw != s->str[CAPT].sw)
-               return 1;
-       return 0;
-}
-
-static int elem_write_volume(selem_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];
-       memset(&ctl, 0, sizeof(ctl));
-       if ((err = snd_hctl_elem_read(c->elem, &ctl)) < 0)
-               return err;
-       for (idx = 0; idx < c->values; idx++)
-               ctl.value.integer.value[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_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];
-       memset(&ctl, 0, sizeof(ctl));
-       if ((err = snd_hctl_elem_read(c->elem, &ctl)) < 0)
-               return err;
-       for (idx = 0; idx < c->values; idx++)
-               ctl.value.integer.value[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_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];
-       memset(&ctl, 0, sizeof(ctl));
-       if ((err = snd_hctl_elem_read(c->elem, &ctl)) < 0)
-               return err;
-       for (idx = 0; idx < c->values; idx++)
-               ctl.value.integer.value[idx] = !!val;
-       if ((err = snd_hctl_elem_write(c->elem, &ctl)) < 0)
-               return err;
-       return 0;
-}
-
-static int elem_write_route(selem_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];
-       memset(&ctl, 0, sizeof(ctl));
-       if ((err = snd_hctl_elem_read(c->elem, &ctl)) < 0)
-               return err;
-       for (idx = 0; idx < c->values * c->values; idx++)
-               ctl.value.integer.value[idx] = 0;
-       for (idx = 0; idx < c->values; idx++)
-               ctl.value.integer.value[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_t *s)
-{
-       snd_ctl_elem_value_t ctl;
-       unsigned int idx;
-       int err;
-       selem_ctl_t *c = &s->ctls[CTL_ENUMLIST];
-       memset(&ctl, 0, sizeof(ctl));
-       if ((err = snd_hctl_elem_read(c->elem, &ctl)) < 0)
-               return err;
-       for (idx = 0; idx < c->values; idx++)
-               ctl.value.enumerated.item[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(snd_mixer_elem_t *elem)
-{
-       selem_t *s;
-       unsigned int idx;
-       int err;
-
-       assert(elem->type == SND_MIXER_ELEM_SIMPLE);
-       s = elem->private_data;
-
-       if (s->ctls[CTL_ENUMLIST].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, PLAY, CTL_SINGLE);
-               else
-                       err = elem_write_switch(s, PLAY, CTL_SINGLE);
-               if (err < 0)
-                       return err;
-       }
-       if (s->ctls[CTL_GLOBAL_VOLUME].elem) {
-               err = elem_write_volume(s, 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, PLAY, CTL_GLOBAL_SWITCH);
-               if (err < 0)
-                       return err;
-       }
-       if (s->ctls[CTL_PLAYBACK_VOLUME].elem) {
-               err = elem_write_volume(s, PLAY, CTL_PLAYBACK_VOLUME);
-               if (err < 0)
-                       return err;
-       }
-       if (s->ctls[CTL_PLAYBACK_SWITCH].elem) {
-               err = elem_write_switch(s, PLAY, CTL_PLAYBACK_SWITCH);
-               if (err < 0)
-                       return err;
-       }
-       if (s->ctls[CTL_PLAYBACK_ROUTE].elem) {
-               err = elem_write_route(s, PLAY, CTL_PLAYBACK_ROUTE);
-               if (err < 0)
-                       return err;
-       }
-       if (s->ctls[CTL_CAPTURE_VOLUME].elem) {
-               err = elem_write_volume(s, CAPT, CTL_CAPTURE_VOLUME);
-               if (err < 0)
-                       return err;
-       }
-       if (s->ctls[CTL_CAPTURE_SWITCH].elem) {
-               err = elem_write_switch(s, CAPT, CTL_CAPTURE_SWITCH);
-               if (err < 0)
-                       return err;
-       }
-       if (s->ctls[CTL_CAPTURE_ROUTE].elem) {
-               err = elem_write_route(s, 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];
-               memset(&ctl, 0, sizeof(ctl));
-               if ((err = snd_hctl_elem_read(c->elem, &ctl)) < 0)
-                       return err;
-               for (idx = 0; idx < c->values; idx++) {
-                       if (s->str[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 void selem_free(snd_mixer_elem_t *elem)
-{
-       selem_t *s;
-       assert(elem->type == SND_MIXER_ELEM_SIMPLE);
-       s = elem->private_data;
-       elem->private_data = NULL;
-       free(s);
-}
-
-static int simple_update(snd_mixer_elem_t *melem)
-{
-       selem_t *simple;
-       unsigned int caps, pchannels, cchannels;
-       long pmin, pmax, cmin, cmax;
-       selem_ctl_t *ctl;
-
-       caps = 0;
-       pchannels = 0;
-       pmin = pmax = 0;
-       cchannels = 0;
-       cmin = cmax = 0;
-       assert(melem->type == SND_MIXER_ELEM_SIMPLE);
-       simple = melem->private_data;
-       ctl = &simple->ctls[CTL_SINGLE];
-       if (ctl->elem) {
-               pchannels = cchannels = ctl->values;
-               if (ctl->type == SNDRV_CTL_ELEM_TYPE_INTEGER) {
-                       caps |= CAP_GVOLUME;
-                       pmin = cmin = ctl->min;
-                       pmax = cmax = ctl->max;
-               } else
-                       caps |= 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 |= 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 |= 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 |= CAP_GVOLUME;
-       }
-       ctl = &simple->ctls[CTL_PLAYBACK_SWITCH];
-       if (ctl->elem) {
-               if (pchannels < ctl->values)
-                       pchannels = ctl->values;
-               caps |= CAP_PSWITCH;
-               caps &= ~CAP_GSWITCH;
-       }
-       ctl = &simple->ctls[CTL_PLAYBACK_ROUTE];
-       if (ctl->elem) {
-               if (pchannels < ctl->values)
-                       pchannels = ctl->values;
-               caps |= CAP_PSWITCH;
-               caps &= ~CAP_GSWITCH;
-       }
-       ctl = &simple->ctls[CTL_CAPTURE_SWITCH];
-       if (ctl->elem) {
-               if (cchannels < ctl->values)
-                       cchannels = ctl->values;
-               caps |= CAP_CSWITCH;
-               caps &= ~CAP_GSWITCH;
-       }
-       ctl = &simple->ctls[CTL_CAPTURE_ROUTE];
-       if (ctl->elem) {
-               if (cchannels < ctl->values)
-                       cchannels = ctl->values;
-               caps |= CAP_CSWITCH;
-               caps &= ~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 |= CAP_PVOLUME;
-               caps &= ~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 |= CAP_CVOLUME;
-               caps &= ~CAP_GVOLUME;
-       }
-       ctl = &simple->ctls[CTL_CAPTURE_SOURCE];
-       if (ctl->elem) {
-               if (cchannels < ctl->values)
-                       cchannels = ctl->values;
-               caps |= CAP_CSWITCH | CAP_CSWITCH_EXCL;
-               caps &= ~CAP_GSWITCH;
-       }
-       ctl = &simple->ctls[CTL_ENUMLIST];
-       if (ctl->elem) {
-               if (pchannels < ctl->values)
-                       pchannels = ctl->values;
-               caps |= CAP_ENUM;
-       }
-       if (pchannels > 32)
-               pchannels = 32;
-       if (cchannels > 32)
-               cchannels = 32;
-       if (caps & (CAP_GSWITCH|CAP_PSWITCH))
-               caps |= CAP_PSWITCH_JOIN;
-       if (caps & (CAP_GVOLUME|CAP_PVOLUME))
-               caps |= CAP_PVOLUME_JOIN;
-       if (caps & (CAP_GSWITCH|CAP_CSWITCH))
-               caps |= CAP_CSWITCH_JOIN;
-       if (caps & (CAP_GVOLUME|CAP_CVOLUME))
-               caps |= CAP_PVOLUME_JOIN;
-       if (pchannels > 1 || cchannels > 1) {
-               if (simple->ctls[CTL_SINGLE].elem &&
-                   simple->ctls[CTL_SINGLE].values > 1) {
-                       if (caps & CAP_GSWITCH)
-                               caps &= ~CAP_PSWITCH_JOIN;
-                       else
-                               caps &= ~CAP_PVOLUME_JOIN;
-               }
-               if (simple->ctls[CTL_GLOBAL_ROUTE].elem ||
-                   (simple->ctls[CTL_GLOBAL_SWITCH].elem &&
-                    simple->ctls[CTL_GLOBAL_SWITCH].values > 1)) {
-                       caps &= ~(CAP_PSWITCH_JOIN|CAP_CSWITCH_JOIN);
-               }
-               if (simple->ctls[CTL_GLOBAL_VOLUME].elem &&
-                   simple->ctls[CTL_GLOBAL_VOLUME].values > 1) {
-                       caps &= ~(CAP_PVOLUME_JOIN|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 &= ~CAP_PSWITCH_JOIN;
-               }
-               if (simple->ctls[CTL_PLAYBACK_VOLUME].elem &&
-                   simple->ctls[CTL_PLAYBACK_VOLUME].values > 1) {
-                       caps &= ~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)) {
-                       caps &= ~CAP_CSWITCH_JOIN;
-               }
-               if (simple->ctls[CTL_CAPTURE_VOLUME].elem &&
-                   simple->ctls[CTL_CAPTURE_VOLUME].values > 1) {
-                       caps &= ~CAP_CVOLUME_JOIN;
-               }
-       }
-
-       /* exceptions */
-       if ((caps & (CAP_GSWITCH|CAP_PSWITCH|CAP_CSWITCH)) &&
-           (caps & (CAP_GSWITCH|CAP_PSWITCH|CAP_CSWITCH)) == (caps & CAP_GSWITCH)) {
-               caps &= ~(CAP_GSWITCH|CAP_CSWITCH_JOIN|CAP_CSWITCH_EXCL);
-               caps |= CAP_PSWITCH;
-       }
-
-       simple->caps = caps;
-       simple->str[PLAY].channels = pchannels;
-       if (!simple->str[PLAY].range) {
-               simple->str[PLAY].min = pmin;
-               simple->str[PLAY].max = pmax;
-       }
-       simple->str[CAPT].channels = cchannels;
-       if (!simple->str[CAPT].range) {
-               simple->str[CAPT].min = cmin;
-               simple->str[CAPT].max = cmax;
-       }
-       return 0;
-}         
-
-#ifndef DOC_HIDDEN
-static struct suf {
-       const char *suffix;
-       selem_ctl_type_t type;
-} suffixes[] = {
-       {" Playback Switch", CTL_PLAYBACK_SWITCH},
-       {" Playback Route", CTL_PLAYBACK_ROUTE},
-       {" Playback Volume", CTL_PLAYBACK_VOLUME},
-       {" Capture Switch", CTL_CAPTURE_SWITCH},
-       {" Capture Route", CTL_CAPTURE_ROUTE},
-       {" Capture Volume", CTL_CAPTURE_VOLUME},
-       {" 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)
-{
-       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++;
-       }
-       return 0;
-}
-
-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_t *simple;
-       const char *name1;
-       memset(&info, 0, sizeof(info));
-       err = snd_hctl_elem_info(helem, &info);
-       if (err < 0)
-               return err;
-       switch (type) {
-       case CTL_SINGLE:
-               if (info.type == SND_CTL_ELEM_TYPE_ENUMERATED)
-                       type = CTL_ENUMLIST;
-               else if (info.type != SND_CTL_ELEM_TYPE_BOOLEAN &&
-                   info.type != SND_CTL_ELEM_TYPE_INTEGER)
-                       return 0;
-               break;
-       case CTL_GLOBAL_ROUTE:
-       case CTL_PLAYBACK_ROUTE:
-       case CTL_CAPTURE_ROUTE:
-       {
-               unsigned int n;
-               if (info.type == SND_CTL_ELEM_TYPE_ENUMERATED) {
-                       type = CTL_ENUMLIST;
-                       break;
-               }
-               if (info.type != SND_CTL_ELEM_TYPE_BOOLEAN)
-                       return 0;
-               n = sqrt((double)info.count);
-               if (n * n != info.count)
-                       return 0;
-               info.count = n;
-               break;
-       }
-       case CTL_GLOBAL_SWITCH:
-       case CTL_PLAYBACK_SWITCH:
-       case CTL_CAPTURE_SWITCH:
-               if (info.type == SND_CTL_ELEM_TYPE_ENUMERATED) {
-                       type = CTL_ENUMLIST;
-                       break;
-               }
-               if (info.type != SND_CTL_ELEM_TYPE_BOOLEAN)
-                       return 0;
-               break;
-       case CTL_GLOBAL_VOLUME:
-       case CTL_PLAYBACK_VOLUME:
-       case CTL_CAPTURE_VOLUME:
-               if (info.type == SND_CTL_ELEM_TYPE_ENUMERATED) {
-                       type = CTL_ENUMLIST;
-                       break;
-               }
-               if (info.type != SND_CTL_ELEM_TYPE_INTEGER)
-                       return 0;
-               break;
-       case CTL_CAPTURE_SOURCE:
-               if (info.type != SND_CTL_ELEM_TYPE_ENUMERATED)
-                       return 0;
-               break;
-       default:
-               assert(0);
-               break;
-       }
-       name1 = get_short_name(name);
-       strncpy(id.name, name1, sizeof(id.name));
-       id.index = snd_hctl_elem_get_index(helem);
-       melem = snd_mixer_find_selem(class->mixer, &id);
-       if (!melem) {
-               simple = calloc(1, sizeof(*simple));
-               if (!simple)
-                       return -ENOMEM;
-               melem = calloc(1, sizeof(*melem));
-               if (!melem) {
-                       free(simple);
-                       return -ENOMEM;
-               }
-               simple->id = id;
-               melem->type = SND_MIXER_ELEM_SIMPLE;
-               melem->private_data = simple;
-               melem->private_free = selem_free;
-               INIT_LIST_HEAD(&melem->helems);
-               melem->compare_weight = get_compare_weight(simple->id.name, simple->id.index);
-               new = 1;
-       } else {
-               simple = melem->private_data;
-       }
-       if (simple->ctls[type].elem) {
-               SNDERR("helem (%s,'%s',%li,%li,%li) 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));
-               return -EINVAL;
-       }
-       simple->ctls[type].elem = helem;
-       simple->ctls[type].type = info.type;
-       simple->ctls[type].access = info.access;
-       simple->ctls[type].values = info.count;
-       if (type == CTL_ENUMLIST) {
-               simple->ctls[type].min = 0;
-               simple->ctls[type].max = info.value.enumerated.items;
-       } else {
-               simple->ctls[type].min = info.value.integer.min;
-               simple->ctls[type].max = info.value.integer.max;
-       }
-       switch (type) {
-       case CTL_CAPTURE_SOURCE:
-               simple->capture_item = value;
-               break;
-       default:
-               break;
-       }
-       err = snd_mixer_elem_attach(melem, helem);
-       if (err < 0)
-               return err;
-       err = simple_update(melem);
-       if (err < 0)
-               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;
-}
-
-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;
-       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_t *simple = melem->private_data;
-       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;
-}
-
-static int simple_compare(const snd_mixer_elem_t *c1, const snd_mixer_elem_t *c2)
-{
-       selem_t *s1 = c1->private_data;
-       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;
-}
-
+#include "mixer_simple.h"
+#include "alisp.h"
 
 /**
  * \brief Register mixer simple element class
  * \param mixer Mixer handle
- * \param options Options container (not used now)
+ * \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 ATTRIBUTE_UNUSED,
+int snd_mixer_selem_register(snd_mixer_t *mixer,
+                            struct snd_mixer_selem_regopt *options,
                             snd_mixer_class_t **classp)
 {
-       snd_mixer_class_t *class = calloc(1, sizeof(*class));
-       int err;
-       if (!class)
-               return -ENOMEM;
-       class->event = simple_event;
-       class->compare = simple_compare;
-       err = snd_mixer_class_register(class, mixer);
-       if (err < 0) {
-               free(class);
-               return err;
+       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 (classp)
-               *classp = class;
-       return 0;
+       if (options == NULL ||
+           (options->ver == 1 && options->abstract == SND_MIXER_SABSTRACT_NONE)) {
+               return snd_mixer_simple_none_register(mixer, options, classp);
+       } 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
+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
@@ -1142,14 +93,15 @@ 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) {
-               snd_mixer_elem_t *e;
-               selem_t *s;
                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)
+               if (!strcmp(s->id->name, id->name) && s->id->index == id->index)
                        return e;
        }
        return NULL;
@@ -1163,11 +115,11 @@ snd_mixer_elem_t *snd_mixer_find_selem(snd_mixer_t *mixer,
 void snd_mixer_selem_get_id(snd_mixer_elem_t *elem,
                            snd_mixer_selem_id_t *id)
 {
-       selem_t *s;
+       sm_selem_t *s;
        assert(elem && id);
        assert(elem->type == SND_MIXER_ELEM_SIMPLE);
        s = elem->private_data;
-       *id = s->id;
+       *id = *s->id;
 }
 
 /**
@@ -1177,11 +129,11 @@ void snd_mixer_selem_get_id(snd_mixer_elem_t *elem,
  */
 const char *snd_mixer_selem_get_name(snd_mixer_elem_t *elem)
 {
-       selem_t *s;
+       sm_selem_t *s;
        assert(elem);
        assert(elem->type == SND_MIXER_ELEM_SIMPLE);
        s = elem->private_data;
-       return s->id.name;
+       return s->id->name;
 }
 
 /**
@@ -1191,11 +143,11 @@ const char *snd_mixer_selem_get_name(snd_mixer_elem_t *elem)
  */
 unsigned int snd_mixer_selem_get_index(snd_mixer_elem_t *elem)
 {
-       selem_t *s;
+       sm_selem_t *s;
        assert(elem);
        assert(elem->type == SND_MIXER_ELEM_SIMPLE);
        s = elem->private_data;
-       return s->id.index;
+       return s->id->index;
 }
 
 /**
@@ -1205,11 +157,11 @@ unsigned int snd_mixer_selem_get_index(snd_mixer_elem_t *elem)
  */
 int snd_mixer_selem_has_common_volume(snd_mixer_elem_t *elem)
 {
-       selem_t *s;
+       sm_selem_t *s;
        assert(elem);
        assert(elem->type == SND_MIXER_ELEM_SIMPLE);
        s = elem->private_data;
-       return !!(s->caps & CAP_GVOLUME);
+       return !!(s->caps & SM_CAP_GVOLUME);
 }
 
 /**
@@ -1219,83 +171,11 @@ int snd_mixer_selem_has_common_volume(snd_mixer_elem_t *elem)
  */
 int snd_mixer_selem_has_common_switch(snd_mixer_elem_t *elem)
 {
-       selem_t *s;
+       sm_selem_t *s;
        assert(elem);
        assert(elem->type == SND_MIXER_ELEM_SIMPLE);
        s = elem->private_data;
-       return !!(s->caps & CAP_GSWITCH);
-}
-
-static int _snd_mixer_selem_set_volume(snd_mixer_elem_t *elem, int dir, snd_mixer_selem_channel_id_t channel, long value)
-{
-       selem_t *s = elem->private_data;
-       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->caps & 
-           (dir == PLAY ? CAP_PVOLUME_JOIN : 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_volume_all(snd_mixer_elem_t *elem, int dir, long value)
-{
-       int changed = 0;
-       snd_mixer_selem_channel_id_t channel;   
-       selem_t *s = elem->private_data;
-       if (value < s->str[dir].min || value > s->str[dir].max)
-               return 0;
-       for (channel = 0; (unsigned int) channel < s->str[dir].channels; channel++) {
-               if (value != s->str[dir].vol[channel]) {
-                       s->str[dir].vol[channel] = value;
-                       changed = 1;
-               }
-       }
-       return changed;
-}
-
-static int _snd_mixer_selem_set_switch(snd_mixer_elem_t *elem, int dir, snd_mixer_selem_channel_id_t channel, int value)
-{
-       selem_t *s = elem->private_data;
-       if ((unsigned int) channel >= s->str[dir].channels)
-               return 0;
-       if (s->caps & 
-           (dir == PLAY ? CAP_PSWITCH_JOIN : 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 _snd_mixer_selem_set_switch_all(snd_mixer_elem_t *elem, int dir, int value)
-{
-       selem_t *s = elem->private_data;
-       if (value) {
-               if (s->str[dir].sw != ~0U) {
-                       s->str[dir].sw = ~0U;
-                       return 1;
-               }
-       } else {
-               if (s->str[dir].sw != 0U) {
-                       s->str[dir].sw = 0U;
-                       return 1;
-               }
-       }
-       return 0;
+       return !!(s->caps & SM_CAP_GSWITCH);
 }
 
 /**
@@ -1308,10 +188,13 @@ const char *snd_mixer_selem_channel_name(snd_mixer_selem_channel_id_t channel)
        static const char *array[SND_MIXER_SCHN_LAST + 1] = {
                [SND_MIXER_SCHN_FRONT_LEFT] = "Front Left",
                [SND_MIXER_SCHN_FRONT_RIGHT] = "Front Right",
-               [SND_MIXER_SCHN_FRONT_CENTER] = "Front Center",
                [SND_MIXER_SCHN_REAR_LEFT] = "Rear Left",
                [SND_MIXER_SCHN_REAR_RIGHT] = "Rear Right",
-               [SND_MIXER_SCHN_WOOFER] = "Woofer"
+               [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);
@@ -1328,15 +211,9 @@ const char *snd_mixer_selem_channel_name(snd_mixer_selem_channel_id_t channel)
  */
 int snd_mixer_selem_is_active(snd_mixer_elem_t *elem)
 {
-       selem_t *s;
-       selem_ctl_type_t ctl;
        assert(elem);
        assert(elem->type == SND_MIXER_ELEM_SIMPLE);
-       s = elem->private_data;
-       for (ctl = CTL_SINGLE; ctl <= CTL_LAST; ctl++)
-               if (s->ctls[ctl].elem != NULL && (s->ctls[ctl].access & SNDRV_CTL_ELEM_ACCESS_INACTIVE) != 0)
-                       return 0;
-       return 1;
+       return sm_selem_ops(elem)->is(elem, SM_PLAY, SM_OPS_IS_ACTIVE, 0);
 }
 
 /**
@@ -1346,11 +223,9 @@ int snd_mixer_selem_is_active(snd_mixer_elem_t *elem)
  */
 int snd_mixer_selem_is_playback_mono(snd_mixer_elem_t *elem)
 {
-       selem_t *s;
        assert(elem);
        assert(elem->type == SND_MIXER_ELEM_SIMPLE);
-       s = elem->private_data;
-       return s->str[PLAY].channels == 1;
+       return sm_selem_ops(elem)->is(elem, SM_PLAY, SM_OPS_IS_MONO, 0);
 }
 
 /**
@@ -1361,11 +236,9 @@ int snd_mixer_selem_is_playback_mono(snd_mixer_elem_t *elem)
  */
 int snd_mixer_selem_has_playback_channel(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel)
 {
-       selem_t *s;
        assert(elem);
        assert(elem->type == SND_MIXER_ELEM_SIMPLE);
-       s = elem->private_data;
-       return (unsigned int) channel < s->str[PLAY].channels;
+       return sm_selem_ops(elem)->is(elem, SM_PLAY, SM_OPS_IS_CHANNEL, (int)channel);
 }
 
 /**
@@ -1377,12 +250,23 @@ int snd_mixer_selem_has_playback_channel(snd_mixer_elem_t *elem, snd_mixer_selem
 void snd_mixer_selem_get_playback_volume_range(snd_mixer_elem_t *elem,
                                               long *min, long *max)
 {
-       selem_t *s;
        assert(elem);
        assert(elem->type == SND_MIXER_ELEM_SIMPLE);
-       s = elem->private_data;
-       *min = s->str[PLAY].min;
-       *max = s->str[PLAY].max;
+       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)
+ */
+void snd_mixer_selem_get_playback_dB_range(snd_mixer_elem_t *elem,
+                                          long *min, long *max)
+{
+       assert(elem);
+       assert(elem->type == SND_MIXER_ELEM_SIMPLE);
+       sm_selem_ops(elem)->get_dB_range(elem, SM_PLAY, min, max);
 }
 
 /**
@@ -1394,15 +278,10 @@ void snd_mixer_selem_get_playback_volume_range(snd_mixer_elem_t *elem,
 void snd_mixer_selem_set_playback_volume_range(snd_mixer_elem_t *elem, 
                                               long min, long max)
 {
-       selem_t *s;
        assert(elem);
        assert(elem->type == SND_MIXER_ELEM_SIMPLE);
        assert(min < max);
-       s = elem->private_data;
-       s->str[PLAY].range = 1;
-       s->str[PLAY].min = min;
-       s->str[PLAY].max = max;
-       selem_read(elem);
+       sm_selem_ops(elem)->set_range(elem, SM_PLAY, min, max);
 }
 
 /**
@@ -1412,11 +291,11 @@ void snd_mixer_selem_set_playback_volume_range(snd_mixer_elem_t *elem,
  */
 int snd_mixer_selem_has_playback_volume(snd_mixer_elem_t *elem)
 {
-       selem_t *s;
+       sm_selem_t *s;
        assert(elem);
        assert(elem->type == SND_MIXER_ELEM_SIMPLE);
        s = elem->private_data;
-       return !!(s->caps & CAP_PVOLUME) || !!(s->caps & CAP_GVOLUME);
+       return !!(s->caps & SM_CAP_PVOLUME) || !!(s->caps & SM_CAP_GVOLUME);
 }
 
 /**
@@ -1426,11 +305,11 @@ 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)
 {
-       selem_t *s;
+       sm_selem_t *s;
        assert(elem);
        assert(elem->type == SND_MIXER_ELEM_SIMPLE);
        s = elem->private_data;
-       return !!(s->caps & CAP_PVOLUME_JOIN);
+       return !!(s->caps & SM_CAP_PVOLUME_JOIN);
 }
 
 /**
@@ -1440,11 +319,11 @@ int snd_mixer_selem_has_playback_volume_joined(snd_mixer_elem_t *elem)
  */
 int snd_mixer_selem_has_playback_switch(snd_mixer_elem_t *elem)
 {
-       selem_t *s;
+       sm_selem_t *s;
        assert(elem);
        assert(elem->type == SND_MIXER_ELEM_SIMPLE);
        s = elem->private_data;
-       return !!(s->caps & CAP_PSWITCH) || !!(s->caps & CAP_GSWITCH);
+       return !!(s->caps & SM_CAP_PSWITCH) || !!(s->caps & SM_CAP_GSWITCH);
 }
 
 /**
@@ -1454,11 +333,11 @@ 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)
 {
-       selem_t *s;
+       sm_selem_t *s;
        assert(elem);
        assert(elem->type == SND_MIXER_ELEM_SIMPLE);
        s = elem->private_data;
-       return !!(s->caps & CAP_PSWITCH_JOIN);
+       return !!(s->caps & SM_CAP_PSWITCH_JOIN);
 }
 
 /**
@@ -1470,18 +349,23 @@ int snd_mixer_selem_has_playback_switch_joined(snd_mixer_elem_t *elem)
  */
 int snd_mixer_selem_get_playback_volume(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, long *value)
 {
-       selem_t *s;
        assert(elem);
        assert(elem->type == SND_MIXER_ELEM_SIMPLE);
-       s = elem->private_data;
-       if ((unsigned int) channel >= s->str[PLAY].channels)
-               return -EINVAL;
-       if (! (s->caps & (CAP_PVOLUME|CAP_GVOLUME)))
-               return -EINVAL;
-       if (s->caps & CAP_PVOLUME_JOIN)
-               channel = 0;
-       *value = s->str[PLAY].vol[channel];
-       return 0;
+       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)
+{
+       assert(elem);
+       assert(elem->type == SND_MIXER_ELEM_SIMPLE);
+       return sm_selem_ops(elem)->get_dB(elem, SM_PLAY, channel, value);
 }
 
 /**
@@ -1493,18 +377,9 @@ int snd_mixer_selem_get_playback_volume(snd_mixer_elem_t *elem, snd_mixer_selem_
  */
 int snd_mixer_selem_get_playback_switch(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, int *value)
 {
-       selem_t *s;
        assert(elem);
        assert(elem->type == SND_MIXER_ELEM_SIMPLE);
-       s = elem->private_data;
-       if ((unsigned int) channel >= s->str[PLAY].channels)
-               return -EINVAL;
-       if (! (s->caps & (CAP_PSWITCH|CAP_GSWITCH)))
-               return -EINVAL;
-       if (s->caps & CAP_PSWITCH_JOIN)
-               channel = 0;
-       *value = !!(s->str[PLAY].sw & (1 << channel));
-       return 0;
+       return sm_selem_ops(elem)->get_switch(elem, SM_PLAY, channel, value);
 }
 
 /**
@@ -1516,19 +391,23 @@ int snd_mixer_selem_get_playback_switch(snd_mixer_elem_t *elem, snd_mixer_selem_
  */
 int snd_mixer_selem_set_playback_volume(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, long value)
 {
-       int changed;
-       selem_t *s;
        assert(elem);
        assert(elem->type == SND_MIXER_ELEM_SIMPLE);
-       s = elem->private_data;
-       if (! (s->caps & (CAP_GVOLUME|CAP_PVOLUME)))
-               return -EINVAL;
-       changed = _snd_mixer_selem_set_volume(elem, PLAY, channel, value);
-       if (changed < 0)
-               return changed;
-       if (changed)
-               return selem_write(elem);
-       return 0;
+       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
+ * \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)
+{
+       assert(elem);
+       assert(elem->type == SND_MIXER_ELEM_SIMPLE);
+       return sm_selem_ops(elem)->set_dB(elem, SM_PLAY, channel, value, dir);
 }
 
 /**
@@ -1539,19 +418,22 @@ int snd_mixer_selem_set_playback_volume(snd_mixer_elem_t *elem, snd_mixer_selem_
  */
 int snd_mixer_selem_set_playback_volume_all(snd_mixer_elem_t *elem, long value)
 {
-       int changed;
-       selem_t *s;
        assert(elem);
        assert(elem->type == SND_MIXER_ELEM_SIMPLE);
-       s = elem->private_data;
-       if (! (s->caps & (CAP_GVOLUME|CAP_PVOLUME)))
-               return -EINVAL;
-       changed = _snd_mixer_selem_set_volume_all(elem, PLAY, value);
-       if (changed < 0)
-               return changed;
-       if (changed)
-               return selem_write(elem);
-       return 0;
+       return sm_selem_ops(elem)->set_volume_all(elem, SM_PLAY, value);
+}
+
+/**
+ * \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
+ * \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)
+{
+       assert(elem);
+       assert(elem->type == SND_MIXER_ELEM_SIMPLE);
+       return sm_selem_ops(elem)->set_dB_all(elem, SM_PLAY, value, dir);
 }
 
 /**
@@ -1563,19 +445,9 @@ int snd_mixer_selem_set_playback_volume_all(snd_mixer_elem_t *elem, long value)
  */
 int snd_mixer_selem_set_playback_switch(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, int value)
 {
-       int changed;
-       selem_t *s;
        assert(elem);
        assert(elem->type == SND_MIXER_ELEM_SIMPLE);
-       s = elem->private_data;
-       if (! (s->caps & (CAP_GSWITCH|CAP_PSWITCH)))
-               return -EINVAL;
-       changed = _snd_mixer_selem_set_switch(elem, PLAY, channel, value);
-       if (changed < 0)
-               return changed;
-       if (changed)
-               return selem_write(elem);
-       return 0;
+       return sm_selem_ops(elem)->set_switch(elem, SM_PLAY, channel, value);
 }
 
 /**
@@ -1586,19 +458,9 @@ int snd_mixer_selem_set_playback_switch(snd_mixer_elem_t *elem, snd_mixer_selem_
  */
 int snd_mixer_selem_set_playback_switch_all(snd_mixer_elem_t *elem, int value)
 {
-       int changed;
-       selem_t *s;
        assert(elem);
        assert(elem->type == SND_MIXER_ELEM_SIMPLE);
-       s = elem->private_data;
-       if (! (s->caps & (CAP_GSWITCH|CAP_PSWITCH)))
-               return -EINVAL;
-       changed = _snd_mixer_selem_set_switch_all(elem, PLAY, value);
-       if (changed < 0)
-               return changed;
-       if (changed)
-               return selem_write(elem);
-       return 0;
+       return sm_selem_ops(elem)->set_switch_all(elem, SM_PLAY, value);
 }
 
 /**
@@ -1608,11 +470,9 @@ int snd_mixer_selem_set_playback_switch_all(snd_mixer_elem_t *elem, int value)
  */
 int snd_mixer_selem_is_capture_mono(snd_mixer_elem_t *elem)
 {
-       selem_t *s;
        assert(elem);
        assert(elem->type == SND_MIXER_ELEM_SIMPLE);
-       s = elem->private_data;
-       return s->str[CAPT].channels == 1;
+       return sm_selem_ops(elem)->is(elem, SM_CAPT, SM_OPS_IS_MONO, 0);
 }
 
 /**
@@ -1623,11 +483,9 @@ int snd_mixer_selem_is_capture_mono(snd_mixer_elem_t *elem)
  */
 int snd_mixer_selem_has_capture_channel(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel)
 {
-       selem_t *s;
        assert(elem);
        assert(elem->type == SND_MIXER_ELEM_SIMPLE);
-       s = elem->private_data;
-       return (unsigned int) channel < s->str[CAPT].channels;
+       return sm_selem_ops(elem)->is(elem, SM_CAPT, SM_OPS_IS_CHANNEL, channel);
 }
 
 /**
@@ -1639,12 +497,23 @@ int snd_mixer_selem_has_capture_channel(snd_mixer_elem_t *elem, snd_mixer_selem_
 void snd_mixer_selem_get_capture_volume_range(snd_mixer_elem_t *elem,
                                              long *min, long *max)
 {
-       selem_t *s;
        assert(elem);
        assert(elem->type == SND_MIXER_ELEM_SIMPLE);
-       s = elem->private_data;
-       *min = s->str[CAPT].min;
-       *max = s->str[CAPT].max;
+       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)
+ */
+void snd_mixer_selem_get_capture_dB_range(snd_mixer_elem_t *elem,
+                                         long *min, long *max)
+{
+       assert(elem);
+       assert(elem->type == SND_MIXER_ELEM_SIMPLE);
+       sm_selem_ops(elem)->get_dB_range(elem, SM_CAPT, min, max);
 }
 
 /**
@@ -1656,15 +525,10 @@ void snd_mixer_selem_get_capture_volume_range(snd_mixer_elem_t *elem,
 void snd_mixer_selem_set_capture_volume_range(snd_mixer_elem_t *elem, 
                                              long min, long max)
 {
-       selem_t *s;
        assert(elem);
        assert(elem->type == SND_MIXER_ELEM_SIMPLE);
        assert(min < max);
-       s = elem->private_data;
-       s->str[CAPT].range = 1;
-       s->str[CAPT].min = min;
-       s->str[CAPT].max = max;
-       selem_read(elem);
+       sm_selem_ops(elem)->set_range(elem, SM_CAPT, min, max);
 }
 
 /**
@@ -1674,11 +538,11 @@ void snd_mixer_selem_set_capture_volume_range(snd_mixer_elem_t *elem,
  */
 int snd_mixer_selem_has_capture_volume(snd_mixer_elem_t *elem)
 {
-       selem_t *s;
+       sm_selem_t *s;
        assert(elem);
        assert(elem->type == SND_MIXER_ELEM_SIMPLE);
        s = elem->private_data;
-       return !!(s->caps & CAP_CVOLUME) /*|| !!(s->caps & CAP_GVOLUME)*/;
+       return !!(s->caps & SM_CAP_CVOLUME);
 }
 
 /**
@@ -1688,11 +552,11 @@ 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)
 {
-       selem_t *s;
+       sm_selem_t *s;
        assert(elem);
        assert(elem->type == SND_MIXER_ELEM_SIMPLE);
        s = elem->private_data;
-       return !!(s->caps & CAP_CVOLUME_JOIN);
+       return !!(s->caps & SM_CAP_CVOLUME_JOIN);
 }
 
 /**
@@ -1702,11 +566,11 @@ int snd_mixer_selem_has_capture_volume_joined(snd_mixer_elem_t *elem)
  */
 int snd_mixer_selem_has_capture_switch(snd_mixer_elem_t *elem)
 {
-       selem_t *s;
+       sm_selem_t *s;
        assert(elem);
        assert(elem->type == SND_MIXER_ELEM_SIMPLE);
        s = elem->private_data;
-       return !!(s->caps & CAP_CSWITCH) /*|| !!(s->caps & CAP_GSWITCH)*/;
+       return !!(s->caps & SM_CAP_CSWITCH);
 }
 
 /**
@@ -1716,11 +580,11 @@ 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)
 {
-       selem_t *s;
+       sm_selem_t *s;
        assert(elem);
        assert(elem->type == SND_MIXER_ELEM_SIMPLE);
        s = elem->private_data;
-       return !!(s->caps & CAP_CSWITCH_JOIN);
+       return !!(s->caps & SM_CAP_CSWITCH_JOIN);
 }
 
 /**
@@ -1730,11 +594,11 @@ 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)
 {
-       selem_t *s;
+       sm_selem_t *s;
        assert(elem);
        assert(elem->type == SND_MIXER_ELEM_SIMPLE);
        s = elem->private_data;
-       return !!(s->caps & CAP_CSWITCH_EXCL);
+       return !!(s->caps & SM_CAP_CSWITCH_EXCL);
 }
 
 /**
@@ -1744,11 +608,11 @@ int snd_mixer_selem_has_capture_switch_exclusive(snd_mixer_elem_t *elem)
  */
 int snd_mixer_selem_get_capture_group(snd_mixer_elem_t *elem)
 {
-       selem_t *s;
+       sm_selem_t *s;
        assert(elem);
        assert(elem->type == SND_MIXER_ELEM_SIMPLE);
        s = elem->private_data;
-       if (! (s->caps & CAP_CSWITCH_EXCL))
+       if (! (s->caps & SM_CAP_CSWITCH_EXCL))
                return -EINVAL;
        return s->capture_group;
 }
@@ -1762,18 +626,23 @@ int snd_mixer_selem_get_capture_group(snd_mixer_elem_t *elem)
  */
 int snd_mixer_selem_get_capture_volume(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, long *value)
 {
-       selem_t *s;
        assert(elem);
        assert(elem->type == SND_MIXER_ELEM_SIMPLE);
-       s = elem->private_data;
-       if ((unsigned int) channel >= s->str[CAPT].channels)
-               return -EINVAL;
-       if (! (s->caps & (CAP_GVOLUME|CAP_CVOLUME)))
-               return -EINVAL;
-       if (s->caps & CAP_CVOLUME_JOIN)
-               channel = 0;
-       *value = s->str[CAPT].vol[channel];
-       return 0;
+       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)
+{
+       assert(elem);
+       assert(elem->type == SND_MIXER_ELEM_SIMPLE);
+       return sm_selem_ops(elem)->get_dB(elem, SM_CAPT, channel, value);
 }
 
 /**
@@ -1785,18 +654,9 @@ int snd_mixer_selem_get_capture_volume(snd_mixer_elem_t *elem, snd_mixer_selem_c
  */
 int snd_mixer_selem_get_capture_switch(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, int *value)
 {
-       selem_t *s;
        assert(elem);
        assert(elem->type == SND_MIXER_ELEM_SIMPLE);
-       s = elem->private_data;
-       if ((unsigned int) channel >= s->str[CAPT].channels)
-               return -EINVAL;
-       if (! (s->caps & (CAP_GSWITCH | CAP_CSWITCH)))
-               return -EINVAL;
-       if (s->caps & CAP_CSWITCH_JOIN)
-               channel = 0;
-       *value = !!(s->str[CAPT].sw & (1 << channel));
-       return 0;
+       return sm_selem_ops(elem)->get_switch(elem, SM_CAPT, channel, value);
 }
 
 /**
@@ -1808,19 +668,23 @@ int snd_mixer_selem_get_capture_switch(snd_mixer_elem_t *elem, snd_mixer_selem_c
  */
 int snd_mixer_selem_set_capture_volume(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, long value)
 {
-       int changed;
-       selem_t *s;
        assert(elem);
        assert(elem->type == SND_MIXER_ELEM_SIMPLE);
-       s = elem->private_data;
-       if (! (s->caps & (CAP_GVOLUME | CAP_CVOLUME)))
-               return -EINVAL;
-       changed = _snd_mixer_selem_set_volume(elem, CAPT, channel, value);
-       if (changed < 0)
-               return changed;
-       if (changed)
-               return selem_write(elem);
-       return 0;
+       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
+ * \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)
+{
+       assert(elem);
+       assert(elem->type == SND_MIXER_ELEM_SIMPLE);
+       return sm_selem_ops(elem)->set_dB(elem, SM_CAPT, channel, value, dir);
 }
 
 /**
@@ -1831,19 +695,22 @@ int snd_mixer_selem_set_capture_volume(snd_mixer_elem_t *elem, snd_mixer_selem_c
  */
 int snd_mixer_selem_set_capture_volume_all(snd_mixer_elem_t *elem, long value)
 {
-       int changed;
-       selem_t *s;
        assert(elem);
        assert(elem->type == SND_MIXER_ELEM_SIMPLE);
-       s = elem->private_data;
-       if (! (s->caps & (CAP_GVOLUME | CAP_CVOLUME)))
-               return -EINVAL;
-       changed = _snd_mixer_selem_set_volume_all(elem, CAPT, value);
-       if (changed < 0)
-               return changed;
-       if (changed)
-               return selem_write(elem);
-       return 0;
+       return sm_selem_ops(elem)->set_volume_all(elem, SM_CAPT, value);
+}
+
+/**
+ * \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
+ * \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)
+{
+       assert(elem);
+       assert(elem->type == SND_MIXER_ELEM_SIMPLE);
+       return sm_selem_ops(elem)->set_dB_all(elem, SM_CAPT, value, dir);
 }
 
 /**
@@ -1855,19 +722,9 @@ int snd_mixer_selem_set_capture_volume_all(snd_mixer_elem_t *elem, long value)
  */
 int snd_mixer_selem_set_capture_switch(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, int value)
 {
-       int changed;
-       selem_t *s;
        assert(elem);
        assert(elem->type == SND_MIXER_ELEM_SIMPLE);
-       s = elem->private_data;
-       if (! (s->caps & (CAP_GSWITCH | CAP_CSWITCH)))
-               return -EINVAL;
-       changed = _snd_mixer_selem_set_switch(elem, CAPT, channel, value);
-       if (changed < 0)
-               return changed;
-       if (changed)
-               return selem_write(elem);
-       return 0;
+       return sm_selem_ops(elem)->set_switch(elem, SM_CAPT, channel, value);
 }
 
 /**
@@ -1878,19 +735,9 @@ int snd_mixer_selem_set_capture_switch(snd_mixer_elem_t *elem, snd_mixer_selem_c
  */
 int snd_mixer_selem_set_capture_switch_all(snd_mixer_elem_t *elem, int value)
 {
-       int changed;
-       selem_t *s;
        assert(elem);
        assert(elem->type == SND_MIXER_ELEM_SIMPLE);
-       s = elem->private_data;
-       if (! (s->caps & (CAP_GSWITCH | CAP_CSWITCH)))
-               return -EINVAL;
-       changed = _snd_mixer_selem_set_switch_all(elem, CAPT, value);
-       if (changed < 0)
-               return changed;
-       if (changed)
-               return selem_write(elem);
-       return 0;
+       return sm_selem_ops(elem)->set_switch_all(elem, SM_CAPT, value);
 }
 
 /**
@@ -1900,11 +747,9 @@ int snd_mixer_selem_set_capture_switch_all(snd_mixer_elem_t *elem, int value)
  */
 int snd_mixer_selem_is_enumerated(snd_mixer_elem_t *elem)
 {
-       selem_t *s;
        assert(elem);
        assert(elem->type == SND_MIXER_ELEM_SIMPLE);
-       s = elem->private_data;
-       return s->ctls[CTL_ENUMLIST].elem != 0;
+       return sm_selem_ops(elem)->is(elem, SM_PLAY, SM_OPS_IS_ENUMERATED, 0);
 }
 
 /**
@@ -1914,13 +759,9 @@ int snd_mixer_selem_is_enumerated(snd_mixer_elem_t *elem)
  */
 int snd_mixer_selem_get_enum_items(snd_mixer_elem_t *elem)
 {
-       selem_t *s;
        assert(elem);
        assert(elem->type == SND_MIXER_ELEM_SIMPLE);
-       s = elem->private_data;
-       if (! s->ctls[CTL_ENUMLIST].elem)
-               return -EINVAL;
-       return s->ctls[CTL_ENUMLIST].max;
+       return sm_selem_ops(elem)->is(elem, SM_PLAY, SM_OPS_IS_ENUMCNT, 0);
 }
 
 /**
@@ -1935,22 +776,9 @@ int snd_mixer_selem_get_enum_item_name(snd_mixer_elem_t *elem,
                                       unsigned int item,
                                       size_t maxlen, char *buf)
 {
-       selem_t *s;
-       snd_ctl_elem_info_t *info;
-       snd_hctl_elem_t *helem;
        assert(elem);
        assert(elem->type == SND_MIXER_ELEM_SIMPLE);
-       s = elem->private_data;
-       helem = s->ctls[CTL_ENUMLIST].elem;
-       assert(helem);
-       if (item >= (unsigned int)s->ctls[CTL_ENUMLIST].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;
+       return sm_selem_ops(elem)->enum_item_name(elem, item, maxlen, buf);
 }
 
 /**
@@ -1964,22 +792,9 @@ int snd_mixer_selem_get_enum_item(snd_mixer_elem_t *elem,
                                  snd_mixer_selem_channel_id_t channel,
                                  unsigned int *itemp)
 {
-       selem_t *s;
-       snd_ctl_elem_value_t ctl;
-       snd_hctl_elem_t *helem;
-       int err;
        assert(elem);
        assert(elem->type == SND_MIXER_ELEM_SIMPLE);
-       s = elem->private_data;
-       if ((unsigned int) channel >= s->str[0].channels)
-               return -EINVAL;
-       helem = s->ctls[CTL_ENUMLIST].elem;
-       assert(helem);
-       memset(&ctl, 0, sizeof(ctl));
-       err = snd_hctl_elem_read(helem, &ctl);
-       if (! err)
-               *itemp = ctl.value.enumerated.item[channel];
-       return err;
+       return sm_selem_ops(elem)->get_enum_item(elem, channel, itemp);
 }
 
 /**
@@ -1993,23 +808,9 @@ int snd_mixer_selem_set_enum_item(snd_mixer_elem_t *elem,
                                  snd_mixer_selem_channel_id_t channel,
                                  unsigned int item)
 {
-       selem_t *s;
-       snd_ctl_elem_value_t ctl;
-       snd_hctl_elem_t *helem;
-       int err;
        assert(elem);
        assert(elem->type == SND_MIXER_ELEM_SIMPLE);
-       s = elem->private_data;
-       if ((unsigned int) channel >= s->str[0].channels)
-               return -EINVAL;
-       helem = s->ctls[CTL_ENUMLIST].elem;
-       assert(helem);
-       if (item >= (unsigned int)s->ctls[CTL_ENUMLIST].max)
-               return -EINVAL;
-       memset(&ctl, 0, sizeof(ctl));
-       err = snd_hctl_elem_read(helem, &ctl);
-       ctl.value.enumerated.item[channel] = item;
-       return snd_hctl_elem_write(helem, &ctl);
+       return sm_selem_ops(elem)->set_enum_item(elem, channel, item);
 }
 
 /**
@@ -2037,7 +838,7 @@ int snd_mixer_selem_id_malloc(snd_mixer_selem_id_t **ptr)
 
 /**
  * \brief frees a previously allocated #snd_mixer_selem_id_t
- * \param obj pointer to object to free
+ * \param pointer to object to free
  */
 void snd_mixer_selem_id_free(snd_mixer_selem_id_t *obj)
 {
@@ -2086,6 +887,7 @@ 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';
 }
 
 /**
@@ -2098,4 +900,3 @@ 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 (file)
index 0000000..ab43c4f
--- /dev/null
@@ -0,0 +1,77 @@
+/**
+ * \file mixer/simple_abst.c
+ * \brief Mixer Simple Element Class Interface - Module Abstraction
+ * \author Jaroslav Kysela <perex@suse.cz>
+ * \date 2005
+ *
+ * Mixer simple element class interface.
+ */
+/*
+ *  Mixer Interface - simple controls - abstraction module
+ *  Copyright (c) 2005 by Jaroslav Kysela <perex@suse.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 "mixer_local.h"
+#include "mixer_simple.h"
+
+/**
+ * \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 = calloc(1, sizeof(*class));
+       const char *file;
+       snd_input_t *input;
+       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);
+       file = getenv("ALSA_MIXER_SIMPLE");
+       if (!file)
+               file = DATADIR "/alsa/smixer.conf";
+       if ((err = snd_input_stdio_open(&input, file, "r")) < 0) {
+               SNDERR("unable to open simple mixer configuration file '%s'", file);
+               goto __error;
+       }
+       err = snd_mixer_class_register(class, mixer);
+       if (err < 0) {
+             __error:
+               if (class)
+                       snd_mixer_class_free(class);
+               return err;
+       }
+       if (classp)
+               *classp = class;
+       return 0;
+}
diff --git a/src/mixer/simple_none.c b/src/mixer/simple_none.c
new file mode 100644 (file)
index 0000000..37b6ea5
--- /dev/null
@@ -0,0 +1,1491 @@
+/**
+ * \file mixer/simple.c
+ * \brief Mixer Simple Element Class Interface
+ * \author Jaroslav Kysela <perex@suse.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@suse.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 <alsa/asoundlib.h>
+#include "mixer_simple.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_ENUMLIST,
+       CTL_GLOBAL_SWITCH,
+       CTL_GLOBAL_VOLUME,
+       CTL_GLOBAL_ROUTE,
+       CTL_PLAYBACK_SWITCH,
+       CTL_PLAYBACK_VOLUME,
+       CTL_PLAYBACK_ROUTE,
+       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 {
+               unsigned int range: 1;  /* Forced range */
+               long min, max;
+               unsigned int channels;
+               long vol[32];
+               unsigned int sw;
+       } str[2];
+} selem_none_t;
+
+static 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)
+{
+       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 *names[] = {
+               "Master",
+               "Headphone",
+               "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",
+               "Aux",
+               "Mono",
+               "Playback",
+               "Capture",
+               "Mix",
+               NULL
+       };
+       static const char *names1[] = {
+               "-",
+               NULL,
+       };
+       static const char *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;
+       selem_ctl_t *c = &s->ctls[CTL_ENUMLIST];
+       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_ENUMLIST].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;
+       selem_ctl_t *c = &s->ctls[CTL_ENUMLIST];
+       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(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_ENUMLIST].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 void selem_free(snd_mixer_elem_t *elem)
+{
+       assert(snd_mixer_elem_get_type(elem) == SND_MIXER_ELEM_SIMPLE);
+       free(snd_mixer_elem_get_private(elem));
+}
+
+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;
+
+       caps = 0;
+       pchannels = 0;
+       pmin = pmax = 0;
+       cchannels = 0;
+       cmin = cmax = 0;
+       assert(snd_mixer_elem_get_type(melem) == SND_MIXER_ELEM_SIMPLE);
+       simple = snd_mixer_elem_get_private(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_ENUMLIST];
+       if (ctl->elem) {
+               if (pchannels < ctl->values)
+                       pchannels = ctl->values;
+               caps |= SM_CAP_ENUM;
+       }
+       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_PVOLUME_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;
+                       else
+                               caps &= ~SM_CAP_PVOLUME_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)) {
+                       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;
+       }
+
+       simple->selem.caps = caps;
+       simple->str[SM_PLAY].channels = pchannels;
+       if (!simple->str[SM_PLAY].range) {
+               simple->str[SM_PLAY].min = pmin;
+               simple->str[SM_PLAY].max = pmax;
+       }
+       simple->str[SM_CAPT].channels = cchannels;
+       if (!simple->str[SM_CAPT].range) {
+               simple->str[SM_CAPT].min = cmin;
+               simple->str[SM_CAPT].max = cmax;
+       }
+       return 0;
+}         
+
+#ifndef DOC_HIDDEN
+static struct suf {
+       const char *suffix;
+       selem_ctl_type_t type;
+} suffixes[] = {
+       {" Playback Switch", CTL_PLAYBACK_SWITCH},
+       {" Playback Route", CTL_PLAYBACK_ROUTE},
+       {" Playback Volume", CTL_PLAYBACK_VOLUME},
+       {" Capture Switch", CTL_CAPTURE_SWITCH},
+       {" Capture Route", CTL_CAPTURE_ROUTE},
+       {" Capture Volume", CTL_CAPTURE_VOLUME},
+       {" 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)
+{
+       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++;
+       }
+       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 ((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_volume_all(snd_mixer_elem_t *elem, int dir, long value)
+{
+       int changed = 0;
+       snd_mixer_selem_channel_id_t channel;   
+       selem_none_t *s = snd_mixer_elem_get_private(elem);
+       if (value < s->str[dir].min || value > s->str[dir].max)
+               return 0;
+       for (channel = 0; (unsigned int) channel < s->str[dir].channels; channel++) {
+               if (value != s->str[dir].vol[channel]) {
+                       s->str[dir].vol[channel] = value;
+                       changed = 1;
+               }
+       }
+       return changed;
+}
+
+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 _snd_mixer_selem_set_switch_all(snd_mixer_elem_t *elem, int dir, int value)
+{
+       selem_none_t *s = snd_mixer_elem_get_private(elem);
+       if (value) {
+               if (s->str[dir].sw != ~0U) {
+                       s->str[dir].sw = ~0U;
+                       return 1;
+               }
+       } else {
+               if (s->str[dir].sw != 0U) {
+                       s->str[dir].sw = 0U;
+                       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:
+               return s->ctls[CTL_ENUMLIST].elem != 0;
+       
+       case SM_OPS_IS_ENUMCNT:
+               if (! s->ctls[CTL_ENUMLIST].elem)
+                       return -EINVAL;
+               return s->ctls[CTL_ENUMLIST].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 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)
+{
+       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 ((unsigned int) channel >= s->str[dir].channels)
+               return -EINVAL;
+       if (dir == SM_PLAY) {
+               if (! (s->selem.caps & (SM_CAP_PVOLUME|SM_CAP_GVOLUME)))
+                       return -EINVAL;
+               if (s->selem.caps & SM_CAP_PVOLUME_JOIN)
+                       channel = 0;
+       } else {
+               if (! (s->selem.caps & (SM_CAP_CVOLUME|SM_CAP_GVOLUME)))
+                       return -EINVAL;
+               if (s->selem.caps & SM_CAP_CVOLUME_JOIN)
+                       channel = 0;
+       }
+       *value = s->str[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, 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 -EINVAL;
+       if (dir == SM_PLAY) {
+               if (! (s->selem.caps & (SM_CAP_PSWITCH|SM_CAP_GSWITCH)))
+                       return -EINVAL;
+               if (s->selem.caps & SM_CAP_PSWITCH_JOIN)
+                       channel = 0;
+       } else {
+               if (! (s->selem.caps & (SM_CAP_CSWITCH|SM_CAP_GSWITCH)))
+                       return -EINVAL;
+               if (s->selem.caps & SM_CAP_CSWITCH_JOIN)
+                       channel = 0;
+       }
+       *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;
+       selem_none_t *s = snd_mixer_elem_get_private(elem);
+       if (dir == SM_PLAY) {
+               if (! (s->selem.caps & (SM_CAP_GVOLUME|SM_CAP_PVOLUME)))
+                       return -EINVAL;
+       } else {
+               if (! (s->selem.caps & (SM_CAP_GVOLUME|SM_CAP_CVOLUME)))
+                       return -EINVAL;
+       }
+       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 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_volume_all_ops(snd_mixer_elem_t *elem, int dir, long value)
+{
+       int changed;
+       selem_none_t *s = snd_mixer_elem_get_private(elem);
+       if (dir == SM_PLAY) {
+               if (! (s->selem.caps & (SM_CAP_GVOLUME|SM_CAP_PVOLUME)))
+                       return -EINVAL;
+       } else {
+               if (! (s->selem.caps & (SM_CAP_GVOLUME|SM_CAP_CVOLUME)))
+                       return -EINVAL;
+       }
+       changed = _snd_mixer_selem_set_volume_all(elem, dir, value);
+       if (changed < 0)
+               return changed;
+       if (changed)
+               return selem_write(elem);
+       return 0;
+}
+
+static int set_dB_all_ops(snd_mixer_elem_t *elem ATTRIBUTE_UNUSED,
+                         int dir ATTRIBUTE_UNUSED,
+                         long value ATTRIBUTE_UNUSED,
+                         int xdir ATTRIBUTE_UNUSED)
+{
+       return -ENXIO;
+}
+
+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 (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 set_switch_all_ops(snd_mixer_elem_t *elem, int dir, int value)
+{
+       int changed;
+       selem_none_t *s = snd_mixer_elem_get_private(elem);
+       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_all(elem, dir, 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;
+
+       helem = s->ctls[CTL_ENUMLIST].elem;
+       assert(helem);
+       if (item >= (unsigned int)s->ctls[CTL_ENUMLIST].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_ENUMLIST].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;
+
+       if ((unsigned int) channel >= s->str[0].channels)
+               return -EINVAL;
+       helem = s->ctls[CTL_ENUMLIST].elem;
+       assert(helem);
+       if (item >= (unsigned int)s->ctls[CTL_ENUMLIST].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,
+       .get_volume     = get_volume_ops,
+       .get_dB         = get_dB_ops,
+       .set_volume     = set_volume_ops,
+       .set_dB         = set_dB_ops,
+       .set_volume_all = set_volume_all_ops,
+       .set_dB_all     = set_dB_all_ops,
+       .get_switch     = get_switch_ops,
+       .set_switch     = set_switch_ops,
+       .set_switch_all = set_switch_all_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_ENUMLIST;
+               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) {
+                       type = CTL_ENUMLIST;
+                       break;
+               }
+               if (ctype != SND_CTL_ELEM_TYPE_BOOLEAN)
+                       return 0;
+               n = sqrt((double)values);
+               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) {
+                       type = CTL_ENUMLIST;
+                       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) {
+                       type = CTL_ENUMLIST;
+                       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;
+       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) {
+                       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) {
+                       free(id);
+                       free(simple);
+                       return err;
+               }
+               new = 1;
+       } else {
+               simple = snd_mixer_elem_get_private(melem);
+               free(id);
+       }
+       if (simple->ctls[type].elem) {
+               SNDERR("helem (%s,'%s',%li,%li,%li) 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_ENUMLIST) {
+               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;
+       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) {
+               if (class)
+                       free(class);
+               return err;
+       }
+       if (classp)
+               *classp = class;
+       return 0;
+}