Skip to main content

mx_remote/types/
audio.rs

1// Author: Lars Op den Kamp (lars@opdenkamp-it.nl)
2// Copyright (c) 2026 Op den Kamp IT Solutions
3
4//! The audio endpoint tree a V2IP device or amplifier reports.
5
6use std::collections::BTreeMap;
7
8use crate::wire::DeviceUid;
9
10use super::V2ipStreamSource;
11
12/// What one audio endpoint can do.
13#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
14pub struct AudioFeatures(u32);
15
16impl AudioFeatures {
17    /// Accepts audio.
18    pub const INPUT: Self = Self(1 << 0);
19    /// Produces audio.
20    pub const OUTPUT: Self = Self(1 << 1);
21    /// Sends a V2IP audio stream.
22    pub const V2IP_TX: Self = Self(1 << 2);
23    /// Receives a V2IP audio stream.
24    pub const V2IP_RX: Self = Self(1 << 3);
25    /// Carries HDMI audio.
26    pub const HDMI: Self = Self(1 << 4);
27    /// Is an analogue RCA connector.
28    pub const RCA: Self = Self(1 << 5);
29    /// Is an S/PDIF connector.
30    pub const SPDIF: Self = Self(1 << 6);
31    /// Drives a trigger output.
32    pub const TRIGGER: Self = Self(1 << 7);
33    /// Can be muted.
34    pub const MUTE: Self = Self(1 << 8);
35    /// Can be routed to as an input.
36    pub const ROUTE_INPUT: Self = Self(1 << 9);
37    /// Can be routed from as an output.
38    pub const ROUTE_OUTPUT: Self = Self(1 << 10);
39    /// Accepts "no input" as a route.
40    pub const ROUTE_IN_NONE: Self = Self(1 << 11);
41    /// Is an amplifier output.
42    pub const AMP_OUTPUT: Self = Self(1 << 12);
43    /// Has a volume control.
44    pub const VOLUME_CONTROL: Self = Self(1 << 13);
45    /// Has a gain control.
46    pub const GAIN_CONTROL: Self = Self(1 << 14);
47
48    /// Wraps the raw wire bits, including ones this library has no name for.
49    pub const fn from_bits(bits: u32) -> Self {
50        Self(bits)
51    }
52
53    /// Returns the raw wire bits.
54    pub const fn bits(self) -> u32 {
55        self.0
56    }
57
58    /// Reports whether every bit of `other` is set.
59    pub const fn has(self, other: Self) -> bool {
60        self.0 & other.0 == other.0
61    }
62
63    /// Reports whether this endpoint is either end of a V2IP audio stream.
64    pub const fn is_v2ip(self) -> bool {
65        self.has(Self::V2IP_TX) || self.has(Self::V2IP_RX)
66    }
67}
68
69/// One audio endpoint: an input, an output, or a processing node between them.
70///
71/// The tree is held by id rather than by reference: `parent` and `children`
72/// name endpoints in the same [`AudioEndpoints`] collection.
73#[derive(Clone, Debug, Default, PartialEq, Eq)]
74pub struct AudioEndpoint {
75    /// The endpoint's id within its device.
76    pub id: u8,
77    /// What this endpoint can do.
78    pub features: AudioFeatures,
79    /// The stream this endpoint sends or receives, when it has one.
80    pub address: Option<V2ipStreamSource>,
81    /// The endpoint this one feeds into.
82    pub parent: Option<u8>,
83    /// The endpoints feeding into this one.
84    pub children: Vec<u8>,
85    /// Bitmask of the endpoints that may be routed to this one.
86    pub inputs_available: Option<u32>,
87    /// Bitmask of the endpoints currently routed to this one.
88    pub inputs_routed: Option<u32>,
89    /// The device holding the endpoint this one is linked to.
90    pub linked_device: DeviceUid,
91    /// The endpoint on `linked_device` this one is linked to.
92    pub linked_endpoint: Option<u8>,
93}
94
95impl AudioEndpoint {
96    /// The endpoint currently routed to this one, or `None` when none is.
97    pub fn input(&self) -> Option<u8> {
98        let routed = self.inputs_routed?;
99        (0..32).find(|id| routed & (1 << id) != 0)
100    }
101
102    /// The endpoints that may be routed to this one.
103    pub fn available_inputs(&self) -> Vec<u8> {
104        let Some(mask) = self.inputs_available else {
105            return Vec::new();
106        };
107        (0..32).filter(|id| mask & (1 << id) != 0).collect()
108    }
109}
110
111/// The audio endpoints a device reports, in the order it reported them.
112#[derive(Clone, Debug, Default, PartialEq, Eq)]
113pub struct AudioEndpoints {
114    order: Vec<u8>,
115    endpoints: BTreeMap<u8, AudioEndpoint>,
116}
117
118impl AudioEndpoints {
119    /// Adds an endpoint, replacing one with the same id and keeping its place
120    /// in the reported order.
121    pub(crate) fn add(&mut self, endpoint: AudioEndpoint) {
122        if !self.endpoints.contains_key(&endpoint.id) {
123            self.order.push(endpoint.id);
124        }
125        self.endpoints.insert(endpoint.id, endpoint);
126    }
127
128    /// The endpoint with the given id.
129    pub fn get(&self, id: u8) -> Option<&AudioEndpoint> {
130        self.endpoints.get(&id)
131    }
132
133    pub(crate) fn get_mut(&mut self, id: u8) -> Option<&mut AudioEndpoint> {
134        self.endpoints.get_mut(&id)
135    }
136
137    /// Every endpoint, in the order the device reported them.
138    pub fn list(&self) -> impl Iterator<Item = &AudioEndpoint> {
139        self.order.iter().filter_map(|id| self.endpoints.get(id))
140    }
141
142    /// The endpoints with no parent: the roots of the device's audio tree.
143    pub fn roots(&self) -> impl Iterator<Item = &AudioEndpoint> {
144        self.list().filter(|ep| ep.parent.is_none())
145    }
146
147    /// The first root that accepts audio.
148    pub fn first_root_input(&self) -> Option<&AudioEndpoint> {
149        self.roots()
150            .find(|ep| ep.features.has(AudioFeatures::INPUT))
151    }
152
153    /// The first root that produces audio.
154    pub fn first_root_output(&self) -> Option<&AudioEndpoint> {
155        self.roots()
156            .find(|ep| ep.features.has(AudioFeatures::OUTPUT))
157    }
158
159    /// Reports whether two collections describe the same tree.
160    ///
161    /// Only the id, the features and the parent are compared: the routing and
162    /// link fields change on every report, and a device that re-sends an
163    /// unchanged tree must not read as a new one.
164    pub(crate) fn same_tree(&self, other: &Self) -> bool {
165        self.endpoints.len() == other.endpoints.len()
166            && self.endpoints.iter().all(|(id, ep)| {
167                other
168                    .endpoints
169                    .get(id)
170                    .is_some_and(|o| o.features == ep.features && o.parent == ep.parent)
171            })
172    }
173
174    /// Records the endpoint and device one of these endpoints is linked to.
175    pub(crate) fn apply_link(&mut self, link: &AudioLink) {
176        if let Some(ep) = self.endpoints.get_mut(&link.endpoint) {
177            ep.linked_device = link.linked_device;
178            ep.linked_endpoint = Some(link.linked_endpoint);
179        }
180    }
181}
182
183/// A link from an audio endpoint on this device to one on another.
184#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
185pub struct AudioLink {
186    /// The local endpoint.
187    pub endpoint: u8,
188    /// The endpoint it is linked to.
189    pub linked_endpoint: u8,
190    /// The device holding the linked endpoint.
191    pub linked_device: DeviceUid,
192}