diff options
author | TermeHansen <termopeten@gmail.com> | 2017-01-05 12:07:01 +0100 |
---|---|---|
committer | Max Kellermann <max@musicpd.org> | 2017-01-07 16:26:36 +0100 |
commit | 8a32ee30a5c1f714a91d942bf3e086963ae9af5b (patch) | |
tree | 7ae020b5a0b8590b7de621691801f90359a09181 /src/mixer | |
parent | 981dc0626b71e309f6e27d4161620b762bed8545 (diff) |
Adding volume_mapping from alsa-utils/alsamixer
source:
http://git.alsa-project.org/?p=alsa-utils.git;a=blob_plain;f=alsamixer/volume_mapping.c;hb=HEAD
http://git.alsa-project.org/?p=alsa-utils.git;a=blob_plain;f=alsamixer/volume_mapping.h;hb=HEAD
Diffstat (limited to 'src/mixer')
-rw-r--r-- | src/mixer/plugins/volume_mapping.c | 180 | ||||
-rw-r--r-- | src/mixer/plugins/volume_mapping.h | 17 |
2 files changed, 197 insertions, 0 deletions
diff --git a/src/mixer/plugins/volume_mapping.c b/src/mixer/plugins/volume_mapping.c new file mode 100644 index 000000000..4e559cf54 --- /dev/null +++ b/src/mixer/plugins/volume_mapping.c @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2010 Clemens Ladisch <clemens@ladisch.de> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * The functions in this file map the value ranges of ALSA mixer controls onto + * the interval 0..1. + * + * The mapping is designed so that the position in the interval is proportional + * to the volume as a human ear would perceive it (i.e., the position is the + * cubic root of the linear sample multiplication factor). For controls with + * a small range (24 dB or less), the mapping is linear in the dB values so + * that each step has the same size visually. Only for controls without dB + * information, a linear mapping of the hardware volume register values is used + * (this is the same algorithm as used in the old alsamixer). + * + * When setting the volume, 'dir' is the rounding direction: + * -1/0/1 = down/nearest/up. + */ + +#include <math.h> +#include <stdbool.h> +#include "volume_mapping.h" + +#ifdef __UCLIBC__ +/* 10^x = 10^(log e^x) = (e^x)^log10 = e^(x * log 10) */ +#define exp10(x) (exp((x) * log(10))) +#endif /* __UCLIBC__ */ + +#define MAX_LINEAR_DB_SCALE 24 + +static inline bool use_linear_dB_scale(long dBmin, long dBmax) +{ + return dBmax - dBmin <= MAX_LINEAR_DB_SCALE * 100; +} + +static long lrint_dir(double x, int dir) +{ + if (dir > 0) + return lrint(ceil(x)); + else if (dir < 0) + return lrint(floor(x)); + else + return lrint(x); +} + +enum ctl_dir { PLAYBACK, CAPTURE }; + +static int (* const get_dB_range[2])(snd_mixer_elem_t *, long *, long *) = { + snd_mixer_selem_get_playback_dB_range, + snd_mixer_selem_get_capture_dB_range, +}; +static int (* const get_raw_range[2])(snd_mixer_elem_t *, long *, long *) = { + snd_mixer_selem_get_playback_volume_range, + snd_mixer_selem_get_capture_volume_range, +}; +static int (* const get_dB[2])(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long *) = { + snd_mixer_selem_get_playback_dB, + snd_mixer_selem_get_capture_dB, +}; +static int (* const get_raw[2])(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long *) = { + snd_mixer_selem_get_playback_volume, + snd_mixer_selem_get_capture_volume, +}; +static int (* const set_dB[2])(snd_mixer_elem_t *, long, int) = { + snd_mixer_selem_set_playback_dB_all, + snd_mixer_selem_set_capture_dB_all, +}; +static int (* const set_raw[2])(snd_mixer_elem_t *, long) = { + snd_mixer_selem_set_playback_volume_all, + snd_mixer_selem_set_capture_volume_all, +}; + +static double get_normalized_volume(snd_mixer_elem_t *elem, + snd_mixer_selem_channel_id_t channel, + enum ctl_dir ctl_dir) +{ + long min, max, value; + double normalized, min_norm; + int err; + + err = get_dB_range[ctl_dir](elem, &min, &max); + if (err < 0 || min >= max) { + err = get_raw_range[ctl_dir](elem, &min, &max); + if (err < 0 || min == max) + return 0; + + err = get_raw[ctl_dir](elem, channel, &value); + if (err < 0) + return 0; + + return (value - min) / (double)(max - min); + } + + err = get_dB[ctl_dir](elem, channel, &value); + if (err < 0) + return 0; + + if (use_linear_dB_scale(min, max)) + return (value - min) / (double)(max - min); + + normalized = exp10((value - max) / 6000.0); + if (min != SND_CTL_TLV_DB_GAIN_MUTE) { + min_norm = exp10((min - max) / 6000.0); + normalized = (normalized - min_norm) / (1 - min_norm); + } + + return normalized; +} + +static int set_normalized_volume(snd_mixer_elem_t *elem, + double volume, + int dir, + enum ctl_dir ctl_dir) +{ + long min, max, value; + double min_norm; + int err; + + err = get_dB_range[ctl_dir](elem, &min, &max); + if (err < 0 || min >= max) { + err = get_raw_range[ctl_dir](elem, &min, &max); + if (err < 0) + return err; + + value = lrint_dir(volume * (max - min), dir) + min; + return set_raw[ctl_dir](elem, value); + } + + if (use_linear_dB_scale(min, max)) { + value = lrint_dir(volume * (max - min), dir) + min; + return set_dB[ctl_dir](elem, value, dir); + } + + if (min != SND_CTL_TLV_DB_GAIN_MUTE) { + min_norm = exp10((min - max) / 6000.0); + volume = volume * (1 - min_norm) + min_norm; + } + value = lrint_dir(6000.0 * log10(volume), dir) + max; + return set_dB[ctl_dir](elem, value, dir); +} + +double get_normalized_playback_volume(snd_mixer_elem_t *elem, + snd_mixer_selem_channel_id_t channel) +{ + return get_normalized_volume(elem, channel, PLAYBACK); +} + +double get_normalized_capture_volume(snd_mixer_elem_t *elem, + snd_mixer_selem_channel_id_t channel) +{ + return get_normalized_volume(elem, channel, CAPTURE); +} + + +int set_normalized_playback_volume(snd_mixer_elem_t *elem, + double volume, + int dir) +{ + return set_normalized_volume(elem, volume, dir, PLAYBACK); +} + +int set_normalized_capture_volume(snd_mixer_elem_t *elem, + double volume, + int dir) +{ + return set_normalized_volume(elem, volume, dir, CAPTURE); +} diff --git a/src/mixer/plugins/volume_mapping.h b/src/mixer/plugins/volume_mapping.h new file mode 100644 index 000000000..f781542cf --- /dev/null +++ b/src/mixer/plugins/volume_mapping.h @@ -0,0 +1,17 @@ +#ifndef VOLUME_MAPPING_H_INCLUDED +#define VOLUME_MAPPING_H_INCLUDED + +#include <alsa/asoundlib.h> + +double get_normalized_playback_volume(snd_mixer_elem_t *elem, + snd_mixer_selem_channel_id_t channel); +double get_normalized_capture_volume(snd_mixer_elem_t *elem, + snd_mixer_selem_channel_id_t channel); +int set_normalized_playback_volume(snd_mixer_elem_t *elem, + double volume, + int dir); +int set_normalized_capture_volume(snd_mixer_elem_t *elem, + double volume, + int dir); + +#endif |