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