Added bag operations for the high-level control interface.

Added event layer for simple mixer controls (not fully finished).
This commit is contained in:
Jaroslav Kysela 2000-08-16 13:35:36 +00:00
parent 5bb1fcb00a
commit 7376ad380e
7 changed files with 304 additions and 85 deletions

View file

@ -113,6 +113,12 @@ int snd_ctl_hcallback_rebuild(snd_ctl_t *handle, snd_ctl_hcallback_rebuild_t *ca
int snd_ctl_hcallback_add(snd_ctl_t *handle, snd_ctl_hcallback_add_t *callback, void *private_data);
int snd_ctl_hevent(snd_ctl_t *handle);
int snd_ctl_hbag_create(void **bag);
int snd_ctl_hbag_destroy(void **bag, void (*hcontrol_free)(snd_hcontrol_t *hcontrol));
int snd_ctl_hbag_add(void **bag, snd_hcontrol_t *hcontrol);
int snd_ctl_hbag_del(void **bag, snd_hcontrol_t *hcontrol);
snd_hcontrol_t *snd_ctl_hbag_find(void **bag, snd_control_id_t *id);
#ifdef __cplusplus
}
#endif

View file

@ -1,6 +1,6 @@
EXTRA_LTLIBRARIES = libcontrol.la
libcontrol_la_SOURCES = cards.c control.c controls.c defaults.c
libcontrol_la_SOURCES = cards.c control.c controls.c bag.c defaults.c
all: libcontrol.la

87
src/control/bag.c Normal file
View file

@ -0,0 +1,87 @@
/*
* Control Interface - highlevel API - hcontrol bag operations
* 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>
#define __USE_GNU
#include <search.h>
#include "asoundlib.h"
#include "control_local.h"
int snd_ctl_hbag_create(void **bag)
{
assert(bag != NULL);
*bag = NULL;
return 0;
}
static void snd_ctl_hbag_free_private(snd_hcontrol_t *hcontrol ATTRIBUTE_UNUSED)
{
/* nothing */
}
int snd_ctl_hbag_destroy(void **bag, void (*hcontrol_free)(snd_hcontrol_t *hcontrol))
{
assert(bag != NULL);
if (hcontrol_free == NULL)
hcontrol_free = snd_ctl_hbag_free_private;
tdestroy(*bag, (__free_fn_t)hcontrol_free);
*bag = NULL;
return 0;
}
int snd_ctl_hbag_add(void **bag, snd_hcontrol_t *hcontrol)
{
void *res;
assert(bag != NULL && hcontrol != NULL);
res = tsearch(hcontrol, bag, (__compar_fn_t)snd_ctl_hsort);
if (res == NULL)
return -ENOMEM;
if ((snd_hcontrol_t *)res == hcontrol)
return -EALREADY;
return 0;
}
int snd_ctl_hbag_del(void **bag, snd_hcontrol_t *hcontrol)
{
assert(bag != NULL && hcontrol != NULL);
if (tdelete(hcontrol, bag, (__compar_fn_t)snd_ctl_hsort) == NULL)
return -ENOENT;
return 0;
}
snd_hcontrol_t *snd_ctl_hbag_find(void **bag, snd_control_id_t *id)
{
void *res;
assert(bag != NULL && id != NULL);
if (*bag == NULL)
return NULL;
res = tfind(id, bag, (__compar_fn_t)snd_ctl_hsort);
return res == NULL ? NULL : *(snd_hcontrol_t **)res;
}

View file

