1#[cfg(test)]
9mod tests;
10
11use num_enum::{IntoPrimitive, TryFromPrimitive};
12use openlogi_hidpp_derive::Feature;
13
14use crate::{feature::FeatureEndpoint, protocol::v20::Hidpp20Error};
15
16const FREQUENCIES_PER_PAGE: u8 = 7;
18
19bitflags::bitflags! {
20 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
23 #[cfg_attr(feature = "serde", derive(serde::Serialize))]
24 pub struct EqCapabilities: u8 {
25 const STORED_AS_GAINS = 1 << 0;
27 const STORED_AS_COEFFICIENTS = 1 << 1;
29 }
30}
31
32#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
34#[cfg_attr(feature = "serde", derive(serde::Serialize))]
35#[non_exhaustive]
36#[repr(u8)]
37pub enum GainLocation {
38 Eeprom = 0,
40 Ram = 1,
42}
43
44#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
47#[cfg_attr(feature = "serde", derive(serde::Serialize))]
48#[non_exhaustive]
49#[repr(u8)]
50pub enum GainPersistence {
51 Volatile = 0,
53 VolatileAndNonVolatile = 1,
55 NonVolatileOnly = 2,
57}
58
59#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
61#[cfg_attr(feature = "serde", derive(serde::Serialize))]
62#[non_exhaustive]
63pub struct EqInfo {
64 pub band_count: u8,
66 pub db_range: u8,
68 pub capabilities: EqCapabilities,
70 pub db_min: i8,
72 pub db_max: i8,
74}
75
76impl EqInfo {
77 fn from_payload(payload: &[u8; 16]) -> Self {
78 Self {
79 band_count: payload[0],
80 db_range: payload[1],
81 capabilities: EqCapabilities::from_bits_retain(payload[2]),
82 db_min: payload[3].cast_signed(),
83 db_max: payload[4].cast_signed(),
84 }
85 }
86
87 #[must_use]
91 pub fn effective_range(&self) -> (i8, i8) {
92 if self.db_min == 0 && self.db_max == 0 {
93 let range = i8::try_from(self.db_range).unwrap_or(i8::MAX);
94 (-range, range)
95 } else {
96 (self.db_min, self.db_max)
97 }
98 }
99}
100
101#[derive(Clone, Feature)]
103#[creatable(id = 0x8310, version = 2)]
104pub struct EqualizerFeature {
105 endpoint: FeatureEndpoint,
107}
108
109impl EqualizerFeature {
110 pub async fn get_eq_info(&self) -> Result<EqInfo, Hidpp20Error> {
112 let payload = self.endpoint.call(0, [0; 3]).await?.extend_payload();
113 Ok(EqInfo::from_payload(&payload))
114 }
115
116 pub async fn get_frequencies(&self, band_count: u8) -> Result<Vec<u16>, Hidpp20Error> {
122 let mut frequencies = Vec::with_capacity(usize::from(band_count));
123 let mut index = 0u8;
124 while index < band_count {
125 let payload = self.endpoint.call(1, [index, 0, 0]).await?.extend_payload();
126 if payload[0] != index {
128 return Err(Hidpp20Error::UnsupportedResponse);
129 }
130 let page = (band_count - index).min(FREQUENCIES_PER_PAGE);
131 frequencies.extend(parse_frequency_page(&payload, page)?);
132 index += page;
133 }
134 Ok(frequencies)
135 }
136
137 pub async fn get_frequency_gains(
142 &self,
143 location: GainLocation,
144 band_count: u8,
145 ) -> Result<Vec<i8>, Hidpp20Error> {
146 let payload = self
147 .endpoint
148 .call(2, [location.into(), 0, 0])
149 .await?
150 .extend_payload();
151 parse_gains(&payload, 0, band_count)
152 }
153
154 pub async fn set_frequency_gains(
159 &self,
160 persistence: GainPersistence,
161 gains: &[i8],
162 ) -> Result<Vec<i8>, Hidpp20Error> {
163 let count = u8::try_from(gains.len()).map_err(|_| Hidpp20Error::UnsupportedResponse)?;
164 let mut args = [0; 16];
165 args[0] = persistence.into();
166 for (i, &gain) in gains.iter().enumerate() {
169 let slot = 1 + i;
170 if slot >= args.len() {
171 return Err(Hidpp20Error::UnsupportedResponse);
172 }
173 args[slot] = gain.cast_unsigned();
174 }
175 let payload = self.endpoint.call_long(3, args).await?.extend_payload();
176 parse_gains(&payload, 1, count)
179 }
180
181 pub async fn get_mic_noise_reduction(&self) -> Result<bool, Hidpp20Error> {
183 let payload = self.endpoint.call(4, [0; 3]).await?.extend_payload();
184 Ok(payload[0] != 0)
185 }
186
187 pub async fn set_mic_noise_reduction(&self, enabled: bool) -> Result<(), Hidpp20Error> {
189 self.endpoint.call(5, [u8::from(enabled), 0, 0]).await?;
190 Ok(())
191 }
192}
193
194fn parse_frequency_page(payload: &[u8; 16], count: u8) -> Result<Vec<u16>, Hidpp20Error> {
197 let count = usize::from(count);
198 if 1 + 2 * count > payload.len() {
199 return Err(Hidpp20Error::UnsupportedResponse);
200 }
201 Ok((0..count)
202 .map(|i| u16::from_be_bytes([payload[1 + 2 * i], payload[2 + 2 * i]]))
203 .collect())
204}
205
206fn parse_gains(payload: &[u8; 16], offset: usize, count: u8) -> Result<Vec<i8>, Hidpp20Error> {
208 let count = usize::from(count);
209 if offset + count > payload.len() {
210 return Err(Hidpp20Error::UnsupportedResponse);
211 }
212 Ok(payload[offset..offset + count]
213 .iter()
214 .map(|&byte| byte.cast_signed())
215 .collect())
216}