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);