#ifdef LINUX
#include <alsa/asoundlib.h>
#include <alloca.h>
+#include <math.h>
#endif
#if defined(__FreeBSD__) || defined(__DragonFly__)
#include "i3status.h"
#include "queue.h"
+#define ALSA_VOLUME(channel) \
+ err = snd_mixer_selem_get_##channel##_dB_range(elem, &min, &max) || \
+ snd_mixer_selem_get_##channel##_dB(elem, 0, &val); \
+ if (err != 0 || min >= max) { \
+ err = snd_mixer_selem_get_##channel##_volume_range(elem, &min, &max) || \
+ snd_mixer_selem_get_##channel##_volume(elem, 0, &val); \
+ force_linear = true; \
+ }
+
+#define ALSA_MUTE_SWITCH(channel) \
+ if ((err = snd_mixer_selem_get_##channel##_switch(elem, 0, &pbval)) < 0) \
+ fprintf(stderr, "i3status: ALSA: " #channel "_switch: %s\n", snd_strerror(err)); \
+ if (!pbval) { \
+ START_COLOR("color_degraded"); \
+ fmt = fmt_muted; \
+ }
+
static char *apply_volume_format(const char *fmt, char *outwalk, int ivolume) {
const char *walk = fmt;
free(instance);
}
-#ifndef __OpenBSD__
+#if !defined(__DragonFly__) && !defined(__OpenBSD__)
/* Try PulseAudio first */
/* If the device name has the format "pulse[:N]" where N is the
#endif
#ifdef LINUX
+ const long MAX_LINEAR_DB_SCALE = 24;
int err;
snd_mixer_t *m;
snd_mixer_selem_id_t *sid;
snd_mixer_elem_t *elem;
long min, max, val;
+ bool force_linear = false;
int avg;
if ((err = snd_mixer_open(&m, 0)) < 0) {
}
/* Get the volume range to convert the volume later */
- snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
-
snd_mixer_handle_events(m);
- snd_mixer_selem_get_playback_volume(elem, 0, &val);
- if (max != 100) {
- float avgf = ((float)val / max) * 100;
+ if (!strncasecmp(mixer, "capture", strlen("capture"))) {
+ ALSA_VOLUME(capture)
+ } else {
+ ALSA_VOLUME(playback)
+ }
+
+ if (err != 0) {
+ fprintf(stderr, "i3status: ALSA: Cannot get playback volume.\n");
+ goto out;
+ }
+
+ /* Use linear mapping for raw register values or small ranges of 24 dB */
+ if (force_linear || max - min <= MAX_LINEAR_DB_SCALE * 100) {
+ float avgf = ((float)(val - min) / (max - min)) * 100;
avg = (int)avgf;
avg = (avgf - avg < 0.5 ? avg : (avg + 1));
- } else
- avg = (int)val;
+ } else {
+ /* mapped volume to be more natural for the human ear */
+ double normalized = exp10((val - max) / 6000.0);
+ if (min != SND_CTL_TLV_DB_GAIN_MUTE) {
+ double min_norm = exp10((min - max) / 6000.0);
+ normalized = (normalized - min_norm) / (1 - min_norm);
+ }
+ avg = lround(normalized * 100);
+ }
/* Check for mute */
if (snd_mixer_selem_has_playback_switch(elem)) {
- if ((err = snd_mixer_selem_get_playback_switch(elem, 0, &pbval)) < 0)
- fprintf(stderr, "i3status: ALSA: playback_switch: %s\n", snd_strerror(err));
- if (!pbval) {
- START_COLOR("color_degraded");
- fmt = fmt_muted;
- }
+ ALSA_MUTE_SWITCH(playback)
+ } else if (snd_mixer_selem_has_capture_switch(elem)) {
+ ALSA_MUTE_SWITCH(capture)
}
snd_mixer_close(m);