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