]> git.alsa-project.org Git - alsa-lib.git/commitdiff
More control/mixer API updates.
authorJaroslav Kysela <perex@perex.cz>
Fri, 28 Jul 2000 20:21:12 +0000 (20:21 +0000)
committerJaroslav Kysela <perex@perex.cz>
Fri, 28 Jul 2000 20:21:12 +0000 (20:21 +0000)
Added highlevel control layer.
Recoded simple control mixer API.

include/control.h
src/control/Makefile.am
src/control/control.c
src/control/control_local.h [new file with mode: 0644]
src/control/controls.c [new file with mode: 0644]
src/mixer/mixer.c
src/mixer/mixer_local.h
src/mixer/simple.c

index b1718211165638af98c3c52c9b344fa117254951..c30a3ce5f7401ed62d63d8fb91a394d52627a586 100644 (file)
@@ -50,6 +50,43 @@ int snd_ctl_rawmidi_info(snd_ctl_t *handle, snd_rawmidi_info_t * info);
 
 int snd_ctl_read(snd_ctl_t *handle, snd_ctl_callbacks_t * callbacks);
 
+/*
+ *  Highlevel API for controls
+ */
+
+typedef struct snd_hcontrol_stru snd_hcontrol_t;
+
+struct snd_hcontrol_stru {
+       snd_control_id_t id; 
+       int change: 1,          /* structure change */
+           value: 1;           /* value change */
+       /* event callbacks */
+       void (*event_change)(snd_ctl_t *handle, snd_hcontrol_t *hcontrol);
+       void (*event_value)(snd_ctl_t *handle, snd_hcontrol_t *hcontrol);
+       void (*event_remove)(snd_ctl_t *handle, snd_hcontrol_t *hcontrol);
+       /* private data */
+       void *private_data;
+       void (*private_free)(void *private_data);
+       /* links */
+       snd_hcontrol_t *prev;
+       snd_hcontrol_t *next;
+};
+
+typedef int (snd_ctl_csort_t)(const snd_hcontrol_t **c1, const snd_hcontrol_t **c2);
+typedef int (snd_ctl_ccallback_rebuild_t)(snd_ctl_t *handle, void *private_data);
+typedef int (snd_ctl_ccallback_add_t)(snd_ctl_t *handle, void *private_data, snd_hcontrol_t *hcontrol);
+
+int snd_ctl_cbuild(snd_ctl_t *handle, snd_ctl_csort_t *csort);
+int snd_ctl_cfree(snd_ctl_t *handle);
+snd_hcontrol_t *snd_ctl_cfirst(snd_ctl_t *handle);
+snd_hcontrol_t *snd_ctl_clast(snd_ctl_t *handle);
+snd_hcontrol_t *snd_ctl_cfind(snd_ctl_t *handle, snd_control_id_t *id);
+int snd_ctl_csort(const snd_hcontrol_t **c1, const snd_hcontrol_t **c2);
+int snd_ctl_cresort(snd_ctl_t *handle, snd_ctl_csort_t *csort);
+int snd_ctl_ccallback_rebuild(snd_ctl_t *handle, snd_ctl_ccallback_rebuild_t *callback, void *private_data);
+int snd_ctl_ccallback_add(snd_ctl_t *handle, snd_ctl_ccallback_add_t *callback, void *private_data);
+int snd_ctl_cevent(snd_ctl_t *handle);
+
 #ifdef __cplusplus
 }
 #endif
index 34712264823befe59134697f7ef849bd2ed171e9..22002c4a3e49bf486c5f5aa2ea88c03c82f74098 100644 (file)
@@ -1,6 +1,6 @@
 EXTRA_LTLIBRARIES = libcontrol.la
 
-libcontrol_la_SOURCES = cards.c control.c defaults.c
+libcontrol_la_SOURCES = cards.c control.c controls.c defaults.c
 
 all: libcontrol.la
 
index 70409de334a4e2be005e0c87bc382d5a4b4d5d0f..9a14c3d9a3d5c5cd26e22b7122fe7094936ad93f 100644 (file)
 #include <sys/ioctl.h>
 #include <assert.h>
 #include "asoundlib.h"
+#include "control_local.h"
 
 #define SND_FILE_CONTROL       "/dev/snd/controlC%i"
 #define SND_CTL_VERSION_MAX    SND_PROTOCOL_VERSION(2, 0, 0)
 
