Home | History | Annotate | Download | only in control
      1 /**
      2  * \file control/tlv.c
      3  * \brief dB conversion functions from control TLV information
      4  * \author Takashi Iwai <tiwai (at) suse.de>
      5  * \date 2007
      6  */
      7 /*
      8  *  Control Interface - dB conversion functions from control TLV information
      9  *
     10  *  Copyright (c) 2007 Takashi Iwai <tiwai (at) suse.de>
     11  *
     12  *
     13  *   This library is free software; you can redistribute it and/or modify
     14  *   it under the terms of the GNU Lesser General Public License as
     15  *   published by the Free Software Foundation; either version 2.1 of
     16  *   the License, or (at your option) any later version.
     17  *
     18  *   This program is distributed in the hope that it will be useful,
     19  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
     20  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     21  *   GNU Lesser General Public License for more details.
     22  *
     23  *   You should have received a copy of the GNU Lesser General Public
     24  *   License along with this library; if not, write to the Free Software
     25  *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
     26  *
     27  */
     28 
     29 #include <stdio.h>
     30 #include <stdlib.h>
     31 #include <unistd.h>
     32 #include <string.h>
     33 #ifndef HAVE_SOFT_FLOAT
     34 #include <math.h>
     35 #endif
     36 #include "control_local.h"
     37 
     38 #ifndef DOC_HIDDEN
     39 /* convert to index of integer array */
     40 #define int_index(size)	(((size) + sizeof(int) - 1) / sizeof(int))
     41 /* max size of a TLV entry for dB information (including compound one) */
     42 #define MAX_TLV_RANGE_SIZE	256
     43 #endif
     44 
     45 /**
     46  * \brief Parse TLV stream and retrieve dB information
     47  * \param tlv the TLV source
     48  * \param tlv_size the byte size of TLV source
     49  * \param db_tlvp the pointer stored the dB TLV information
     50  * \return the byte size of dB TLV information if found in the given
     51  *   TLV source, or a negative error code.
     52  *
     53  * This function parses the given TLV source and stores the TLV start
     54  * point if the TLV information regarding dB conversion is found.
     55  * The stored TLV pointer can be passed to the convesion functions
     56  * #snd_tlv_convert_to_dB(), #snd_tlv_convert_from_dB() and
     57  * #snd_tlv_get_dB_range().
     58  */
     59 int snd_tlv_parse_dB_info(unsigned int *tlv,
     60 			  unsigned int tlv_size,
     61 			  unsigned int **db_tlvp)
     62 {
     63 	unsigned int type;
     64 	unsigned int size;
     65 	int err;
     66 
     67 	*db_tlvp = NULL;
     68 	type = tlv[0];
     69 	size = tlv[1];
     70 	tlv_size -= 2 * sizeof(int);
     71 	if (size > tlv_size) {
     72 		SNDERR("TLV size error");
     73 		return -EINVAL;
     74 	}
     75 	switch (type) {
     76 	case SND_CTL_TLVT_CONTAINER:
     77 		size = int_index(size) * sizeof(int);
     78 		tlv += 2;
     79 		while (size > 0) {
     80 			unsigned int len;
     81 			err = snd_tlv_parse_dB_info(tlv, size, db_tlvp);
     82 			if (err < 0)
     83 				return err; /* error */
     84 			if (err > 0)
     85 				return err; /* found */
     86 			len = int_index(tlv[1]) + 2;
     87 			size -= len * sizeof(int);
     88 			tlv += len;
     89 		}
     90 		break;
     91 	case SND_CTL_TLVT_DB_SCALE:
     92 #ifndef HAVE_SOFT_FLOAT
     93 	case SND_CTL_TLVT_DB_LINEAR:
     94 #endif
     95 	case SND_CTL_TLVT_DB_RANGE: {
     96 		unsigned int minsize;
     97 		if (type == SND_CTL_TLVT_DB_RANGE)
     98 			minsize = 4 * sizeof(int);
     99 		else
    100 			minsize = 2 * sizeof(int);
    101 		if (size < minsize) {
    102 			SNDERR("Invalid dB_scale TLV size");
    103 			return -EINVAL;
    104 		}
    105 		if (size > MAX_TLV_RANGE_SIZE) {
    106 			SNDERR("Too big dB_scale TLV size: %d", size);
    107 			return -EINVAL;
    108 		}
    109 		*db_tlvp = tlv;
    110 		return size + sizeof(int) * 2;
    111 	}
    112 	default:
    113 		break;
    114 	}
    115 	return -EINVAL; /* not found */
    116 }
    117 
    118 /**
    119  * \brief Get the dB min/max values
    120  * \param tlv the TLV source returned by #snd_tlv_parse_dB_info()
    121  * \param rangemin the minimum value of the raw volume
    122  * \param rangemax the maximum value of the raw volume
    123  * \param min the pointer to store the minimum dB value (in 0.01dB unit)
    124  * \param max the pointer to store the maximum dB value (in 0.01dB unit)
    125  * \return 0 if successful, or a negative error code
    126  */
    127 int snd_tlv_get_dB_range(unsigned int *tlv, long rangemin, long rangemax,
    128 			 long *min, long *max)
    129 {
    130 	int err;
    131 
    132 	switch (tlv[0]) {
    133 	case SND_CTL_TLVT_DB_RANGE: {
    134 		unsigned int pos, len;
    135 		len = int_index(tlv[1]);
    136 		if (len > MAX_TLV_RANGE_SIZE)
    137 			return -EINVAL;
    138 		pos = 2;
    139 		while (pos + 4 <= len) {
    140 			long rmin, rmax;
    141 			rangemin = (int)tlv[pos];
    142 			rangemax = (int)tlv[pos + 1];
    143 			err = snd_tlv_get_dB_range(tlv + pos + 2,
    144 						   rangemin, rangemax,
    145 						   &rmin, &rmax);
    146 			if (err < 0)
    147 				return err;
    148 			if (pos > 2) {
    149 				if (rmin < *min)
    150 					*min = rmin;
    151 				if (rmax > *max)
    152 					*max = rmax;
    153 			} else {
    154 				*min = rmin;
    155 				*max = rmax;
    156 			}
    157 			pos += int_index(tlv[pos + 3]) + 4;
    158 		}
    159 		return 0;
    160 	}
    161 	case SND_CTL_TLVT_DB_SCALE: {
    162 		int step;
    163 		*min = (int)tlv[2];
    164 		step = (tlv[3] & 0xffff);
    165 		*max = *min + (long)(step * (rangemax - rangemin));
    166 		return 0;
    167 	}
    168 	case SND_CTL_TLVT_DB_LINEAR:
    169 		*min = (int)tlv[2];
    170 		*max = (int)tlv[3];
    171 		return 0;
    172 	}
    173 	return -EINVAL;
    174 }
    175 
    176 /**
    177  * \brief Convert the given raw volume value to a dB gain
    178  * \param tlv the TLV source returned by #snd_tlv_parse_dB_info()
    179  * \param rangemin the minimum value of the raw volume
    180  * \param rangemax the maximum value of the raw volume
    181  * \param volume the raw volume value to convert
    182  * \param db_gain the dB gain (in 0.01dB unit)
    183  * \return 0 if successful, or a negative error code
    184  */
    185 int snd_tlv_convert_to_dB(unsigned int *tlv, long rangemin, long rangemax,
    186 			  long volume, long *db_gain)
    187 {
    188 	switch (tlv[0]) {
    189 	case SND_CTL_TLVT_DB_RANGE: {
    190 		unsigned int pos, len;
    191 		len = int_index(tlv[1]);
    192 		if (len > MAX_TLV_RANGE_SIZE)
    193 			return -EINVAL;
    194 		pos = 2;
    195 		while (pos + 4 <= len) {
    196 			rangemin = (int)tlv[pos];
    197 			rangemax = (int)tlv[pos + 1];
    198 			if (volume >= rangemin && volume <= rangemax)
    199 				return snd_tlv_convert_to_dB(tlv + pos + 2,
    200 							     rangemin, rangemax,
    201 							     volume, db_gain);
    202 			pos += int_index(tlv[pos + 3]) + 4;
    203 		}
    204 		return -EINVAL;
    205 	}
    206 	case SND_CTL_TLVT_DB_SCALE: {
    207 		int min, step, mute;
    208 		min = tlv[2];
    209 		step = (tlv[3] & 0xffff);
    210 		mute = (tlv[3] >> 16) & 1;
    211 		if (mute && volume == rangemin)
    212 			*db_gain = SND_CTL_TLV_DB_GAIN_MUTE;
    213 		else
    214 			*db_gain = (volume - rangemin) * step + min;
    215 		return 0;
    216 	}
    217 #ifndef HAVE_SOFT_FLOAT
    218 	case SND_CTL_TLVT_DB_LINEAR: {
    219 		int mindb = tlv[2];
    220 		int maxdb = tlv[3];
    221 		if (volume <= rangemin || rangemax <= rangemin)
    222 			*db_gain = mindb;
    223 		else if (volume >= rangemax)
    224 			*db_gain = maxdb;
    225 		else {
    226 			double val = (double)(volume - rangemin) /
    227 				(double)(rangemax - rangemin);
    228 			if (mindb <= SND_CTL_TLV_DB_GAIN_MUTE)
    229 				*db_gain = (long)(100.0 * 20.0 * log10(val)) +
    230 					maxdb;
    231 			else {
    232 				/* FIXME: precalculate and cache these values */
    233 				double lmin = pow(10.0, mindb/2000.0);
    234 				double lmax = pow(10.0, maxdb/2000.0);
    235 				val = (lmax - lmin) * val + lmin;
    236 				*db_gain = (long)(100.0 * 20.0 * log10(val));
    237 			}
    238 		}
    239 		return 0;
    240 	}
    241 #endif
    242 	}
    243 	return -EINVAL;
    244 }
    245 
    246 /**
    247  * \brief Convert from dB gain to the corresponding raw value
    248  * \param tlv the TLV source returned by #snd_tlv_parse_dB_info()
    249  * \param rangemin the minimum value of the raw volume
    250  * \param rangemax the maximum value of the raw volume
    251  * \param db_gain the dB gain to convert (in 0.01dB unit)
    252  * \param value the pointer to store the converted raw volume value
    253  * \param xdir the direction for round-up. The value is round up
    254  *        when this is positive.
    255  * \return 0 if successful, or a negative error code
    256  */
    257 int snd_tlv_convert_from_dB(unsigned int *tlv, long rangemin, long rangemax,
    258 			    long db_gain, long *value, int xdir)
    259 {
    260 	switch (tlv[0]) {
    261 	case SND_CTL_TLVT_DB_RANGE: {
    262 		unsigned int pos, len;
    263 		len = int_index(tlv[1]);
    264 		if (len > MAX_TLV_RANGE_SIZE)
    265 			return -EINVAL;
    266 		pos = 2;
    267 		while (pos + 4 <= len) {
    268 			long dbmin, dbmax;
    269 			rangemin = (int)tlv[pos];
    270 			rangemax = (int)tlv[pos + 1];
    271 			if (!snd_tlv_get_dB_range(tlv + pos + 2,
    272 						  rangemin, rangemax,
    273 						  &dbmin, &dbmax) &&
    274 			    db_gain >= dbmin && db_gain <= dbmax)
    275 				return snd_tlv_convert_from_dB(tlv + pos + 2,
    276 							       rangemin, rangemax,
    277 							       db_gain, value, xdir);
    278 			pos += int_index(tlv[pos + 3]) + 4;
    279 		}
    280 		return -EINVAL;
    281 	}
    282 	case SND_CTL_TLVT_DB_SCALE: {
    283 		int min, step, max;
    284 		min = tlv[2];
    285 		step = (tlv[3] & 0xffff);
    286 		max = min + (int)(step * (rangemax - rangemin));
    287 		if (db_gain <= min)
    288 			*value = rangemin;
    289 		else if (db_gain >= max)
    290 			*value = rangemax;
    291 		else {
    292 			long v = (db_gain - min) * (rangemax - rangemin);
    293 			if (xdir > 0)
    294 				v += (max - min) - 1;
    295 			v = v / (max - min) + rangemin;
    296 			*value = v;
    297 		}
    298 		return 0;
    299 	}
    300 #ifndef HAVE_SOFT_FLOAT
    301 	case SND_CTL_TLVT_DB_LINEAR: {
    302 		int min, max;
    303 		min = tlv[2];
    304 		max = tlv[3];
    305 		if (db_gain <= min)
    306 			*value = rangemin;
    307 		else if (db_gain >= max)
    308 			*value = rangemax;
    309 		else {
    310 			/* FIXME: precalculate and cache vmin and vmax */
    311 			double vmin, vmax, v;
    312 			vmin = (min <= SND_CTL_TLV_DB_GAIN_MUTE) ? 0.0 :
    313 				pow(10.0,  (double)min / 2000.0);
    314 			vmax = !max ? 1.0 : pow(10.0,  (double)max / 2000.0);
    315 			v = pow(10.0, (double)db_gain / 2000.0);
    316 			v = (v - vmin) * (rangemax - rangemin) / (vmax - vmin);
    317 			if (xdir > 0)
    318 				v = ceil(v);
    319 			*value = (long)v + rangemin;
    320 		}
    321 		return 0;
    322 	}
    323 #endif
    324 	default:
    325 		break;
    326 	}
    327 	return -EINVAL;
    328 }
    329 
    330 #ifndef DOC_HIDDEN
    331 #define TEMP_TLV_SIZE		4096
    332 struct tlv_info {
    333 	long minval, maxval;
    334 	unsigned int *tlv;
    335 	unsigned int buf[TEMP_TLV_SIZE];
    336 };
    337 #endif
    338 
    339 static int get_tlv_info(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
    340 			struct tlv_info *rec)
    341 {
    342 	snd_ctl_elem_info_t *info;
    343 	int err;
    344 
    345 	snd_ctl_elem_info_alloca(&info);
    346 	snd_ctl_elem_info_set_id(info, id);
    347 	err = snd_ctl_elem_info(ctl, info);
    348 	if (err < 0)
    349 		return err;
    350 	if (!snd_ctl_elem_info_is_tlv_readable(info))
    351 		return -EINVAL;
    352 	if (snd_ctl_elem_info_get_type(info) != SND_CTL_ELEM_TYPE_INTEGER)
    353 		return -EINVAL;
    354 	rec->minval = snd_ctl_elem_info_get_min(info);
    355 	rec->maxval = snd_ctl_elem_info_get_max(info);
    356 	err = snd_ctl_elem_tlv_read(ctl, id, rec->buf, sizeof(rec->buf));
    357 	if (err < 0)
    358 		return err;
    359 	err = snd_tlv_parse_dB_info(rec->buf, sizeof(rec->buf), &rec->tlv);
    360 	if (err < 0)
    361 		return err;
    362 	return 0;
    363 }
    364 
    365 /**
    366  * \brief Get the dB min/max values on the given control element
    367  * \param ctl the control handler
    368  * \param id the element id
    369  * \param volume the raw volume value to convert
    370  * \param min the pointer to store the minimum dB value (in 0.01dB unit)
    371  * \param max the pointer to store the maximum dB value (in 0.01dB unit)
    372  * \return 0 if successful, or a negative error code
    373  */
    374 int snd_ctl_get_dB_range(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
    375 			 long *min, long *max)
    376 {
    377 	struct tlv_info info;
    378 	int err;
    379 
    380 	err = get_tlv_info(ctl, id, &info);
    381 	if (err < 0)
    382 		return err;
    383 	return snd_tlv_get_dB_range(info.tlv, info.minval, info.maxval,
    384 				    min, max);
    385 }
    386 
    387 /**
    388  * \brief Convert the volume value to dB on the given control element
    389  * \param ctl the control handler
    390  * \param id the element id
    391  * \param volume the raw volume value to convert
    392  * \param db_gain the dB gain (in 0.01dB unit)
    393  * \return 0 if successful, or a negative error code
    394  */
    395 int snd_ctl_convert_to_dB(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
    396 			  long volume, long *db_gain)
    397 {
    398 	struct tlv_info info;
    399 	int err;
    400 
    401 	err = get_tlv_info(ctl, id, &info);
    402 	if (err < 0)
    403 		return err;
    404 	return snd_tlv_convert_to_dB(info.tlv, info.minval, info.maxval,
    405 				     volume, db_gain);
    406 }
    407 
    408 /**
    409  * \brief Convert from dB gain to the raw volume value on the given control element
    410  * \param ctl the control handler
    411  * \param id the element id
    412  * \param db_gain the dB gain to convert (in 0.01dB unit)
    413  * \param value the pointer to store the converted raw volume value
    414  * \param xdir the direction for round-up. The value is round up
    415  *        when this is positive.
    416  * \return 0 if successful, or a negative error code
    417  */
    418 int snd_ctl_convert_from_dB(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
    419 			    long db_gain, long *value, int xdir)
    420 {
    421 	struct tlv_info info;
    422 	int err;
    423 
    424 	err = get_tlv_info(ctl, id, &info);
    425 	if (err < 0)
    426 		return err;
    427 	return snd_tlv_convert_from_dB(info.tlv, info.minval, info.maxval,
    428 				       db_gain, value, xdir);
    429 }
    430