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