alsa-lib/src/mixer/simple_none.c
Takashi Sakamoto fce5b6dcd1 mixer: remove alloca() from simple_event_add()
Both of alloca() and automatic variables keep storages on stack, while
the former generates more instructions than the latter. It's better to use
the latter if the size of storage is computable at pre-compile or compile
time; i.e. just for structures.

This commit obsolete usages of alloca() with automatic variables.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2016-07-15 08:16:36 +02:00

1734 lines
44 KiB
C

/**
* \file mixer/simple_none.c
* \brief Mixer Simple Element Class Interface
* \author Jaroslav Kysela <perex@perex.cz>
* \author Abramo Bagnara <abramo@alsa-project.org>
* \date 2001-2004
*
* Mixer simple element class interface.
*/
/*
* Mixer Interface - simple controls
* Copyright (c) 2000,2004 by Jaroslav Kysela <perex@perex.cz>
* Copyright (c) 2001 by Abramo Bagnara <abramo@alsa-project.org>
*
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <assert.h>
#include <math.h>
#include <limits.h>
#include "local.h"
#include "config.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_GLOBAL_ENUM,
CTL_GLOBAL_SWITCH,
CTL_GLOBAL_VOLUME,
CTL_GLOBAL_ROUTE,
CTL_PLAYBACK_ENUM,
CTL_PLAYBACK_SWITCH,
CTL_PLAYBACK_VOLUME,
CTL_PLAYBACK_ROUTE,
CTL_CAPTURE_ENUM,
CTL_CAPTURE_SWITCH,
CTL_CAPTURE_VOLUME,
CTL_CAPTURE_ROUTE,
CTL_CAPTURE_SOURCE,
CTL_LAST = CTL_CAPTURE_SOURCE,
} selem_ctl_type_t;
typedef struct _selem_ctl {
snd_hctl_elem_t *elem;
snd_ctl_elem_type_t type;
unsigned int inactive: 1;
unsigned int values;
long min, max;
} selem_ctl_t;
typedef struct _selem_none {
sm_selem_t selem;
selem_ctl_t ctls[CTL_LAST + 1];
unsigned int capture_item;
struct selem_str {
unsigned int range: 1; /* Forced range */
unsigned int db_initialized: 1;
unsigned int db_init_error: 1;
long min, max;
unsigned int channels;
long vol[32];
unsigned int sw;
unsigned int *db_info;
} str[2];
} selem_none_t;
static const struct mixer_name_table {
const char *longname;
const char *shortname;
} name_table[] = {
{"Tone Control - Switch", "Tone"},
{"Tone Control - Bass", "Bass"},
{"Tone Control - Treble", "Treble"},
{"Synth Tone Control - Switch", "Synth Tone"},
{"Synth Tone Control - Bass", "Synth Bass"},
{"Synth Tone Control - Treble", "Synth Treble"},
{0, 0},
};
#endif /* !DOC_HIDDEN */
static const char *get_short_name(const char *lname)
{
const struct mixer_name_table *p;
for (p = name_table; p->longname; p++) {
if (!strcmp(lname, p->longname))
return p->shortname;
}
return lname;
}
static int compare_mixer_priority_lookup(const char **name, const char * const *names, int coef)
{
int res;
for (res = 0; *names; names++, res += coef) {
if (!strncmp(*name, *names, strlen(*names))) {
*name += strlen(*names);
if (**name == ' ')
(*name)++;
return res+1;
}
}
return MIXER_COMPARE_WEIGHT_NOT_FOUND;
}
static int get_compare_weight(const char *name, unsigned int idx)
{
static const char *const names[] = {
"Master",
"Headphone",
"Speaker",
"Tone",
"Bass",
"Treble",
"3D Control",
"PCM",
"Front",
"Surround",
"Center",
"LFE",
"Side",
"Synth",
"FM",
"Wave",
"Music",
"DSP",
"Line",
"CD",
"Mic",
"Video",
"Zoom Video",
"Phone",
"I2S",
"IEC958",
"PC Speaker",
"Beep",
"Aux",
"Mono",
"Playback",
"Capture",
"Mix",
NULL
};
static const char *const names1[] = {
"-",
NULL,
};
static const char *const names2[] = {
"Mono",
"Digital",
"Switch",
"Depth",
"Wide",
"Space",
"Level",
"Center",
"Output",
"Boost",
"Tone",
"Bass",
"Treble",
NULL,
};
const char *name1;
int res, res1;
if ((res = compare_mixer_priority_lookup((const char **)&name, names, 1000)) == MIXER_COMPARE_WEIGHT_NOT_FOUND)
return MIXER_COMPARE_WEIGHT_NOT_FOUND;
if (*name == '\0')
goto __res;
for (name1 = name; *name1 != '\0'; name1++);
for (name1--; name1 != name && *name1 != ' '; name1--);
while (name1 != name && *name1 == ' ')
name1--;
if (name1 != name) {
for (; name1 != name && *name1 != ' '; name1--);
name = name1;
if ((res1 = compare_mixer_priority_lookup((const char **)&name, names1, 200)) == MIXER_COMPARE_WEIGHT_NOT_FOUND)
return res;
res += res1;
} else {
name = name1;
}
if ((res1 = compare_mixer_priority_lookup((const char **)&name, names2, 20)) == MIXER_COMPARE_WEIGHT_NOT_FOUND)
return res;
__res:
return MIXER_COMPARE_WEIGHT_SIMPLE_BASE + res + idx;
}
static long to_user(selem_none_t *s, int dir, selem_ctl_t *c, long value)
{
int64_t n;
if (c->max == c->min)
return s->str[dir].min;
n = (int64_t) (value - c->min) * (s->str[dir].max - s->str[dir].min);
return s->str[dir].min + (n + (c->max - c->min) / 2) / (c->max - c->min);
}
static long from_user(selem_none_t *s, int dir, selem_ctl_t *c, long value)
{
int64_t n;
if (s->str[dir].max == s->str[dir].min)
return c->min;
n = (int64_t) (value - s->str[dir].min) * (c->max - c->min);
return c->min + (n + (s->str[dir].max - s->str[dir].min) / 2) / (s->str[dir].max - s->str[dir].min);
}
static int elem_read_volume(selem_none_t *s, int dir, selem_ctl_type_t type)
{
snd_ctl_elem_value_t ctl = {0};
unsigned int idx;
int err;
selem_ctl_t *c = &s->ctls[type];
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 = {0};
unsigned int idx;
int err;
selem_ctl_t *c = &s->ctls[type];
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 = {0};
unsigned int idx;
int err;
selem_ctl_t *c = &s->ctls[type];
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 = {0};
unsigned int idx;
int err;
int type;
selem_ctl_t *c;
type = CTL_GLOBAL_ENUM;
if ((s->selem.caps & (SM_CAP_CENUM | SM_CAP_PENUM)) ==
(SM_CAP_CENUM | SM_CAP_PENUM))
type = CTL_GLOBAL_ENUM;
else if (s->selem.caps & SM_CAP_PENUM)
type = CTL_PLAYBACK_ENUM;
else if (s->selem.caps & SM_CAP_CENUM)
type = CTL_CAPTURE_ENUM;
c = &s->ctls[type];
if ((err = snd_hctl_elem_read(c->elem, &ctl)) < 0)
return err;
for (idx = 0; idx < s->str[0].channels; idx++) {
unsigned int idx1 = idx;
if (idx >= c->values)
idx1 = 0;
s->str[0].vol[idx] =
snd_ctl_elem_value_get_enumerated(&ctl, idx1);
}
return 0;
}
static int selem_read(snd_mixer_elem_t *elem)
{
selem_none_t *s;
unsigned int idx;
int err = 0;
long pvol[32], cvol[32];
unsigned int psw, csw;
assert(snd_mixer_elem_get_type(elem) == SND_MIXER_ELEM_SIMPLE);
s = snd_mixer_elem_get_private(elem);
memcpy(pvol, s->str[SM_PLAY].vol, sizeof(pvol));
memset(&s->str[SM_PLAY].vol, 0, sizeof(s->str[SM_PLAY].vol));
psw = s->str[SM_PLAY].sw;
s->str[SM_PLAY].sw = ~0U;
memcpy(cvol, s->str[SM_CAPT].vol, sizeof(cvol));
memset(&s->str[SM_CAPT].vol, 0, sizeof(s->str[SM_CAPT].vol));
csw = s->str[SM_CAPT].sw;
s->str[SM_CAPT].sw = ~0U;
if (s->ctls[CTL_GLOBAL_ENUM].elem) {
err = elem_read_enum(s);
if (err < 0)
return err;
goto __skip_cswitch;
}
if (s->ctls[CTL_CAPTURE_ENUM].elem) {
err = elem_read_enum(s);
if (err < 0)
return err;
goto __skip_cswitch;
}
if (s->ctls[CTL_PLAYBACK_ENUM].elem) {
err = elem_read_enum(s);
if (err < 0)
return err;
goto __skip_cswitch;
}
if (s->ctls[CTL_PLAYBACK_VOLUME].elem)
err = elem_read_volume(s, SM_PLAY, CTL_PLAYBACK_VOLUME);
else if (s->ctls[CTL_GLOBAL_VOLUME].elem)
err = elem_read_volume(s, SM_PLAY, CTL_GLOBAL_VOLUME);
else if (s->ctls[CTL_SINGLE].elem &&
s->ctls[CTL_SINGLE].type == SND_CTL_ELEM_TYPE_INTEGER)
err = elem_read_volume(s, SM_PLAY, CTL_SINGLE);
if (err < 0)
return err;
if ((s->selem.caps & (SM_CAP_GSWITCH|SM_CAP_PSWITCH)) == 0) {
s->str[SM_PLAY].sw = 0;
goto __skip_pswitch;
}
if (s->ctls[CTL_PLAYBACK_SWITCH].elem) {
err = elem_read_switch(s, SM_PLAY, CTL_PLAYBACK_SWITCH);
if (err < 0)
return err;
}
if (s->ctls[CTL_GLOBAL_SWITCH].elem) {
err = elem_read_switch(s, SM_PLAY, CTL_GLOBAL_SWITCH);
if (err < 0)
return err;
}
if (s->ctls[CTL_SINGLE].elem &&
s->ctls[CTL_SINGLE].type == SND_CTL_ELEM_TYPE_BOOLEAN) {
err = elem_read_switch(s, SM_PLAY, CTL_SINGLE);
if (err < 0)
return err;
}
if (s->ctls[CTL_PLAYBACK_ROUTE].elem) {
err = elem_read_route(s, SM_PLAY, CTL_PLAYBACK_ROUTE);
if (err < 0)
return err;
}
if (s->ctls[CTL_GLOBAL_ROUTE].elem) {
err = elem_read_route(s, SM_PLAY, CTL_GLOBAL_ROUTE);
if (err < 0)
return err;
}
__skip_pswitch:
if (s->ctls[CTL_CAPTURE_VOLUME].elem)
err = elem_read_volume(s, SM_CAPT, CTL_CAPTURE_VOLUME);
else if (s->ctls[CTL_GLOBAL_VOLUME].elem)
err = elem_read_volume(s, SM_CAPT, CTL_GLOBAL_VOLUME);
else if (s->ctls[CTL_SINGLE].elem &&
s->ctls[CTL_SINGLE].type == SND_CTL_ELEM_TYPE_INTEGER)
err = elem_read_volume(s, SM_CAPT, CTL_SINGLE);
if (err < 0)
return err;
if ((s->selem.caps & (SM_CAP_GSWITCH|SM_CAP_CSWITCH)) == 0) {
s->str[SM_CAPT].sw = 0;
goto __skip_cswitch;
}
if (s->ctls[CTL_CAPTURE_SWITCH].elem) {
err = elem_read_switch(s, SM_CAPT, CTL_CAPTURE_SWITCH);
if (err < 0)
return err;
}
if (s->ctls[CTL_GLOBAL_SWITCH].elem) {
err = elem_read_switch(s, SM_CAPT, CTL_GLOBAL_SWITCH);
if (err < 0)
return err;
}
if (s->ctls[CTL_SINGLE].elem &&
s->ctls[CTL_SINGLE].type == SND_CTL_ELEM_TYPE_BOOLEAN) {
err = elem_read_switch(s, SM_CAPT, CTL_SINGLE);
if (err < 0)
return err;
}
if (s->ctls[CTL_CAPTURE_ROUTE].elem) {
err = elem_read_route(s, SM_CAPT, CTL_CAPTURE_ROUTE);
if (err < 0)
return err;
}
if (s->ctls[CTL_GLOBAL_ROUTE].elem) {
err = elem_read_route(s, SM_CAPT, CTL_GLOBAL_ROUTE);
if (err < 0)
return err;
}
if (s->ctls[CTL_CAPTURE_SOURCE].elem) {
snd_ctl_elem_value_t ctl = {0};
selem_ctl_t *c = &s->ctls[CTL_CAPTURE_SOURCE];
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 = {0};
unsigned int idx;
int err;
selem_ctl_t *c = &s->ctls[type];
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 = {0};
unsigned int idx;
int err;
selem_ctl_t *c = &s->ctls[type];
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 = {0};
unsigned int idx;
int err;
selem_ctl_t *c = &s->ctls[type];
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 = {0};
unsigned int idx;
int err;
selem_ctl_t *c = &s->ctls[type];
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 = {0};
unsigned int idx;
int err;
int type;
selem_ctl_t *c;
type = CTL_GLOBAL_ENUM;
if ((s->selem.caps & (SM_CAP_CENUM | SM_CAP_PENUM)) ==
(SM_CAP_CENUM | SM_CAP_PENUM))
type = CTL_GLOBAL_ENUM;
else if (s->selem.caps & SM_CAP_PENUM)
type = CTL_PLAYBACK_ENUM;
else if (s->selem.caps & SM_CAP_CENUM)
type = CTL_CAPTURE_ENUM;
c = &s->ctls[type];
if ((err = snd_hctl_elem_read(c->elem, &ctl)) < 0)
return err;
for (idx = 0; idx < c->values; idx++)
snd_ctl_elem_value_set_enumerated(&ctl, idx,
(unsigned int)s->str[0].vol[idx]);
if ((err = snd_hctl_elem_write(c->elem, &ctl)) < 0)
return err;
return 0;
}
static int selem_write_main(snd_mixer_elem_t *elem)
{
selem_none_t *s;
unsigned int idx;
int err;
assert(snd_mixer_elem_get_type(elem) == SND_MIXER_ELEM_SIMPLE);
s = snd_mixer_elem_get_private(elem);
if (s->ctls[CTL_GLOBAL_ENUM].elem)
return elem_write_enum(s);
if (s->ctls[CTL_PLAYBACK_ENUM].elem)
return elem_write_enum(s);
if (s->ctls[CTL_CAPTURE_ENUM].elem)
return elem_write_enum(s);
if (s->ctls[CTL_SINGLE].elem) {
if (s->ctls[CTL_SINGLE].type == SND_CTL_ELEM_TYPE_INTEGER)
err = elem_write_volume(s, SM_PLAY, CTL_SINGLE);
else
err = elem_write_switch(s, SM_PLAY, CTL_SINGLE);
if (err < 0)
return err;
}
if (s->ctls[CTL_GLOBAL_VOLUME].elem) {
err = elem_write_volume(s, SM_PLAY, CTL_GLOBAL_VOLUME);
if (err < 0)
return err;
}
if (s->ctls[CTL_GLOBAL_SWITCH].elem) {
if (s->ctls[CTL_PLAYBACK_SWITCH].elem &&
s->ctls[CTL_CAPTURE_SWITCH].elem)
err = elem_write_switch_constant(s, CTL_GLOBAL_SWITCH,
1);
else
err = elem_write_switch(s, SM_PLAY, CTL_GLOBAL_SWITCH);
if (err < 0)
return err;
}
if (s->ctls[CTL_PLAYBACK_VOLUME].elem) {
err = elem_write_volume(s, SM_PLAY, CTL_PLAYBACK_VOLUME);
if (err < 0)
return err;
}
if (s->ctls[CTL_PLAYBACK_SWITCH].elem) {
err = elem_write_switch(s, SM_PLAY, CTL_PLAYBACK_SWITCH);
if (err < 0)
return err;
}
if (s->ctls[CTL_PLAYBACK_ROUTE].elem) {
err = elem_write_route(s, SM_PLAY, CTL_PLAYBACK_ROUTE);
if (err < 0)
return err;
}
if (s->ctls[CTL_CAPTURE_VOLUME].elem) {
err = elem_write_volume(s, SM_CAPT, CTL_CAPTURE_VOLUME);
if (err < 0)
return err;
}
if (s->ctls[CTL_CAPTURE_SWITCH].elem) {
err = elem_write_switch(s, SM_CAPT, CTL_CAPTURE_SWITCH);
if (err < 0)
return err;
}
if (s->ctls[CTL_CAPTURE_ROUTE].elem) {
err = elem_write_route(s, SM_CAPT, CTL_CAPTURE_ROUTE);
if (err < 0)
return err;
}
if (s->ctls[CTL_CAPTURE_SOURCE].elem) {
snd_ctl_elem_value_t ctl = {0};
selem_ctl_t *c = &s->ctls[CTL_CAPTURE_SOURCE];
if ((err = snd_hctl_elem_read(c->elem, &ctl)) < 0)
return err;
for (idx = 0; idx < c->values; idx++) {
if (s->str[SM_CAPT].sw & (1 << idx))
snd_ctl_elem_value_set_enumerated(&ctl,
idx, s->capture_item);
}
if ((err = snd_hctl_elem_write(c->elem, &ctl)) < 0)
return err;
/* update the element, don't remove */
err = selem_read(elem);
if (err < 0)
return err;
}
return 0;
}
static int selem_write(snd_mixer_elem_t *elem)
{
int err;
err = selem_write_main(elem);
if (err < 0)
selem_read(elem);
return err;
}
static void selem_free(snd_mixer_elem_t *elem)
{
selem_none_t *simple = snd_mixer_elem_get_private(elem);
assert(snd_mixer_elem_get_type(elem) == SND_MIXER_ELEM_SIMPLE);
if (simple->selem.id)
snd_mixer_selem_id_free(simple->selem.id);
/* free db range information */
free(simple->str[0].db_info);
free(simple->str[1].db_info);
free(simple);
}
static int simple_update(snd_mixer_elem_t *melem)
{
selem_none_t *simple;
unsigned int caps, pchannels, cchannels;
long pmin, pmax, cmin, cmax;
selem_ctl_t *ctl;
caps = 0;
pchannels = 0;
pmin = LONG_MAX;
pmax = LONG_MIN;
cchannels = 0;
cmin = LONG_MAX;
cmax = LONG_MIN;
assert(snd_mixer_elem_get_type(melem) == SND_MIXER_ELEM_SIMPLE);
simple = snd_mixer_elem_get_private(melem);
ctl = &simple->ctls[CTL_SINGLE];
if (ctl->elem) {
pchannels = cchannels = ctl->values;
if (ctl->type == SND_CTL_ELEM_TYPE_INTEGER) {
caps |= SM_CAP_GVOLUME;
pmin = cmin = ctl->min;
pmax = cmax = ctl->max;
} else
caps |= SM_CAP_GSWITCH;
}
ctl = &simple->ctls[CTL_GLOBAL_SWITCH];
if (ctl->elem) {
if (pchannels < ctl->values)
pchannels = ctl->values;
if (cchannels < ctl->values)
cchannels = ctl->values;
caps |= SM_CAP_GSWITCH;
}
ctl = &simple->ctls[CTL_GLOBAL_ROUTE];
if (ctl->elem) {
if (pchannels < ctl->values)
pchannels = ctl->values;
if (cchannels < ctl->values)
cchannels = ctl->values;
caps |= SM_CAP_GSWITCH;
}
ctl = &simple->ctls[CTL_GLOBAL_VOLUME];
if (ctl->elem) {
if (pchannels < ctl->values)
pchannels = ctl->values;
if (pmin > ctl->min)
pmin = ctl->min;
if (pmax < ctl->max)
pmax = ctl->max;
if (cchannels < ctl->values)
cchannels = ctl->values;
if (cmin > ctl->min)
cmin = ctl->min;
if (cmax < ctl->max)
cmax = ctl->max;
caps |= SM_CAP_GVOLUME;
}
ctl = &simple->ctls[CTL_PLAYBACK_SWITCH];
if (ctl->elem) {
if (pchannels < ctl->values)
pchannels = ctl->values;
caps |= SM_CAP_PSWITCH;
caps &= ~SM_CAP_GSWITCH;
}
ctl = &simple->ctls[CTL_PLAYBACK_ROUTE];
if (ctl->elem) {
if (pchannels < ctl->values)
pchannels = ctl->values;
caps |= SM_CAP_PSWITCH;
caps &= ~SM_CAP_GSWITCH;
}
ctl = &simple->ctls[CTL_CAPTURE_SWITCH];
if (ctl->elem) {
if (cchannels < ctl->values)
cchannels = ctl->values;
caps |= SM_CAP_CSWITCH;
caps &= ~SM_CAP_GSWITCH;
}
ctl = &simple->ctls[CTL_CAPTURE_ROUTE];
if (ctl->elem) {
if (cchannels < ctl->values)
cchannels = ctl->values;
caps |= SM_CAP_CSWITCH;
caps &= ~SM_CAP_GSWITCH;
}
ctl = &simple->ctls[CTL_PLAYBACK_VOLUME];
if (ctl->elem) {
if (pchannels < ctl->values)
pchannels = ctl->values;
if (pmin > ctl->min)
pmin = ctl->min;
if (pmax < ctl->max)
pmax = ctl->max;
caps |= SM_CAP_PVOLUME;
caps &= ~SM_CAP_GVOLUME;
}
ctl = &simple->ctls[CTL_CAPTURE_VOLUME];
if (ctl->elem) {
if (cchannels < ctl->values)
cchannels = ctl->values;
if (cmin > ctl->min)
cmin = ctl->min;
if (cmax < ctl->max)
cmax = ctl->max;
caps |= SM_CAP_CVOLUME;
caps &= ~SM_CAP_GVOLUME;
}
ctl = &simple->ctls[CTL_CAPTURE_SOURCE];
if (ctl->elem) {
if (cchannels < ctl->values)
cchannels = ctl->values;
caps |= SM_CAP_CSWITCH | SM_CAP_CSWITCH_EXCL;
caps &= ~SM_CAP_GSWITCH;
}
ctl = &simple->ctls[CTL_GLOBAL_ENUM];
if (ctl->elem) {
if (pchannels < ctl->values)
pchannels = ctl->values;
caps |= SM_CAP_PENUM | SM_CAP_CENUM;
}
ctl = &simple->ctls[CTL_PLAYBACK_ENUM];
if (ctl->elem) {
if (pchannels < ctl->values)
pchannels = ctl->values;
caps |= SM_CAP_PENUM;
}
ctl = &simple->ctls[CTL_CAPTURE_ENUM];
if (ctl->elem) {
if (pchannels < ctl->values)
pchannels = ctl->values;
caps |= SM_CAP_CENUM;
}
if (pchannels > 32)
pchannels = 32;
if (cchannels > 32)
cchannels = 32;
if (caps & (SM_CAP_GSWITCH|SM_CAP_PSWITCH))
caps |= SM_CAP_PSWITCH_JOIN;
if (caps & (SM_CAP_GVOLUME|SM_CAP_PVOLUME))
caps |= SM_CAP_PVOLUME_JOIN;
if (caps & (SM_CAP_GSWITCH|SM_CAP_CSWITCH))
caps |= SM_CAP_CSWITCH_JOIN;
if (caps & (SM_CAP_GVOLUME|SM_CAP_CVOLUME))
caps |= SM_CAP_CVOLUME_JOIN;
if (pchannels > 1 || cchannels > 1) {
if (simple->ctls[CTL_SINGLE].elem &&
simple->ctls[CTL_SINGLE].values > 1) {
if (caps & SM_CAP_GSWITCH)
caps &= ~(SM_CAP_PSWITCH_JOIN|SM_CAP_CSWITCH_JOIN);
else
caps &= ~(SM_CAP_PVOLUME_JOIN|SM_CAP_CVOLUME_JOIN);
}
if (simple->ctls[CTL_GLOBAL_ROUTE].elem ||
(simple->ctls[CTL_GLOBAL_SWITCH].elem &&
simple->ctls[CTL_GLOBAL_SWITCH].values > 1)) {
caps &= ~(SM_CAP_PSWITCH_JOIN|SM_CAP_CSWITCH_JOIN);
}
if (simple->ctls[CTL_GLOBAL_VOLUME].elem &&
simple->ctls[CTL_GLOBAL_VOLUME].values > 1) {
caps &= ~(SM_CAP_PVOLUME_JOIN|SM_CAP_CVOLUME_JOIN);
}
}
if (pchannels > 1) {
if (simple->ctls[CTL_PLAYBACK_ROUTE].elem ||
(simple->ctls[CTL_PLAYBACK_SWITCH].elem &&
simple->ctls[CTL_PLAYBACK_SWITCH].values > 1)) {
caps &= ~SM_CAP_PSWITCH_JOIN;
}
if (simple->ctls[CTL_PLAYBACK_VOLUME].elem &&
simple->ctls[CTL_PLAYBACK_VOLUME].values > 1) {
caps &= ~SM_CAP_PVOLUME_JOIN;
}
}
if (cchannels > 1) {
if (simple->ctls[CTL_CAPTURE_ROUTE].elem ||
(simple->ctls[CTL_CAPTURE_SWITCH].elem &&
simple->ctls[CTL_CAPTURE_SWITCH].values > 1) ||
(simple->ctls[CTL_CAPTURE_SOURCE].elem &&
simple->ctls[CTL_CAPTURE_SOURCE].values > 1)) {
caps &= ~SM_CAP_CSWITCH_JOIN;
}
if (simple->ctls[CTL_CAPTURE_VOLUME].elem &&
simple->ctls[CTL_CAPTURE_VOLUME].values > 1) {
caps &= ~SM_CAP_CVOLUME_JOIN;
}
}
/* exceptions */
if ((caps & (SM_CAP_GSWITCH|SM_CAP_PSWITCH|SM_CAP_CSWITCH)) &&
(caps & (SM_CAP_GSWITCH|SM_CAP_PSWITCH|SM_CAP_CSWITCH)) == (caps & SM_CAP_GSWITCH)) {
caps &= ~(SM_CAP_GSWITCH|SM_CAP_CSWITCH_JOIN|SM_CAP_CSWITCH_EXCL);
caps |= SM_CAP_PSWITCH;
}
if ((caps & SM_CAP_GSWITCH) &&
(caps & (SM_CAP_PSWITCH|SM_CAP_CSWITCH)) == 0)
caps |= SM_CAP_PSWITCH|SM_CAP_CSWITCH;
if ((caps & SM_CAP_GVOLUME) &&
(caps & (SM_CAP_PVOLUME|SM_CAP_CVOLUME)) == 0)
caps |= SM_CAP_PVOLUME|SM_CAP_CVOLUME;
simple->selem.caps = caps;
simple->str[SM_PLAY].channels = pchannels;
if (!simple->str[SM_PLAY].range) {
simple->str[SM_PLAY].min = pmin != LONG_MAX ? pmin : 0;
simple->str[SM_PLAY].max = pmax != LONG_MIN ? pmax : 0;
}
simple->str[SM_CAPT].channels = cchannels;
if (!simple->str[SM_CAPT].range) {
simple->str[SM_CAPT].min = cmin != LONG_MAX ? cmin : 0;
simple->str[SM_CAPT].max = cmax != LONG_MIN ? cmax : 0;
}
return 0;
}
#ifndef DOC_HIDDEN
static const struct suf {
const char *suffix;
selem_ctl_type_t type;
} suffixes[] = {
{" Playback Enum", CTL_PLAYBACK_ENUM},
{" Playback Switch", CTL_PLAYBACK_SWITCH},
{" Playback Route", CTL_PLAYBACK_ROUTE},
{" Playback Volume", CTL_PLAYBACK_VOLUME},
{" Capture Enum", CTL_CAPTURE_ENUM},
{" Capture Switch", CTL_CAPTURE_SWITCH},
{" Capture Route", CTL_CAPTURE_ROUTE},
{" Capture Volume", CTL_CAPTURE_VOLUME},
{" Enum", CTL_GLOBAL_ENUM},
{" Switch", CTL_GLOBAL_SWITCH},
{" Route", CTL_GLOBAL_ROUTE},
{" Volume", CTL_GLOBAL_VOLUME},
{NULL, 0}
};
#endif
/* Return base length or 0 on failure */
static int base_len(const char *name, selem_ctl_type_t *type)
{
const struct suf *p;
size_t nlen = strlen(name);
p = suffixes;
while (p->suffix) {
size_t slen = strlen(p->suffix);
size_t l;
if (nlen > slen) {
l = nlen - slen;
if (strncmp(name + l, p->suffix, slen) == 0 &&
(l < 1 || name[l-1] != '-')) { /* 3D Control - Switch */
*type = p->type;
return l;
}
}
p++;
}
/* Special case - handle "Input Source" as a capture route.
* Note that it's *NO* capture source. A capture source is split over
* sub-elements, and multiple capture-sources will result in an error.
* That's why some drivers use "Input Source" as a workaround.
* Hence, this is a workaround for a workaround to get the things
* straight back again. Sigh.
*/
if (!strcmp(name, "Input Source")) {
*type = CTL_CAPTURE_ROUTE;
return strlen(name);
}
if (strstr(name, "3D Control")) {
if (strstr(name, "Depth")) {
*type = CTL_PLAYBACK_VOLUME;
return strlen(name);
}
}
return 0;
}
/*
* Simple Mixer Operations
*/
static int _snd_mixer_selem_set_volume(snd_mixer_elem_t *elem, int dir, snd_mixer_selem_channel_id_t channel, long value)
{
selem_none_t *s = snd_mixer_elem_get_private(elem);
if (s->selem.caps & SM_CAP_GVOLUME)
dir = SM_PLAY;
if ((unsigned int) channel >= s->str[dir].channels)
return 0;
if (value < s->str[dir].min || value > s->str[dir].max)
return 0;
if (s->selem.caps &
(dir == SM_PLAY ? SM_CAP_PVOLUME_JOIN : SM_CAP_CVOLUME_JOIN))
channel = 0;
if (value != s->str[dir].vol[channel]) {
s->str[dir].vol[channel] = value;
return 1;
}
return 0;
}
static int _snd_mixer_selem_set_switch(snd_mixer_elem_t *elem, int dir, snd_mixer_selem_channel_id_t channel, int value)
{
selem_none_t *s = snd_mixer_elem_get_private(elem);
if ((unsigned int) channel >= s->str[dir].channels)
return 0;
if (s->selem.caps &
(dir == SM_PLAY ? SM_CAP_PSWITCH_JOIN : SM_CAP_CSWITCH_JOIN))
channel = 0;
if (value) {
if (!(s->str[dir].sw & (1 << channel))) {
s->str[dir].sw |= 1 << channel;
return 1;
}
} else {
if (s->str[dir].sw & (1 << channel)) {
s->str[dir].sw &= ~(1 << channel);
return 1;
}
}
return 0;
}
static int is_ops(snd_mixer_elem_t *elem, int dir, int cmd, int val)
{
selem_none_t *s = snd_mixer_elem_get_private(elem);
switch (cmd) {
case SM_OPS_IS_ACTIVE: {
selem_ctl_type_t ctl;
for (ctl = CTL_SINGLE; ctl <= CTL_LAST; ctl++)
if (s->ctls[ctl].elem != NULL && s->ctls[ctl].inactive)
return 0;
return 1;
}
case SM_OPS_IS_MONO:
return s->str[dir].channels == 1;
case SM_OPS_IS_CHANNEL:
return (unsigned int) val < s->str[dir].channels;
case SM_OPS_IS_ENUMERATED:
if (val == 1) {
if (dir == SM_PLAY && (s->selem.caps & SM_CAP_PENUM) && !(s->selem.caps & SM_CAP_CENUM) )
return 1;
if (dir == SM_CAPT && (s->selem.caps & SM_CAP_CENUM) && !(s->selem.caps & SM_CAP_PENUM) )
return 1;
return 0;
}
if (s->selem.caps & (SM_CAP_CENUM | SM_CAP_PENUM) )
return 1;
return 0;
case SM_OPS_IS_ENUMCNT:
/* Both */
if ( (s->selem.caps & (SM_CAP_CENUM | SM_CAP_PENUM)) == (SM_CAP_CENUM | SM_CAP_PENUM) ) {
if (! s->ctls[CTL_GLOBAL_ENUM].elem)
return -EINVAL;
return s->ctls[CTL_GLOBAL_ENUM].max;
/* Only Playback */
} else if (s->selem.caps & SM_CAP_PENUM ) {
if (! s->ctls[CTL_PLAYBACK_ENUM].elem)
return -EINVAL;
return s->ctls[CTL_PLAYBACK_ENUM].max;
/* Only Capture */
} else if (s->selem.caps & SM_CAP_CENUM ) {
if (! s->ctls[CTL_CAPTURE_ENUM].elem)
return -EINVAL;
return s->ctls[CTL_CAPTURE_ENUM].max;
}
}
return 1;
}
static int get_range_ops(snd_mixer_elem_t *elem, int dir,
long *min, long *max)
{
selem_none_t *s = snd_mixer_elem_get_private(elem);
*min = s->str[dir].min;
*max = s->str[dir].max;
return 0;
}
static int set_range_ops(snd_mixer_elem_t *elem, int dir,
long min, long max)
{
selem_none_t *s = snd_mixer_elem_get_private(elem);
int err;
s->str[dir].range = 1;
s->str[dir].min = min;
s->str[dir].max = max;
if ((err = selem_read(elem)) < 0)
return err;
return 0;
}
static int get_volume_ops(snd_mixer_elem_t *elem, int dir,
snd_mixer_selem_channel_id_t channel, long *value)
{
selem_none_t *s = snd_mixer_elem_get_private(elem);
if (s->selem.caps & SM_CAP_GVOLUME)
dir = SM_PLAY;
if ((unsigned int) channel >= s->str[dir].channels)
return -EINVAL;
*value = s->str[dir].vol[channel];
return 0;
}
static int init_db_range(snd_hctl_elem_t *ctl, struct selem_str *rec);
static int convert_to_dB(snd_hctl_elem_t *ctl, struct selem_str *rec,
long volume, long *db_gain)
{
if (init_db_range(ctl, rec) < 0)
return -EINVAL;
return snd_tlv_convert_to_dB(rec->db_info, rec->min, rec->max,
volume, db_gain);
}
/* initialize dB range information, reading TLV via hcontrol
*/
static int init_db_range(snd_hctl_elem_t *ctl, struct selem_str *rec)
{
snd_ctl_elem_info_t info = {0};
unsigned int *tlv = NULL;
const unsigned int tlv_size = 4096;
unsigned int *dbrec;
int db_size;
if (rec->db_init_error)
return -EINVAL;
if (rec->db_initialized)
return 0;
if (snd_hctl_elem_info(ctl, &info) < 0)
goto error;
if (!snd_ctl_elem_info_is_tlv_readable(&info))
goto error;
tlv = malloc(tlv_size);
if (!tlv)
return -ENOMEM;
if (snd_hctl_elem_tlv_read(ctl, tlv, tlv_size) < 0)
goto error;
db_size = snd_tlv_parse_dB_info(tlv, tlv_size, &dbrec);
if (db_size < 0)
goto error;
rec->db_info = malloc(db_size);
if (!rec->db_info)
goto error;
memcpy(rec->db_info, dbrec, db_size);
free(tlv);
rec->db_initialized = 1;
return 0;
error:
free(tlv);
rec->db_init_error = 1;
return -EINVAL;
}
/* get selem_ctl for TLV access */
static selem_ctl_t *get_selem_ctl(selem_none_t *s, int dir)
{
selem_ctl_t *c;
if (dir == SM_PLAY)
c = &s->ctls[CTL_PLAYBACK_VOLUME];
else if (dir == SM_CAPT)
c = &s->ctls[CTL_CAPTURE_VOLUME];
else
return NULL;
if (! c->elem) {
c = &s->ctls[CTL_GLOBAL_VOLUME];
if (! c->elem)
return NULL;
}
if (c->type != SND_CTL_ELEM_TYPE_INTEGER)
return NULL;
return c;
}
static int get_dB_range(snd_hctl_elem_t *ctl, struct selem_str *rec,
long *min, long *max)
{
if (init_db_range(ctl, rec) < 0)
return -EINVAL;
return snd_tlv_get_dB_range(rec->db_info, rec->min, rec->max, min, max);
}
static int get_dB_range_ops(snd_mixer_elem_t *elem, int dir,
long *min, long *max)
{
selem_none_t *s = snd_mixer_elem_get_private(elem);
selem_ctl_t *c;
if (s->selem.caps & SM_CAP_GVOLUME)
dir = SM_PLAY;
c = get_selem_ctl(s, dir);
if (! c)
return -EINVAL;
return get_dB_range(c->elem, &s->str[dir], min, max);
}
static int convert_from_dB(snd_hctl_elem_t *ctl, struct selem_str *rec,
long db_gain, long *value, int xdir)
{
if (init_db_range(ctl, rec) < 0)
return -EINVAL;
return snd_tlv_convert_from_dB(rec->db_info, rec->min, rec->max,
db_gain, value, xdir);
}
static int ask_vol_dB_ops(snd_mixer_elem_t *elem,
int dir,
long value,
long *dBvalue)
{
selem_none_t *s = snd_mixer_elem_get_private(elem);
selem_ctl_t *c;
c = get_selem_ctl(s, dir);
if (! c)
return -EINVAL;
int res = convert_to_dB(c->elem, &s->str[dir], value, dBvalue);
return res;
}
static int get_dB_ops(snd_mixer_elem_t *elem,
int dir,
snd_mixer_selem_channel_id_t channel,
long *value)
{
selem_none_t *s = snd_mixer_elem_get_private(elem);
selem_ctl_t *c;
int err;
long volume, db_gain;
if (s->selem.caps & SM_CAP_GVOLUME)
dir = SM_PLAY;
c = get_selem_ctl(s, dir);
if (! c)
return -EINVAL;
if ((err = get_volume_ops(elem, dir, channel, &volume)) < 0)
goto _err;
if ((err = convert_to_dB(c->elem, &s->str[dir], volume, &db_gain)) < 0)
goto _err;
err = 0;
*value = db_gain;
_err:
return err;
}
static int get_switch_ops(snd_mixer_elem_t *elem, int dir,
snd_mixer_selem_channel_id_t channel, int *value)
{
selem_none_t *s = snd_mixer_elem_get_private(elem);
if (s->selem.caps & SM_CAP_GSWITCH)
dir = SM_PLAY;
if ((unsigned int) channel >= s->str[dir].channels)
return -EINVAL;
*value = !!(s->str[dir].sw & (1 << channel));
return 0;
}
static int set_volume_ops(snd_mixer_elem_t *elem, int dir,
snd_mixer_selem_channel_id_t channel, long value)
{
int changed;
changed = _snd_mixer_selem_set_volume(elem, dir, channel, value);
if (changed < 0)
return changed;
if (changed)
return selem_write(elem);
return 0;
}
static int ask_dB_vol_ops(snd_mixer_elem_t *elem, int dir,
long dbValue, long *value, int xdir)
{
selem_none_t *s = snd_mixer_elem_get_private(elem);
selem_ctl_t *c;
if (s->selem.caps & SM_CAP_GVOLUME)
dir = SM_PLAY;
c = get_selem_ctl(s, dir);
if (! c)
return -EINVAL;
return convert_from_dB(c->elem, &s->str[dir], dbValue, value, xdir);
}
static int set_dB_ops(snd_mixer_elem_t *elem, int dir,
snd_mixer_selem_channel_id_t channel,
long db_gain, int xdir)
{
selem_none_t *s = snd_mixer_elem_get_private(elem);
selem_ctl_t *c;
long value;
int err;
if (s->selem.caps & SM_CAP_GVOLUME)
dir = SM_PLAY;
c = get_selem_ctl(s, dir);
if (! c)
return -EINVAL;
err = convert_from_dB(c->elem, &s->str[dir], db_gain, &value, xdir);
if (err < 0)
return err;
return set_volume_ops(elem, dir, channel, value);
}
static int set_switch_ops(snd_mixer_elem_t *elem, int dir,
snd_mixer_selem_channel_id_t channel, int value)
{
int changed;
selem_none_t *s = snd_mixer_elem_get_private(elem);
if (s->selem.caps & SM_CAP_GSWITCH)
dir = SM_PLAY;
if (dir == SM_PLAY) {
if (! (s->selem.caps & (SM_CAP_GSWITCH|SM_CAP_PSWITCH)))
return -EINVAL;
} else {
if (! (s->selem.caps & (SM_CAP_GSWITCH|SM_CAP_CSWITCH)))
return -EINVAL;
}
changed = _snd_mixer_selem_set_switch(elem, dir, channel, value);
if (changed < 0)
return changed;
if (changed)
return selem_write(elem);
return 0;
}
static int enum_item_name_ops(snd_mixer_elem_t *elem,
unsigned int item,
size_t maxlen, char *buf)
{
selem_none_t *s = snd_mixer_elem_get_private(elem);
snd_ctl_elem_info_t info = {0};
snd_hctl_elem_t *helem;
int type;
type = CTL_GLOBAL_ENUM;
helem = s->ctls[type].elem;
if (!helem) {
type = CTL_PLAYBACK_ENUM;
helem = s->ctls[type].elem;
}
if (!helem) {
type = CTL_CAPTURE_ENUM;
helem = s->ctls[type].elem;
}
assert(helem);
if (item >= (unsigned int)s->ctls[type].max)
return -EINVAL;
snd_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 = {0};
snd_hctl_elem_t *helem;
int err;
if ((unsigned int) channel >= s->str[0].channels)
return -EINVAL;
helem = s->ctls[CTL_GLOBAL_ENUM].elem;
if (!helem) helem = s->ctls[CTL_PLAYBACK_ENUM].elem;
if (!helem) helem = s->ctls[CTL_CAPTURE_ENUM].elem;
assert(helem);
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 = {0};
snd_hctl_elem_t *helem;
int err;
int type;
if ((unsigned int) channel >= s->str[0].channels) {
return -EINVAL;
}
type = CTL_GLOBAL_ENUM;
helem = s->ctls[type].elem;
if (!helem) {
type = CTL_PLAYBACK_ENUM;
helem = s->ctls[type].elem;
}
if (!helem) {
type = CTL_CAPTURE_ENUM;
helem = s->ctls[type].elem;
}
assert(helem);
if (item >= (unsigned int)s->ctls[type].max) {
return -EINVAL;
}
err = snd_hctl_elem_read(helem, &ctl);
if (err < 0) {
return err;
}
snd_ctl_elem_value_set_enumerated(&ctl, channel, item);
return snd_hctl_elem_write(helem, &ctl);
}
static struct sm_elem_ops simple_none_ops = {
.is = is_ops,
.get_range = get_range_ops,
.get_dB_range = get_dB_range_ops,
.set_range = set_range_ops,
.ask_vol_dB = ask_vol_dB_ops,
.ask_dB_vol = ask_dB_vol_ops,
.get_volume = get_volume_ops,
.get_dB = get_dB_ops,
.set_volume = set_volume_ops,
.set_dB = set_dB_ops,
.get_switch = get_switch_ops,
.set_switch = set_switch_ops,
.enum_item_name = enum_item_name_ops,
.get_enum_item = get_enum_item_ops,
.set_enum_item = set_enum_item_ops
};
static int simple_add1(snd_mixer_class_t *class, const char *name,
snd_hctl_elem_t *helem, selem_ctl_type_t type,
unsigned int value)
{
snd_mixer_elem_t *melem;
snd_mixer_selem_id_t *id;
int new = 0;
int err;
snd_ctl_elem_info_t info = {0};
selem_none_t *simple;
const char *name1;
snd_ctl_elem_type_t ctype;
unsigned long values;
err = snd_hctl_elem_info(helem, &info);
if (err < 0)
return err;
ctype = snd_ctl_elem_info_get_type(&info);
values = snd_ctl_elem_info_get_count(&info);
switch (type) {
case CTL_SINGLE:
if (ctype == SND_CTL_ELEM_TYPE_ENUMERATED)
type = CTL_GLOBAL_ENUM;
else if (ctype != SND_CTL_ELEM_TYPE_BOOLEAN &&
ctype != SND_CTL_ELEM_TYPE_INTEGER)
return 0;
break;
case CTL_GLOBAL_ROUTE:
case CTL_PLAYBACK_ROUTE:
case CTL_CAPTURE_ROUTE:
{
unsigned int n;
if (ctype == SND_CTL_ELEM_TYPE_ENUMERATED) {
if (type == CTL_PLAYBACK_ROUTE)
type = CTL_PLAYBACK_ENUM;
else if (type == CTL_CAPTURE_ROUTE)
type = CTL_CAPTURE_ENUM;
else
type = CTL_GLOBAL_ENUM;
break;
}
if (ctype != SND_CTL_ELEM_TYPE_BOOLEAN)
return 0;
#ifdef HAVE_SOFT_FLOAT
/* up to 256 channels */
for (n = 1; n < 256; n++)
if (n * n == values)
break;
#else
n = sqrt((double)values);
#endif
if (n * n != values)
return 0;
values = n;
break;
}
case CTL_GLOBAL_SWITCH:
case CTL_PLAYBACK_SWITCH:
case CTL_CAPTURE_SWITCH:
if (ctype == SND_CTL_ELEM_TYPE_ENUMERATED) {
if (type == CTL_PLAYBACK_SWITCH)
type = CTL_PLAYBACK_ENUM;
else if (type == CTL_CAPTURE_SWITCH)
type = CTL_CAPTURE_ENUM;
else
type = CTL_GLOBAL_ENUM;
break;
}
if (ctype != SND_CTL_ELEM_TYPE_BOOLEAN)
return 0;
break;
case CTL_GLOBAL_VOLUME:
case CTL_PLAYBACK_VOLUME:
case CTL_CAPTURE_VOLUME:
if (ctype == SND_CTL_ELEM_TYPE_ENUMERATED) {
if (type == CTL_PLAYBACK_VOLUME)
type = CTL_PLAYBACK_ENUM;
else if (type == CTL_CAPTURE_VOLUME)
type = CTL_CAPTURE_ENUM;
else
type = CTL_GLOBAL_ENUM;
break;
}
if (ctype != SND_CTL_ELEM_TYPE_INTEGER)
return 0;
break;
case CTL_CAPTURE_SOURCE:
if (ctype != SND_CTL_ELEM_TYPE_ENUMERATED)
return 0;
break;
case CTL_GLOBAL_ENUM:
case CTL_PLAYBACK_ENUM:
case CTL_CAPTURE_ENUM:
if (ctype != SND_CTL_ELEM_TYPE_ENUMERATED)
return 0;
break;
default:
assert(0);
break;
}
name1 = get_short_name(name);
if (snd_mixer_selem_id_malloc(&id))
return -ENOMEM;
snd_mixer_selem_id_set_name(id, name1);
snd_mixer_selem_id_set_index(id, snd_hctl_elem_get_index(helem));
melem = snd_mixer_find_selem(snd_mixer_class_get_mixer(class), id);
if (!melem) {
simple = calloc(1, sizeof(*simple));
if (!simple) {
snd_mixer_selem_id_free(id);
return -ENOMEM;
}
simple->selem.id = id;
simple->selem.ops = &simple_none_ops;
err = snd_mixer_elem_new(&melem, SND_MIXER_ELEM_SIMPLE,
get_compare_weight(
snd_mixer_selem_id_get_name(simple->selem.id),
snd_mixer_selem_id_get_index(simple->selem.id)),
simple, selem_free);
if (err < 0) {
snd_mixer_selem_id_free(id);
free(simple);
return err;
}
new = 1;
} else {
simple = snd_mixer_elem_get_private(melem);
snd_mixer_selem_id_free(id);
}
if (simple->ctls[type].elem) {
SNDERR("helem (%s,'%s',%u,%u,%u) appears twice or more",
snd_ctl_elem_iface_name(
snd_hctl_elem_get_interface(helem)),
snd_hctl_elem_get_name(helem),
snd_hctl_elem_get_index(helem),
snd_hctl_elem_get_device(helem),
snd_hctl_elem_get_subdevice(helem));
err = -EINVAL;
goto __error;
}
simple->ctls[type].elem = helem;
simple->ctls[type].type = snd_ctl_elem_info_get_type(&info);
simple->ctls[type].inactive = snd_ctl_elem_info_is_inactive(&info);
simple->ctls[type].values = values;
if ( (type == CTL_GLOBAL_ENUM) ||
(type == CTL_PLAYBACK_ENUM) ||
(type == CTL_CAPTURE_ENUM) ) {
simple->ctls[type].min = 0;
simple->ctls[type].max = snd_ctl_elem_info_get_items(&info);
} else {
if (ctype == SND_CTL_ELEM_TYPE_INTEGER) {
simple->ctls[type].min =
snd_ctl_elem_info_get_min(&info);
simple->ctls[type].max =
snd_ctl_elem_info_get_max(&info);
}
}
switch (type) {
case CTL_CAPTURE_SOURCE:
simple->capture_item = value;
break;
default:
break;
}
err = snd_mixer_elem_attach(melem, helem);
if (err < 0)
goto __error;
err = simple_update(melem);
if (err < 0) {
if (new)
goto __error;
return err;
}
if (new)
err = snd_mixer_elem_add(melem, class);
else
err = snd_mixer_elem_info(melem);
if (err < 0)
return err;
err = selem_read(melem);
if (err < 0)
return err;
if (err)
err = snd_mixer_elem_value(melem);
return err;
__error:
if (new)
snd_mixer_elem_free(melem);
return -EINVAL;
}
static int simple_event_add(snd_mixer_class_t *class, snd_hctl_elem_t *helem)
{
const char *name = snd_hctl_elem_get_name(helem);
size_t len;
selem_ctl_type_t type = CTL_SINGLE; /* to shut up warning */
if (snd_hctl_elem_get_interface(helem) != SND_CTL_ELEM_IFACE_MIXER)
return 0;
if (strcmp(name, "Capture Source") == 0) {
snd_ctl_elem_info_t info = {0};
unsigned int k, items;
int err;
err = snd_hctl_elem_info(helem, &info);
assert(err >= 0);
if (snd_ctl_elem_info_get_type(&info) !=
SND_CTL_ELEM_TYPE_ENUMERATED)
return 0;
items = snd_ctl_elem_info_get_items(&info);
for (k = 0; k < items; ++k) {
const char *n;
snd_ctl_elem_info_set_item(&info, k);
err = snd_hctl_elem_info(helem, &info);
if (err < 0)
return err;
n = snd_ctl_elem_info_get_item_name(&info);
err = simple_add1(class, n, helem, CTL_CAPTURE_SOURCE,
k);
if (err < 0)
return err;
}
return 0;
}
len = base_len(name, &type);
if (len == 0) {
return simple_add1(class, name, helem, CTL_SINGLE, 0);
} else {
char ename[128];
if (len >= sizeof(ename))
len = sizeof(ename) - 1;
memcpy(ename, name, len);
ename[len] = 0;
/* exception: Capture Volume and Capture Switch */
if (type == CTL_GLOBAL_VOLUME && !strcmp(ename, "Capture"))
type = CTL_CAPTURE_VOLUME;
else if (type == CTL_GLOBAL_SWITCH && !strcmp(ename, "Capture"))
type = CTL_CAPTURE_SWITCH;
return simple_add1(class, ename, helem, type, 0);
}
}
static int simple_event_remove(snd_hctl_elem_t *helem,
snd_mixer_elem_t *melem)
{
selem_none_t *simple = snd_mixer_elem_get_private(melem);
int err;
int k;
for (k = 0; k <= CTL_LAST; k++) {
if (simple->ctls[k].elem == helem)
break;
}
assert(k <= CTL_LAST);
simple->ctls[k].elem = NULL;
err = snd_mixer_elem_detach(melem, helem);
if (err < 0)
return err;
if (snd_mixer_elem_empty(melem))
return snd_mixer_elem_remove(melem);
err = simple_update(melem);
return snd_mixer_elem_info(melem);
}
static int simple_event(snd_mixer_class_t *class, unsigned int mask,
snd_hctl_elem_t *helem, snd_mixer_elem_t *melem)
{
int err;
if (mask == SND_CTL_EVENT_MASK_REMOVE)
return simple_event_remove(helem, melem);
if (mask & SND_CTL_EVENT_MASK_ADD) {
err = simple_event_add(class, helem);
if (err < 0)
return err;
}
if (mask & SND_CTL_EVENT_MASK_INFO) {
err = simple_event_remove(helem, melem);
if (err < 0)
return err;
err = simple_event_add(class, helem);
if (err < 0)
return err;
return 0;
}
if (mask & SND_CTL_EVENT_MASK_VALUE) {
err = selem_read(melem);
if (err < 0)
return err;
if (err) {
err = snd_mixer_elem_value(melem);
if (err < 0)
return err;
}
}
return 0;
}
/**
* \brief Register mixer simple element class - none abstraction
* \param mixer Mixer handle
* \param options Options container
* \param classp Pointer to returned mixer simple element class handle (or NULL)
* \return 0 on success otherwise a negative error code
*/
int snd_mixer_simple_none_register(snd_mixer_t *mixer,
struct snd_mixer_selem_regopt *options ATTRIBUTE_UNUSED,
snd_mixer_class_t **classp)
{
snd_mixer_class_t *class;
int err;
if (snd_mixer_class_malloc(&class))
return -ENOMEM;
snd_mixer_class_set_event(class, simple_event);
snd_mixer_class_set_compare(class, snd_mixer_selem_compare);
err = snd_mixer_class_register(class, mixer);
if (err < 0) {
free(class);
return err;
}
if (classp)
*classp = class;
return 0;
}