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 }