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