#![allow(clippy::field_reassign_with_default)]
#![allow(clippy::upper_case_acronyms)]
use std::convert::TryInto;
use anyhow::anyhow;
use async_trait::async_trait;
use client::Client;
use futures::{Stream, StreamExt};
use minidsp_protocol::commands::Addr;
pub use minidsp_protocol::{
dialect::*, eeprom, Commands, DeviceInfo, FromMemory, MasterStatus, Source,
};
use tokio::time::Duration;
pub use transport::MiniDSPError;
use utils::ErrInto;
pub use crate::commands::Gain;
pub type Result<T, E = MiniDSPError> = core::result::Result<T, E>;
pub mod biquad;
pub use minidsp_protocol::{commands, device, device::Gate, packet};
pub mod tcp_server;
pub use minidsp_protocol::source;
pub mod transport;
pub mod utils;
pub use biquad::Biquad;
pub mod builder;
pub use builder::Builder;
pub mod client;
pub mod formats;
pub mod logging;
pub mod model;
#[derive(Clone)]
pub struct MiniDSP<'a> {
pub client: Client,
pub device: &'a device::Device,
device_info: DeviceInfo,
}
impl<'a> MiniDSP<'a> {
pub fn from_client(
client: Client,
device: &'a device::Device,
device_info: DeviceInfo,
) -> Self {
MiniDSP {
client,
device,
device_info,
}
}
}
impl MiniDSP<'_> {
pub async fn get_master_status(&self) -> Result<MasterStatus> {
let device_info = self.device_info;
let memory = self.client.read_memory(eeprom::PRESET, 9).await?;
Ok(
MasterStatus::from_memory(&device_info, &memory).map_err(|e| {
MiniDSPError::MalformedResponse(format!("Couldn't convert to MemoryView: {:?}", e))
})?,
)
}
pub async fn subscribe_master_status(
&self,
) -> Result<impl Stream<Item = MasterStatus> + 'static, MiniDSPError> {
let device_info = self.device_info;
let stream = self
.client
.subscribe()
.await?
.filter_map(move |item| async move {
if let commands::Responses::MemoryData(memory) = item.ok()? {
let status = MasterStatus::from_memory(&device_info, &memory).ok()?;
if !status.eq(&MasterStatus::default()) {
return Some(status);
}
}
None
});
Ok(Box::pin(stream))
}
pub async fn get_input_output_levels(&self) -> Result<(Vec<f32>, Vec<f32>)> {
let inputs: Vec<_> = self
.device
.inputs
.iter()
.filter_map(|idx| idx.meter)
.collect();
let outputs: Vec<_> = self
.device
.outputs
.iter()
.filter_map(|idx| idx.meter)
.collect();
let mut levels = self
.client
.read_floats_multi(inputs.iter().copied().chain(outputs.iter().copied()))
.await?;
let outputs = Vec::from(&levels[inputs.len()..levels.len()]);
levels.truncate(self.device.inputs.len());
Ok((levels, outputs))
}
pub async fn get_input_levels(&self) -> Result<Vec<f32>> {
self.client
.read_floats_multi(self.device.inputs.iter().filter_map(|idx| idx.meter))
.await
}
pub async fn get_output_levels(&self) -> Result<Vec<f32>> {
self.client
.read_floats_multi(self.device.outputs.iter().filter_map(|idx| idx.meter))
.await
}
pub async fn set_master_volume(&self, value: Gain) -> Result<()> {
self.client
.roundtrip(Commands::SetVolume { value })
.await?
.into_ack()
.err_into()
}
pub async fn set_master_mute(&self, value: bool) -> Result<()> {
self.client
.roundtrip(Commands::SetMute { value })
.await?
.into_ack()
.err_into()
}
pub async fn set_source(&self, source: Source) -> Result<()> {
let device_info = self.get_device_info().await?;
self.client
.roundtrip(Commands::SetSource {
source: source.to_id(&device_info),
})
.await?
.into_ack()
.err_into()
}
pub async fn set_config(&self, config: u8) -> Result<()> {
self.client
.roundtrip(Commands::SetConfig {
config,
reset: true,
})
.await?
.into_config_changed()
.err_into()
}
pub async fn set_dirac(&self, enabled: bool) -> Result<()> {
self.client
.roundtrip(Commands::DiracBypass {
value: if enabled { 0 } else { 1 },
})
.await?
.into_ack()
.err_into()
}
pub fn input(&self, index: usize) -> Result<Input> {
if index >= self.device.inputs.len() {
Err(MiniDSPError::OutOfRange)
} else {
Ok(Input {
dsp: self,
spec: &self.device.inputs[index],
})
}
}
pub fn output(&self, index: usize) -> Result<Output> {
if index >= self.device.outputs.len() {
Err(MiniDSPError::OutOfRange)
} else {
Ok(Output {
dsp: self,
spec: &self.device.outputs[index],
})
}
}
pub async fn get_device_info(&self) -> Result<DeviceInfo> {
Ok(self.device_info)
}
pub(crate) fn dialect(&self) -> &Dialect {
&self.device.dialect
}
pub(crate) async fn write_dsp_float(&self, addr: u16, value: f32) -> Result<(), MiniDSPError> {
let dialect = self.dialect();
let addr = dialect.addr(addr);
let value = dialect.float(value);
self.client.write_dsp(addr, value).await
}
pub(crate) async fn write_dsp_db(&self, addr: u16, value: f32) -> Result<(), MiniDSPError> {
let dialect = self.dialect();
let addr = dialect.addr(addr);
let value = dialect.db(value);
self.client.write_dsp(addr, value).await
}
pub(crate) async fn write_dsp_int(&self, addr: u16, value: u16) -> Result<(), MiniDSPError> {
let dialect = self.dialect();
let addr = dialect.addr(addr);
let value = dialect.int(value);
self.client.write_dsp(addr, value).await
}
}
#[async_trait]
pub trait Channel {
#[doc(hidden)]
fn _channel(&self) -> (&MiniDSP, Option<&device::Gate>, &'static [u16]);
async fn set_mute(&self, value: bool) -> Result<()> {
let (dsp, gate, _) = self._channel();
let gate = gate.ok_or(MiniDSPError::NoSuchPeripheral)?;
let dialect = dsp.dialect();
dsp.client
.roundtrip(Commands::Write {
addr: dialect.addr(gate.enable),
value: dialect.mute(value),
})
.await?
.into_ack()
.err_into()
}
async fn set_gain(&self, value: Gain) -> Result<()> {
let (dsp, gate, _) = self._channel();
let gate = gate.ok_or(MiniDSPError::NoSuchPeripheral)?;
let gain = gate.gain.ok_or(MiniDSPError::NoSuchPeripheral)?;
dsp.write_dsp_db(gain, value.0).await
}
fn peq(&self, index: usize) -> Result<BiquadFilter<'_>> {
let (dsp, _, peq) = self._channel();
if index >= peq.len() {
Err(MiniDSPError::OutOfRange)
} else {
Ok(BiquadFilter::new(dsp, dsp.dialect().addr(peq[index])))
}
}
fn peqs_all(&self) -> Vec<BiquadFilter<'_>> {
let (dsp, _, peq) = self._channel();
peq.iter()
.map(move |&x| BiquadFilter::new(dsp, dsp.dialect().addr(x)))
.collect()
}
}
pub struct Input<'a> {
dsp: &'a MiniDSP<'a>,
spec: &'a device::Input,
}
impl<'a> Input<'a> {
pub async fn set_output_enable(&self, output_index: usize, value: bool) -> Result<()> {
let dialect = self.dsp.dialect();
let addr = dialect.addr(self.spec.routing[output_index].enable);
let value = dialect.mute(!value);
self.dsp.client.write_dsp(addr, value).await
}
pub async fn set_output_gain(&self, output_index: usize, gain: Gain) -> Result<()> {
let addr = self.spec.routing[output_index]
.gain
.ok_or(MiniDSPError::NoSuchPeripheral)?;
self.dsp.write_dsp_db(addr, gain.0).await.err_into()
}
}
impl Channel for Input<'_> {
fn _channel(&self) -> (&MiniDSP, Option<&Gate>, &'static [u16]) {
(self.dsp, self.spec.gate.as_ref(), self.spec.peq)
}
}
pub struct Output<'a> {
dsp: &'a MiniDSP<'a>,
spec: &'a device::Output,
}
impl<'a> Output<'a> {
pub async fn set_invert(&self, value: bool) -> Result<()> {
let dialect = self.dsp.dialect();
self.dsp
.client
.write_dsp(dialect.addr(self.spec.invert_addr), dialect.invert(value))
.await
}
pub async fn set_delay(&self, value: Duration) -> Result<()> {
let value = (value.as_micros() as f64 * self.dsp.device.internal_sampling_rate as f64
/ 1_000_000_f64)
.round() as u64;
if value > 8000 {
return Err(MiniDSPError::InternalError(anyhow!(
"Delay should be within [0, 80] ms was {:?}",
value
)));
}
let dialect = self.dsp.dialect();
self.dsp
.client
.write_dsp(
dialect.addr(self.spec.delay_addr.ok_or(MiniDSPError::NoSuchPeripheral)?),
dialect.delay(value as _),
)
.await
}
pub fn crossover(&'_ self) -> Option<Crossover<'_>> {
Some(Crossover::new(self.dsp, self.spec.xover.as_ref()?))
}
pub fn compressor(&'_ self) -> Option<Compressor<'_>> {
Some(Compressor::new(self.dsp, self.spec.compressor.as_ref()?))
}
pub fn fir(&'_ self) -> Option<Fir<'_>> {
Some(Fir::new(self.dsp, self.spec.fir.as_ref()?))
}
}
impl Channel for Output<'_> {
fn _channel(&self) -> (&MiniDSP, Option<&Gate>, &'static [u16]) {
(self.dsp, Some(&self.spec.gate), self.spec.peq)
}
}
pub struct BiquadFilter<'a> {
dsp: &'a MiniDSP<'a>,
addr: Addr,
}
impl<'a> BiquadFilter<'a> {
pub(crate) fn new(dsp: &'a MiniDSP<'a>, addr: Addr) -> Self {
BiquadFilter { dsp, addr }
}
pub async fn clear(&self) -> Result<()> {
self.set_coefficients(Biquad::default().to_array().as_ref())
.await
}
pub async fn set_coefficients(&self, coefficients: &[f32]) -> Result<()> {
if coefficients.len() != 5 {
panic!("biquad coefficients are always 5 floating point values")
}
self.dsp
.client
.roundtrip(Commands::WriteBiquad {
addr: self.addr,
data: coefficients
.iter()
.map(|&coeff| self.dsp.dialect().float(coeff))
.collect::<Vec<_>>()
.try_into()
.unwrap(),
})
.await?
.into_ack()
.err_into()
}
pub async fn set_bypass(&self, bypass: bool) -> Result<()> {
self.dsp
.client
.roundtrip(Commands::WriteBiquadBypass {
addr: self.addr,
value: bypass,
})
.await?
.into_ack()
.err_into()
}
}
pub struct Crossover<'a> {
dsp: &'a MiniDSP<'a>,
spec: &'a device::Crossover,
}
impl<'a> Crossover<'a> {
pub fn new(dsp: &'a MiniDSP<'a>, spec: &'a device::Crossover) -> Self {
Crossover { dsp, spec }
}
pub async fn clear(&self, group: usize) -> Result<()> {
let start = self.spec.peqs[group];
for addr in (start..(start + 5 * 4)).step_by(5) {
BiquadFilter::new(self.dsp, self.dsp.dialect().addr(addr))
.clear()
.await?;
}
Ok(())
}
pub async fn set_coefficients(
&self,
group: usize,
index: usize,
coefficients: &[f32],
) -> Result<()> {
if group >= self.num_groups() || index >= self.num_filter_per_group() {
return Err(MiniDSPError::OutOfRange);
}
let addr = self.spec.peqs[group] + (index as u16) * 5;
let filter = BiquadFilter::new(self.dsp, self.dsp.dialect().addr(addr));
filter.set_coefficients(coefficients).await
}
pub async fn set_bypass(&self, group: usize, bypass: bool) -> Result<()> {
if group >= self.num_groups() {
return Err(MiniDSPError::OutOfRange);
}
let addr = self.spec.peqs[group];
self.dsp
.client
.roundtrip(Commands::WriteBiquadBypass {
addr: self.dsp.dialect().addr(addr),
value: bypass,
})
.await?
.into_ack()
.err_into()
}
pub fn num_groups(&self) -> usize {
self.spec.peqs.len()
}
pub fn num_filter_per_group(&self) -> usize {
4
}
}
pub struct Compressor<'a> {
dsp: &'a MiniDSP<'a>,
spec: &'a device::Compressor,
}
impl<'a> Compressor<'a> {
pub fn new(dsp: &'a MiniDSP<'a>, spec: &'a device::Compressor) -> Self {
Self { dsp, spec }
}
pub async fn set_bypass(&self, value: bool) -> Result<()> {
self.dsp
.write_dsp_int(
self.spec.bypass,
if value {
commands::WriteInt::BYPASSED
} else {
commands::WriteInt::ENABLED
},
)
.await
}
pub async fn set_threshold(&self, value: f32) -> Result<()> {
self.dsp.write_dsp_float(self.spec.threshold, value).await
}
pub async fn set_ratio(&self, value: f32) -> Result<()> {
self.dsp.write_dsp_float(self.spec.ratio, value).await
}
pub async fn set_attack(&self, value: f32) -> Result<()> {
self.dsp.write_dsp_float(self.spec.attack, value).await
}
pub async fn set_release(&self, value: f32) -> Result<()> {
self.dsp.write_dsp_float(self.spec.release, value).await
}
pub async fn get_level(&self) -> Result<f32> {
let meter = self.spec.meter.ok_or(MiniDSPError::NoSuchPeripheral)?;
let view = self
.dsp
.client
.roundtrip(Commands::ReadFloats {
addr: meter,
len: 1,
})
.await?
.into_float_view()?;
Ok(view.get(meter))
}
}
pub struct Fir<'a> {
dsp: &'a MiniDSP<'a>,
spec: &'a device::Fir,
}
impl<'a> Fir<'a> {
pub fn new(dsp: &'a MiniDSP<'a>, spec: &'a device::Fir) -> Self {
Self { dsp, spec }
}
pub async fn set_bypass(&self, bypass: bool) -> Result<()> {
self.dsp
.write_dsp_int(
self.spec.bypass,
if bypass {
commands::WriteInt::BYPASSED
} else {
commands::WriteInt::ENABLED
},
)
.await
}
pub async fn clear(&self) -> Result<()> {
self.set_coefficients([0.0].repeat(16).as_ref()).await
}
pub async fn set_coefficients(&self, coefficients: &[f32]) -> Result<()> {
let master_status = self.dsp.get_master_status().await?;
self.dsp
.write_dsp_int(self.spec.num_coefficients, coefficients.len() as u16)
.await?;
let max_coeff = self
.dsp
.client
.roundtrip(Commands::FirLoadStart {
index: self.spec.index,
})
.await?
.into_fir_size()?;
if coefficients.len() > max_coeff as usize {
return Err(MiniDSPError::TooManyCoefficients);
}
for block in coefficients.chunks(14) {
self.dsp
.client
.roundtrip(Commands::FirLoadData {
index: self.spec.index,
data: Vec::from(block),
})
.await?
.into_ack()?;
}
self.dsp
.client
.roundtrip(Commands::FirLoadEnd)
.await?
.into_ack()?;
self.dsp
.set_master_mute(master_status.mute.unwrap())
.await?;
Ok(())
}
}