summaryrefslogtreecommitdiff
path: root/src/mixer
diff options
context:
space:
mode:
authorTermeHansen <termopeten@gmail.com>2017-01-05 12:07:01 +0100
committerMax Kellermann <max@musicpd.org>2017-01-07 16:26:36 +0100
commit8a32ee30a5c1f714a91d942bf3e086963ae9af5b (patch)
tree7ae020b5a0b8590b7de621691801f90359a09181 /src/mixer
parent981dc0626b71e309f6e27d4161620b762bed8545 (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.c180
-rw-r--r--src/mixer/plugins/volume_mapping.h17
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