@ -124,16 +124,103 @@ int snd_ctl_hfree(snd_ctl_t *handle)
return 0;
}
int snd_ctl_hsort(const snd_hcontrol_t *c1, const snd_hcontrol_t *c2)
#define NOT_FOUND 1000000000
static int snd_ctl_hsort_mixer_priority_lookup(char **name, 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;
}
}
return NOT_FOUND;
}
static int snd_ctl_hsort_mixer_priority(const char *name)
{
static char *names[] = {
"Master",
"Master Digital",
"Master Mono",
"Hardware Master",
"Headphone",
"Tone Control",
"3D Control",
"PCM",
"PCM Front",
"PCM Rear",
"PCM Pan",
"Wave",
"Music",
"Line",
"CD",
"Mic",
"Phone",
"Video",
"PC Speaker",
"Aux",
"ADC",
"Capture Source",
"Capture",
"Playback",
"Loopback",
"Analog Loopback",
"Digital Loopback",
"S/PDIF Input",
"S/PDIF Output",
NULL
};
static char *names1[] = {
"Switch",
"Volume",
"Playback",
"Capture",
"Bypass",
NULL
};
static char *names2[] = {
"Switch",
"Volume",
"Bypass",
NULL
};
char **ptr, *s;
int res, res1;
if ((res = snd_ctl_hsort_mixer_priority_lookup((char **)&name, names, 1000000)) == NOT_FOUND)
return NOT_FOUND;
if ((res1 = snd_ctl_hsort_mixer_priority_lookup((char **)&name, names1, 1000)) == NOT_FOUND)
return res;
res += res1;
if ((res1 = snd_ctl_hsort_mixer_priority_lookup((char **)&name, names2, 1)) == NOT_FOUND)
return res;
return res + res1;
}
int snd_ctl_hsort(const snd_hcontrol_t *c1, const snd_hcontrol_t *c2)
{
int res, p1, p2;
if (c1->id.iface < c2->id.iface)
return -1;
if (c1->id.iface > c2->id.iface)
return 1;
if ((res = strcmp(c1->id.name, c2->id.name)) != 0)
if ((res = strcmp(c1->id.name, c2->id.name)) != 0) {
if (c1->id.iface != SND_CONTROL_IFACE_MIXER)
return res;
p1 = snd_ctl_hsort_mixer_priority(c1->id.name);
p2 = snd_ctl_hsort_mixer_priority(c2->id.name);
if (p1 < p2)
return -1;
if (p1 > p2)
return 1;
return res;
}
if (c1->id.index < c2->id.index)
return -1;
if (c1->id.index > c2->id.index)

View file

@ -29,6 +29,9 @@
#include "asoundlib.h"
#include "mixer_local.h"
static void snd_mixer_simple_read_rebuild(snd_ctl_t *ctl_handle, void *private_data);
static void snd_mixer_simple_read_add(snd_ctl_t *ctl_handle, void *private_data, snd_hcontrol_t *hcontrol);
int snd_mixer_open(snd_mixer_t **r_handle, int card)
{
snd_mixer_t *handle;
@ -40,12 +43,21 @@ int snd_mixer_open(snd_mixer_t **r_handle, int card)
*r_handle = NULL;
if ((err = snd_ctl_open(&ctl_handle, card)) < 0)
return err;
if ((err = snd_ctl_hcallback_rebuild(ctl_handle, snd_mixer_simple_read_rebuild, handle)) < 0) {
snd_ctl_close(ctl_handle);
return err;
}
if ((err = snd_ctl_hcallback_add(ctl_handle, snd_mixer_simple_read_add, handle)) < 0) {
snd_ctl_close(ctl_handle);
return err;
}
handle = (snd_mixer_t *) calloc(1, sizeof(snd_mixer_t));
if (handle == NULL) {
snd_ctl_close(ctl_handle);
return -ENOMEM;
}
handle->ctl_handle = ctl_handle;
INIT_LIST_HEAD(&handle->simples);
*r_handle = handle;
return 0;
}
@ -88,10 +100,10 @@ const char *snd_mixer_simple_channel_name(int channel)
int snd_mixer_simple_control_list(snd_mixer_t *handle, snd_mixer_simple_control_list_t *list)
{
struct list_head *lh;
mixer_simple_t *s;
snd_mixer_sid_t *p;
int err;
unsigned int tmp;
int err, idx;
if (handle == NULL || list == NULL)
return -EINVAL;
@ -99,25 +111,34 @@ int snd_mixer_simple_control_list(snd_mixer_t *handle, snd_mixer_simple_control_
if ((err = snd_mixer_simple_build(handle)) < 0)
return err;
list->controls_count = 0;
tmp = list->controls_offset;
for (s = handle->simple_first; s != NULL && tmp > 0; s = s->next);
tmp = list->controls_request;
p = list->pids;
if (tmp > 0 && p == NULL)
if (list->controls_request > 0 && p == NULL)
return -EINVAL;
for (; s != NULL && tmp > 0; s = s->next, tmp--, p++, list->controls_count++)
memcpy(p, &s->sid, sizeof(*p));
idx = 0;
list_for_each(lh, &handle->simples) {
if (idx >= list->controls_offset + list->controls_request)
break;
if (idx >= list->controls_offset) {
s = list_entry(lh, mixer_simple_t, list);
memcpy(p, &s->sid, sizeof(*p)); p++;
list->controls_count++;
}
idx++;
}
list->controls = handle->simple_count;
return 0;
}
static mixer_simple_t *look_for_simple(snd_mixer_t *handle, snd_mixer_sid_t *sid)
{
struct list_head *list;
mixer_simple_t *s;
for (s = handle->simple_first; s != NULL; s = s->next)
list_for_each(list, &handle->simples) {
s = list_entry(list, mixer_simple_t, list);
if (!strcmp(s->sid.name, sid->name) && s->sid.index == sid->index)
return s;
}
return NULL;
}
@ -162,53 +183,26 @@ static void snd_mixer_simple_read_rebuild(snd_ctl_t *ctl_handle, void *private_d
handle->simple_changes++;
}
static void event_for_all_simple_controls(snd_mixer_t *handle, snd_ctl_event_type_t etype, snd_control_id_t *id)
static void snd_mixer_simple_read_add(snd_ctl_t *ctl_handle, void *private_data, snd_hcontrol_t *hcontrol)
{
snd_mixer_t *handle = (snd_mixer_t *)private_data;
mixer_simple_t *s;
struct list_head *list;
for (s = handle->simple_first; s != NULL; s = s->next) {
if (s->event)
s->event(handle, etype, id);
list_for_each(list, &handle->simples) {
s = list_entry(list, mixer_simple_t, list);
if (s->event_add)
s->event_add(handle, hcontrol);
}
}
static void snd_mixer_simple_read_value(snd_ctl_t *ctl_handle, void *private_data, snd_control_id_t *id)
{
snd_mixer_t *handle = (snd_mixer_t *)private_data;
if (handle->ctl_handle != ctl_handle)
return;
event_for_all_simple_controls(handle, SND_CTL_EVENT_VALUE, id);
}
static void snd_mixer_simple_read_change(snd_ctl_t *ctl_handle, void *private_data, snd_control_id_t *id)
{
snd_mixer_t *handle = (snd_mixer_t *)private_data;
if (handle->ctl_handle != ctl_handle)
return;
event_for_all_simple_controls(handle, SND_CTL_EVENT_CHANGE, id);
}
static void snd_mixer_simple_read_add(snd_ctl_t *ctl_handle, void *private_data, snd_control_id_t *id)
{
snd_mixer_t *handle = (snd_mixer_t *)private_data;
if (handle->ctl_handle != ctl_handle)
return;
event_for_all_simple_controls(handle, SND_CTL_EVENT_ADD, id);
}
static void snd_mixer_simple_read_remove(snd_ctl_t *ctl_handle, void *private_data, snd_control_id_t *id)
{
snd_mixer_t *handle = (snd_mixer_t *)private_data;
if (handle->ctl_handle != ctl_handle)
return;
event_for_all_simple_controls(handle, SND_CTL_EVENT_REMOVE, id);
}
int snd_mixer_simple_read(snd_mixer_t *handle, snd_mixer_simple_callbacks_t *callbacks)
{
mixer_simple_t *s;
struct list_head *list;
int err;
if (handle == NULL)
if (handle == NULL || callbacks == NULL)
return -EINVAL;
if (!handle->simple_valid)
snd_mixer_simple_build(handle);
@ -219,5 +213,13 @@ int snd_mixer_simple_read(snd_mixer_t *handle, snd_mixer_simple_callbacks_t *cal
return err;
}
handle->callbacks = NULL;
list_for_each(list, &handle->simples) {
s = list_entry(list, mixer_simple_t, list);
if (s->change > 0) {
s->change = 0;
if (callbacks->value)
callbacks->value(handle, callbacks->private_data, &s->sid);
}
}
return handle->simple_changes;
}

View file

@ -21,12 +21,14 @@
#include <assert.h>
#include "asoundlib.h"
#include "list.h"
typedef struct mixer_simple mixer_simple_t;
typedef struct mixer_simple_hcontrol_private mixer_simple_hcontrol_private_t;
typedef int (mixer_simple_get_t) (snd_mixer_t *handle, mixer_simple_t *simple, snd_mixer_simple_control_t *control);
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);
typedef int (mixer_simple_event_add_t) (snd_mixer_t *handle, snd_hcontrol_t *hcontrol);
#define MIXER_PRESENT_GLOBAL_SWITCH (1<<0)
#define MIXER_PRESENT_GLOBAL_VOLUME (1<<1)
@ -57,19 +59,22 @@ struct mixer_simple {
snd_mixer_sid_t sid;
mixer_simple_get_t *get;
mixer_simple_put_t *put;
mixer_simple_event_t *event;
mixer_simple_t *prev;
mixer_simple_t *next;
mixer_simple_event_add_t *event_add;
struct list_head list;
void *hcontrols; /* bag of associated hcontrols */
unsigned long private_value;
};
struct mixer_simple_hcontrol_private {
void *simples; /* list of associated hcontrols */
};
struct snd_mixer {
snd_ctl_t *ctl_handle;
int simple_valid;
int simple_count;
int simple_changes; /* total number of changes */
mixer_simple_t *simple_first;
mixer_simple_t *simple_last;
int simple_count;
struct list_head simples; /* list of all simple controls */
snd_mixer_simple_callbacks_t *callbacks;
};

View file

@ -29,7 +29,7 @@
#include "asoundlib.h"
#include "mixer_local.h"
static int test_mixer_id(snd_mixer_t *handle, const char *name, int index)
static snd_hcontrol_t *test_mixer_id(snd_mixer_t *handle, const char *name, int index)
{
snd_control_id_t id;
snd_hcontrol_t *hcontrol;
@ -40,7 +40,7 @@ static int test_mixer_id(snd_mixer_t *handle, const char *name, int index)
id.index = index;
hcontrol = snd_ctl_hfind(handle->ctl_handle, &id);
// fprintf(stderr, "Looking for control: '%s', %i (0x%lx)\n", name, index, (long)hcontrol);
return hcontrol != NULL;
return hcontrol;
}
static int get_mixer_info(snd_mixer_t *handle, const char *name, int index, snd_control_info_t *info)
@ -86,19 +86,42 @@ static mixer_simple_t *simple_new(mixer_simple_t *scontrol)
return s;
}
static void hcontrol_event_change(snd_ctl_t *ctl_handle, snd_hcontrol_t *hcontrol)
{
/* ignore at this moment */
}
static void hcontrol_event_value(snd_ctl_t *ctl_handle, snd_hcontrol_t *hcontrol)
{
snd_mixer_t *handle = (snd_mixer_t *)hcontrol->private_data;
mixer_simple_t *s;
struct list_head *list;
list_for_each(list, &handle->simples) {
s = list_entry(list, mixer_simple_t, list);
if (snd_ctl_hbag_find(&s->hcontrols, &hcontrol->id))
s->change++;
}
}
static void hcontrol_event_remove(snd_ctl_t *ctl_handle, snd_hcontrol_t *hcontrol)
{
/* ignore at this moment */
}
static void hcontrol_add(snd_mixer_t *handle, void **bag, snd_hcontrol_t *hcontrol)
{
snd_ctl_hbag_add(bag, hcontrol);
hcontrol->event_change = hcontrol_event_change;
hcontrol->event_value = hcontrol_event_value;
hcontrol->event_remove = hcontrol_event_remove;
hcontrol->private_data = handle;
}
static int simple_add(snd_mixer_t *handle, mixer_simple_t *scontrol)
{
if (handle == NULL || scontrol == NULL)
return -EINVAL;
if (handle->simple_last != NULL) {
handle->simple_last->next = scontrol;
scontrol->prev = handle->simple_last;
scontrol->next = NULL;
handle->simple_last = scontrol;
} else {
handle->simple_first = handle->simple_last = scontrol;
scontrol->prev = scontrol->next = NULL;
}
list_add_tail(&scontrol->list, &handle->simples);
handle->simple_count++;
return 0;
}
@ -107,15 +130,10 @@ static int simple_remove(snd_mixer_t *handle, mixer_simple_t *scontrol)
{
if (handle == NULL || scontrol == NULL)
return -EINVAL;
if (handle->simple_first == scontrol)
handle->simple_first = scontrol->next;
if (handle->simple_last == scontrol)
handle->simple_last = scontrol->prev;
if (scontrol->prev)
scontrol->prev->next = scontrol->next;
if (scontrol->next)
scontrol->next->prev = scontrol->prev;
list_del(&scontrol->list);
handle->simple_count--;
snd_ctl_hbag_destroy(&scontrol->hcontrols, NULL);
free(scontrol);
return 0;
}
@ -330,7 +348,9 @@ static int build_input(snd_mixer_t *handle, const char *sname)
snd_control_info_t gvolume_info, pvolume_info, cvolume_info;
snd_control_info_t csource_info;
long min, max;
void *bag;
mixer_simple_t *simple;
snd_hcontrol_t *hcontrol;
memset(&gswitch_info, 0, sizeof(gswitch_info));
memset(&pswitch_info, 0, sizeof(pswitch_info));
@ -343,8 +363,9 @@ static int build_input(snd_mixer_t *handle, const char *sname)
voices = 0;
present = caps = capture_item = 0;
min = max = 0;
bag = NULL;
sprintf(str, "%s Switch", sname);
if (test_mixer_id(handle, str, index)) {
if (hcontrol = 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) {
@ -352,10 +373,11 @@ static int build_input(snd_mixer_t *handle, const char *sname)
voices = gswitch_info.values_count;
caps |= SND_MIXER_SCTCAP_MUTE;
present |= MIXER_PRESENT_GLOBAL_SWITCH;
hcontrol_add(handle, &bag, hcontrol);
}
}
sprintf(str, "%s Volume", sname);
if (test_mixer_id(handle, str, index)) {
if (hcontrol = 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) {
@ -367,10 +389,11 @@ static int build_input(snd_mixer_t *handle, const char *sname)
max = gvolume_info.value.integer.max;
caps |= SND_MIXER_SCTCAP_VOLUME;
present |= MIXER_PRESENT_GLOBAL_VOLUME;
hcontrol_add(handle, &bag, hcontrol);
}
}
sprintf(str, "%s Playback Switch", sname);
if (test_mixer_id(handle, str, index)) {
if (hcontrol = 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) {
@ -378,10 +401,11 @@ static int build_input(snd_mixer_t *handle, const char *sname)
voices = pswitch_info.values_count;
caps |= SND_MIXER_SCTCAP_MUTE;
present |= MIXER_PRESENT_PLAYBACK_SWITCH;
hcontrol_add(handle, &bag, hcontrol);
}
}
sprintf(str, "%s Capture Switch", sname);
if (test_mixer_id(handle, str, index)) {
if (hcontrol = 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) {
@ -389,10 +413,11 @@ static int build_input(snd_mixer_t *handle, const char *sname)
voices = cswitch_info.values_count;
caps |= SND_MIXER_SCTCAP_CAPTURE;
present |= MIXER_PRESENT_CAPTURE_SWITCH;
hcontrol_add(handle, &bag, hcontrol);
}
}
sprintf(str, "%s Playback Volume", sname);
if (test_mixer_id(handle, str, index)) {
if (hcontrol = 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) {
@ -404,10 +429,11 @@ static int build_input(snd_mixer_t *handle, const char *sname)
max = pvolume_info.value.integer.max;
caps |= SND_MIXER_SCTCAP_VOLUME;
present |= MIXER_PRESENT_PLAYBACK_VOLUME;
hcontrol_add(handle, &bag, hcontrol);
}
}
sprintf(str, "%s Capture Volume", sname);
if (test_mixer_id(handle, str, index)) {
if (hcontrol = 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) {
@ -419,9 +445,10 @@ static int build_input(snd_mixer_t *handle, const char *sname)
max = pvolume_info.value.integer.max;
caps |= SND_MIXER_SCTCAP_VOLUME;
present |= MIXER_PRESENT_CAPTURE_VOLUME;
hcontrol_add(handle, &bag, hcontrol);
}
}
if (index == 0 && test_mixer_id(handle, "Capture Source", 0)) {
if (index == 0 && (hcontrol = test_mixer_id(handle, "Capture Source", 0)) != NULL) {
if ((err = get_mixer_info(handle, "Capture Source", 0, &csource_info)) < 0)
return err;
strcpy(str, sname);
@ -436,6 +463,7 @@ static int build_input(snd_mixer_t *handle, const char *sname)
voices = csource_info.values_count;
caps |= SND_MIXER_SCTCAP_CAPTURE;
present |= MIXER_PRESENT_CAPTURE_SOURCE;
hcontrol_add(handle, &bag, hcontrol);
} 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)
@ -445,6 +473,7 @@ static int build_input(snd_mixer_t *handle, const char *sname)
voices = csource_info.values_count;
caps |= SND_MIXER_SCTCAP_CAPTURE;
present |= MIXER_PRESENT_CAPTURE_SOURCE;
hcontrol_add(handle, &bag, hcontrol);
break;
}
}
@ -480,8 +509,10 @@ static int build_input(snd_mixer_t *handle, const char *sname)
if (present == 0)
break;
simple = build_input_scontrol(handle, sname, index);
if (simple == NULL)
if (simple == NULL) {
snd_ctl_hbag_destroy(&bag, NULL);
return -ENOMEM;
}
simple->present = present;
simple->gswitch_values = gswitch_info.values_count;
simple->pswitch_values = pswitch_info.values_count;
@ -495,6 +526,7 @@ static int build_input(snd_mixer_t *handle, const char *sname)
simple->voices = voices;
simple->min = min;
simple->max = max;
simple->hcontrols = bag;
// fprintf(stderr, "sname = '%s', index = %i, present = 0x%x, voices = %i\n", sname, index, present, voices);
};
return 0;
@ -537,8 +569,8 @@ int snd_mixer_simple_build(snd_mixer_t *handle)
int snd_mixer_simple_destroy(snd_mixer_t *handle)
{
while (handle->simple_first)
simple_remove(handle, handle->simple_first);
while (!list_empty(&handle->simples))
simple_remove(handle, list_entry(handle->simples.next, mixer_simple_t, list));
handle->simple_valid = 0;
snd_ctl_hfree(handle->ctl_handle);
return 0;