audio: extract apply_peer_volume seam + A24 regression tests
A24 (per-peer volume slider has no effect) does not reproduce on current main from a code trace: the UI slider's EndpointId is the same key the mixer uses for the incoming jitter frame, and the gain is applied before EQ/pan/output. Extract the inline per-peer lookup into a pure apply_peer_volume() seam and add two regression tests: - matching key scales the frame (0.5 halves it) - mismatched key defaults to unity (guards the key-identity failure mode) No wire/gossip/identity/PeerState change. The field-reported A24 was most likely a stale listener build (volume is listener-side); needs a 2-machine audible re-verify to close. Co-Authored-By: Codex (gpt-5.5) <noreply@openai.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+39
-5
@@ -294,6 +294,17 @@ fn apply_volume(frame: &mut [i16], vol: f32) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply the listener's per-peer volume for the audio sender id currently being
|
||||
/// mixed. The map key must be the same `EndpointId` used for the jitter buffer.
|
||||
fn apply_peer_volume(
|
||||
frame: &mut [i16],
|
||||
peer_id: EndpointId,
|
||||
volumes: &HashMap<EndpointId, f32>,
|
||||
) {
|
||||
let vol = volumes.get(&peer_id).copied().unwrap_or(1.0);
|
||||
apply_volume(frame, vol);
|
||||
}
|
||||
|
||||
/// Normalized RMS level of a frame in `[0.0, 1.0]` (32768 = full scale), for the
|
||||
/// UI level meter. An empty frame reads as 0.0.
|
||||
fn frame_level(frame: &[i16]) -> f32 {
|
||||
@@ -1707,8 +1718,7 @@ async fn run_core_loop(
|
||||
peer_noise_gates.remove(&peer_id);
|
||||
}
|
||||
|
||||
let vol = current_volumes.get(&peer_id).copied().unwrap_or(1.0);
|
||||
apply_volume(&mut frame, vol);
|
||||
apply_peer_volume(&mut frame, peer_id, ¤t_volumes);
|
||||
|
||||
let eq_settings = current_eq
|
||||
.get(&peer_id)
|
||||
@@ -2776,9 +2786,9 @@ async fn run_core_loop(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
admit_retained, apply_volume, audio_datagram_len_ok, frame_level, mix_frames,
|
||||
mix_stereo_frames, next_game_change, should_auto_fetch, stereo_to_mono, KnownPeers,
|
||||
MicLevelMeter, PeerSpeakTicket, MAX_OPUS_PAYLOAD, MAX_RETAINED_PEERS,
|
||||
admit_retained, apply_peer_volume, apply_volume, audio_datagram_len_ok, frame_level,
|
||||
mix_frames, mix_stereo_frames, next_game_change, should_auto_fetch, stereo_to_mono,
|
||||
KnownPeers, MicLevelMeter, PeerSpeakTicket, MAX_OPUS_PAYLOAD, MAX_RETAINED_PEERS,
|
||||
MIC_LEVEL_REPORT_SAMPLES,
|
||||
};
|
||||
|
||||
@@ -3038,6 +3048,30 @@ mod tests {
|
||||
assert_eq!(frame, vec![2000, -2000]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn peer_volume_map_scales_the_matching_audio_peer_frame() {
|
||||
let peer = iroh::SecretKey::generate().public();
|
||||
let other_peer = iroh::SecretKey::generate().public();
|
||||
let volumes = std::collections::HashMap::from([(peer, 0.5), (other_peer, 2.0)]);
|
||||
let mut frame = vec![100, -200, 300, -400];
|
||||
|
||||
apply_peer_volume(&mut frame, peer, &volumes);
|
||||
|
||||
assert_eq!(frame, vec![50, -100, 150, -200]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn peer_volume_map_defaults_to_unity_when_audio_peer_key_is_unmatched() {
|
||||
let ui_peer = iroh::SecretKey::generate().public();
|
||||
let audio_peer = iroh::SecretKey::generate().public();
|
||||
let volumes = std::collections::HashMap::from([(ui_peer, 0.5)]);
|
||||
let mut frame = vec![100, -200, 300, -400];
|
||||
|
||||
apply_peer_volume(&mut frame, audio_peer, &volumes);
|
||||
|
||||
assert_eq!(frame, vec![100, -200, 300, -400]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn three_peers_sum_without_saturation() {
|
||||
let a = vec![10, 20];
|
||||
|
||||
Reference in New Issue
Block a user