]> git.alsa-project.org Git - alsa-utils.git/commitdiff
alsamixer: use cubic scale for volume bars
authorClemens Ladisch <clemens@ladisch.de>
Mon, 6 Dec 2010 13:07:48 +0000 (14:07 +0100)
committerClemens Ladisch <clemens@ladisch.de>
Mon, 6 Dec 2010 13:07:48 +0000 (14:07 +0100)
Instead of mapping the raw volume values linearly to the screen, use
a mapping where the bar height is proportional to the audible volume,
i.e., where the amplitude is the cube of the bar height.

Signed-off-by: Clemens Ladisch <clemens@ladisch.de>
alsamixer/Makefile.am
alsamixer/mixer_display.c
alsamixer/mixer_widget.c
alsamixer/volume_mapping.c [new file with mode: 0644]
alsamixer/volume_mapping.h [new file with mode: 0644]

index 1de47c69f3ad6b407a1f1199da5858af226e7aac..8a82323a5d1a4cf2edd8d621f33b6c577874a3cb 100644 (file)
@@ -15,6 +15,7 @@ alsamixer_SOURCES = card_select.c card_select.h \
                proc_files.c proc_files.h \
                textbox.c textbox.h \
                utils.c utils.h \
+               volume_mapping.c volume_mapping.h \
                widget.c widget.h
 man_MANS = alsamixer.1
 EXTRA_DIST = alsamixer.1
index 20d6d6ae163732c8fcfa4b4ea14d73b8b820897c..51a154622e501a886c5485554d3fa6bfede63c35 100644 (file)
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#define _C99_SOURCE /* lrint() */
 #include "aconfig.h"
 #include <stdlib.h>
 #include <string.h>
 #include <strings.h>
+#include <math.h>
 #include CURSESINC
 #include <alsa/asoundlib.h>
 #include "gettext_curses.h"
@@ -28,6 +30,7 @@
 #include "mem.h"
 #include "colors.h"
 #include "widget.h"
+#include "volume_mapping.h"
 #include "mixer_widget.h"
 #include "mixer_controls.h"
 #include "mixer_display.h"
@@ -390,24 +393,14 @@ static void display_string_centered_in_control(int y, int col, const char *s, in
        display_string_in_field(y, x, s, width, ALIGN_CENTER);
 }
 
