Blame alsamixer/volume_mapping.c

Packit Service a9274b
/*
Packit Service a9274b
 * Copyright (c) 2010 Clemens Ladisch <clemens@ladisch.de>
Packit Service a9274b
 *
Packit Service a9274b
 * Permission to use, copy, modify, and/or distribute this software for any
Packit Service a9274b
 * purpose with or without fee is hereby granted, provided that the above
Packit Service a9274b
 * copyright notice and this permission notice appear in all copies.
Packit Service a9274b
 *
Packit Service a9274b
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
Packit Service a9274b
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
Packit Service a9274b
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
Packit Service a9274b
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
Packit Service a9274b
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
Packit Service a9274b
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
Packit Service a9274b
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
Packit Service a9274b
 */
Packit Service a9274b
Packit Service a9274b
/*
Packit Service a9274b
 * The functions in this file map the value ranges of ALSA mixer controls onto
Packit Service a9274b
 * the interval 0..1.
Packit Service a9274b
 *
Packit Service a9274b
 * The mapping is designed so that the position in the interval is proportional
Packit Service a9274b
 * to the volume as a human ear would perceive it (i.e., the position is the
Packit Service a9274b
 * cubic root of the linear sample multiplication factor).  For controls with
Packit Service a9274b
 * a small range (24 dB or less), the mapping is linear in the dB values so
Packit Service a9274b
 * that each step has the same size visually.  Only for controls without dB
Packit Service a9274b
 * information, a linear mapping of the hardware volume register values is used
Packit Service a9274b
 * (this is the same algorithm as used in the old alsamixer).
Packit Service a9274b
 *
Packit Service a9274b
 * When setting the volume, 'dir' is the rounding direction:
Packit Service a9274b
 * -1/0/1 = down/nearest/up.
Packit Service a9274b
 */