-struct snd_ctl {
-       int card;
-       int fd;
-};
-
 int snd_ctl_open(snd_ctl_t **handle, int card)
 {
        int fd, ver;
diff --git a/src/control/control_local.h b/src/control/control_local.h
new file mode 100644 (file)
index 0000000..45d0ad6
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ *  Control Interface - local header file
+ *  Copyright (c) 2000 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 Library General Public License as
+ *   published by the Free Software Foundation; either version 2 of
+ *   the License, or (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU Library General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Library General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+struct snd_ctl {
+       int card;
+       int fd;
+       int ccount;
+       int cerr;
+       snd_hcontrol_t *cfirst;
+       snd_hcontrol_t *clast;
+       snd_ctl_csort_t *csort;
+       snd_ctl_ccallback_rebuild_t *callback_rebuild;
+       void *callback_rebuild_private_data;
+       snd_ctl_ccallback_add_t *callback_add;
+       void *callback_add_private_data;
+};
diff --git a/src/control/controls.c b/src/control/controls.c
new file mode 100644 (file)
index 0000000..1871fff
--- /dev/null
@@ -0,0 +1,339 @@
+/*
+ *  Control Interface - highlevel API
+ *  Copyright (c) 2000 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 Library General Public License as
+ *   published by the Free Software Foundation; either version 2 of
+ *   the License, or (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU Library General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Library General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <assert.h>
+#include "asoundlib.h"
+#include "control_local.h"
+
+static void snd_ctl_link_after(snd_ctl_t *handle, snd_hcontrol_t *point, snd_hcontrol_t *hcontrol);
+
+int snd_ctl_cbuild(snd_ctl_t *handle, snd_ctl_csort_t *csort)
+{
+       snd_control_list_t list;
+       snd_hcontrol_t *hcontrol, *prev;
+       int err, idx;
+
+       assert(handle != NULL);
+       if ((err = snd_ctl_cfree(handle)) < 0)
+               return err;
+      __rebuild:
+       memset(&list, 0, sizeof(list));
+       do {
+               if (list.pids != NULL)
+                       free(list.pids);
+               list.controls_offset = 0;
+               list.controls_request = 0;
+               list.controls_count = 0;
+               if ((err = snd_ctl_clist(handle, &list)) < 0)
+                       return err;
+               if (list.controls == 0)
+                       break;
+               list.pids = (snd_control_id_t *)calloc(list.controls, sizeof(snd_control_id_t));
+               if (list.pids == NULL)
+                       return -ENOMEM;
+               list.controls_request = list.controls;
+               if ((err = snd_ctl_clist(handle, &list)) < 0)
+                       return err;
+       } while (list.controls != list.controls_count);
+       for (idx = 0, prev = NULL; idx < list.controls_count; idx++) {
+               hcontrol = (snd_hcontrol_t *)calloc(1, sizeof(snd_hcontrol_t));
+               if (hcontrol == NULL) {
+                       snd_ctl_cfree(handle);
+                       free(list.pids);
+                       return -ENOMEM;
+               }
+               hcontrol->id = list.pids[idx];
+               if (prev == NULL) {
+                       handle->cfirst = handle->clast = hcontrol;
+                       handle->ccount = 1;
+               } else {
+                       snd_ctl_link_after(handle, prev, hcontrol);
+               }
+               prev = hcontrol;
+       }
+       if (list.pids != NULL)
+               free(list.pids);
+       if (csort != NULL && (err = snd_ctl_cresort(handle, csort)) < 0)
+               return err;
+       return 0;
+}
+
+snd_hcontrol_t *snd_ctl_cfirst(snd_ctl_t *handle)
+{
+       assert(handle != NULL);
+       return handle->cfirst;
+}
+
+snd_hcontrol_t *snd_ctl_clast(snd_ctl_t *handle)
+{
+       assert(handle != NULL);
+       return handle->clast;
+}
+
+static void snd_ctl_unlink(snd_ctl_t *handle, snd_hcontrol_t *hcontrol)
+{
+       if (handle->cfirst == hcontrol)
+               handle->cfirst = hcontrol->next;
+       if (handle->clast == hcontrol)
+               handle->clast = hcontrol->prev;
+       if (hcontrol->prev != NULL)
+               hcontrol->prev->next = hcontrol->next;
+       if (hcontrol->next != NULL)
+               hcontrol->next->prev = hcontrol->prev;
+       hcontrol->prev = hcontrol->next = NULL;
+       handle->ccount--;
+}
+
+static void snd_ctl_link_before(snd_ctl_t *handle, snd_hcontrol_t *point, snd_hcontrol_t *hcontrol)
+{
+       if (point == handle->cfirst)
+               handle->cfirst = hcontrol;
+       hcontrol->next = point;
+       hcontrol->prev = point->prev;
+       if (point->prev != NULL)
+               point->prev->next = hcontrol;
+       point->prev = hcontrol;
+       handle->ccount++;
+}
+
+static void snd_ctl_link_after(snd_ctl_t *handle, snd_hcontrol_t *point, snd_hcontrol_t *hcontrol)
+{
+       if (point == handle->clast)
+               handle->clast = hcontrol;
+       hcontrol->prev = point;
+       hcontrol->next = point->next;
+       if (point->next != NULL)
+               point->next->prev = hcontrol;
+       point->next = hcontrol;
+       handle->ccount++;
+}
+
+static void snd_ctl_cfree1(snd_ctl_t *handle, snd_hcontrol_t *hcontrol)
+{
+       snd_ctl_unlink(handle, hcontrol);
+       if (hcontrol->event_remove)
+               hcontrol->event_remove(handle, hcontrol);
+       if (hcontrol->private_free)
+               hcontrol->private_free(hcontrol->private_data);
+       free(hcontrol);
+}
+
+int snd_ctl_cfree(snd_ctl_t *handle)
+{
+       handle->csort = NULL;
+       while (handle->cfirst)
+               snd_ctl_cfree1(handle, handle->cfirst);
+       assert(handle->ccount == 0);
+}
+
+int snd_ctl_csort(const snd_hcontrol_t **_c1, const snd_hcontrol_t **_c2)
+{
+       const snd_hcontrol_t *c1 = *_c1;
+       const snd_hcontrol_t *c2 = *_c2;
+       int res;
+
+       res = strcmp(c1->id.name, c2->id.name);
+       if (res == 0) {
+               if (c1->id.index < c2->id.index)
+                       return -1;
+               if (c1->id.index > c2->id.index)
+                       return 1;
+               return 0;
+       }
+}
+
+int snd_ctl_cresort(snd_ctl_t *handle, snd_ctl_csort_t *csort)
+{
+       int idx, count;
+       snd_hcontrol_t **pmap, *hcontrol;
+
+       assert(handle != NULL && csort != NULL);
+       if (handle->ccount == 0)
+               return 0;
+       pmap = (snd_hcontrol_t **)calloc(handle->ccount, sizeof(snd_hcontrol_t *));
+       if (pmap == NULL)
+               return -ENOMEM;
+       for (hcontrol = handle->cfirst, idx = 0; hcontrol != NULL; hcontrol = hcontrol->next, idx++) {
+               printf("idx = %i, hcontrol = 0x%x (0x%x), '%s'\n", idx, (int)hcontrol, (int)&pmap[idx], hcontrol->id.name);
+               pmap[idx] = hcontrol;
+       }
+       assert(idx == handle->ccount);
+       handle->csort = csort;
+       qsort(pmap, count = handle->ccount, sizeof(snd_hcontrol_t *), (int (*)(const void *, const void *))csort);
+       while (handle->cfirst)
+               snd_ctl_unlink(handle, handle->cfirst);
+       handle->cfirst = handle->clast = pmap[0]; handle->ccount = 1;
+       for (idx = 1; idx < count; idx++)
+               snd_ctl_link_after(handle, pmap[idx-1], pmap[idx]);
+       free(pmap);
+       return 0;
+}
+
+snd_hcontrol_t *snd_ctl_cfind(snd_ctl_t *handle, snd_control_id_t *id)
+{
+       snd_hcontrol_t *hcontrol;
+       
+       assert(handle != NULL);
+       for (hcontrol = handle->cfirst; hcontrol != NULL; hcontrol = hcontrol->next) {
+               if (hcontrol->id.iface != id->iface)
+                       continue;
+               if (hcontrol->id.device != id->device)
+                       continue;
+               if (hcontrol->id.subdevice != id->subdevice)
+                       continue;
+               if (strncmp(hcontrol->id.name, id->name, sizeof(hcontrol->id.name)))
+                       continue;
+               if (hcontrol->id.index != id->index)
+                       continue;
+               return hcontrol;
+       }
+       return NULL;
+}
+
+int snd_ctl_ccallback_rebuild(snd_ctl_t *handle, snd_ctl_ccallback_rebuild_t *callback, void *private_data)
+{
+       assert(handle != NULL);
+       handle->callback_rebuild = callback;
+       handle->callback_rebuild_private_data = private_data;
+}
+
+int snd_ctl_ccallback_add(snd_ctl_t *handle, snd_ctl_ccallback_add_t *callback, void *private_data)
+{
+       assert(handle != NULL);
+       handle->callback_add = callback;
+       handle->callback_add_private_data = private_data;
+}
+
+static void callback_rebuild(snd_ctl_t *handle, void *private_data)
+{
+       handle->cerr = snd_ctl_cbuild(handle, handle->csort);
+       if (handle->cerr >= 0 && handle->callback_rebuild)
+               handle->callback_rebuild(handle, handle->callback_rebuild_private_data);
+}
+
+static void callback_change(snd_ctl_t *handle, void *private_data, snd_control_id_t *id)
+{
+       snd_hcontrol_t *hcontrol;
+
+       if (handle->cerr < 0)
+               return;
+       hcontrol = snd_ctl_cfind(handle, id);
+       if (hcontrol == NULL) {
+               handle->cerr = -ENOENT;
+               return;
+       }
+       hcontrol->change = 1;
+}
+
+static void callback_value(snd_ctl_t *handle, void *private_data, snd_control_id_t *id)
+{
+       snd_hcontrol_t *hcontrol;
+
+       if (handle->cerr < 0)
+               return;
+       hcontrol = snd_ctl_cfind(handle, id);
+       if (hcontrol == NULL) {
+               handle->cerr = -ENOENT;
+               return;
+       }
+       hcontrol->value = 1;
+}
+
+static void callback_add(snd_ctl_t *handle, void *private_data, snd_control_id_t *id)
+{
+       snd_hcontrol_t *hcontrol, *icontrol;
+
+       if (handle->cerr < 0)
+               return;
+       hcontrol = (snd_hcontrol_t *)calloc(1, sizeof(snd_hcontrol_t));
+       if (hcontrol == NULL) {
+               handle->cerr = -ENOMEM;
+               return;
+       }
+       hcontrol->id = *id;
+       if (handle->csort != NULL) {
+               for (icontrol = handle->cfirst; icontrol != NULL; icontrol = icontrol->next) {
+                       if (handle->csort((const snd_hcontrol_t **)&icontrol, (const snd_hcontrol_t **)&hcontrol) > 0) {
+                               snd_ctl_link_before(handle, icontrol, hcontrol);
+                               break;
+                       }
+               }
+               if (icontrol == NULL)
+                       snd_ctl_link_after(handle, handle->clast, hcontrol);
+       } else {
+               snd_ctl_link_after(handle, handle->clast, hcontrol);
+       }
+       if (handle->callback_add)
+               handle->callback_add(handle, handle->callback_add_private_data, hcontrol);
+}
+
+static void callback_remove(snd_ctl_t *handle, void *private_data, snd_control_id_t *id)
+{
+       snd_hcontrol_t *hcontrol;
+
+       if (handle->cerr < 0)
+               return;
+       hcontrol = snd_ctl_cfind(handle, id);
+       if (hcontrol == NULL) {
+               handle->cerr = -ENOENT;
+               return;
+       }
+       snd_ctl_cfree1(handle, hcontrol);
+}
+
+int snd_ctl_cevent(snd_ctl_t *handle)
+{
+       snd_ctl_callbacks_t callbacks = {
+               rebuild: callback_rebuild,
+               value: callback_value,
+               change: callback_change,
+               add: callback_add,
+               remove: callback_remove
+       };
+       snd_hcontrol_t *hcontrol;
+       int res;
+
+       assert(handle != NULL);
+       handle->cerr = 0;
+       res = snd_ctl_read(handle, &callbacks);
+       if (res < 0)
+               return res;
+       if (handle->cerr < 0)
+               return handle->cerr;
+       for (hcontrol = handle->cfirst; hcontrol != NULL; hcontrol = hcontrol->next) {
+               if (hcontrol->change && hcontrol->event_change) {
+                       hcontrol->event_change(handle, hcontrol);
+                       hcontrol->change = 0;
+               }
+               if (hcontrol->value && hcontrol->event_value) {
+                       hcontrol->event_value(handle, hcontrol);
+                       hcontrol->value = 0;
+               }                       
+       }
+       return res;
+}
index 83b048e833196a13596087e5e853d0323ced53a6..d28f9cf431887a331312f3583d47729e7efe7bfb 100644 (file)
@@ -103,11 +103,10 @@ int snd_mixer_simple_control_list(snd_mixer_t *handle, snd_mixer_simple_control_
        for (s = handle->simple_first; s != NULL && tmp > 0; s = s->next);
        tmp = list->controls_request;
        p = list->pids;
-       printf("request = %i\n", tmp);
        if (tmp > 0 && p == NULL)
                return -EINVAL;
        for (; s != NULL && tmp > 0; s = s->next, tmp--, p++, list->controls_count++)
-               memcpy(p, &s->id, sizeof(*p));
+               memcpy(p, &s->sid, sizeof(*p));
        list->controls = handle->simple_count;
        return 0;
 }
@@ -117,7 +116,7 @@ static mixer_simple_t *look_for_simple(snd_mixer_t *handle, snd_mixer_sid_t *sid
        mixer_simple_t *s;
        
        for (s = handle->simple_first; s != NULL; s = s->next)
-               if (!strcmp(s->id.name, sid->name) && s->id.index == sid->index)
+               if (!strcmp(s->sid.name, sid->name) && s->sid.index == sid->index)
                        return s;
        return NULL;
 }
@@ -214,16 +213,9 @@ int snd_mixer_simple_read(snd_mixer_t *handle, snd_mixer_simple_callbacks_t *cal
                return -EINVAL;
        if (!handle->simple_valid)
                snd_mixer_simple_build(handle);
-       memset(&xcallbacks, 0, sizeof(xcallbacks));
-       xcallbacks.private_data = handle;
-       xcallbacks.rebuild = snd_mixer_simple_read_rebuild;
-       xcallbacks.value = snd_mixer_simple_read_value;
-       xcallbacks.change = snd_mixer_simple_read_change;
-       xcallbacks.add = snd_mixer_simple_read_add;
-       xcallbacks.remove = snd_mixer_simple_read_remove;
        handle->callbacks = callbacks;
        handle->simple_changes = 0;
-       if ((err = snd_ctl_read(handle->ctl_handle, &xcallbacks)) <= 0) {
+       if ((err = snd_ctl_cevent(handle->ctl_handle)) <= 0) {
                handle->callbacks = NULL;
                return err;
        }
index dfb67b30ac5789ebe4e101cefc612ef88d8eb609..e37ba575ba9481f0ba077dc73e932926470a7af3 100644 (file)
@@ -28,10 +28,33 @@ typedef int (mixer_simple_get_t) (snd_mixer_t *handle, mixer_simple_t *simple, s
 typedef int (mixer_simple_put_t) (snd_mixer_t *handle, mixer_simple_t *simple, snd_mixer_simple_control_t *control);
 typedef int (mixer_simple_event_t) (snd_mixer_t *handle, snd_ctl_event_type_t etype, snd_control_id_t *id);
 
+#define MIXER_PRESENT_GLOBAL_SWITCH    (1<<0)
+#define MIXER_PRESENT_GLOBAL_VOLUME    (1<<1)
+#define MIXER_PRESENT_PLAYBACK_SWITCH  (1<<2)
+#define MIXER_PRESENT_PLAYBACK_VOLUME  (1<<3)
+#define MIXER_PRESENT_CAPTURE_SWITCH   (1<<4)
+#define MIXER_PRESENT_CAPTURE_VOLUME   (1<<5)
+#define MIXER_PRESENT_CAPTURE_SOURCE   (1<<6)
+
 struct mixer_simple {
+       /* this may be moved to a private area */
+       unsigned int present;           /* present controls */
+       unsigned int gswitch_values;
+       unsigned int pswitch_values;
+       unsigned int cswitch_values;
+       unsigned int gvolume_values;
+       unsigned int pvolume_values;
+       unsigned int cvolume_values;
+       unsigned int ccapture_values;
+       unsigned int capture_item;
+       unsigned int caps;
+       long min;
+       long max;
+       int voices;
+       /* -- */
        int refs;                       /* number of references */
        int change;                     /* simple control was changed */
-       snd_mixer_sid_t id;
+       snd_mixer_sid_t sid;
        mixer_simple_get_t *get;
        mixer_simple_put_t *put;
        mixer_simple_event_t *event;
index 5fa2c5ae0676943624d3db202deff666ae57114d..c0e554eb0e27508d80e4d196b4060f450fc16de0 100644 (file)
 
 static int test_mixer_id(snd_mixer_t *handle, const char *name, int index)
 {
-       snd_control_t c;
-       int err;
+       snd_control_id_t id;
+       snd_hcontrol_t *hcontrol;
        
-       memset(&c, 0, sizeof(c));
-       c.id.iface = SND_CONTROL_IFACE_MIXER;
-       strcpy(c.id.name, name);
-       c.id.index = index;
-       err = snd_ctl_cread(handle->ctl_handle, &c);
-       fprintf(stderr, "Looking for control: '%s', %i (%i)\n", name, index, err);
-       switch (err) {
-       case 0:
-       case -EBUSY:
-               return 1;
-       default:
-               return 0;
-       }
+       memset(&id, 0, sizeof(id));
+       id.iface = SND_CONTROL_IFACE_MIXER;
+       strcpy(id.name, name);
+       id.index = index;
+       hcontrol = snd_ctl_cfind(handle->ctl_handle, &id);
+       // fprintf(stderr, "Looking for control: '%s', %i (0x%lx)\n", name, index, (long)hcontrol);
+       return hcontrol != NULL;
 }
 
 static int get_mixer_info(snd_mixer_t *handle, const char *name, int index, snd_control_info_t *info)
@@ -58,6 +52,27 @@ static int get_mixer_info(snd_mixer_t *handle, const char *name, int index, snd_
        return snd_ctl_cinfo(handle->ctl_handle, info);
 }
 
+static int get_mixer_read(snd_mixer_t *handle, const char *name, int index, snd_control_t *control)
+{
+       memset(control, 0, sizeof(*control));
+       control->id.iface = SND_CONTROL_IFACE_MIXER;
+       strcpy(control->id.name, name);
+       control->id.index = index;
+       return snd_ctl_cread(handle->ctl_handle, control);
+}
+
+static int put_mixer_write(snd_mixer_t *handle, const char *name, int index, snd_control_t *control)
+{
+       control->id.numid = 0;
+       control->id.iface = SND_CONTROL_IFACE_MIXER;
+       strcpy(control->id.name, name);
+       control->id.device = control->id.subdevice = 0;
+       control->id.index = index;
+       control->indirect = 0;
+       memset(&control->reserved, 0, sizeof(control->reserved));
+       return snd_ctl_cwrite(handle->ctl_handle, control);
+}
+
 static mixer_simple_t *simple_new(mixer_simple_t *scontrol)
 {
        mixer_simple_t *s;
@@ -104,297 +119,378 @@ static int simple_remove(snd_mixer_t *handle, mixer_simple_t *scontrol)
        return 0;
 }
 
-static int input_get(snd_mixer_t *handle, mixer_simple_t *simple, snd_mixer_simple_control_t *control)
+static int input_get_volume(snd_mixer_t *handle, mixer_simple_t *simple, snd_mixer_simple_control_t *control, const char *direction, int voices)
 {
        char str[128];
-       int mute_is_valid = 0;
-       snd_control_info_t mute_info;
-       int volume_is_valid = 0;
-       snd_control_info_t volume_info;
-       int capture_is_valid = 0, capture1_is_valid = 0;
-       unsigned int capture1_item = 0;
-       snd_control_info_t capture_info;
-       unsigned int voices = 0, idx;
        snd_control_t ctl;
-       int err;
+       int idx, err;
+       
+       sprintf(str, "%s %sVolume", simple->sid.name, direction);
+       if ((err = get_mixer_read(handle, str, simple->sid.index, &ctl)) < 0)
+               return err;
+       for (idx = 0; idx < simple->voices && idx < 32; idx++)
+               control->volume.values[idx] = ctl.value.integer.value[voices == 1 ? 0 : idx];
+}
+
+static int input_get_mute_switch(snd_mixer_t *handle, mixer_simple_t *simple, snd_mixer_simple_control_t *control, const char *direction, int voices)
+{
+       char str[128];
+       snd_control_t ctl;
+       int idx, err;
+       
+       sprintf(str, "%s %sSwitch", simple->sid.name, direction);
+       if ((err = get_mixer_read(handle, str, simple->sid.index, &ctl)) < 0)
+               return err;
+       for (idx = 0; idx < simple->voices && idx < 32; idx++)
+               if (ctl.value.integer.value[voices == 1 ? 0 : idx] == 0)
+                       control->mute |= 1 << idx;
+}
+
+static int input_get_capture_switch(snd_mixer_t *handle, mixer_simple_t *simple, snd_mixer_simple_control_t *control, const char *direction, int voices)
+{
+       char str[128];
+       snd_control_t ctl;
+       int idx, err;
+       
+       sprintf(str, "%s %sSwitch", simple->sid.name, direction);
+       if ((err = get_mixer_read(handle, str, simple->sid.index, &ctl)) < 0)
+               return err;
+       for (idx = 0; idx < simple->voices && idx < 32; idx++)
+               if (ctl.value.integer.value[voices == 1 ? 0 : idx])
+                       control->capture |= 1 << idx;
+}
+
+static int input_get(snd_mixer_t *handle, mixer_simple_t *simple, snd_mixer_simple_control_t *control)
+{
+       int idx, err;
 
        if (simple == NULL)
                return -EINVAL;
 
-       control->caps = 0;
+       control->caps = simple->caps;
        control->channels = 0;
        control->mute = 0;
        control->capture = 0;
        control->capture_group = 0;
-       control->min = 0;
-       control->max = 0;
+       control->min = simple->min;
+       control->max = simple->max;
        for (idx = 0; idx < 32; idx++)
                control->volume.values[idx] = 0;
 
-       sprintf(str, "%s Mute", control->sid.name);
-       if (test_mixer_id(handle, str, control->sid.index)) {
-               if ((err = get_mixer_info(handle, str, control->sid.index, &mute_info)) < 0)
-                       return err;
-               if (mute_info.type == SND_CONTROL_TYPE_BOOLEAN) {
-                       if (voices < mute_info.values_count)
-                               voices = mute_info.values_count;
-                       mute_is_valid++;
-                       control->caps |= SND_MIXER_SCTCAP_MUTE;
+       for (idx = 0; idx < simple->voices && idx < 32; idx++)
+               control->channels |= 1 << idx;
+       if (simple->caps & SND_MIXER_SCTCAP_VOLUME) {
+               if (simple->present & MIXER_PRESENT_PLAYBACK_VOLUME) {
+                       input_get_volume(handle, simple, control, "Playback ", simple->pvolume_values);
+               } else if (simple->present & MIXER_PRESENT_GLOBAL_VOLUME) {
+                       input_get_volume(handle, simple, control, "", simple->gvolume_values);
                }
        }
-       sprintf(str, "%s Volume", control->sid.name);
-       if (test_mixer_id(handle, str, control->sid.index)) {
-               if ((err = get_mixer_info(handle, str, control->sid.index, &volume_info)) < 0)
-                       return err;
-               if (volume_info.type == SND_CONTROL_TYPE_INTEGER) {
-                       if (voices < volume_info.values_count)
-                               voices = volume_info.values_count;
-                       volume_is_valid++;
-                       control->caps |= SND_MIXER_SCTCAP_VOLUME;
-                       control->min = volume_info.value.integer.min;
-                       control->max = volume_info.value.integer.max;
+       if (simple->caps & SND_MIXER_SCTCAP_MUTE) {
+               if (simple->present & MIXER_PRESENT_PLAYBACK_SWITCH) {
+                       input_get_mute_switch(handle, simple, control, "Playback ", simple->pswitch_values);
+               } else if (simple->present & MIXER_PRESENT_GLOBAL_VOLUME) {
+                       input_get_mute_switch(handle, simple, control, "", simple->pvolume_values);
                }
        }
-       sprintf(str, "%s Capture", control->sid.name);
-       if (test_mixer_id(handle, str, control->sid.index)) {
-               if ((err = get_mixer_info(handle, str, control->sid.index, &capture_info)) < 0)
-                       return err;
-               if (capture_info.type == SND_CONTROL_TYPE_BOOLEAN) {
-                       if (voices < capture_info.values_count)
-                               voices = capture_info.values_count;
-                       capture_is_valid++;
-                       control->caps |= SND_MIXER_SCTCAP_CAPTURE;
-               }
-       } else if (test_mixer_id(handle, "Capture Source", 0)) {
-               if ((err = get_mixer_info(handle, "Capture Source", 0, &capture_info)) < 0)
-                       return err;
-               strcpy(str, control->sid.name);
-               if (!strcmp(str, "Master"))     /* special case */
-                       strcpy(str, "Mix");
-               else if (!strcmp(str, "Master Mono")) /* special case */
-                       strcpy(str, "Mono Mix");
-               if (capture_info.type == SND_CONTROL_TYPE_ENUMERATED) {
-                       if (!strcmp(capture_info.value.enumerated.name, str)) {
-                               if (voices < capture_info.values_count)
-                                       voices = capture_info.values_count;
-                               capture1_is_valid++;
-                               control->caps |= SND_MIXER_SCTCAP_CAPTURE | SND_MIXER_SCTCAP_EXCL_CAPTURE;
-                               control->capture_group = 1;
-                       }
-                       for (capture1_item = 1; capture1_item < capture_info.value.enumerated.items; capture1_item++) {
-                               capture_info.value.enumerated.item = capture1_item;
-                               if ((err = snd_ctl_cinfo(handle->ctl_handle, &capture_info)) < 0)
-                                       return err;
-                               if (!strcmp(capture_info.value.enumerated.name, str)) {
-                                       if (voices < capture_info.values_count)
-                                               voices = capture_info.values_count;
-                                       capture1_is_valid++;
-                                       control->caps |= SND_MIXER_SCTCAP_CAPTURE | SND_MIXER_SCTCAP_EXCL_CAPTURE;
-                                       control->capture_group = 1;
-                                       break;
-                               }
-                       }
+       if (simple->caps & SND_MIXER_SCTCAP_CAPTURE) {
+               if (simple->present & MIXER_PRESENT_CAPTURE_SWITCH) {
+                       input_get_capture_switch(handle, simple, control, "Capture ", simple->cswitch_values);
+               } else if (simple->present & MIXER_PRESENT_CAPTURE_SOURCE) {
+                       char str[128];
+                       snd_control_t ctl;
+                       if ((err = get_mixer_read(handle, "Capture Source", 0, &ctl)) < 0)
+                               return err;
+                       for (idx = 0; idx < simple->voices && idx < 32; idx++)
+                               if (ctl.value.enumerated.item[simple->ccapture_values == 1 ? 0 : idx] == simple->capture_item)
+                                       control->capture |= 1 << idx;
                }
        }
+       return 0;
+}
 
-       for (idx = 0; idx < voices && idx < 32; idx++)
-               control->channels |= 1 << idx;
-       if (voices > 1) {
-               if (volume_is_valid && volume_info.values_count == 1)
-                       control->caps |= SND_MIXER_SCTCAP_JOINTLY_VOLUME;
-               if (mute_is_valid && mute_info.values_count == 1)
-                       control->caps |= SND_MIXER_SCTCAP_JOINTLY_MUTE;
-               if ((capture_is_valid || capture1_is_valid) && capture_info.values_count == 1)
-                       control->caps |= SND_MIXER_SCTCAP_JOINTLY_CAPTURE;
-       }
-       if (volume_is_valid) {
-               memset(&ctl, 0, sizeof(ctl));
-               ctl.id = volume_info.id;
-               if ((err = snd_ctl_cread(handle->ctl_handle, &ctl)) < 0)
-                       return err;
-               for (idx = 0; idx < voices && idx < 32; idx++)
-                       control->volume.values[idx] = ctl.value.integer.value[volume_info.values_count == 1 ? 0 : idx];
-       }
-       if (mute_is_valid) {
-               memset(&ctl, 0, sizeof(ctl));
-               ctl.id = mute_info.id;
-               if ((err = snd_ctl_cread(handle->ctl_handle, &ctl)) < 0)
-                       return err;
-               for (idx = 0; idx < voices && idx < 32; idx++)
-                       if (ctl.value.integer.value[mute_info.values_count == 1 ? 0 : idx])
-                               control->mute |= 1 << idx;
+static int input_put_volume(snd_mixer_t *handle, mixer_simple_t *simple, snd_mixer_simple_control_t *control, const char *direction, int voices)
+{
+       char str[128];
+       snd_control_t ctl;
+       int idx, err;
+       
+       sprintf(str, "%s %sVolume", simple->sid.name, direction);
+       if ((err = get_mixer_read(handle, str, simple->sid.index, &ctl)) < 0)
+               return err;
+       for (idx = 0; idx < voices && idx < 32; idx++) {
+               ctl.value.integer.value[idx] = control->volume.values[idx];
+               // fprintf(stderr, "ctl.id.name = '%s', volume = %i\n", ctl.id.name, ctl.value.integer.value[idx]);
        }
-       if (capture_is_valid) {
-               memset(&ctl, 0, sizeof(ctl));
-               ctl.id = capture_info.id;
-               if ((err = snd_ctl_cread(handle->ctl_handle, &ctl)) < 0)
-                       return err;
-               for (idx = 0; idx < voices && idx < 32; idx++)
-                       if (ctl.value.integer.value[capture_info.values_count == 1 ? 0 : idx])
-                               control->capture |= 1 << idx;
-       } else if (capture1_is_valid) {
-               memset(&ctl, 0, sizeof(ctl));
-               ctl.id = capture_info.id;
-               if ((err = snd_ctl_cread(handle->ctl_handle, &ctl)) < 0)
-                       return err;
-               for (idx = 0; idx < voices && idx < 32; idx++)
-                       if (ctl.value.enumerated.item[capture_info.values_count == 1 ? 0 : idx] == capture1_item)
-                               control->capture |= 1 << idx;
+       if ((err = put_mixer_write(handle, str, simple->sid.index, &ctl)) < 0)
+               return err;
+}
+
+static int input_put_mute_switch(snd_mixer_t *handle, mixer_simple_t *simple, snd_mixer_simple_control_t *control, const char *direction, int voices)
+{
+       char str[128];
+       snd_control_t ctl;
+       int idx, err;
+       
+       sprintf(str, "%s %sSwitch", simple->sid.name, direction);
+       if ((err = get_mixer_read(handle, str, simple->sid.index, &ctl)) < 0)
+               return err;
+       for (idx = 0; idx < voices && idx < 32; idx++) {
+               ctl.value.integer.value[idx] = (control->mute & (1 << idx)) ? 0 : 1;
+               // fprintf(stderr, "ctl.id.name = '%s', switch = %i\n", ctl.id.name, ctl.value.integer.value[idx]);
        }
+       err = put_mixer_write(handle, str, simple->sid.index, &ctl);
+       if ((err = put_mixer_write(handle, str, simple->sid.index, &ctl)) < 0)
+               return err;
+}
 
-       return 0;
+static int input_put_capture_switch(snd_mixer_t *handle, mixer_simple_t *simple, snd_mixer_simple_control_t *control, const char *direction, int voices)
+{
+       char str[128];
+       snd_control_t ctl;
+       int idx, err;
+       
+       sprintf(str, "%s %sSwitch", simple->sid.name, direction);
+       if ((err = get_mixer_read(handle, str, simple->sid.index, &ctl)) < 0)
+               return err;
+       for (idx = 0; idx < voices && idx < 32; idx++)
+               ctl.value.integer.value[idx] = (control->capture & (1 << idx)) ? 1 : 0;
+       if ((err = put_mixer_write(handle, str, simple->sid.index, &ctl)) < 0)
+               return err;
 }
 
 static int input_put(snd_mixer_t *handle, mixer_simple_t *simple, snd_mixer_simple_control_t *control)
 {
-       char str[128];
-       int mute_is_valid = 0;
-       snd_control_info_t mute_info;
-       int volume_is_valid = 0;
-       snd_control_info_t volume_info;
-       int capture_is_valid = 0, capture1_is_valid = 0;
-       unsigned int capture1_item = 0;
-       snd_control_info_t capture_info;
-       unsigned int voices = 0, idx;
-       snd_control_t ctl_mute, ctl_volume, ctl_capture;
-       int err;
+       int err, idx;
 
        if (simple == NULL)
                return -EINVAL;
 
-       sprintf(str, "%s Mute", control->sid.name);
-       if (test_mixer_id(handle, str, control->sid.index)) {
-               if ((err = get_mixer_info(handle, str, control->sid.index, &mute_info)) < 0)
-                       return err;
-               if (mute_info.type == SND_CONTROL_TYPE_BOOLEAN) {
-                       if (voices < mute_info.values_count)
-                               voices = mute_info.values_count;
-                       mute_is_valid++;
-               }
-       }
-       sprintf(str, "%s Volume", control->sid.name);
-       if (test_mixer_id(handle, str, control->sid.index)) {
-               if ((err = get_mixer_info(handle, str, control->sid.index, &volume_info)) < 0)
-                       return err;
-               if (volume_info.type == SND_CONTROL_TYPE_INTEGER) {
-                       if (voices < volume_info.values_count)
-                               voices = volume_info.values_count;
-                       volume_is_valid++;
+       if (simple->caps & SND_MIXER_SCTCAP_VOLUME) {
+               if (simple->present & MIXER_PRESENT_PLAYBACK_VOLUME) {
+                       input_put_volume(handle, simple, control, "Playback ", simple->pvolume_values);
+               } else if (simple->present & MIXER_PRESENT_GLOBAL_VOLUME) {
+                       input_put_volume(handle, simple, control, "", simple->gvolume_values);
                }
        }
-       sprintf(str, "%s Capture", control->sid.name);
-       if (test_mixer_id(handle, str, control->sid.index)) {
-               if ((err = get_mixer_info(handle, str, control->sid.index, &capture_info)) < 0)
-                       return err;
-               if (capture_info.type == SND_CONTROL_TYPE_BOOLEAN) {
-                       if (voices < capture_info.values_count)
-                               voices = capture_info.values_count;
-                       capture_is_valid++;
-               }
-       } else if (test_mixer_id(handle, "Capture Source", 0)) {
-               if ((err = get_mixer_info(handle, "Capture Source", 0, &capture_info)) < 0)
-                       return err;
-               strcpy(str, control->sid.name);
-               if (!strcmp(str, "Master"))     /* special case */
-                       strcpy(str, "Mix");
-               else if (!strcmp(str, "Master Mono")) /* special case */
-                       strcpy(str, "Mono Mix");
-               if (capture_info.type == SND_CONTROL_TYPE_ENUMERATED) {
-                       if (!strcmp(capture_info.value.enumerated.name, str)) {
-                               if (voices < capture_info.values_count)
-                                       voices = capture_info.values_count;
-                               capture1_is_valid++;
-                       }
-                       for (capture1_item = 1; capture1_item < capture_info.value.enumerated.items; capture1_item++) {
-                               capture_info.value.enumerated.item = capture1_item;
-                               if ((err = snd_ctl_cinfo(handle->ctl_handle, &capture_info)) < 0)
-                                       return err;
-                               if (!strcmp(capture_info.value.enumerated.name, str)) {
-                                       if (voices < capture_info.values_count)
-                                               voices = capture_info.values_count;
-                                       capture1_is_valid++;
-                                       break;
-                               }
-                       }
+       if (simple->caps & SND_MIXER_SCTCAP_MUTE) {
+               if (simple->present & MIXER_PRESENT_PLAYBACK_SWITCH) {
+                       input_put_mute_switch(handle, simple, control, "Playback ", simple->pswitch_values);
+               } else if (simple->present & MIXER_PRESENT_GLOBAL_VOLUME) {
+                       input_put_mute_switch(handle, simple, control, "", simple->pvolume_values);
                }
        }
-
-       memset(&ctl_mute, 0, sizeof(ctl_mute));
-       memset(&ctl_volume, 0, sizeof(ctl_volume));
-       memset(&ctl_capture, 0, sizeof(ctl_capture));
-       if (mute_is_valid) {
-               ctl_mute.id = mute_info.id;
-               if ((err = snd_ctl_cread(handle->ctl_handle, &ctl_mute)) < 0)
-                       return err;
-       }
-       if (volume_is_valid) {
-               ctl_volume.id = volume_info.id;
-               if ((err = snd_ctl_cread(handle->ctl_handle, &ctl_volume)) < 0)
-                       return err;
-       }
-       if (capture_is_valid || capture1_is_valid) {
-               ctl_capture.id = capture_info.id;
-               if ((err = snd_ctl_cread(handle->ctl_handle, &ctl_capture)) < 0)
-                       return err;
-       }
-       for (idx = 0; idx < voices && idx < 32; idx++) {
-               if (control->channels & (1 << idx)) {
-                       if (volume_is_valid) {
-                               if (control->volume.values[idx] < volume_info.value.integer.min ||
-                                   control->volume.values[idx] > volume_info.value.integer.max)
-                                       return -EINVAL;
-                               ctl_volume.value.integer.value[idx] = control->volume.values[idx];
-                       }
-                       if (mute_is_valid) {
-                               ctl_mute.value.integer.value[idx] = (control->mute & (1 << idx)) ? 1 : 0;
-                       }
-                       if (capture_is_valid) {
-                               ctl_capture.value.integer.value[idx] = (control->capture & (1 << idx)) ? 1 : 0;
-                       } else if (capture1_is_valid && (control->capture & (1 << idx))) {
-                               ctl_capture.value.enumerated.item[idx] = capture1_item;
+       if (simple->caps & SND_MIXER_SCTCAP_CAPTURE) {
+               // fprintf(stderr, "capture: present = 0x%x\n", simple->present);
+               if (simple->present & MIXER_PRESENT_CAPTURE_SWITCH) {
+                       input_put_capture_switch(handle, simple, control, "Capture ", simple->cswitch_values);
+               } else if (simple->present & MIXER_PRESENT_CAPTURE_SOURCE) {
+                       char str[128];
+                       snd_control_t ctl;
+                       if ((err = get_mixer_read(handle, "Capture Source", 0, &ctl)) < 0)
+                               return err;
+                       // fprintf(stderr, "put capture source : %i [0x%x]\n", simple->capture_item, control->capture);
+                       for (idx = 0; idx < simple->voices && idx < 32; idx++) {
+                               if (control->capture & (1 << idx))
+                                       ctl.value.enumerated.item[idx] = simple->capture_item;
                        }
+                       if ((err = put_mixer_write(handle, "Capture Source", 0, &ctl)) < 0)
+                               return err;
                }
        }
-       if (volume_is_valid) {
-               if ((err = snd_ctl_cwrite(handle->ctl_handle, &ctl_volume)) < 0)
-                       return err;
-       }
-       if (mute_is_valid) {
-               if ((err = snd_ctl_cwrite(handle->ctl_handle, &ctl_mute)) < 0)
-                       return err;
-       }
-       if (capture_is_valid || capture1_is_valid) {
-               if ((err = snd_ctl_cwrite(handle->ctl_handle, &ctl_capture)) < 0)
-                       return err;
-       }
        return 0;
 }
 
-static int build_input_scontrol(snd_mixer_t *handle, const char *sname, int index)
+static mixer_simple_t *build_input_scontrol(snd_mixer_t *handle, const char *sname, int index)
 {
-       mixer_simple_t s;
+       mixer_simple_t s, *p;
 
        memset(&s, 0, sizeof(s));
-       strcpy(s.id.name, sname);
-       s.id.index = index;
+       strcpy(s.sid.name, sname);
+       s.sid.index = index;
        s.get = input_get;
        s.put = input_put;
-       return simple_add(handle, simple_new(&s));
+       if (simple_add(handle, p = simple_new(&s)) < 0) {
+               free(p);
+               return NULL;
+       }
+       return p;
 }
 
 static int build_input(snd_mixer_t *handle, const char *sname)
 {
        char str[128];
+       unsigned int present, caps, capture_item;
+       int index = -1, voices, err;
+       snd_control_info_t gswitch_info, pswitch_info, cswitch_info;
+       snd_control_info_t gvolume_info, pvolume_info, cvolume_info;
+       snd_control_info_t csource_info;
+       long min, max;
+       mixer_simple_t *simple;
 
-       fprintf(stderr, "build_input: '%s'\n", sname);
-       sprintf(str, "%s Mute", sname);
-       if (test_mixer_id(handle, str, 0)) {
-               fprintf(stderr, "id ok (mute): %s\n", str);
-               return build_input_scontrol(handle, sname, 0);
-       }
-       sprintf(str, "%s Volume", sname);
-       if (test_mixer_id(handle, str, 0))
-               return build_input_scontrol(handle, sname, 0);
+       memset(&gswitch_info, 0, sizeof(gswitch_info));
+       memset(&pswitch_info, 0, sizeof(pswitch_info));
+       memset(&cswitch_info, 0, sizeof(cswitch_info));
+       memset(&gvolume_info, 0, sizeof(gvolume_info));
+       memset(&pvolume_info, 0, sizeof(pvolume_info));
+       memset(&cvolume_info, 0, sizeof(cvolume_info));
+       do {
+               index++;
+               voices = 0;
+               present = caps = capture_item = 0;
+               min = max = 0;
+               sprintf(str, "%s Switch", sname);
+               if (test_mixer_id(handle, str, index)) {
+                       if ((err = get_mixer_info(handle, str, index, &gswitch_info)) < 0)
+                               return err;
+                       if (gswitch_info.type == SND_CONTROL_TYPE_BOOLEAN) {
+                               if (voices < gswitch_info.values_count)
+                                       voices = gswitch_info.values_count;
+                               caps |= SND_MIXER_SCTCAP_MUTE;
+                               present |= MIXER_PRESENT_GLOBAL_SWITCH;
+                       }
+               }
+               sprintf(str, "%s Volume", sname);
+               if (test_mixer_id(handle, str, index)) {
+                       if ((err = get_mixer_info(handle, str, index, &gvolume_info)) < 0)
+                               return err;
+                       if (gvolume_info.type == SND_CONTROL_TYPE_INTEGER) {
+                               if (voices < gvolume_info.values_count)
+                                       voices = gvolume_info.values_count;
+                               if (min > gvolume_info.value.integer.min)
+                                       min = gvolume_info.value.integer.min;
+                               if (max < gvolume_info.value.integer.max)
+                                       max = gvolume_info.value.integer.max;
+                               caps |= SND_MIXER_SCTCAP_VOLUME;
+                               present |= MIXER_PRESENT_GLOBAL_VOLUME;
+                       }
+               }
+               sprintf(str, "%s Playback Switch", sname);
+               if (test_mixer_id(handle, str, index)) {
+                       if ((err = get_mixer_info(handle, str, index, &pswitch_info)) < 0)
+                               return err;
+                       if (pswitch_info.type == SND_CONTROL_TYPE_BOOLEAN) {
+                               if (voices < pswitch_info.values_count)
+                                       voices = pswitch_info.values_count;
+                               caps |= SND_MIXER_SCTCAP_MUTE;
+                               present |= MIXER_PRESENT_PLAYBACK_SWITCH;
+                       }
+               }
+               sprintf(str, "%s Capture Switch", sname);
+               if (test_mixer_id(handle, str, index)) {
+                       if ((err = get_mixer_info(handle, str, index, &cswitch_info)) < 0)
+                               return err;
+                       if (cswitch_info.type == SND_CONTROL_TYPE_BOOLEAN) {
+                               if (voices < cswitch_info.values_count)
+                                       voices = cswitch_info.values_count;
+                               caps |= SND_MIXER_SCTCAP_CAPTURE;
+                               present |= MIXER_PRESENT_CAPTURE_SWITCH;
+                       }
+               }
+               sprintf(str, "%s Playback Volume", sname);
+               if (test_mixer_id(handle, str, index)) {
+                       if ((err = get_mixer_info(handle, str, index, &pvolume_info)) < 0)
+                               return err;
+                       if (pvolume_info.type == SND_CONTROL_TYPE_INTEGER) {
+                               if (voices < pvolume_info.values_count)
+                                       voices = pvolume_info.values_count;
+                               if (min > pvolume_info.value.integer.min)
+                                       min = pvolume_info.value.integer.min;
+                               if (max < pvolume_info.value.integer.max)
+                                       max = pvolume_info.value.integer.max;
+                               caps |= SND_MIXER_SCTCAP_VOLUME;
+                               present |= MIXER_PRESENT_PLAYBACK_VOLUME;
+                       }
+               }
+               sprintf(str, "%s Capture Volume", sname);
+               if (test_mixer_id(handle, str, index)) {
+                       if ((err = get_mixer_info(handle, str, index, &cvolume_info)) < 0)
+                               return err;
+                       if (cvolume_info.type == SND_CONTROL_TYPE_INTEGER) {
+                               if (voices < cvolume_info.values_count)
+                                       voices = cvolume_info.values_count;
+                               if (min > pvolume_info.value.integer.min)
+                                       min = pvolume_info.value.integer.min;
+                               if (max < pvolume_info.value.integer.max)
+                                       max = pvolume_info.value.integer.max;
+                               caps |= SND_MIXER_SCTCAP_VOLUME;
+                               present |= MIXER_PRESENT_CAPTURE_VOLUME;
+                       }
+               }
+               if (index == 0 && test_mixer_id(handle, "Capture Source", 0)) {
+                       if ((err = get_mixer_info(handle, "Capture Source", 0, &csource_info)) < 0)
+                               return err;
+                       strcpy(str, sname);
+                       if (!strcmp(str, "Master"))     /* special case */
+                               strcpy(str, "Mix");
+                       else if (!strcmp(str, "Master Mono")) /* special case */
+                               strcpy(str, "Mono Mix");
+                       if (csource_info.type == SND_CONTROL_TYPE_ENUMERATED) {
+                               capture_item = 0;
+                               if (!strcmp(csource_info.value.enumerated.name, str)) {
+                                       if (voices < csource_info.values_count)
+                                               voices = csource_info.values_count;
+                                       caps |= SND_MIXER_SCTCAP_CAPTURE;
+                                       present |= MIXER_PRESENT_CAPTURE_SOURCE;
+                               } else for (capture_item = 1; capture_item < csource_info.value.enumerated.items; capture_item++) {
+                                       csource_info.value.enumerated.item = capture_item;
+                                       if ((err = snd_ctl_cinfo(handle->ctl_handle, &csource_info)) < 0)
+                                               return err;
+                                       if (!strcmp(csource_info.value.enumerated.name, str)) {
+                                               if (voices < csource_info.values_count)
+                                                       voices = csource_info.values_count;
+                                               caps |= SND_MIXER_SCTCAP_CAPTURE;
+                                               present |= MIXER_PRESENT_CAPTURE_SOURCE;
+                                               break;
+                                       }
+                               }
+                       }
+               }
+               if (voices > 1) {
+                       caps |= SND_MIXER_SCTCAP_JOINTLY_MUTE | SND_MIXER_SCTCAP_JOINTLY_CAPTURE | SND_MIXER_SCTCAP_JOINTLY_VOLUME;
+                       if (present & MIXER_PRESENT_GLOBAL_SWITCH) {
+                               if (gswitch_info.values_count > 1)
+                                       caps &= ~SND_MIXER_SCTCAP_JOINTLY_MUTE;
+                       }
+                       if (present & MIXER_PRESENT_PLAYBACK_SWITCH) {
+                               if (pswitch_info.values_count > 1)
+                                       caps &= ~SND_MIXER_SCTCAP_JOINTLY_MUTE;
+                       }
+                       if (present & MIXER_PRESENT_CAPTURE_SWITCH) {
+                               if (cswitch_info.values_count > 1)
+                                       caps &= ~SND_MIXER_SCTCAP_JOINTLY_CAPTURE;
+                       }
+                       if (present & MIXER_PRESENT_GLOBAL_VOLUME) {
+                               if (gswitch_info.values_count > 1)
+                                       caps &= ~SND_MIXER_SCTCAP_JOINTLY_VOLUME;
+                       }
+                       if (present & MIXER_PRESENT_PLAYBACK_VOLUME) {
+                               if (pswitch_info.values_count > 1)
+                                       caps &= ~SND_MIXER_SCTCAP_JOINTLY_VOLUME;
+                       }
+                       if (present & MIXER_PRESENT_CAPTURE_VOLUME) {
+                               if (cswitch_info.values_count > 1)
+                                       caps &= ~SND_MIXER_SCTCAP_JOINTLY_VOLUME;
+                       }
+               }
+               simple = build_input_scontrol(handle, sname, index);
+               if (simple == NULL)
+                       return -ENOMEM;
+               simple->present = present;
+               simple->gswitch_values = gswitch_info.values_count;
+               simple->pswitch_values = pswitch_info.values_count;
+               simple->cswitch_values = cswitch_info.values_count;
+               simple->gvolume_values = gvolume_info.values_count;
+               simple->pvolume_values = pvolume_info.values_count;
+               simple->cvolume_values = cvolume_info.values_count;
+               simple->ccapture_values = csource_info.values_count;
+               simple->capture_item = capture_item;
+               simple->caps = caps;
+               simple->voices = voices;
+               simple->min = min;
+               simple->max = max;
+               // fprintf(stderr, "sname = '%s', index = %i, present = 0x%x, voices = %i\n", sname, index, present, voices);
+       } while (present != 0);
        return 0;
 }
 
@@ -420,6 +516,8 @@ int snd_mixer_simple_build(snd_mixer_t *handle)
        char **input = inputs;
        int err;
 
+       if ((err = snd_ctl_cbuild(handle->ctl_handle, snd_ctl_csort)) < 0)
+               return err;
        while (*input) {
                if ((err = build_input(handle, *input)) < 0) {
                        snd_mixer_simple_destroy(handle);
@@ -436,5 +534,6 @@ int snd_mixer_simple_destroy(snd_mixer_t *handle)
        while (handle->simple_first)
                simple_remove(handle, handle->simple_first);
        handle->simple_valid = 0;
+       snd_ctl_cfree(handle->ctl_handle);
        return 0;
 }