-static long clamp(long value, long min, long max)
-{
-       if (value < min)
-               return min;
-       if (value > max)
-               return max;
-       return value;
-}
-
 static void display_control(unsigned int control_index)
 {
        struct control *control;
        int col;
        int i, c;
        int left, frame_left;
-       int bar_height, value;
-       long volumes[2];
-       long min, max;
+       int bar_height;
+       double volumes[2];
        int switches[2];
        unsigned int index;
        const char *s;
@@ -452,35 +445,22 @@ static void display_control(unsigned int control_index)
                waddch(mixer_widget.window, ACS_LRCORNER);
        }
        if (control->flags & (TYPE_PVOLUME | TYPE_CVOLUME)) {
-               int (*get_vol_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long *);
+               double (*get_vol_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t);
 
                if (control->flags & TYPE_PVOLUME)
-                       get_vol_func = snd_mixer_selem_get_playback_volume;
+                       get_vol_func = get_normalized_playback_volume;
                else
-                       get_vol_func = snd_mixer_selem_get_capture_volume;
-               err = get_vol_func(control->elem, control->volume_channels[0], &volumes[0]);
-               if (err >= 0 && (control->flags & HAS_VOLUME_1))
-                       err = get_vol_func(control->elem, control->volume_channels[1], &volumes[1]);
+                       get_vol_func = get_normalized_capture_volume;
+               volumes[0] = get_vol_func(control->elem, control->volume_channels[0]);
+               if (control->flags & HAS_VOLUME_1)
+                       volumes[1] = get_vol_func(control->elem, control->volume_channels[1]);
                else
                        volumes[1] = volumes[0];
-               if (err < 0)
-                       return;
-               if (control->flags & TYPE_PVOLUME)
-                       err = snd_mixer_selem_get_playback_volume_range(control->elem, &min, &max);
-               else
-                       err = snd_mixer_selem_get_capture_volume_range(control->elem, &min, &max);
-               if (err < 0)
-                       return;
-               if (min >= max)
-                       max = min + 1;
-               volumes[0] = clamp(volumes[0], min, max);
-               volumes[1] = clamp(volumes[1], min, max);
 
                if (control->flags & IS_ACTIVE)
                        wattrset(mixer_widget.window, 0);
                for (c = 0; c < 2; c++) {
-                       bar_height = ((volumes[c] - min) * volume_height +
-                                     max - min - 1) / (max - min);
+                       bar_height = lrint(volumes[c] * volume_height);
                        for (i = 0; i < volume_height; ++i) {
                                chtype ch;
                                if (i + 1 > bar_height)
@@ -505,19 +485,18 @@ static void display_control(unsigned int control_index)
                }
                if (control->flags & IS_ACTIVE)
                        wattrset(mixer_widget.window, attr_mixer_active);
-               value = ((volumes[0] - min) * 100 + (max - min) / 2) / (max - min);
                if (!(control->flags & HAS_VOLUME_1)) {
-                       sprintf(buf, "%d", value);
+                       sprintf(buf, "%d", lrint(volumes[0] * 100));
                        display_string_in_field(values_y, frame_left - 2, buf, 8, ALIGN_CENTER);
                } else {
-                       mvwprintw(mixer_widget.window, values_y, frame_left - 2, "%3d", value);
+                       mvwprintw(mixer_widget.window, values_y, frame_left - 2,
+                                 "%3d", lrint(volumes[0] * 100));
                        if (control->flags & IS_ACTIVE)
                                wattrset(mixer_widget.window, attr_ctl_frame);
                        waddstr(mixer_widget.window, "<>");
                        if (control->flags & IS_ACTIVE)
                                wattrset(mixer_widget.window, attr_mixer_active);
-                       value = ((volumes[1] - min) * 100 + (max - min) / 2) / (max - min);
-                       wprintw(mixer_widget.window, "%-3d", value);
+                       wprintw(mixer_widget.window, "%-3d", lrint(volumes[1] * 100));
                }
        }
 
index adb45684e38d90abd85dadae5352916e50faabe7..fb352d3e4783f16f696dc60938ea0e6841328a0a 100644 (file)
@@ -33,6 +33,7 @@
 #include "textbox.h"
 #include "proc_files.h"
 #include "card_select.h"
+#include "volume_mapping.h"
 #include "mixer_controls.h"
 #include "mixer_display.h"
 #include "mixer_widget.h"
@@ -295,82 +296,57 @@ static void change_enum_relative(struct control *control, int delta)
 
 static void change_volume_to_percent(struct control *control, int value, unsigned int channels)
 {
-       int (*get_range_func)(snd_mixer_elem_t *, long *, long *);
-       int (*set_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long);
-       long min, max;
-       int err;
+       int (*set_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, double, int);
 
        if (!(control->flags & HAS_VOLUME_1))
                channels = LEFT;
-       if (control->flags & TYPE_PVOLUME) {
-               get_range_func = snd_mixer_selem_get_playback_volume_range;
-               set_func = snd_mixer_selem_set_playback_volume;
-       } else {
-               get_range_func = snd_mixer_selem_get_capture_volume_range;
-               set_func = snd_mixer_selem_set_capture_volume;
-       }
-       err = get_range_func(control->elem, &min, &max);
-       if (err < 0)
-               return;
+       if (control->flags & TYPE_PVOLUME)
+               set_func = set_normalized_playback_volume;
+       else
+               set_func = set_normalized_capture_volume;
        if (channels & LEFT)
-               set_func(control->elem, control->volume_channels[0], min + (max - min) * value / 100);
+               set_func(control->elem, control->volume_channels[0], value / 100.0, 0);
        if (channels & RIGHT)
-               set_func(control->elem, control->volume_channels[1], min + (max - min) * value / 100);
+               set_func(control->elem, control->volume_channels[1], value / 100.0, 0);
 }
 
-static void change_volume_relative(struct control *control, long delta, unsigned int channels)
+static double clamp_volume(double v)
 {
-       int (*get_range_func)(snd_mixer_elem_t *, long *, long *);
-       int (*get_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long *);
-       int (*set_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long);
-       long min, max;
-       long left, right;
-       long value;
-       int err;
+       if (v < 0)
+               return 0;
+       if (v > 1)
+               return 1;
+       return v;
+}
+
+static void change_volume_relative(struct control *control, int delta, unsigned int channels)
+{
+       double (*get_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t);
+       int (*set_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, double, int);
+       double left, right;
+       int dir;
 
        if (!(control->flags & HAS_VOLUME_1))
                channels = LEFT;
        if (control->flags & TYPE_PVOLUME) {
-               get_range_func = snd_mixer_selem_get_playback_volume_range;
-               get_func = snd_mixer_selem_get_playback_volume;
-               set_func = snd_mixer_selem_set_playback_volume;
+               get_func = get_normalized_playback_volume;
+               set_func = set_normalized_playback_volume;
        } else {
-               get_range_func = snd_mixer_selem_get_capture_volume_range;
-               get_func = snd_mixer_selem_get_capture_volume;
-               set_func = snd_mixer_selem_set_capture_volume;
+               get_func = get_normalized_capture_volume;
+               set_func = set_normalized_capture_volume;
        }
-       err = get_range_func(control->elem, &min, &max);
-       if (err < 0)
-               return;
-       if (channels & LEFT) {
-               err = get_func(control->elem, control->volume_channels[0], &left);
-               if (err < 0)
-                       return;
-       }
-       if (channels & RIGHT) {
-               err = get_func(control->elem, control->volume_channels[1], &right);
-               if (err < 0)
-                       return;
-       }
-       if (max - min > 100)
-               delta = (delta * (max - min) + (delta > 0 ? 99 : -99)) / 100;
+       if (channels & LEFT)
+               left = get_func(control->elem, control->volume_channels[0]);
+       if (channels & RIGHT)
+               right = get_func(control->elem, control->volume_channels[1]);
+       dir = delta > 0 ? 1 : -1;
        if (channels & LEFT) {
-               value = left + delta;
-               if (value < min)
-                       value = min;
-               else if (value > max)
-                       value = max;
-               if (value != left)
-                       set_func(control->elem, control->volume_channels[0], value);
+               left = clamp_volume(left + delta / 100.0);
+               set_func(control->elem, control->volume_channels[0], left, dir);
        }
        if (channels & RIGHT) {
-               value = right + delta;
-               if (value < min)
-                       value = min;
-               else if (value > max)
-                       value = max;
-               if (value != right)
-                       set_func(control->elem, control->volume_channels[1], value);
+               right = clamp_volume(right + delta / 100.0);
+               set_func(control->elem, control->volume_channels[1], right, dir);
        }
 }
 
@@ -460,34 +436,26 @@ static void toggle_capture(unsigned int channels)
 static void balance_volumes(void)
 {
        struct control *control;
-       long left, right;
+       double left, right;
        int err;
 
        control = get_focus_control(TYPE_PVOLUME | TYPE_CVOLUME);
        if (!control || !(control->flags & HAS_VOLUME_1))
                return;
        if (control->flags & TYPE_PVOLUME) {
-               err = snd_mixer_selem_get_playback_volume(control->elem, control->volume_channels[0], &left);
-               if (err < 0)
-                       return;
-               err = snd_mixer_selem_get_playback_volume(control->elem, control->volume_channels[1], &right);
-               if (err < 0)
-                       return;
+               left = get_normalized_playback_volume(control->elem, control->volume_channels[0]);
+               right = get_normalized_playback_volume(control->elem, control->volume_channels[1]);
        } else {
-               err = snd_mixer_selem_get_capture_volume(control->elem, control->volume_channels[0], &left);
-               if (err < 0)
-                       return;
-               err = snd_mixer_selem_get_capture_volume(control->elem, control->volume_channels[1], &right);
-               if (err < 0)
-                       return;
+               left = get_normalized_capture_volume(control->elem, control->volume_channels[0]);
+               right = get_normalized_capture_volume(control->elem, control->volume_channels[1]);
        }
        left = (left + right) / 2;
        if (control->flags & TYPE_PVOLUME) {
-               snd_mixer_selem_set_playback_volume(control->elem, control->volume_channels[0], left);
-               snd_mixer_selem_set_playback_volume(control->elem, control->volume_channels[1], left);
+               set_normalized_playback_volume(control->elem, control->volume_channels[0], left, 0);
+               set_normalized_playback_volume(control->elem, control->volume_channels[1], left, 0);
        } else {
-               snd_mixer_selem_set_capture_volume(control->elem, control->volume_channels[0], left);
-               snd_mixer_selem_set_capture_volume(control->elem, control->volume_channels[1], left);
+               set_normalized_capture_volume(control->elem, control->volume_channels[0], left, 0);
+               set_normalized_capture_volume(control->elem, control->volume_channels[1], left, 0);
        }
        display_controls();
 }
diff --git a/alsamixer/volume_mapping.c b/alsamixer/volume_mapping.c
new file mode 100644 (file)
index 0000000..9cacad8
--- /dev/null
@@ -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.
+ */
+
+#define _ISOC99_SOURCE /* lrint() */
+#define _GNU_SOURCE /* exp10() */
+#include "aconfig.h"
+#include <math.h>
+#include <stdbool.h>
+#include "volume_mapping.h"
+
+#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 *, snd_mixer_selem_channel_id_t, long, int) = {
+       snd_mixer_selem_set_playback_dB,
+       snd_mixer_selem_set_capture_dB,
+};
+static int (* const set_raw[2])(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long) = {
+       snd_mixer_selem_set_playback_volume,
+       snd_mixer_selem_set_capture_volume,
+};
+
+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,
+                                snd_mixer_selem_channel_id_t channel,
+                                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, channel, value);
+       }
+
+       if (use_linear_dB_scale(min, max)) {
+               value = lrint_dir(volume * (max - min), dir) + min;
+               return set_dB[ctl_dir](elem, channel, 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, channel, 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,
+                                  snd_mixer_selem_channel_id_t channel,
+                                  double volume,
+                                  int dir)
+{
+       return set_normalized_volume(elem, channel, volume, dir, PLAYBACK);
+}
+
+int set_normalized_capture_volume(snd_mixer_elem_t *elem,
+                                 snd_mixer_selem_channel_id_t channel,
+                                 double volume,
+                                 int dir)
+{
+       return set_normalized_volume(elem, channel, volume, dir, CAPTURE);
+}
diff --git a/alsamixer/volume_mapping.h b/alsamixer/volume_mapping.h
new file mode 100644 (file)
index 0000000..d4251d6
--- /dev/null
@@ -0,0 +1,19 @@
+#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,
+                                  snd_mixer_selem_channel_id_t channel,
+                                  double volume,
+                                  int dir);
+int set_normalized_capture_volume(snd_mixer_elem_t *elem,
+                                 snd_mixer_selem_channel_id_t channel,
+                                 double volume,
+                                 int dir);
+
+#endif