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 #include <math.h>
34 #include <stdbool.h>
35 #include "volume_mapping.h"
36 
37 #define MAX_LINEAR_DB_SCALE	24
38 
use_linear_dB_scale(long dBmin,long dBmax)39 static inline bool use_linear_dB_scale(long dBmin, long dBmax)
40 {
41 	return dBmax - dBmin <= MAX_LINEAR_DB_SCALE * 100;
42 }
43 
lrint_dir(double x,int dir)44 static long lrint_dir(double x, int dir)
45 {
46 	if (dir > 0)
47 		return lrint(ceil(x));
48 	else if (dir < 0)
49 		return lrint(floor(x));
50 	else
51 		return lrint(x);
52 }
53 
54 enum ctl_dir { PLAYBACK, CAPTURE };
55 
56 static int (* const get_dB_range[2])(snd_mixer_elem_t *, long *, long *) = {
57 	snd_mixer_selem_get_playback_dB_range,
58 	snd_mixer_selem_get_capture_dB_range,
59 };
60 static int (* const get_raw_range[2])(snd_mixer_elem_t *, long *, long *) = {
61 	snd_mixer_selem_get_playback_volume_range,
62 	snd_mixer_selem_get_capture_volume_range,
63 };
64 static int (* const get_dB[2])(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long *) = {
65 	snd_mixer_selem_get_playback_dB,
66 	snd_mixer_selem_get_capture_dB,
67 };
68 static int (* const get_raw[2])(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long *) = {
69 	snd_mixer_selem_get_playback_volume,
70 	snd_mixer_selem_get_capture_volume,
71 };
72 static int (* const set_dB[2])(snd_mixer_elem_t *, long, int) = {
73 	snd_mixer_selem_set_playback_dB_all,
74 	snd_mixer_selem_set_capture_dB_all,
75 };
76 static int (* const set_raw[2])(snd_mixer_elem_t *, long) = {
77 	snd_mixer_selem_set_playback_volume_all,
78 	snd_mixer_selem_set_capture_volume_all,
79 };
80 
get_normalized_volume(snd_mixer_elem_t * elem,snd_mixer_selem_channel_id_t channel,enum ctl_dir ctl_dir)81 static double get_normalized_volume(snd_mixer_elem_t *elem,
82 				    snd_mixer_selem_channel_id_t channel,
83 				    enum ctl_dir ctl_dir)
84 {
85 	long min, max, value;
86 	double normalized, min_norm;
87 	int err;
88 
89 	err = get_dB_range[ctl_dir](elem, &min, &max);
90 	if (err < 0 || min >= max) {
91 		err = get_raw_range[ctl_dir](elem, &min, &max);
92 		if (err < 0 || min == max)
93 			return 0;
94 
95 		err = get_raw[ctl_dir](elem, channel, &value);
96 		if (err < 0)
97 			return 0;
98 
99 		return (value - min) / (double)(max - min);
100 	}
101 
102 	err = get_dB[ctl_dir](elem, channel, &value);
103 	if (err < 0)
104 		return 0;
105 
106 	if (use_linear_dB_scale(min, max))
107 		return (value - min) / (double)(max - min);
108 
109 	normalized = pow(10, (value - max) / 6000.0);
110 	if (min != SND_CTL_TLV_DB_GAIN_MUTE) {
111 		min_norm = pow(10, (min - max) / 6000.0);
112 		normalized = (normalized - min_norm) / (1 - min_norm);
113 	}
114 
115 	return normalized;
116 }
117 
set_normalized_volume(snd_mixer_elem_t * elem,double volume,int dir,enum ctl_dir ctl_dir)118 static int set_normalized_volume(snd_mixer_elem_t *elem,
119 				 double volume,
120 				 int dir,
121 				 enum ctl_dir ctl_dir)
122 {
123 	long min, max, value;
124 	double min_norm;
125 	int err;
126 
127 	err = get_dB_range[ctl_dir](elem, &min, &max);
128 	if (err < 0 || min >= max) {
129 		err = get_raw_range[ctl_dir](elem, &min, &max);
130 		if (err < 0)
131 			return err;
132 
133 		/* two special cases to avoid rounding errors at 0% and
134 		   100% */
135 		if (volume <= 0)
136 			return set_raw[ctl_dir](elem, min);
137 		else if (volume >= 1)
138 			return set_raw[ctl_dir](elem, max);
139 
140 		value = lrint_dir(volume * (max - min), dir) + min;
141 		return set_raw[ctl_dir](elem, value);
142 	}
143 
144 	/* two special cases to avoid rounding errors at 0% and
145 	   100% */
146 	if (volume <= 0)
147 		return set_dB[ctl_dir](elem, min, dir);
148 	else if (volume >= 1)
149 		return set_dB[ctl_dir](elem, max, dir);
150 
151 	if (use_linear_dB_scale(min, max)) {
152 		value = lrint_dir(volume * (max - min), dir) + min;
153 		return set_dB[ctl_dir](elem, value, dir);
154 	}
155 
156 	if (min != SND_CTL_TLV_DB_GAIN_MUTE) {
157 		min_norm = pow(10, (min - max) / 6000.0);
158 		volume = volume * (1 - min_norm) + min_norm;
159 	}
160 	value = lrint_dir(6000.0 * log10(volume), dir) + max;
161 	return set_dB[ctl_dir](elem, value, dir);
162 }
163 
get_normalized_playback_volume(snd_mixer_elem_t * elem,snd_mixer_selem_channel_id_t channel)164 double get_normalized_playback_volume(snd_mixer_elem_t *elem,
165 				      snd_mixer_selem_channel_id_t channel)
166 {
167 	return get_normalized_volume(elem, channel, PLAYBACK);
168 }
169 
get_normalized_capture_volume(snd_mixer_elem_t * elem,snd_mixer_selem_channel_id_t channel)170 double get_normalized_capture_volume(snd_mixer_elem_t *elem,
171 				     snd_mixer_selem_channel_id_t channel)
172 {
173 	return get_normalized_volume(elem, channel, CAPTURE);
174 }
175 
176 
set_normalized_playback_volume(snd_mixer_elem_t * elem,double volume,int dir)177 int set_normalized_playback_volume(snd_mixer_elem_t *elem,
178 				   double volume,
179 				   int dir)
180 {
181 	return set_normalized_volume(elem, volume, dir, PLAYBACK);
182 }
183 
set_normalized_capture_volume(snd_mixer_elem_t * elem,double volume,int dir)184 int set_normalized_capture_volume(snd_mixer_elem_t *elem,
185 				  double volume,
186 				  int dir)
187 {
188 	return set_normalized_volume(elem, volume, dir, CAPTURE);
189 }
190