fix(volume): average all channels instead of returning the first
Previously returned the first channel percentage found, which is incorrect when channels are unbalanced. Average across all channels matches what OS volume sliders display and avoids a misleading reading in edge cases such as hard-panned mono (e.g. left=100%, right=0% would have incorrectly reported 100% rather than 50%). Add a test covering the unbalanced case.
This commit is contained in:
+27
-11
@@ -65,18 +65,27 @@ fn query_volume() -> Option<u8> {
|
||||
parse_volume(&String::from_utf8_lossy(&out.stdout))
|
||||
}
|
||||
|
||||
/// Parse the first percentage value from pactl output such as:
|
||||
/// Parse the average percentage across all channels from pactl output such as:
|
||||
/// `"Volume: front-left: 65536 / 100% / 0.00 dB, front-right: 65536 / 100% ..."`
|
||||
///
|
||||
/// Averaging across channels matches what OS volume sliders display and avoids
|
||||
/// a misleading reading when channels are unbalanced (e.g. hard-panned mono).
|
||||
fn parse_volume(s: &str) -> Option<u8> {
|
||||
for part in s.split('/') {
|
||||
let trimmed = part.trim();
|
||||
if let Some(stripped) = trimmed.strip_suffix('%') {
|
||||
if let Ok(n) = stripped.trim().parse::<u8>() {
|
||||
return Some(n);
|
||||
}
|
||||
}
|
||||
let levels: Vec<u32> = s
|
||||
.split('/')
|
||||
.filter_map(|part| {
|
||||
part.trim()
|
||||
.strip_suffix('%')
|
||||
.and_then(|n| n.trim().parse::<u32>().ok())
|
||||
})
|
||||
.collect();
|
||||
|
||||
if levels.is_empty() {
|
||||
return None;
|
||||
}
|
||||
None
|
||||
|
||||
let avg = levels.iter().sum::<u32>() / levels.len() as u32;
|
||||
Some(avg.min(100) as u8)
|
||||
}
|
||||
|
||||
fn broadcast(pkt: Packet, keyboards: &[Keyboard]) {
|
||||
@@ -99,17 +108,24 @@ mod tests {
|
||||
use super::parse_volume;
|
||||
|
||||
#[test]
|
||||
fn parses_stereo_output() {
|
||||
fn parses_stereo_equal() {
|
||||
let s = "Volume: front-left: 65536 / 100% / 0.00 dB, front-right: 65536 / 100% / 0.00 dB";
|
||||
assert_eq!(parse_volume(s), Some(100));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_partial_volume() {
|
||||
fn parses_stereo_equal_partial() {
|
||||
let s = "Volume: front-left: 45875 / 70% / -9.03 dB, front-right: 45875 / 70% / -9.03 dB";
|
||||
assert_eq!(parse_volume(s), Some(70));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn averages_unbalanced_channels() {
|
||||
// left=80%, right=20% → average=50%
|
||||
let s = "Volume: front-left: 52429 / 80% / -5.08 dB, front-right: 13107 / 20% / -13.98 dB";
|
||||
assert_eq!(parse_volume(s), Some(50));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_none_for_garbage() {
|
||||
assert_eq!(parse_volume("no percentage here"), None);
|
||||
|
||||
Reference in New Issue
Block a user