forked from external/yambar
module/alsa: use channel’s dB range instead of raw volume, if available
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
This commit is contained in:
parent
a0c07d7836
commit
6c10eb2153
4 changed files with 141 additions and 20 deletions
|
@ -25,10 +25,11 @@
|
||||||
* network: request link stats and expose under tags `dl-speed` and
|
* network: request link stats and expose under tags `dl-speed` and
|
||||||
`ul-speed` when `poll-interval` is set.
|
`ul-speed` when `poll-interval` is set.
|
||||||
* new module: disk-io.
|
* new module: disk-io.
|
||||||
|
* alsa: `dB` tag ([#202][202])
|
||||||
|
|
||||||
[153]: https://codeberg.org/dnkl/yambar/issues/153
|
[153]: https://codeberg.org/dnkl/yambar/issues/153
|
||||||
[159]: https://codeberg.org/dnkl/yambar/issues/159
|
[159]: https://codeberg.org/dnkl/yambar/issues/159
|
||||||
|
[202]: https://codeberg.org/dnkl/yambar/issues/202
|
||||||
|
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
@ -42,6 +43,8 @@
|
||||||
(e.g. `string: {text: "{tx-bitrate:mb}"}`).
|
(e.g. `string: {text: "{tx-bitrate:mb}"}`).
|
||||||
* i3: newly created, and **unfocused** workspaces are now considered
|
* i3: newly created, and **unfocused** workspaces are now considered
|
||||||
non-empty ([#191][191])
|
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
|
* **BREAKING CHANGE**: overhaul of the `map` particle. Instead of
|
||||||
specifying a `tag` and then an array of `values`, you must now
|
specifying a `tag` and then an array of `values`, you must now
|
||||||
simply use an array of `conditions`, that consist of:
|
simply use an array of `conditions`, that consist of:
|
||||||
|
@ -83,6 +86,7 @@
|
||||||
[137]: https://codeberg.org/dnkl/yambar/issues/137
|
[137]: https://codeberg.org/dnkl/yambar/issues/137
|
||||||
[175]: https://codeberg.org/dnkl/yambar/issues/172
|
[175]: https://codeberg.org/dnkl/yambar/issues/172
|
||||||
[191]: https://codeberg.org/dnkl/yambar/issues/191
|
[191]: https://codeberg.org/dnkl/yambar/issues/191
|
||||||
|
[202]: https://codeberg.org/dnkl/yambar/issues/202
|
||||||
|
|
||||||
|
|
||||||
### Deprecated
|
### Deprecated
|
||||||
|
|
|
@ -11,12 +11,17 @@ alsa - Monitors an alsa soundcard for volume and mute/unmute changes
|
||||||
| online
|
| online
|
||||||
: bool
|
: bool
|
||||||
: True when the ALSA device has successfully been opened
|
: 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
|
| volume
|
||||||
: range
|
: 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
|
| percent
|
||||||
: range
|
: 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
|
| muted
|
||||||
: bool
|
: bool
|
||||||
: True if muted, otherwise false
|
: True if muted, otherwise false
|
||||||
|
|
|
@ -215,7 +215,7 @@ bar:
|
||||||
muted: {string: {text: , font: *awesome, foreground: ffffff66}}
|
muted: {string: {text: , font: *awesome, foreground: ffffff66}}
|
||||||
~muted:
|
~muted:
|
||||||
ramp:
|
ramp:
|
||||||
tag: volume
|
tag: percent
|
||||||
items:
|
items:
|
||||||
- string: {text: , font: *awesome}
|
- string: {text: , font: *awesome}
|
||||||
- string: {text: , font: *awesome}
|
- string: {text: , font: *awesome}
|
||||||
|
|
142
modules/alsa.c
142
modules/alsa.c
|
@ -23,7 +23,9 @@ struct channel {
|
||||||
enum channel_type type;
|
enum channel_type type;
|
||||||
char *name;
|
char *name;
|
||||||
|
|
||||||
|
bool use_db;
|
||||||
long vol_cur;
|
long vol_cur;
|
||||||
|
long db_cur;
|
||||||
bool muted;
|
bool muted;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -42,10 +44,18 @@ struct private {
|
||||||
long playback_vol_min;
|
long playback_vol_min;
|
||||||
long playback_vol_max;
|
long playback_vol_max;
|
||||||
|
|
||||||
|
bool has_playback_db;
|
||||||
|
long playback_db_min;
|
||||||
|
long playback_db_max;
|
||||||
|
|
||||||
bool has_capture_volume;
|
bool has_capture_volume;
|
||||||
long capture_vol_min;
|
long capture_vol_min;
|
||||||
long capture_vol_max;
|
long capture_vol_max;
|
||||||
|
|
||||||
|
long has_capture_db;
|
||||||
|
long capture_db_min;
|
||||||
|
long capture_db_max;
|
||||||
|
|
||||||
const struct channel *volume_chan;
|
const struct channel *volume_chan;
|
||||||
const struct channel *muted_chan;
|
const struct channel *muted_chan;
|
||||||
};
|
};
|
||||||
|
@ -94,30 +104,57 @@ content(struct module *mod)
|
||||||
|
|
||||||
bool muted = muted_chan != NULL ? muted_chan->muted : false;
|
bool muted = muted_chan != NULL ? muted_chan->muted : false;
|
||||||
long vol_min = 0, vol_max = 0, vol_cur = 0;
|
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 != NULL) {
|
||||||
if (volume_chan->type == CHANNEL_PLAYBACK) {
|
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_min = m->playback_vol_min;
|
||||||
vol_max = m->playback_vol_max;
|
vol_max = m->playback_vol_max;
|
||||||
} else {
|
} else {
|
||||||
|
db_min = m->capture_db_min;
|
||||||
|
db_max = m->capture_db_max;
|
||||||
vol_min = m->capture_vol_min;
|
vol_min = m->capture_vol_min;
|
||||||
vol_max = m->capture_vol_max;
|
vol_max = m->capture_vol_max;
|
||||||
}
|
}
|
||||||
vol_cur = volume_chan->vol_cur;
|
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
|
int percent;
|
||||||
? round(100. * vol_cur / (vol_max - vol_min))
|
|
||||||
|
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;
|
: 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 = {
|
struct tag_set tags = {
|
||||||
.tags = (struct tag *[]){
|
.tags = (struct tag *[]){
|
||||||
tag_new_bool(mod, "online", m->online),
|
tag_new_bool(mod, "online", m->online),
|
||||||
tag_new_int_range(mod, "volume", vol_cur, vol_min, vol_max),
|
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_int_range(mod, "percent", percent, 0, 100),
|
||||||
tag_new_bool(mod, "muted", muted),
|
tag_new_bool(mod, "muted", muted),
|
||||||
},
|
},
|
||||||
.count = 4,
|
.count = 5,
|
||||||
};
|
};
|
||||||
mtx_unlock(&mod->lock);
|
mtx_unlock(&mod->lock);
|
||||||
|
|
||||||
|
@ -132,6 +169,8 @@ update_state(struct module *mod, snd_mixer_elem_t *elem)
|
||||||
{
|
{
|
||||||
struct private *m = mod->private;
|
struct private *m = mod->private;
|
||||||
|
|
||||||
|
mtx_lock(&mod->lock);
|
||||||
|
|
||||||
/* If volume level can be changed (i.e. this isn't just a switch;
|
/* If volume level can be changed (i.e. this isn't just a switch;
|
||||||
* e.g. a digital channel), get current channel levels */
|
* e.g. a digital channel), get current channel levels */
|
||||||
tll_foreach(m->channels, it) {
|
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
|
const bool has_volume = chan->type == CHANNEL_PLAYBACK
|
||||||
? m->has_playback_volume : m->has_capture_volume;
|
? 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
|
const long min = chan->type == CHANNEL_PLAYBACK
|
||||||
? m->playback_vol_min : m->capture_vol_min;
|
? m->playback_vol_min : m->capture_vol_min;
|
||||||
const long max = chan->type == CHANNEL_PLAYBACK
|
const long max = chan->type == CHANNEL_PLAYBACK
|
||||||
? m->playback_vol_max : m->capture_vol_max;
|
? m->playback_vol_max : m->capture_vol_max;
|
||||||
|
|
||||||
if (!has_volume)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
assert(min <= max);
|
assert(min <= max);
|
||||||
|
|
||||||
int r = chan->type == CHANNEL_PLAYBACK
|
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);
|
LOG_DBG("%s,%s: %s: muted: %d", m->card, m->mixer, chan->name, !unmuted);
|
||||||
}
|
}
|
||||||
|
|
||||||
mtx_lock(&mod->lock);
|
|
||||||
m->online = true;
|
m->online = true;
|
||||||
mtx_unlock(&mod->lock);
|
|
||||||
|
|
||||||
|
mtx_unlock(&mod->lock);
|
||||||
mod->bar->refresh(mod->bar);
|
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 */
|
/* Get capture volume range */
|
||||||
m->has_capture_volume = snd_mixer_selem_has_capture_volume(elem) > 0;
|
m->has_capture_volume = snd_mixer_selem_has_capture_volume(elem) > 0;
|
||||||
if (m->has_capture_volume) {
|
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 */
|
/* Get available channels */
|
||||||
for (size_t i = 0; i < SND_MIXER_SCHN_LAST; i++) {
|
for (size_t i = 0; i < SND_MIXER_SCHN_LAST; i++) {
|
||||||
bool is_playback = snd_mixer_selem_has_playback_channel(elem, i) == 1;
|
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);
|
update_state(mod, elem);
|
||||||
|
|
||||||
LOG_INFO(
|
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->card, m->mixer,
|
||||||
m->volume_chan->type == CHANNEL_PLAYBACK
|
m->volume_chan->use_db ? "dB" : "volume",
|
||||||
? m->playback_vol_min : m->capture_vol_min,
|
(m->volume_chan->type == CHANNEL_PLAYBACK
|
||||||
m->volume_chan->type == CHANNEL_PLAYBACK
|
? (m->volume_chan->use_db
|
||||||
? m->playback_vol_max : m->capture_vol_max,
|
? m->playback_db_min
|
||||||
m->volume_chan->vol_cur,
|
: 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->muted_chan->muted ? " (muted)" : "",
|
||||||
m->volume_chan->name, m->muted_chan->name);
|
m->volume_chan->name, m->muted_chan->name);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue