From 6c10eb2153a14ad12ee5d49c52669d4d0bc13466 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 16 Jun 2022 18:24:42 +0200 Subject: [PATCH] =?UTF-8?q?module/alsa:=20use=20channel=E2=80=99s=20dB=20r?= =?UTF-8?q?ange=20instead=20of=20raw=20volume,=20if=20available?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For channels that have a defined dB range, use that instead of the raw volume range when calculating the volume percent. Also use the same logic as alsamixer when calculating the percent from the dB values: assume a linear scale if the dB range is “small enough”, and otherwise normalize it against a logarithmic scale. With this, yambar’s “percent” value matches alsamixer’s exactly. The ‘volume’ tag remains unchanged - it always reflects the raw volume values. Instead, we add a new tag ‘dB’, that reflects the dB values. Closes #202 --- CHANGELOG.md | 6 +- doc/yambar-modules-alsa.5.scd | 9 +- examples/configurations/laptop.conf | 2 +- modules/alsa.c | 144 ++++++++++++++++++++++++---- 4 files changed, 141 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d72922..edaa31e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,10 +25,11 @@ * network: request link stats and expose under tags `dl-speed` and `ul-speed` when `poll-interval` is set. * new module: disk-io. - +* alsa: `dB` tag ([#202][202]) [153]: https://codeberg.org/dnkl/yambar/issues/153 [159]: https://codeberg.org/dnkl/yambar/issues/159 +[202]: https://codeberg.org/dnkl/yambar/issues/202 ### Changed @@ -42,6 +43,8 @@ (e.g. `string: {text: "{tx-bitrate:mb}"}`). * i3: newly created, and **unfocused** workspaces are now considered non-empty ([#191][191]) +* alsa: use dB instead of raw volume values, if possible, when + calculating the `percent` tag ([#202][202]) * **BREAKING CHANGE**: overhaul of the `map` particle. Instead of specifying a `tag` and then an array of `values`, you must now simply use an array of `conditions`, that consist of: @@ -83,6 +86,7 @@ [137]: https://codeberg.org/dnkl/yambar/issues/137 [175]: https://codeberg.org/dnkl/yambar/issues/172 [191]: https://codeberg.org/dnkl/yambar/issues/191 +[202]: https://codeberg.org/dnkl/yambar/issues/202 ### Deprecated diff --git a/doc/yambar-modules-alsa.5.scd b/doc/yambar-modules-alsa.5.scd index 58a4343..17f1a29 100644 --- a/doc/yambar-modules-alsa.5.scd +++ b/doc/yambar-modules-alsa.5.scd @@ -11,12 +11,17 @@ alsa - Monitors an alsa soundcard for volume and mute/unmute changes | online : bool : True when the ALSA device has successfully been opened +| dB +: range +: Volume level (in dB), with min and max as start and end range + values. | volume : range -: Volume level, with min and max as start and end range values +: Volume level (raw), with min and max as start and end range values | percent : range -: Volume level, as a percentage +: Volume level, as a percentage. This value is based on the *dB* tag + if available, otherwise the *volume* tag. | muted : bool : True if muted, otherwise false diff --git a/examples/configurations/laptop.conf b/examples/configurations/laptop.conf index cb90111..85f43ad 100644 --- a/examples/configurations/laptop.conf +++ b/examples/configurations/laptop.conf @@ -215,7 +215,7 @@ bar: muted: {string: {text: , font: *awesome, foreground: ffffff66}} ~muted: ramp: - tag: volume + tag: percent items: - string: {text: , font: *awesome} - string: {text: , font: *awesome} diff --git a/modules/alsa.c b/modules/alsa.c index f22b340..b3aaba8 100644 --- a/modules/alsa.c +++ b/modules/alsa.c @@ -23,7 +23,9 @@ struct channel { enum channel_type type; char *name; + bool use_db; long vol_cur; + long db_cur; bool muted; }; @@ -42,10 +44,18 @@ struct private { long playback_vol_min; long playback_vol_max; + bool has_playback_db; + long playback_db_min; + long playback_db_max; + bool has_capture_volume; long capture_vol_min; long capture_vol_max; + long has_capture_db; + long capture_db_min; + long capture_db_max; + const struct channel *volume_chan; const struct channel *muted_chan; }; @@ -94,30 +104,57 @@ content(struct module *mod) bool muted = muted_chan != NULL ? muted_chan->muted : false; long vol_min = 0, vol_max = 0, vol_cur = 0; + long db_min = 0, db_max = 0, db_cur = 0; + bool use_db = false; if (volume_chan != NULL) { if (volume_chan->type == CHANNEL_PLAYBACK) { + db_min = m->playback_db_min; + db_max = m->playback_db_max; vol_min = m->playback_vol_min; vol_max = m->playback_vol_max; } else { + db_min = m->capture_db_min; + db_max = m->capture_db_max; vol_min = m->capture_vol_min; vol_max = m->capture_vol_max; } vol_cur = volume_chan->vol_cur; + db_cur = volume_chan->db_cur; + use_db = volume_chan->use_db; } - int percent = vol_max - vol_min > 0 - ? round(100. * vol_cur / (vol_max - vol_min)) - : 0; + int percent; + + if (use_db) { + bool use_linear = db_max - db_min <= 24 * 100; + if (use_linear) { + percent = db_min - db_max > 0 + ? round(100. * (db_cur - db_min) / (db_max - db_min)) + : 0; + } else { + double normalized = pow(10, (double)(db_cur - db_max) / 6000.); + if (db_min != SND_CTL_TLV_DB_GAIN_MUTE) { + double min_norm = pow(10, (double)(db_min - db_max) / 6000.); + normalized = (normalized - min_norm) / (1. - min_norm); + } + percent = round(100. * normalized); + } + } else { + percent = vol_max - vol_min > 0 + ? round(100. * (vol_cur - vol_min) / (vol_max - vol_min)) + : 0; + } struct tag_set tags = { .tags = (struct tag *[]){ tag_new_bool(mod, "online", m->online), tag_new_int_range(mod, "volume", vol_cur, vol_min, vol_max), + tag_new_int_range(mod, "dB", db_cur, db_min, db_max), tag_new_int_range(mod, "percent", percent, 0, 100), tag_new_bool(mod, "muted", muted), }, - .count = 4, + .count = 5, }; mtx_unlock(&mod->lock); @@ -132,6 +169,8 @@ update_state(struct module *mod, snd_mixer_elem_t *elem) { struct private *m = mod->private; + mtx_lock(&mod->lock); + /* If volume level can be changed (i.e. this isn't just a switch; * e.g. a digital channel), get current channel levels */ tll_foreach(m->channels, it) { @@ -139,14 +178,57 @@ update_state(struct module *mod, snd_mixer_elem_t *elem) const bool has_volume = chan->type == CHANNEL_PLAYBACK ? m->has_playback_volume : m->has_capture_volume; + const bool has_db = chan->type == CHANNEL_PLAYBACK + ? m->has_playback_db : m->has_capture_db; + + if (!has_volume && !has_db) + continue; + + + if (has_db) { + chan->use_db = true; + + const long min = chan->type == CHANNEL_PLAYBACK + ? m->playback_db_min : m->capture_db_min; + const long max = chan->type == CHANNEL_PLAYBACK + ? m->playback_db_max : m->capture_db_max; + assert(min <= max); + + int r = chan->type == CHANNEL_PLAYBACK + ? snd_mixer_selem_get_playback_dB(elem, chan->id, &chan->db_cur) + : snd_mixer_selem_get_capture_dB(elem, chan->id, &chan->db_cur); + + if (r < 0) { + LOG_ERR("%s,%s: %s: failed to get current dB", + m->card, m->mixer, chan->name); + } + + if (chan->db_cur < min) { + LOG_WARN( + "%s,%s: %s: current dB is less than the indicated minimum: " + "%ld < %ld", m->card, m->mixer, chan->name, chan->db_cur, min); + chan->db_cur = min; + } + + if (chan->db_cur > max) { + LOG_WARN( + "%s,%s: %s: current dB is greater than the indicated maximum: " + "%ld > %ld", m->card, m->mixer, chan->name, chan->db_cur, max); + chan->db_cur = max; + } + + assert(chan->db_cur >= min); + assert(chan->db_cur <= max ); + + LOG_DBG("%s,%s: %s: dB: %ld", + m->card, m->mixer, chan->name, chan->db_cur); + } else + chan->use_db = false; + const long min = chan->type == CHANNEL_PLAYBACK ? m->playback_vol_min : m->capture_vol_min; const long max = chan->type == CHANNEL_PLAYBACK ? m->playback_vol_max : m->capture_vol_max; - - if (!has_volume) - continue; - assert(min <= max); int r = chan->type == CHANNEL_PLAYBACK @@ -199,10 +281,9 @@ update_state(struct module *mod, snd_mixer_elem_t *elem) LOG_DBG("%s,%s: %s: muted: %d", m->card, m->mixer, chan->name, !unmuted); } - mtx_lock(&mod->lock); m->online = true; - mtx_unlock(&mod->lock); + mtx_unlock(&mod->lock); mod->bar->refresh(mod->bar); } @@ -269,6 +350,16 @@ run_while_online(struct module *mod) } } + if (snd_mixer_selem_get_playback_dB_range( + elem, &m->playback_db_min, &m->playback_db_max) < 0) + { + LOG_WARN( + "%s,%s: failed to get playback dB range, " + "will use raw volume values instead", m->card, m->mixer); + m->has_playback_db = false; + } else + m->has_playback_db = true; + /* Get capture volume range */ m->has_capture_volume = snd_mixer_selem_has_capture_volume(elem) > 0; if (m->has_capture_volume) { @@ -290,6 +381,16 @@ run_while_online(struct module *mod) } } + if (snd_mixer_selem_get_capture_dB_range( + elem, &m->capture_db_min, &m->capture_db_max) < 0) + { + LOG_WARN( + "%s,%s: failed to get capture dB range, " + "will use raw volume values instead", m->card, m->mixer); + m->has_capture_db = false; + } else + m->has_capture_db = true; + /* Get available channels */ for (size_t i = 0; i < SND_MIXER_SCHN_LAST; i++) { bool is_playback = snd_mixer_selem_has_playback_channel(elem, i) == 1; @@ -361,13 +462,24 @@ run_while_online(struct module *mod) update_state(mod, elem); LOG_INFO( - "%s,%s: volume range=%ld-%ld, current=%ld%s (sources: volume=%s, muted=%s)", + "%s,%s: %s range=%ld-%ld, current=%ld%s (sources: volume=%s, muted=%s)", m->card, m->mixer, - m->volume_chan->type == CHANNEL_PLAYBACK - ? m->playback_vol_min : m->capture_vol_min, - m->volume_chan->type == CHANNEL_PLAYBACK - ? m->playback_vol_max : m->capture_vol_max, - m->volume_chan->vol_cur, + m->volume_chan->use_db ? "dB" : "volume", + (m->volume_chan->type == CHANNEL_PLAYBACK + ? (m->volume_chan->use_db + ? m->playback_db_min + : m->playback_vol_min) + : (m->volume_chan->use_db + ? m->capture_db_min + : m->capture_vol_min)), + (m->volume_chan->type == CHANNEL_PLAYBACK + ? (m->volume_chan->use_db + ? m->playback_db_max + : m->playback_vol_max) + : (m->volume_chan->use_db + ? m->capture_db_max + : m->capture_vol_max)), + m->volume_chan->use_db ? m->volume_chan->db_cur : m->volume_chan->vol_cur, m->muted_chan->muted ? " (muted)" : "", m->volume_chan->name, m->muted_chan->name);