Export dB conversion helper functions

Export helper functions to convert dB level and range.

snd_tlv_*dB*() are to convert dB level or range directly from TLV data.
snd_ctl_*dB*() are to get dB level or range from a control element.
This commit is contained in:
Takashi Iwai 2007-10-24 13:04:14 +02:00
parent 631f7cde82
commit f38e5feca3
5 changed files with 469 additions and 265 deletions

View file

@ -442,6 +442,21 @@ const void * snd_ctl_elem_value_get_bytes(const snd_ctl_elem_value_t *obj);
void snd_ctl_elem_value_get_iec958(const snd_ctl_elem_value_t *obj, snd_aes_iec958_t *ptr);
void snd_ctl_elem_value_set_iec958(snd_ctl_elem_value_t *obj, const snd_aes_iec958_t *ptr);
int snd_tlv_parse_dB_info(unsigned int *tlv, unsigned int tlv_size,
unsigned int **db_tlvp);
int snd_tlv_get_dB_range(unsigned int *tlv, long rangemin, long rangemax,
long *min, long *max);
int snd_tlv_convert_to_dB(unsigned int *tlv, long rangemin, long rangemax,
long volume, long *db_gain);
int snd_tlv_convert_from_dB(unsigned int *tlv, long rangemin, long rangemax,
long db_gain, long *value, int xdir);
int snd_ctl_get_dB_range(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
long *min, long *max);
int snd_ctl_convert_to_dB(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
long volume, long *db_gain);
int snd_ctl_convert_from_dB(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
long db_gain, long *value, int xdir);
/**
* \defgroup HControl High level Control Interface
* \ingroup Control

View file

@ -296,3 +296,14 @@ ALSA_1.0.14 {
@SYMBOL_PREFIX@snd_device_name_free_hint;
@SYMBOL_PREFIX@snd_device_name_get_hint;
} ALSA_1.0.12;
ALSA_1.0.16 {
global:
@SYMBOL_PREFIX@snd_tlv_parse_dB_info;
@SYMBOL_PREFIX@snd_tlv_get_dB_range;
@SYMBOL_PREFIX@snd_tlv_convert_to_dB;
@SYMBOL_PREFIX@snd_tlv_convert_from_dB;
@SYMBOL_PREFIX@snd_ctl_get_dB_range;
@SYMBOL_PREFIX@snd_ctl_convert_to_dB;
@SYMBOL_PREFIX@snd_ctl_convert_from_dB;
} ALSA_1.0.14;

View file

@ -1,6 +1,6 @@
EXTRA_LTLIBRARIES = libcontrol.la
libcontrol_la_SOURCES = cards.c namehint.c hcontrol.c \
libcontrol_la_SOURCES = cards.c tlv.c namehint.c hcontrol.c \
control.c control_hw.c setup.c control_symbols.c
if BUILD_CTL_PLUGIN_SHM
libcontrol_la_SOURCES += control_shm.c

429
src/control/tlv.c Normal file
View file

@ -0,0 +1,429 @@
/**
* \file control/tlv.c
* \brief dB conversion functions from control TLV information
* \author Takashi Iwai <tiwai@suse.de>
* \date 2007
*/
/*
* Control Interface - dB conversion functions from control TLV information
*
* Copyright (c) 2007 Takashi Iwai <tiwai@suse.de>
*
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#ifndef HAVE_SOFT_FLOAT
#include <math.h>
#endif
#include "control_local.h"
#ifndef DOC_HIDDEN
/* convert to index of integer array */
#define int_index(size) (((size) + sizeof(int) - 1) / sizeof(int))
/* max size of a TLV entry for dB information (including compound one) */
#define MAX_TLV_RANGE_SIZE 256
#endif
/**
* \brief Parse TLV stream and retrieve dB information
* \param tlv the TLV source
* \param tlv_size the byte size of TLV source
* \param db_tlvp the pointer stored the dB TLV information
* \return the byte size of dB TLV information if found in the given
* TLV source, or a negative error code.
*
* This function parses the given TLV source and stores the TLV start
* point if the TLV information regarding dB conversion is found.
* The stored TLV pointer can be passed to the convesion functions
* #snd_tlv_convert_to_dB(), #snd_tlv_convert_from_dB() and
* #snd_tlv_get_dB_range().
*/
int snd_tlv_parse_dB_info(unsigned int *tlv,
unsigned int tlv_size,
unsigned int **db_tlvp)
{
unsigned int type;
unsigned int size;
int err;
*db_tlvp = NULL;
type = tlv[0];
size = tlv[1];
tlv_size -= 2 * sizeof(int);
if (size > tlv_size) {
SNDERR("TLV size error");
return -EINVAL;
}
switch (type) {
case SND_CTL_TLVT_CONTAINER:
size = int_index(size) * sizeof(int);
tlv += 2;
while (size > 0) {
unsigned int len;
err = snd_tlv_parse_dB_info(tlv, size, db_tlvp);
if (err < 0)
return err; /* error */
if (err > 0)
return err; /* found */
len = int_index(tlv[1]) + 2;
size -= len * sizeof(int);
tlv += len;
}
break;
case SND_CTL_TLVT_DB_SCALE:
#ifndef HAVE_SOFT_FLOAT
case SND_CTL_TLVT_DB_LINEAR:
#endif
case SND_CTL_TLVT_DB_RANGE: {
unsigned int minsize;
if (type == SND_CTL_TLVT_DB_RANGE)
minsize = 4 * sizeof(int);
else
minsize = 2 * sizeof(int);
if (size < minsize) {
SNDERR("Invalid dB_scale TLV size");
return -EINVAL;
}
if (size > MAX_TLV_RANGE_SIZE) {
SNDERR("Too big dB_scale TLV size: %d", size);
return -EINVAL;
}
*db_tlvp = tlv;
return size + sizeof(int) * 2;
}
default:
break;
}
return -EINVAL; /* not found */
}
/**
* \brief Get the dB min/max values
* \param tlv the TLV source returned by #snd_tlv_parse_dB_info()
* \param rangemin the minimum value of the raw volume
* \param rangemax the maximum value of the raw volume
* \param min the pointer to store the minimum dB value (in 0.01dB unit)
* \param max the pointer to store the maximum dB value (in 0.01dB unit)
* \return 0 if successful, or a negative error code
*/
int snd_tlv_get_dB_range(unsigned int *tlv, long rangemin, long rangemax,
long *min, long *max)
{
int err;
switch (tlv[0]) {
case SND_CTL_TLVT_DB_RANGE: {
unsigned int pos, len;
len = int_index(tlv[1]);
if (len > MAX_TLV_RANGE_SIZE)
return -EINVAL;
pos = 2;
while (pos + 4 <= len) {
long rmin, rmax;
rangemin = (int)tlv[pos];
rangemax = (int)tlv[pos + 1];
err = snd_tlv_get_dB_range(tlv + pos + 2,
rangemin, rangemax,
&rmin, &rmax);
if (err < 0)
return err;
if (pos > 2) {
if (rmin < *min)
*min = rmin;
if (rmax > *max)
*max = rmax;
} else {
*min = rmin;
*max = rmax;
}
pos += int_index(tlv[pos + 3]) + 4;
}
return 0;
}
case SND_CTL_TLVT_DB_SCALE: {
int step;
*min = (int)tlv[2];
step = (tlv[3] & 0xffff);
*max = *min + (long)(step * (rangemax - rangemin));
return 0;
}
case SND_CTL_TLVT_DB_LINEAR:
*min = (int)tlv[2];
*max = (int)tlv[3];
return 0;
}
return -EINVAL;
}
/**
* \brief Convert the given raw volume value to a dB gain
* \param tlv the TLV source returned by #snd_tlv_parse_dB_info()
* \param rangemin the minimum value of the raw volume
* \param rangemax the maximum value of the raw volume
* \param volume the raw volume value to convert
* \param db_gain the dB gain (in 0.01dB unit)
* \return 0 if successful, or a negative error code
*/
int snd_tlv_convert_to_dB(unsigned int *tlv, long rangemin, long rangemax,
long volume, long *db_gain)
{
switch (tlv[0]) {
case SND_CTL_TLVT_DB_RANGE: {
unsigned int pos, len;
len = int_index(tlv[1]);
if (len > MAX_TLV_RANGE_SIZE)
return -EINVAL;
pos = 2;
while (pos + 4 <= len) {
rangemin = (int)tlv[pos];
rangemax = (int)tlv[pos + 1];
if (volume >= rangemin && volume <= rangemax)
return snd_tlv_convert_to_dB(tlv + pos + 2,
rangemin, rangemax,
volume, db_gain);
pos += int_index(tlv[pos + 3]) + 4;
}
return -EINVAL;
}
case SND_CTL_TLVT_DB_SCALE: {
int min, step, mute;
min = tlv[2];
step = (tlv[3] & 0xffff);
mute = (tlv[3] >> 16) & 1;
if (mute && volume == rangemin)
*db_gain = SND_CTL_TLV_DB_GAIN_MUTE;
else
*db_gain = (volume - rangemin) * step + min;
return 0;
}
#ifndef HAVE_SOFT_FLOAT
case SND_CTL_TLVT_DB_LINEAR: {
int mindb = tlv[2];
int maxdb = tlv[3];
if (volume <= rangemin || rangemax <= rangemin)
*db_gain = mindb;
else if (volume >= rangemax)
*db_gain = maxdb;
else {
double val = (double)(volume - rangemin) /
(double)(rangemax - rangemin);
if (mindb <= SND_CTL_TLV_DB_GAIN_MUTE)
*db_gain = (long)(100.0 * 20.0 * log10(val)) +
maxdb;
else {
/* FIXME: precalculate and cache these values */
double lmin = pow(10.0, mindb/2000.0);
double lmax = pow(10.0, maxdb/2000.0);
val = (lmax - lmin) * val + lmin;
*db_gain = (long)(100.0 * 20.0 * log10(val));
}
}
return 0;
}
#endif
}
return -EINVAL;
}
/**
* \brief Convert from dB gain to the corresponding raw value
* \param tlv the TLV source returned by #snd_tlv_parse_dB_info()
* \param rangemin the minimum value of the raw volume
* \param rangemax the maximum value of the raw volume
* \param db_gain the dB gain to convert (in 0.01dB unit)
* \param value the pointer to store the converted raw volume value
* \param xdir the direction for round-up. The value is round up
* when this is positive.
* \return 0 if successful, or a negative error code
*/
int snd_tlv_convert_from_dB(unsigned int *tlv, long rangemin, long rangemax,
long db_gain, long *value, int xdir)
{
switch (tlv[0]) {
case SND_CTL_TLVT_DB_RANGE: {
unsigned int pos, len;
len = int_index(tlv[1]);
if (len > MAX_TLV_RANGE_SIZE)
return -EINVAL;
pos = 2;
while (pos + 4 <= len) {
long dbmin, dbmax;
rangemin = (int)tlv[pos];
rangemax = (int)tlv[pos + 1];
if (!snd_tlv_get_dB_range(tlv + pos + 2,
rangemin, rangemax,
&dbmin, &dbmax) &&
db_gain >= dbmin && db_gain <= dbmax)
return snd_tlv_convert_from_dB(tlv + pos + 2,
rangemin, rangemax,
db_gain, value, xdir);
pos += int_index(tlv[pos + 3]) + 4;
}
return -EINVAL;
}
case SND_CTL_TLVT_DB_SCALE: {
int min, step, max;
min = tlv[2];
step = (tlv[3] & 0xffff);
max = min + (int)(step * (rangemax - rangemin));
if (db_gain <= min)
*value = rangemin;
else if (db_gain >= max)
*value = rangemax;
else {
long v = (db_gain - min) * (rangemax - rangemin);
if (xdir > 0)
v += (max - min) - 1;
v = v / (max - min) + rangemin;
*value = v;
}
return 0;
}
#ifndef HAVE_SOFT_FLOAT
case SND_CTL_TLVT_DB_LINEAR: {
int min, max;
min = tlv[2];
max = tlv[3];
if (db_gain <= min)
*value = rangemin;
else if (db_gain >= max)
*value = rangemax;
else {
/* FIXME: precalculate and cache vmin and vmax */
double vmin, vmax, v;
vmin = (min <= SND_CTL_TLV_DB_GAIN_MUTE) ? 0.0 :
pow(10.0, (double)min / 2000.0);
vmax = !max ? 1.0 : pow(10.0, (double)max / 2000.0);
v = pow(10.0, (double)db_gain / 2000.0);
v = (v - vmin) * (rangemax - rangemin) / (vmax - vmin);
if (xdir > 0)
v = ceil(v);
*value = (long)v + rangemin;
}
return 0;
}
#endif
default:
break;
}
return -EINVAL;
}
#ifndef DOC_HIDDEN
#define TEMP_TLV_SIZE 4096
struct tlv_info {
long minval, maxval;
unsigned int *tlv;
unsigned int buf[TEMP_TLV_SIZE];
};
#endif
static int get_tlv_info(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
struct tlv_info *rec)
{
snd_ctl_elem_info_t *info;
int err;
snd_ctl_elem_info_alloca(&info);
snd_ctl_elem_info_set_id(info, id);
err = snd_ctl_elem_info(ctl, info);
if (err < 0)
return err;
if (!snd_ctl_elem_info_is_tlv_readable(info))
return -EINVAL;
if (snd_ctl_elem_info_get_type(info) != SND_CTL_ELEM_TYPE_INTEGER)
return -EINVAL;
rec->minval = snd_ctl_elem_info_get_min(info);
rec->maxval = snd_ctl_elem_info_get_max(info);
err = snd_ctl_elem_tlv_read(ctl, id, rec->buf, sizeof(rec->buf));
if (err < 0)
return err;
err = snd_tlv_parse_dB_info(rec->buf, sizeof(rec->buf), &rec->tlv);
if (err < 0)
return err;
return 0;
}
/**
* \brief Get the dB min/max values on the given control element
* \param ctl the control handler
* \param id the element id
* \param volume the raw volume value to convert
* \param min the pointer to store the minimum dB value (in 0.01dB unit)
* \param max the pointer to store the maximum dB value (in 0.01dB unit)
* \return 0 if successful, or a negative error code
*/
int snd_ctl_get_dB_range(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
long *min, long *max)
{
struct tlv_info info;
int err;
err = get_tlv_info(ctl, id, &info);
if (err < 0)
return err;
return snd_tlv_get_dB_range(info.tlv, info.minval, info.maxval,
min, max);
}
/**
* \brief Convert the volume value to dB on the given control element
* \param ctl the control handler
* \param id the element id
* \param volume the raw volume value to convert
* \param db_gain the dB gain (in 0.01dB unit)
* \return 0 if successful, or a negative error code
*/
int snd_ctl_convert_to_dB(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
long volume, long *db_gain)
{
struct tlv_info info;
int err;
err = get_tlv_info(ctl, id, &info);
if (err < 0)
return err;
return snd_tlv_convert_to_dB(info.tlv, info.minval, info.maxval,
volume, db_gain);
}
/**
* \brief Convert from dB gain to the raw volume value on the given control element
* \param ctl the control handler
* \param id the element id
* \param db_gain the dB gain to convert (in 0.01dB unit)
* \param value the pointer to store the converted raw volume value
* \param xdir the direction for round-up. The value is round up
* when this is positive.
* \return 0 if successful, or a negative error code
*/
int snd_ctl_convert_from_dB(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
long db_gain, long *value, int xdir)
{
struct tlv_info info;
int err;
err = get_tlv_info(ctl, id, &info);
if (err < 0)
return err;
return snd_tlv_convert_from_dB(info.tlv, info.minval, info.maxval,
db_gain, value, xdir);
}

