#[cfg(test)]
mod tests;
use std::sync::Arc;
use num_enum::{IntoPrimitive, TryFromPrimitive};
use crate::{
channel::HidppChannel,
feature::{CreatableFeature, Feature, FeatureEndpoint},
protocol::v20::Hidpp20Error,
};
const FREQUENCIES_PER_PAGE: u8 = 7;
bitflags::bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct EqCapabilities: u8 {
const STORED_AS_GAINS = 1 << 0;
const STORED_AS_COEFFICIENTS = 1 << 1;
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
#[repr(u8)]
pub enum GainLocation {
Eeprom = 0,
Ram = 1,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
#[repr(u8)]
pub enum GainPersistence {
Volatile = 0,
VolatileAndNonVolatile = 1,
NonVolatileOnly = 2,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct EqInfo {
pub band_count: u8,
pub db_range: u8,
pub capabilities: EqCapabilities,
pub db_min: i8,
pub db_max: i8,
}
impl EqInfo {
fn from_payload(payload: &[u8; 16]) -> Self {
Self {
band_count: payload[0],
db_range: payload[1],
capabilities: EqCapabilities::from_bits_retain(payload[2]),
db_min: payload[3] as i8,
db_max: payload[4] as i8,
}
}
#[must_use]
pub fn effective_range(&self) -> (i8, i8) {
if self.db_min == 0 && self.db_max == 0 {
let range = i8::try_from(self.db_range).unwrap_or(i8::MAX);
(-range, range)
} else {
(self.db_min, self.db_max)
}
}
}
#[derive(Clone)]
pub struct EqualizerFeature {
endpoint: FeatureEndpoint,
}
impl CreatableFeature for EqualizerFeature {
const ID: u16 = 0x8310;
const STARTING_VERSION: u8 = 2;
fn new(chan: Arc<HidppChannel>, device_index: u8, feature_index: u8) -> Self {
Self {
endpoint: FeatureEndpoint::new(chan, device_index, feature_index),
}
}
}
impl Feature for EqualizerFeature {}
impl EqualizerFeature {
pub async fn get_eq_info(&self) -> Result<EqInfo, Hidpp20Error> {
let payload = self.endpoint.call(0, [0; 3]).await?.extend_payload();
Ok(EqInfo::from_payload(&payload))
}
pub async fn get_frequencies(&self, band_count: u8) -> Result<Vec<u16>, Hidpp20Error> {
let mut frequencies = Vec::with_capacity(usize::from(band_count));
let mut index = 0u8;
while index < band_count {
let payload = self.endpoint.call(1, [index, 0, 0]).await?.extend_payload();
if payload[0] != index {
return Err(Hidpp20Error::UnsupportedResponse);
}
let page = (band_count - index).min(FREQUENCIES_PER_PAGE);
frequencies.extend(parse_frequency_page(&payload, page)?);
index += page;
}
Ok(frequencies)
}
pub async fn get_frequency_gains(
&self,
location: GainLocation,
band_count: u8,
) -> Result<Vec<i8>, Hidpp20Error> {
let payload = self
.endpoint
.call(2, [location.into(), 0, 0])
.await?
.extend_payload();
parse_gains(&payload, 0, band_count)
}
pub async fn set_frequency_gains(
&self,
persistence: GainPersistence,
gains: &[i8],
) -> Result<Vec<i8>, Hidpp20Error> {
let count = u8::try_from(gains.len()).map_err(|_| Hidpp20Error::UnsupportedResponse)?;
let mut args = [0; 16];
args[0] = persistence.into();
for (i, &gain) in gains.iter().enumerate() {
let slot = 1 + i;
if slot >= args.len() {
return Err(Hidpp20Error::UnsupportedResponse);
}
args[slot] = gain as u8;
}
let payload = self.endpoint.call_long(3, args).await?.extend_payload();
parse_gains(&payload, 1, count)
}
pub async fn get_mic_noise_reduction(&self) -> Result<bool, Hidpp20Error> {
let payload = self.endpoint.call(4, [0; 3]).await?.extend_payload();
Ok(payload[0] != 0)
}
pub async fn set_mic_noise_reduction(&self, enabled: bool) -> Result<(), Hidpp20Error> {
self.endpoint.call(5, [u8::from(enabled), 0, 0]).await?;
Ok(())
}
}
fn parse_frequency_page(payload: &[u8; 16], count: u8) -> Result<Vec<u16>, Hidpp20Error> {
let count = usize::from(count);
if 1 + 2 * count > payload.len() {
return Err(Hidpp20Error::UnsupportedResponse);
}
Ok((0..count)
.map(|i| u16::from_be_bytes([payload[1 + 2 * i], payload[2 + 2 * i]]))
.collect())
}
fn parse_gains(payload: &[u8; 16], offset: usize, count: u8) -> Result<Vec<i8>, Hidpp20Error> {
let count = usize::from(count);
if offset + count > payload.len() {
return Err(Hidpp20Error::UnsupportedResponse);
}
Ok(payload[offset..offset + count]
.iter()
.map(|&byte| byte as i8)
.collect())
}