1c72fcc34Sopenharmony_ci/*
2c72fcc34Sopenharmony_ci * Copyright (c) 2010 Clemens Ladisch <clemens@ladisch.de>
3c72fcc34Sopenharmony_ci *
4c72fcc34Sopenharmony_ci * Permission to use, copy, modify, and/or distribute this software for any
5c72fcc34Sopenharmony_ci * purpose with or without fee is hereby granted, provided that the above
6c72fcc34Sopenharmony_ci * copyright notice and this permission notice appear in all copies.
7c72fcc34Sopenharmony_ci *
8c72fcc34Sopenharmony_ci * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9c72fcc34Sopenharmony_ci * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10c72fcc34Sopenharmony_ci * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11c72fcc34Sopenharmony_ci * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12c72fcc34Sopenharmony_ci * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13c72fcc34Sopenharmony_ci * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14c72fcc34Sopenharmony_ci * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15c72fcc34Sopenharmony_ci */
16c72fcc34Sopenharmony_ci
17c72fcc34Sopenharmony_ci/*
18c72fcc34Sopenharmony_ci * The functions in this file map the value ranges of ALSA mixer controls onto
19c72fcc34Sopenharmony_ci * the interval 0..1.
20c72fcc34Sopenharmony_ci *
21c72fcc34Sopenharmony_ci * The mapping is designed so that the position in the interval is proportional
22c72fcc34Sopenharmony_ci * to the volume as a human ear would perceive it (i.e., the position is the
23c72fcc34Sopenharmony_ci * cubic root of the linear sample multiplication factor).  For controls with
24c72fcc34Sopenharmony_ci * a small range (24 dB or less), the mapping is linear in the dB values so
25c72fcc34Sopenharmony_ci * that each step has the same size visually.  Only for controls without dB
26c72fcc34Sopenharmony_ci * information, a linear mapping of the hardware volume register values is used
27c72fcc34Sopenharmony_ci * (this is the same algorithm as used in the old alsamixer).
28c72fcc34Sopenharmony_ci *
29c72fcc34Sopenharmony_ci * When setting the volume, 'dir' is the rounding direction:
30c72fcc34Sopenharmony_ci * -1/0/1 = down/nearest/up.
31c72fcc34Sopenharmony_ci */
32c72fcc34Sopenharmony_ci
33c72fcc34Sopenharmony_ci#define _ISOC99_SOURCE /* lrint() */
34c72fcc34Sopenharmony_ci#include "aconfig.h"
35c72fcc34Sopenharmony_ci#include <math.h>
36c72fcc34Sopenharmony_ci#include <stdbool.h>
37c72fcc34Sopenharmony_ci#include "volume_mapping.h"
38c72fcc34Sopenharmony_ci
39c72fcc34Sopenharmony_ci#define MAX_LINEAR_DB_SCALE	24
40c72fcc34Sopenharmony_ci
41c72fcc34Sopenharmony_cistatic inline bool use_linear_dB_scale(long dBmin, long dBmax)
42c72fcc34Sopenharmony_ci{
43c72fcc34Sopenharmony_ci	return dBmax - dBmin <= MAX_LINEAR_DB_SCALE * 100;
44c72fcc34Sopenharmony_ci}
45c72fcc34Sopenharmony_ci
46c72fcc34Sopenharmony_cistatic long lrint_dir(double x, int dir)
47c72fcc34Sopenharmony_ci{
48c72fcc34Sopenharmony_ci	if (dir > 0)
49c72fcc34Sopenharmony_ci		return lrint(ceil(x));
50c72fcc34Sopenharmony_ci	else if (dir < 0)
51c72fcc34Sopenharmony_ci		return lrint(floor(x));
52c72fcc34Sopenharmony_ci	else
53c72fcc34Sopenharmony_ci		return lrint(x);
54c72fcc34Sopenharmony_ci}
55c72fcc34Sopenharmony_ci
56c72fcc34Sopenharmony_cienum ctl_dir { PLAYBACK, CAPTURE };
57c72fcc34Sopenharmony_ci
58c72fcc34Sopenharmony_cistatic int (* const get_dB_range[2])(snd_mixer_elem_t *, long *, long *) = {
59c72fcc34Sopenharmony_ci	snd_mixer_selem_get_playback_dB_range,
60c72fcc34Sopenharmony_ci	snd_mixer_selem_get_capture_dB_range,
61c72fcc34Sopenharmony_ci};
62c72fcc34Sopenharmony_cistatic int (* const get_raw_range[2])(snd_mixer_elem_t *, long *, long *) = {
63c72fcc34Sopenharmony_ci	snd_mixer_selem_get_playback_volume_range,
64c72fcc34Sopenharmony_ci	snd_mixer_selem_get_capture_volume_range,
65c72fcc34Sopenharmony_ci};
66c72fcc34Sopenharmony_cistatic int (* const get_dB[2])(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long *) = {
67c72fcc34Sopenharmony_ci	snd_mixer_selem_get_playback_dB,
68c72fcc34Sopenharmony_ci	snd_mixer_selem_get_capture_dB,
69c72fcc34Sopenharmony_ci};
70c72fcc34Sopenharmony_cistatic int (* const get_raw[2])(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long *) = {
71c72fcc34Sopenharmony_ci	snd_mixer_selem_get_playback_volume,
72c72fcc34Sopenharmony_ci	snd_mixer_selem_get_capture_volume,
73c72fcc34Sopenharmony_ci};
74c72fcc34Sopenharmony_cistatic int (* const set_dB[2])(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long, int) = {
75c72fcc34Sopenharmony_ci	snd_mixer_selem_set_playback_dB,
76c72fcc34Sopenharmony_ci	snd_mixer_selem_set_capture_dB,
77c72fcc34Sopenharmony_ci};
78c72fcc34Sopenharmony_cistatic int (* const set_raw[2])(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long) = {
79c72fcc34Sopenharmony_ci	snd_mixer_selem_set_playback_volume,
80c72fcc34Sopenharmony_ci	snd_mixer_selem_set_capture_volume,
81c72fcc34Sopenharmony_ci};
82c72fcc34Sopenharmony_ci
83c72fcc34Sopenharmony_cistatic double get_normalized_volume(snd_mixer_elem_t *elem,
84c72fcc34Sopenharmony_ci				    snd_mixer_selem_channel_id_t channel,
85c72fcc34Sopenharmony_ci				    enum ctl_dir ctl_dir)
86c72fcc34Sopenharmony_ci{
87c72fcc34Sopenharmony_ci	long min, max, value;
88c72fcc34Sopenharmony_ci	double normalized, min_norm;
89c72fcc34Sopenharmony_ci	int err;
90c72fcc34Sopenharmony_ci
91c72fcc34Sopenharmony_ci	err = get_dB_range[ctl_dir](elem, &min, &max);
92c72fcc34Sopenharmony_ci	if (err < 0 || min >= max) {
93c72fcc34Sopenharmony_ci		err = get_raw_range[ctl_dir](elem, &min, &max);
94c72fcc34Sopenharmony_ci		if (err < 0 || min == max)
95c72fcc34Sopenharmony_ci			return 0;
96c72fcc34Sopenharmony_ci
97c72fcc34Sopenharmony_ci		err = get_raw[ctl_dir](elem, channel, &value);
98c72fcc34Sopenharmony_ci		if (err < 0)
99c72fcc34Sopenharmony_ci			return 0;
100c72fcc34Sopenharmony_ci
101c72fcc34Sopenharmony_ci		return (value - min) / (double)(max - min);
102c72fcc34Sopenharmony_ci	}
103c72fcc34Sopenharmony_ci
104c72fcc34Sopenharmony_ci	err = get_dB[ctl_dir](elem, channel, &value);
105c72fcc34Sopenharmony_ci	if (err < 0)
106c72fcc34Sopenharmony_ci		return 0;
107c72fcc34Sopenharmony_ci
108c72fcc34Sopenharmony_ci	if (use_linear_dB_scale(min, max))
109c72fcc34Sopenharmony_ci		return (value - min) / (double)(max - min);
110c72fcc34Sopenharmony_ci
111c72fcc34Sopenharmony_ci	normalized = pow(10, (value - max) / 6000.0);
112c72fcc34Sopenharmony_ci	if (min != SND_CTL_TLV_DB_GAIN_MUTE) {
113c72fcc34Sopenharmony_ci		min_norm = pow(10, (min - max) / 6000.0);
114c72fcc34Sopenharmony_ci		normalized = (normalized - min_norm) / (1 - min_norm);
115c72fcc34Sopenharmony_ci	}
116c72fcc34Sopenharmony_ci
117c72fcc34Sopenharmony_ci	return normalized;
118c72fcc34Sopenharmony_ci}
119c72fcc34Sopenharmony_ci
120c72fcc34Sopenharmony_cistatic int set_normalized_volume(snd_mixer_elem_t *elem,
121c72fcc34Sopenharmony_ci				 snd_mixer_selem_channel_id_t channel,
122c72fcc34Sopenharmony_ci				 double volume,
123c72fcc34Sopenharmony_ci				 int dir,
124c72fcc34Sopenharmony_ci				 enum ctl_dir ctl_dir)
125c72fcc34Sopenharmony_ci{
126c72fcc34Sopenharmony_ci	long min, max, value;
127c72fcc34Sopenharmony_ci	double min_norm;
128c72fcc34Sopenharmony_ci	int err;
129c72fcc34Sopenharmony_ci
130c72fcc34Sopenharmony_ci	err = get_dB_range[ctl_dir](elem, &min, &max);
131c72fcc34Sopenharmony_ci	if (err < 0 || min >= max) {
132c72fcc34Sopenharmony_ci		err = get_raw_range[ctl_dir](elem, &min, &max);
133c72fcc34Sopenharmony_ci		if (err < 0)
134c72fcc34Sopenharmony_ci			return err;
135c72fcc34Sopenharmony_ci
136c72fcc34Sopenharmony_ci		value = lrint_dir(volume * (max - min), dir) + min;
137c72fcc34Sopenharmony_ci		return set_raw[ctl_dir](elem, channel, value);
138c72fcc34Sopenharmony_ci	}
139c72fcc34Sopenharmony_ci
140c72fcc34Sopenharmony_ci	if (use_linear_dB_scale(min, max)) {
141c72fcc34Sopenharmony_ci		value = lrint_dir(volume * (max - min), dir) + min;
142c72fcc34Sopenharmony_ci		return set_dB[ctl_dir](elem, channel, value, dir);
143c72fcc34Sopenharmony_ci	}
144c72fcc34Sopenharmony_ci
145c72fcc34Sopenharmony_ci	if (min != SND_CTL_TLV_DB_GAIN_MUTE) {
146c72fcc34Sopenharmony_ci		min_norm = pow(10, (min - max) / 6000.0);
147c72fcc34Sopenharmony_ci		volume = volume * (1 - min_norm) + min_norm;
148c72fcc34Sopenharmony_ci	}
149c72fcc34Sopenharmony_ci	value = lrint_dir(6000.0 * log10(volume), dir) + max;
150c72fcc34Sopenharmony_ci	return set_dB[ctl_dir](elem, channel, value, dir);
151c72fcc34Sopenharmony_ci}
152c72fcc34Sopenharmony_ci
153c72fcc34Sopenharmony_cidouble get_normalized_playback_volume(snd_mixer_elem_t *elem,
154c72fcc34Sopenharmony_ci				      snd_mixer_selem_channel_id_t channel)
155c72fcc34Sopenharmony_ci{
156c72fcc34Sopenharmony_ci	return get_normalized_volume(elem, channel, PLAYBACK);
157c72fcc34Sopenharmony_ci}
158c72fcc34Sopenharmony_ci
159c72fcc34Sopenharmony_cidouble get_normalized_capture_volume(snd_mixer_elem_t *elem,
160c72fcc34Sopenharmony_ci				     snd_mixer_selem_channel_id_t channel)
161c72fcc34Sopenharmony_ci{
162c72fcc34Sopenharmony_ci	return get_normalized_volume(elem, channel, CAPTURE);
163c72fcc34Sopenharmony_ci}
164c72fcc34Sopenharmony_ci
165c72fcc34Sopenharmony_ciint set_normalized_playback_volume(snd_mixer_elem_t *elem,
166c72fcc34Sopenharmony_ci				   snd_mixer_selem_channel_id_t channel,
167c72fcc34Sopenharmony_ci				   double volume,
168c72fcc34Sopenharmony_ci				   int dir)
169c72fcc34Sopenharmony_ci{
170c72fcc34Sopenharmony_ci	return set_normalized_volume(elem, channel, volume, dir, PLAYBACK);
171c72fcc34Sopenharmony_ci}
172c72fcc34Sopenharmony_ci
173c72fcc34Sopenharmony_ciint set_normalized_capture_volume(snd_mixer_elem_t *elem,
174c72fcc34Sopenharmony_ci				  snd_mixer_selem_channel_id_t channel,
175c72fcc34Sopenharmony_ci				  double volume,
176c72fcc34Sopenharmony_ci				  int dir)
177c72fcc34Sopenharmony_ci{
178c72fcc34Sopenharmony_ci	return set_normalized_volume(elem, channel, volume, dir, CAPTURE);
179c72fcc34Sopenharmony_ci}
180