1#[cfg(test)]
9mod tests;
10
11use std::sync::Arc;
12
13use num_enum::{IntoPrimitive, TryFromPrimitive};
14
15use crate::{
16 channel::HidppChannel,
17 feature::{CreatableFeature, Feature, FeatureEndpoint},
18 protocol::v20::Hidpp20Error,
19};
20
21const FREQUENCIES_PER_PAGE: u8 = 7;
23
24bitflags::bitflags! {
25 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
28 #[cfg_attr(feature = "serde", derive(serde::Serialize))]
29 pub struct EqCapabilities: u8 {
30 const STORED_AS_GAINS = 1 << 0;
32 const STORED_AS_COEFFICIENTS = 1 << 1;
34 }
35}
36
37#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
39#[cfg_attr(feature = "serde", derive(serde::Serialize))]
40#[non_exhaustive]
41#[repr(u8)]
42pub enum GainLocation {
43 Eeprom = 0,
45 Ram = 1,
47}
48
49#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
52#[cfg_attr(feature = "serde", derive(serde::Serialize))]
53#[non_exhaustive]
54#[repr(u8)]
55pub enum GainPersistence {
56 Volatile = 0,
58 VolatileAndNonVolatile = 1,
60 NonVolatileOnly = 2,
62}
63
64#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
66#[cfg_attr(feature = "serde", derive(serde::Serialize))]
67#[non_exhaustive]
68pub struct EqInfo {
69 pub band_count: u8,
71 pub db_range: u8,
73 pub capabilities: EqCapabilities,
75 pub db_min: i8,
77 pub db_max: i8,
79}
80
81impl EqInfo {
82 fn from_payload(payload: &[u8; 16]) -> Self {
83 Self {
84 band_count: payload[0],
85 db_range: payload[1],
86 capabilities: EqCapabilities::from_bits_retain(payload[2]),
87 db_min: payload[3] as i8,
88 db_max: payload[4] as i8,
89 }
90 }
91
92 #[must_use]
96 pub fn effective_range(&self) -> (i8, i8) {
97 if self.db_min == 0 && self.db_max == 0 {
98 let range = i8::try_from(self.db_range).unwrap_or(i8::MAX);
99 (-range, range)
100 } else {
101 (self.db_min, self.db_max)
102 }
103 }
104}
105
106#[derive(Clone)]
108pub struct EqualizerFeature {
109 endpoint: FeatureEndpoint,
111}
112
113impl CreatableFeature for EqualizerFeature {
114 const ID: u16 = 0x8310;
115 const STARTING_VERSION: u8 = 2;
116
117 fn new(chan: Arc<HidppChannel>, device_index: u8, feature_index: u8) -> Self {
118 Self {
119 endpoint: FeatureEndpoint::new(chan, device_index, feature_index),
120 }
121 }
122}
123
124impl Feature for EqualizerFeature {}
125
126impl EqualizerFeature {
127 pub async fn get_eq_info(&self) -> Result<EqInfo, Hidpp20Error> {
129 let payload = self.endpoint.call(0, [0; 3]).await?.extend_payload();
130 Ok(EqInfo::from_payload(&payload))
131 }
132
133 pub async fn get_frequencies(&self, band_count: u8) -> Result<Vec<u16>, Hidpp20Error> {
139 let mut frequencies = Vec::with_capacity(usize::from(band_count));
140 let mut index = 0u8;
141 while index < band_count {
142 let payload = self.endpoint.call(1, [index, 0, 0]).await?.extend_payload();
143 if payload[0] != index {
145 return Err(Hidpp20Error::UnsupportedResponse);
146 }
147 let page = (band_count - index).min(FREQUENCIES_PER_PAGE);
148 frequencies.extend(parse_frequency_page(&payload, page)?);
149 index += page;
150 }
151 Ok(frequencies)
152 }
153
154 pub async fn get_frequency_gains(
159 &self,
160 location: GainLocation,
161 band_count: u8,
162 ) -> Result<Vec<i8>, Hidpp20Error> {
163 let payload = self
164 .endpoint
165 .call(2, [location.into(), 0, 0])
166 .await?
167 .extend_payload();
168 parse_gains(&payload, 0, band_count)
169 }
170
171 pub async fn set_frequency_gains(
176 &self,
177 persistence: GainPersistence,
178 gains: &[i8],
179 ) -> Result<Vec<i8>, Hidpp20Error> {
180 let count = u8::try_from(gains.len()).map_err(|_| Hidpp20Error::UnsupportedResponse)?;
181 let mut args = [0; 16];
182 args[0] = persistence.into();
183 for (i, &gain) in gains.iter().enumerate() {
186 let slot = 1 + i;
187 if slot >= args.len() {
188 return Err(Hidpp20Error::UnsupportedResponse);
189 }
190 args[slot] = gain as u8;
191 }
192 let payload = self.endpoint.call_long(3, args).await?.extend_payload();
193 parse_gains(&payload, 1, count)
196 }
197
198 pub async fn get_mic_noise_reduction(&self) -> Result<bool, Hidpp20Error> {
200 let payload = self.endpoint.call(4, [0; 3]).await?.extend_payload();
201 Ok(payload[0] != 0)
202 }
203
204 pub async fn set_mic_noise_reduction(&self, enabled: bool) -> Result<(), Hidpp20Error> {
206 self.endpoint.call(5, [u8::from(enabled), 0, 0]).await?;
207 Ok(())
208 }
209}
210
211fn parse_frequency_page(payload: &[u8; 16], count: u8) -> Result<Vec<u16>, Hidpp20Error> {
214 let count = usize::from(count);
215 if 1 + 2 * count > payload.len() {
216 return Err(Hidpp20Error::UnsupportedResponse);
217 }
218 Ok((0..count)
219 .map(|i| u16::from_be_bytes([payload[1 + 2 * i], payload[2 + 2 * i]]))
220 .collect())
221}
222
223fn parse_gains(payload: &[u8; 16], offset: usize, count: u8) -> Result<Vec<i8>, Hidpp20Error> {
225 let count = usize::from(count);
226 if offset + count > payload.len() {
227 return Err(Hidpp20Error::UnsupportedResponse);
228 }
229 Ok(payload[offset..offset + count]
230 .iter()
231 .map(|&byte| byte as i8)
232 .collect())
233}