View file

@ -1068,150 +1068,13 @@ static int get_volume_ops(snd_mixer_elem_t *elem, int dir,
static int init_db_range(snd_hctl_elem_t *ctl, struct selem_str *rec);
/* convert to index of integer array */
#ifndef DOC_HIDDEN
#define int_index(size) (((size) + sizeof(int) - 1) / sizeof(int))
#endif
/* max size of a TLV entry for dB information (including compound one) */
#ifndef DOC_HIDDEN
#define MAX_TLV_RANGE_SIZE 256
#endif
/* parse TLV stream and retrieve dB information
* return 0 if successly found and stored to rec,
* return 1 if no information is found,
* or return a negative error code
*/
static int parse_db_range(struct selem_str *rec, unsigned int *tlv,
unsigned int tlv_size)
{
unsigned int type;
unsigned int size;
int err;
type = tlv[0];
size = tlv[1];
tlv_size -= 2 * sizeof(int);
if (size > tlv_size) {
SNDERR("TLV size error");
return -EINVAL;
}
switch (type) {
case SND_CTL_TLVT_CONTAINER:
size = int_index(size) * sizeof(int);
tlv += 2;
while (size > 0) {
unsigned int len;
err = parse_db_range(rec, tlv, size);
if (err <= 0)
return err; /* error or found dB */
len = int_index(tlv[1]) + 2;
size -= len * sizeof(int);
tlv += len;
}
break;
case SND_CTL_TLVT_DB_SCALE:
#ifndef HAVE_SOFT_FLOAT
case SND_CTL_TLVT_DB_LINEAR:
#endif
case SND_CTL_TLVT_DB_RANGE: {
unsigned int minsize;
if (type == SND_CTL_TLVT_DB_RANGE)
minsize = 4 * sizeof(int);
else
minsize = 2 * sizeof(int);
if (size < minsize) {
SNDERR("Invalid dB_scale TLV size");
return -EINVAL;
}
if (size > MAX_TLV_RANGE_SIZE) {
SNDERR("Too big dB_scale TLV size: %d", size);
return -EINVAL;
}
rec->db_info = malloc(size + sizeof(int) * 2);
if (! rec->db_info)
return -ENOMEM;
memcpy(rec->db_info, tlv, size + sizeof(int) * 2);
return 0;
}
default:
break;
}
return -EINVAL; /* not found */
}
/* convert the given raw volume value to a dB gain
*/
static int do_convert_to_dB(unsigned int *tlv, long rangemin, long rangemax,
long volume, long *db_gain)
{
switch (tlv[0]) {
case SND_CTL_TLVT_DB_RANGE: {
unsigned int pos, len;
len = int_index(tlv[1]);
if (len > MAX_TLV_RANGE_SIZE)
return -EINVAL;
pos = 2;
while (pos + 4 <= len) {
rangemin = (int)tlv[pos];
rangemax = (int)tlv[pos + 1];
if (volume >= rangemin && volume <= rangemax)
return do_convert_to_dB(tlv + pos + 2,
rangemin, rangemax,
volume, db_gain);
pos += int_index(tlv[pos + 3]) + 4;
}
return -EINVAL;
}
case SND_CTL_TLVT_DB_SCALE: {
int min, step, mute;
min = tlv[2];
step = (tlv[3] & 0xffff);
mute = (tlv[3] >> 16) & 1;
if (mute && volume == rangemin)
*db_gain = SND_CTL_TLV_DB_GAIN_MUTE;
else
*db_gain = (volume - rangemin) * step + min;
return 0;
}
#ifndef HAVE_SOFT_FLOAT
case SND_CTL_TLVT_DB_LINEAR: {
int mindb = tlv[2];
int maxdb = tlv[3];
if (volume <= rangemin || rangemax <= rangemin)
*db_gain = mindb;
else if (volume >= rangemax)
*db_gain = maxdb;
else {
double val = (double)(volume - rangemin) /
(double)(rangemax - rangemin);
if (mindb <= SND_CTL_TLV_DB_GAIN_MUTE)
*db_gain = (long)(100.0 * 20.0 * log10(val)) +
maxdb;
else {
/* FIXME: precalculate and cache these values */
double lmin = pow(10.0, mindb/2000.0);
double lmax = pow(10.0, maxdb/2000.0);
val = (lmax - lmin) * val + lmin;
*db_gain = (long)(100.0 * 20.0 * log10(val));
}
}
return 0;
}
#endif
}
return -EINVAL;
}
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 do_convert_to_dB(rec->db_info, rec->min, rec->max,
volume, db_gain);
return snd_tlv_convert_to_dB(rec->db_info, rec->min, rec->max,
volume, db_gain);
}
/* initialize dB range information, reading TLV via hcontrol
@ -1221,6 +1084,8 @@ static int init_db_range(snd_hctl_elem_t *ctl, struct selem_str *rec)
snd_ctl_elem_info_t *info;
unsigned int *tlv = NULL;
const unsigned int tlv_size = 4096;
unsigned int *dbrec;
int db_size;
if (rec->db_init_error)
return -EINVAL;
@ -1237,8 +1102,13 @@ static int init_db_range(snd_hctl_elem_t *ctl, struct selem_str *rec)
return -ENOMEM;
if (snd_hctl_elem_tlv_read(ctl, tlv, tlv_size) < 0)
goto error;
if (parse_db_range(rec, tlv, tlv_size) < 0)
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;
@ -1269,59 +1139,13 @@ static selem_ctl_t *get_selem_ctl(selem_none_t *s, int dir)
return c;
}
/* Get the dB min/max values
*/
static int do_get_dB_range(unsigned int *tlv, long rangemin, long rangemax,
long *min, long *max)
{
switch (tlv[0]) {
case SND_CTL_TLVT_DB_RANGE: {
unsigned int pos, len;
len = int_index(tlv[1]);
if (len > MAX_TLV_RANGE_SIZE)
return -EINVAL;
pos = 2;
while (pos + 4 <= len) {
long rmin, rmax;
rangemin = (int)tlv[pos];
rangemax = (int)tlv[pos + 1];
do_get_dB_range(tlv + pos + 2, rangemin, rangemax,
&rmin, &rmax);
if (pos > 2) {
if (rmin < *min)
*min = rmin;
if (rmax > *max)
*max = rmax;
} else {
*min = rmin;
*max = rmax;
}
pos += int_index(tlv[pos + 3]) + 4;
}
return 0;
}
case SND_CTL_TLVT_DB_SCALE: {
int step;
*min = (int)tlv[2];
step = (tlv[3] & 0xffff);
*max = *min + (long)(step * (rangemax - rangemin));
return 0;
}
case SND_CTL_TLVT_DB_LINEAR:
*min = (int)tlv[2];
*max = (int)tlv[3];
return 0;
}
return -EINVAL;
}
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 do_get_dB_range(rec->db_info, rec->min, rec->max, min, max);
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,
@ -1336,89 +1160,14 @@ static int get_dB_range_ops(snd_mixer_elem_t *elem, int dir,
return get_dB_range(c->elem, &s->str[dir], min, max);
}
/* Convert from dB gain to the corresponding raw value.
* The value is round up when xdir > 0.
*/
static int do_convert_from_dB(unsigned int *tlv, long rangemin, long rangemax,
long db_gain, long *value, int xdir)
{
switch (tlv[0]) {
case SND_CTL_TLVT_DB_RANGE: {
unsigned int pos, len;
len = int_index(tlv[1]);
if (len > MAX_TLV_RANGE_SIZE)
return -EINVAL;
pos = 2;
while (pos + 4 <= len) {
long dbmin, dbmax;
rangemin = (int)tlv[pos];
rangemax = (int)tlv[pos + 1];
if (! do_get_dB_range(tlv + pos + 2, rangemin, rangemax,
&dbmin, &dbmax) &&
db_gain >= dbmin && db_gain <= dbmax)
return do_convert_from_dB(tlv + pos + 2,
rangemin, rangemax,
db_gain, value, xdir);
pos += int_index(tlv[pos + 3]) + 4;
}
return -EINVAL;
}
case SND_CTL_TLVT_DB_SCALE: {
int min, step, max;
min = tlv[2];
step = (tlv[3] & 0xffff);
max = min + (int)(step * (rangemax - rangemin));
if (db_gain <= min)
*value = rangemin;
else if (db_gain >= max)
*value = rangemax;
else {
long v = (db_gain - min) * (rangemax - rangemin);
if (xdir > 0)
v += (max - min) - 1;
v = v / (max - min) + rangemin;
*value = v;
}
return 0;
}
#ifndef HAVE_SOFT_FLOAT
case SND_CTL_TLVT_DB_LINEAR: {
int min, max;
min = tlv[2];
max = tlv[3];
if (db_gain <= min)
*value = rangemin;
else if (db_gain >= max)
*value = rangemax;
else {
/* FIXME: precalculate and cache vmin and vmax */
double vmin, vmax, v;
vmin = (min <= SND_CTL_TLV_DB_GAIN_MUTE) ? 0.0 :
pow(10.0, (double)min / 2000.0);
vmax = !max ? 1.0 : pow(10.0, (double)max / 2000.0);
v = pow(10.0, (double)db_gain / 2000.0);
v = (v - vmin) * (rangemax - rangemin) / (vmax - vmin);
if (xdir > 0)
v = ceil(v);
*value = (long)v + rangemin;
}
return 0;
}
#endif
default:
break;
}
return -EINVAL;
}
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 do_convert_from_dB(rec->db_info, rec->min, rec->max,
db_gain, value, xdir);
return snd_tlv_convert_from_dB(rec->db_info, rec->min, rec->max,
db_gain, value, xdir);
}
static int get_dB_ops(snd_mixer_elem_t *elem,