tarina

git clone https://git.tarina.org/tarina
Log | Files | Refs | README | LICENSE

volume_mapping.c (5680B)


      1 /*
      2  * Copyright (c) 2010 Clemens Ladisch <clemens@ladisch.de>
      3  *
      4  * Permission to use, copy, modify, and/or distribute this software for any
      5  * purpose with or without fee is hereby granted, provided that the above
      6  * copyright notice and this permission notice appear in all copies.
      7  *
      8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
      9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     15  */
     16 
     17 /*
     18  * The functions in this file map the value ranges of ALSA mixer controls onto
     19  * the interval 0..1.
     20  *
     21  * The mapping is designed so that the position in the interval is proportional
     22  * to the volume as a human ear would perceive it (i.e., the position is the
     23  * cubic root of the linear sample multiplication factor).  For controls with
     24  * a small range (24 dB or less), the mapping is linear in the dB values so
     25  * that each step has the same size visually.  Only for controls without dB
     26  * information, a linear mapping of the hardware volume register values is used
     27  * (this is the same algorithm as used in the old alsamixer).
     28  *
     29  * When setting the volume, 'dir' is the rounding direction:
     30  * -1/0/1 = down/nearest/up.
     31  */
     32 
     33 #define _ISOC99_SOURCE /* lrint() */
     34 #define _GNU_SOURCE /* exp10() */
     35 #include "aconfig.h"
     36 #include <math.h>
     37 #include <stdbool.h>
     38 #include "volume_mapping.h"
     39 
     40 #ifdef __UCLIBC__
     41 /* 10^x = 10^(log e^x) = (e^x)^log10 = e^(x * log 10) */
     42 #define exp10(x) (exp((x) * log(10)))
     43 #endif /* __UCLIBC__ */
     44 
     45 #define MAX_LINEAR_DB_SCALE	24
     46 
     47 static inline bool use_linear_dB_scale(long dBmin, long dBmax)
     48 {
     49 	return dBmax - dBmin <= MAX_LINEAR_DB_SCALE * 100;
     50 }
     51 
     52 static long lrint_dir(double x, int dir)
     53 {
     54 	if (dir > 0)
     55 		return lrint(ceil(x));
     56 	else if (dir < 0)
     57 		return lrint(floor(x));
     58 	else
     59 		return lrint(x);
     60 }
     61 
     62 enum ctl_dir { PLAYBACK, CAPTURE };
     63 
     64 static int (* const get_dB_range[2])(snd_mixer_elem_t *, long *, long *) = {
     65 	snd_mixer_selem_get_playback_dB_range,
     66 	snd_mixer_selem_get_capture_dB_range,
     67 };
     68 static int (* const get_raw_range[2])(snd_mixer_elem_t *, long *, long *) = {
     69 	snd_mixer_selem_get_playback_volume_range,
     70 	snd_mixer_selem_get_capture_volume_range,
     71 };
     72 static int (* const get_dB[2])(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long *) = {
     73 	snd_mixer_selem_get_playback_dB,
     74 	snd_mixer_selem_get_capture_dB,
     75 };
     76 static int (* const get_raw[2])(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long *) = {
     77 	snd_mixer_selem_get_playback_volume,
     78 	snd_mixer_selem_get_capture_volume,
     79 };
     80 static int (* const set_dB[2])(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long, int) = {
     81 	snd_mixer_selem_set_playback_dB,
     82 	snd_mixer_selem_set_capture_dB,
     83 };
     84 static int (* const set_raw[2])(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long) = {
     85 	snd_mixer_selem_set_playback_volume,
     86 	snd_mixer_selem_set_capture_volume,
     87 };
     88 
     89 static double get_normalized_volume(snd_mixer_elem_t *elem,
     90 				    snd_mixer_selem_channel_id_t channel,
     91 				    enum ctl_dir ctl_dir)
     92 {
     93 	long min, max, value;
     94 	double normalized, min_norm;
     95 	int err;
     96 
     97 	err = get_dB_range[ctl_dir](elem, &min, &max);
     98 	if (err < 0 || min >= max) {
     99 		err = get_raw_range[ctl_dir](elem, &min, &max);
    100 		if (err < 0 || min == max)
    101 			return 0;
    102 
    103 		err = get_raw[ctl_dir](elem, channel, &value);
    104 		if (err < 0)
    105 			return 0;
    106 
    107 		return (value - min) / (double)(max - min);
    108 	}
    109 
    110 	err = get_dB[ctl_dir](elem, channel, &value);
    111 	if (err < 0)
    112 		return 0;
    113 
    114 	if (use_linear_dB_scale(min, max))
    115 		return (value - min) / (double)(max - min);
    116 
    117 	normalized = exp10((value - max) / 6000.0);
    118 	if (min != SND_CTL_TLV_DB_GAIN_MUTE) {
    119 		min_norm = exp10((min - max) / 6000.0);
    120 		normalized = (normalized - min_norm) / (1 - min_norm);
    121 	}
    122 
    123 	return normalized;
    124 }
    125 
    126 static int set_normalized_volume(snd_mixer_elem_t *elem,
    127 				 snd_mixer_selem_channel_id_t channel,
    128 				 double volume,
    129 				 int dir,
    130 				 enum ctl_dir ctl_dir)
    131 {
    132 	long min, max, value;
    133 	double min_norm;
    134 	int err;
    135 
    136 	err = get_dB_range[ctl_dir](elem, &min, &max);
    137 	if (err < 0 || min >= max) {
    138 		err = get_raw_range[ctl_dir](elem, &min, &max);
    139 		if (err < 0)
    140 			return err;
    141 
    142 		value = lrint_dir(volume * (max - min), dir) + min;
    143 		return set_raw[ctl_dir](elem, channel, value);
    144 	}
    145 
    146 	if (use_linear_dB_scale(min, max)) {
    147 		value = lrint_dir(volume * (max - min), dir) + min;
    148 		return set_dB[ctl_dir](elem, channel, value, dir);
    149 	}
    150 
    151 	if (min != SND_CTL_TLV_DB_GAIN_MUTE) {
    152 		min_norm = exp10((min - max) / 6000.0);
    153 		volume = volume * (1 - min_norm) + min_norm;
    154 	}
    155 	value = lrint_dir(6000.0 * log10(volume), dir) + max;
    156 	return set_dB[ctl_dir](elem, channel, value, dir);
    157 }
    158 
    159 double get_normalized_playback_volume(snd_mixer_elem_t *elem,
    160 				      snd_mixer_selem_channel_id_t channel)
    161 {
    162 	return get_normalized_volume(elem, channel, PLAYBACK);
    163 }
    164 
    165 double get_normalized_capture_volume(snd_mixer_elem_t *elem,
    166 				     snd_mixer_selem_channel_id_t channel)
    167 {
    168 	return get_normalized_volume(elem, channel, CAPTURE);
    169 }
    170 
    171 int set_normalized_playback_volume(snd_mixer_elem_t *elem,
    172 				   snd_mixer_selem_channel_id_t channel,
    173 				   double volume,
    174 				   int dir)
    175 {
    176 	return set_normalized_volume(elem, channel, volume, dir, PLAYBACK);
    177 }
    178 
    179 int set_normalized_capture_volume(snd_mixer_elem_t *elem,
    180 				  snd_mixer_selem_channel_id_t channel,
    181 				  double volume,
    182 				  int dir)
    183 {
    184 	return set_normalized_volume(elem, channel, volume, dir, CAPTURE);
    185 }