Packit Service a9274b
Packit Service a9274b
#define _ISOC99_SOURCE /* lrint() */
Packit Service a9274b
#include "aconfig.h"
Packit Service a9274b
#include <math.h>
Packit Service a9274b
#include <stdbool.h>
Packit Service a9274b
#include "volume_mapping.h"
Packit Service a9274b
Packit Service a9274b
#define MAX_LINEAR_DB_SCALE	24
Packit Service a9274b
Packit Service a9274b
static inline bool use_linear_dB_scale(long dBmin, long dBmax)
Packit Service a9274b
{
Packit Service a9274b
	return dBmax - dBmin <= MAX_LINEAR_DB_SCALE * 100;
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
static long lrint_dir(double x, int dir)
Packit Service a9274b
{
Packit Service a9274b
	if (dir > 0)
Packit Service a9274b
		return lrint(ceil(x));
Packit Service a9274b
	else if (dir < 0)
Packit Service a9274b
		return lrint(floor(x));
Packit Service a9274b
	else
Packit Service a9274b
		return lrint(x);
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
enum ctl_dir { PLAYBACK, CAPTURE };
Packit Service a9274b
Packit Service a9274b
static int (* const get_dB_range[2])(snd_mixer_elem_t *, long *, long *) = {
Packit Service a9274b
	snd_mixer_selem_get_playback_dB_range,
Packit Service a9274b
	snd_mixer_selem_get_capture_dB_range,
Packit Service a9274b
};
Packit Service a9274b
static int (* const get_raw_range[2])(snd_mixer_elem_t *, long *, long *) = {
Packit Service a9274b
	snd_mixer_selem_get_playback_volume_range,
Packit Service a9274b
	snd_mixer_selem_get_capture_volume_range,
Packit Service a9274b
};
Packit Service a9274b
static int (* const get_dB[2])(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long *) = {
Packit Service a9274b
	snd_mixer_selem_get_playback_dB,
Packit Service a9274b
	snd_mixer_selem_get_capture_dB,
Packit Service a9274b
};
Packit Service a9274b
static int (* const get_raw[2])(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long *) = {
Packit Service a9274b
	snd_mixer_selem_get_playback_volume,
Packit Service a9274b
	snd_mixer_selem_get_capture_volume,
Packit Service a9274b
};
Packit Service a9274b
static int (* const set_dB[2])(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long, int) = {
Packit Service a9274b
	snd_mixer_selem_set_playback_dB,
Packit Service a9274b
	snd_mixer_selem_set_capture_dB,
Packit Service a9274b
};
Packit Service a9274b
static int (* const set_raw[2])(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long) = {
Packit Service a9274b
	snd_mixer_selem_set_playback_volume,
Packit Service a9274b
	snd_mixer_selem_set_capture_volume,
Packit Service a9274b
};
Packit Service a9274b
Packit Service a9274b
static double get_normalized_volume(snd_mixer_elem_t *elem,
Packit Service a9274b
				    snd_mixer_selem_channel_id_t channel,
Packit Service a9274b
				    enum ctl_dir ctl_dir)
Packit Service a9274b
{
Packit Service a9274b
	long min, max, value;
Packit Service a9274b
	double normalized, min_norm;
Packit Service a9274b
	int err;
Packit Service a9274b
Packit Service a9274b
	err = get_dB_range[ctl_dir](elem, &min, &max;;
Packit Service a9274b
	if (err < 0 || min >= max) {
Packit Service a9274b
		err = get_raw_range[ctl_dir](elem, &min, &max;;
Packit Service a9274b
		if (err < 0 || min == max)
Packit Service a9274b
			return 0;
Packit Service a9274b
Packit Service a9274b
		err = get_raw[ctl_dir](elem, channel, &value);
Packit Service a9274b
		if (err < 0)
Packit Service a9274b
			return 0;
Packit Service a9274b
Packit Service a9274b
		return (value - min) / (double)(max - min);
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	err = get_dB[ctl_dir](elem, channel, &value);
Packit Service a9274b
	if (err < 0)
Packit Service a9274b
		return 0;
Packit Service a9274b
Packit Service a9274b
	if (use_linear_dB_scale(min, max))
Packit Service a9274b
		return (value - min) / (double)(max - min);
Packit Service a9274b
Packit Service a9274b
	normalized = pow(10, (value - max) / 6000.0);
Packit Service a9274b
	if (min != SND_CTL_TLV_DB_GAIN_MUTE) {
Packit Service a9274b
		min_norm = pow(10, (min - max) / 6000.0);
Packit Service a9274b
		normalized = (normalized - min_norm) / (1 - min_norm);
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	return normalized;
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
static int set_normalized_volume(snd_mixer_elem_t *elem,
Packit Service a9274b
				 snd_mixer_selem_channel_id_t channel,
Packit Service a9274b
				 double volume,
Packit Service a9274b
				 int dir,
Packit Service a9274b
				 enum ctl_dir ctl_dir)
Packit Service a9274b
{
Packit Service a9274b
	long min, max, value;
Packit Service a9274b
	double min_norm;
Packit Service a9274b
	int err;
Packit Service a9274b
Packit Service a9274b
	err = get_dB_range[ctl_dir](elem, &min, &max;;
Packit Service a9274b
	if (err < 0 || min >= max) {
Packit Service a9274b
		err = get_raw_range[ctl_dir](elem, &min, &max;;
Packit Service a9274b
		if (err < 0)
Packit Service a9274b
			return err;
Packit Service a9274b
Packit Service a9274b
		value = lrint_dir(volume * (max - min), dir) + min;
Packit Service a9274b
		return set_raw[ctl_dir](elem, channel, value);
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	if (use_linear_dB_scale(min, max)) {
Packit Service a9274b
		value = lrint_dir(volume * (max - min), dir) + min;
Packit Service a9274b
		return set_dB[ctl_dir](elem, channel, value, dir);
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	if (min != SND_CTL_TLV_DB_GAIN_MUTE) {
Packit Service a9274b
		min_norm = pow(10, (min - max) / 6000.0);
Packit Service a9274b
		volume = volume * (1 - min_norm) + min_norm;
Packit Service a9274b
	}
Packit Service a9274b
	value = lrint_dir(6000.0 * log10(volume), dir) + max;
Packit Service a9274b
	return set_dB[ctl_dir](elem, channel, value, dir);
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
double get_normalized_playback_volume(snd_mixer_elem_t *elem,
Packit Service a9274b
				      snd_mixer_selem_channel_id_t channel)
Packit Service a9274b
{
Packit Service a9274b
	return get_normalized_volume(elem, channel, PLAYBACK);
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
double get_normalized_capture_volume(snd_mixer_elem_t *elem,
Packit Service a9274b
				     snd_mixer_selem_channel_id_t channel)
Packit Service a9274b
{
Packit Service a9274b
	return get_normalized_volume(elem, channel, CAPTURE);
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
int set_normalized_playback_volume(snd_mixer_elem_t *elem,
Packit Service a9274b
				   snd_mixer_selem_channel_id_t channel,
Packit Service a9274b
				   double volume,
Packit Service a9274b
				   int dir)
Packit Service a9274b
{
Packit Service a9274b
	return set_normalized_volume(elem, channel, volume, dir, PLAYBACK);
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
int set_normalized_capture_volume(snd_mixer_elem_t *elem,
Packit Service a9274b
				  snd_mixer_selem_channel_id_t channel,
Packit Service a9274b
				  double volume,
Packit Service a9274b
				  int dir)
Packit Service a9274b
{
Packit Service a9274b
	return set_normalized_volume(elem, channel, volume, dir, CAPTURE);
Packit Service a9274b
}