1c72fcc34Sopenharmony_ci/*
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
41static inline bool use_linear_dB_scale(long dBmin, long dBmax)
42{
43	return dBmax - dBmin <= MAX_LINEAR_DB_SCALE * 100;
44}
45
46static 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
56enum ctl_dir { PLAYBACK, CAPTURE };
57
58static 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};
62static 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};
66static 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};
70static 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};
74static 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};
78static 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
83static 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
120static 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
153double 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
159double 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
165int 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
173int 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