+38
-28
@@ -7,18 +7,16 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::{
|
||||
common::errors::{DSPError, ValidationError},
|
||||
common::errors::DSPError,
|
||||
dsp408::{
|
||||
mapper::{
|
||||
self,
|
||||
constants::{CONFIG_CHUNK_INDEX_MAX, PRESET_INDEX_MAX},
|
||||
constants::{CONFIG_CHUNK_INDEX_MAX, PRESET_INDEX_MAX, delay_range},
|
||||
response::map_response,
|
||||
},
|
||||
protocol::{commands, config::decode_config, frame::extract_frame},
|
||||
types,
|
||||
types::{self},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -154,6 +152,13 @@ impl DSP408 {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn convert_delay_unit(value: f32, from: types::DelayUnit, to: types::DelayUnit) -> f32 {
|
||||
let (_, from_max) = delay_range(from);
|
||||
let (_, to_max) = delay_range(to);
|
||||
|
||||
(value / from_max) * to_max
|
||||
}
|
||||
|
||||
// ---------- Commands ----------
|
||||
// Command `0x10`
|
||||
pub fn connect(&mut self) -> Result<(), DSPError> {
|
||||
@@ -320,7 +325,20 @@ impl DSP408 {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
self.state_mut()?.current_config.delay_unit = unit;
|
||||
let state = self.state_mut()?;
|
||||
let old_unit = state.current_config.delay_unit;
|
||||
|
||||
if old_unit != unit {
|
||||
for input in state.current_config.input_states.values_mut() {
|
||||
input.delay = Self::convert_delay_unit(input.delay, old_unit, unit);
|
||||
}
|
||||
|
||||
for output in state.current_config.output_states.values_mut() {
|
||||
output.delay = Self::convert_delay_unit(output.delay, old_unit, unit);
|
||||
}
|
||||
}
|
||||
|
||||
state.current_config.delay_unit = unit;
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
@@ -810,8 +828,12 @@ impl DSP408 {
|
||||
|
||||
match channel {
|
||||
types::Channel::Input(input) => {
|
||||
state.current_config.input_state_mut(input).peq_chain.bands
|
||||
[band as usize] = peq;
|
||||
state
|
||||
.current_config
|
||||
.input_state_mut(input)
|
||||
.peq_chain
|
||||
.bands
|
||||
.insert(band, peq);
|
||||
}
|
||||
|
||||
types::Channel::Output(output) => {
|
||||
@@ -819,7 +841,8 @@ impl DSP408 {
|
||||
.current_config
|
||||
.output_state_mut(output)
|
||||
.peq_chain
|
||||
.bands[band as usize] = peq;
|
||||
.bands
|
||||
.insert(band, peq);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -947,7 +970,7 @@ impl DSP408 {
|
||||
}
|
||||
|
||||
// Command `0x38` (Set Delay)
|
||||
pub fn set_channel_delay(
|
||||
pub fn set_delay(
|
||||
&mut self,
|
||||
channel: types::Channel,
|
||||
delay: f32,
|
||||
@@ -1350,19 +1373,12 @@ impl DSP408 {
|
||||
|
||||
let state = self.state_mut()?;
|
||||
|
||||
let index = types::InputChannel::iter()
|
||||
.position(|x| x == input_channel)
|
||||
.ok_or_else(|| {
|
||||
DSPError::Validation(ValidationError::Value(
|
||||
"Invalid input channel".to_string(),
|
||||
))
|
||||
})?;
|
||||
|
||||
state
|
||||
.current_config
|
||||
.output_state_mut(output_channel)
|
||||
.matrix_routes
|
||||
.gains[index] = gain;
|
||||
.gains
|
||||
.insert(input_channel, gain);
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
@@ -1398,19 +1414,13 @@ impl DSP408 {
|
||||
|
||||
let state = self.state_mut()?;
|
||||
|
||||
let index = types::DiscreteFrequency::iter()
|
||||
.position(|x| x == frequency_band)
|
||||
.ok_or_else(|| {
|
||||
DSPError::Validation(ValidationError::Value(
|
||||
"Invalid GEQ frequency band".to_string(),
|
||||
))
|
||||
})?;
|
||||
|
||||
state
|
||||
.current_config
|
||||
.input_state_mut(input_channel)
|
||||
.geq
|
||||
.gains[index] = gain;
|
||||
.gains
|
||||
.insert(frequency_band, gain);
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
|
||||
@@ -85,10 +85,6 @@ pub fn map_response(frame: &Frame) -> Result<DeviceResponseType, ProtocolError>
|
||||
|
||||
// Command `0x24` (Get Config Chunk)
|
||||
if cmd == 0x24 {
|
||||
// Mirrors Python's `int.from_bytes(payload[:1], "little")`, which
|
||||
// is `0` for an empty payload rather than an error — Python slices
|
||||
// never panic. `payload.get(1..)` below mirrors the same
|
||||
// forgiving-slice behavior for the remainder.
|
||||
let index = payload.first().copied().unwrap_or(0);
|
||||
|
||||
if index > 0x1c {
|
||||
@@ -163,8 +159,23 @@ pub fn map_response(frame: &Frame) -> Result<DeviceResponseType, ProtocolError>
|
||||
|
||||
// Command `0x40` (Get Level Meters)
|
||||
if cmd == 0x40 {
|
||||
fn float_to_db(v: f32) -> f32 {
|
||||
40.0 * v.abs().max(1e-12).log10()
|
||||
const X0: f32 = -25.0; // breakpoint, in dB
|
||||
const Y0: f32 = 1.0; // raw value at the breakpoint
|
||||
const K1: f32 = 0.0104; // slope below breakpoint (raw < Y0)
|
||||
const K2: f32 = 0.0208; // slope above breakpoint (raw >= Y0)
|
||||
|
||||
const MIN_DB: f32 = -60.0;
|
||||
const MAX_DB: f32 = 12.0;
|
||||
|
||||
fn float_to_db(raw: f32) -> f32 {
|
||||
let db = if raw < Y0 {
|
||||
X0 + (raw - Y0) / K1
|
||||
} else {
|
||||
X0 + (raw - Y0) / K2
|
||||
};
|
||||
// Clamping naturally absorbs noise-floor outliers (e.g. raw ≈ 0.385
|
||||
// resolves to well below -60 dB here, so it correctly floors to -60).
|
||||
db.clamp(MIN_DB, MAX_DB)
|
||||
}
|
||||
|
||||
if payload.len() != 38 {
|
||||
|
||||
@@ -35,12 +35,14 @@ fn read_gate(r: &mut BinaryReader) -> Result<types::Gate, DSPError> {
|
||||
}
|
||||
|
||||
fn read_geq(r: &mut BinaryReader) -> Result<types::GraphicEQ, DSPError> {
|
||||
let gains = types::DiscreteFrequency::iter()
|
||||
.map(|_| {
|
||||
let raw = r.read_u16()?;
|
||||
Ok(mapper::raw_to_eq_gain(raw)?)
|
||||
})
|
||||
.collect::<Result<Vec<_>, DSPError>>()?;
|
||||
let mut gains = std::collections::HashMap::new();
|
||||
|
||||
for frequency in types::DiscreteFrequency::iter() {
|
||||
let raw = r.read_u16()?;
|
||||
let gain = mapper::raw_to_eq_gain(raw)?;
|
||||
|
||||
gains.insert(frequency, gain);
|
||||
}
|
||||
|
||||
Ok(types::GraphicEQ {
|
||||
gains,
|
||||
@@ -49,17 +51,20 @@ fn read_geq(r: &mut BinaryReader) -> Result<types::GraphicEQ, DSPError> {
|
||||
}
|
||||
|
||||
fn read_peq_chain(r: &mut BinaryReader, band_count: usize) -> Result<types::PEQChain, DSPError> {
|
||||
let bands = (0..band_count)
|
||||
.map(|_| {
|
||||
Ok(types::PEQ {
|
||||
let bands = types::PEQBand::iter()
|
||||
.take(band_count)
|
||||
.map(|band| {
|
||||
let peq = types::PEQ {
|
||||
gain: mapper::raw_to_eq_gain(r.read_u16()?)?,
|
||||
frequency: mapper::raw_to_frequency_hz(r.read_u16()?)?,
|
||||
q: mapper::raw_to_peq_q(r.read_u8()?)?,
|
||||
filter_type: mapper::raw_to_peq_filter(r.read_u8()?)?,
|
||||
bypass: false,
|
||||
})
|
||||
};
|
||||
|
||||
Ok((band, peq))
|
||||
})
|
||||
.collect::<Result<Vec<_>, DSPError>>()?;
|
||||
.collect::<Result<HashMap<_, _>, DSPError>>()?;
|
||||
|
||||
Ok(types::PEQChain {
|
||||
bands,
|
||||
@@ -67,6 +72,21 @@ fn read_peq_chain(r: &mut BinaryReader, band_count: usize) -> Result<types::PEQC
|
||||
})
|
||||
}
|
||||
|
||||
fn apply_peq_bypass(
|
||||
r: &mut BinaryReader,
|
||||
bands: &mut HashMap<types::PEQBand, types::PEQ>,
|
||||
) -> Result<(), DSPError> {
|
||||
let bitmap = r.read_u16()?;
|
||||
|
||||
for (index, band) in types::PEQBand::iter().enumerate() {
|
||||
if let Some(peq) = bands.get_mut(&band) {
|
||||
peq.bypass = bitmap & (1 << index) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_crossover(r: &mut BinaryReader) -> Result<types::CrossoverFilters, DSPError> {
|
||||
let hp_freq = r.read_u16()?;
|
||||
let lp_freq = r.read_u16()?;
|
||||
@@ -152,7 +172,13 @@ pub fn decode_config(payload: &[u8]) -> Result<types::DSPConfigState, DSPError>
|
||||
inp.linked_channels = mapper::raw_to_link_channels(
|
||||
mapper::channel_to_raw(types::Channel::Input(channel))?,
|
||||
r.read_u8()?,
|
||||
)?;
|
||||
)?
|
||||
.into_iter()
|
||||
.filter_map(|link| match link {
|
||||
types::Channel::Input(input) => Some(input),
|
||||
types::Channel::Output(_) => None,
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
// ============================
|
||||
@@ -162,17 +188,26 @@ pub fn decode_config(payload: &[u8]) -> Result<types::DSPConfigState, DSPError>
|
||||
for (channel, out) in types::OutputChannel::iter().zip(output_channels.iter_mut()) {
|
||||
out.name = mapper::raw_to_channel_name(r.pop(constants::CHANNEL_NAME_LENGTH)?)?;
|
||||
|
||||
r.skip(1)?;
|
||||
|
||||
out.matrix_routes = types::MatrixRoutes {
|
||||
connected: mapper::raw_to_matrix(r.read_u8()?)?,
|
||||
|
||||
gains: (0..4)
|
||||
.map(|_| {
|
||||
gains: {
|
||||
r.skip(1)?;
|
||||
|
||||
[
|
||||
types::InputChannel::InA,
|
||||
types::InputChannel::InB,
|
||||
types::InputChannel::InC,
|
||||
types::InputChannel::InD,
|
||||
]
|
||||
.into_iter()
|
||||
.map(|input| {
|
||||
let raw = r.read_u16()?;
|
||||
Ok::<f32, DSPError>(mapper::raw_to_matrix_gain(raw)?)
|
||||
let gain = mapper::raw_to_matrix_gain(raw)?;
|
||||
Ok::<(types::InputChannel, f32), DSPError>((input, gain))
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
.collect::<Result<HashMap<_, _>, _>>()?
|
||||
},
|
||||
};
|
||||
|
||||
out.crossover = read_crossover(&mut r)?;
|
||||
@@ -196,7 +231,13 @@ pub fn decode_config(payload: &[u8]) -> Result<types::DSPConfigState, DSPError>
|
||||
out.linked_channels = mapper::raw_to_link_channels(
|
||||
mapper::channel_to_raw(types::Channel::Output(channel))?,
|
||||
r.read_u8()?,
|
||||
)?;
|
||||
)?
|
||||
.into_iter()
|
||||
.filter_map(|link| match link {
|
||||
types::Channel::Output(output) => Some(output),
|
||||
types::Channel::Input(_) => None,
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
// ============================
|
||||
@@ -212,15 +253,11 @@ pub fn decode_config(payload: &[u8]) -> Result<types::DSPConfigState, DSPError>
|
||||
// ============================
|
||||
|
||||
for inp in &mut input_channels {
|
||||
apply_flag(&mut r, inp.peq_chain.bands.as_mut_slice(), |band, value| {
|
||||
band.bypass = value
|
||||
})?;
|
||||
apply_peq_bypass(&mut r, &mut inp.peq_chain.bands)?;
|
||||
}
|
||||
|
||||
for out in &mut output_channels {
|
||||
apply_flag(&mut r, out.peq_chain.bands.as_mut_slice(), |band, value| {
|
||||
band.bypass = value
|
||||
})?;
|
||||
apply_peq_bypass(&mut r, &mut out.peq_chain.bands)?;
|
||||
}
|
||||
|
||||
// ============================
|
||||
@@ -260,7 +297,7 @@ pub fn decode_config(payload: &[u8]) -> Result<types::DSPConfigState, DSPError>
|
||||
for (index, inp) in input_channels.iter_mut().enumerate() {
|
||||
let bypass = input_peq_bitmap & (1 << index) != 0;
|
||||
|
||||
for peq in inp.peq_chain.bands.iter_mut() {
|
||||
for peq in inp.peq_chain.bands.values_mut() {
|
||||
peq.bypass = bypass;
|
||||
}
|
||||
}
|
||||
@@ -270,7 +307,7 @@ pub fn decode_config(payload: &[u8]) -> Result<types::DSPConfigState, DSPError>
|
||||
for (index, out) in output_channels.iter_mut().enumerate() {
|
||||
let bypass = output_peq_bitmap & (1 << index) != 0;
|
||||
|
||||
for peq in out.peq_chain.bands.iter_mut() {
|
||||
for peq in out.peq_chain.bands.values_mut() {
|
||||
peq.bypass = bypass;
|
||||
}
|
||||
}
|
||||
|
||||
+6
-6
@@ -92,6 +92,7 @@ pub enum CrossoverFilter {
|
||||
Bl48,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter)]
|
||||
pub enum PEQBand {
|
||||
B1 = 1,
|
||||
@@ -291,7 +292,7 @@ pub struct Limiter {
|
||||
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct GraphicEQ {
|
||||
pub gains: Vec<f32>,
|
||||
pub gains: HashMap<DiscreteFrequency, f32>,
|
||||
pub bypass: bool,
|
||||
}
|
||||
|
||||
@@ -323,13 +324,13 @@ pub struct Crossover {
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct MatrixRoutes {
|
||||
pub connected: Vec<InputChannel>,
|
||||
pub gains: Vec<f32>,
|
||||
pub gains: HashMap<InputChannel, f32>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct PEQChain {
|
||||
pub bands: Vec<PEQ>,
|
||||
pub bands: HashMap<PEQBand, PEQ>,
|
||||
pub bypass: bool,
|
||||
}
|
||||
|
||||
@@ -360,7 +361,7 @@ pub struct InputChannelState {
|
||||
pub geq: GraphicEQ,
|
||||
pub peq_chain: PEQChain,
|
||||
pub crossover: CrossoverFilters,
|
||||
pub linked_channels: Vec<Channel>,
|
||||
pub linked_channels: Vec<InputChannel>,
|
||||
pub mute: bool,
|
||||
}
|
||||
|
||||
@@ -376,7 +377,7 @@ pub struct OutputChannelState {
|
||||
pub gain: f32,
|
||||
pub phase_inverted: bool,
|
||||
pub delay: f32,
|
||||
pub linked_channels: Vec<Channel>,
|
||||
pub linked_channels: Vec<OutputChannel>,
|
||||
pub mute: bool,
|
||||
}
|
||||
|
||||
@@ -409,6 +410,5 @@ pub struct DSPState {
|
||||
pub(crate) name: String,
|
||||
pub(crate) flags: DeviceFlags,
|
||||
pub(crate) presets: PresetBank,
|
||||
|
||||
pub(crate) current_config: DSPConfigState,
|
||||
}
|
||||
|
||||
+9
-2
@@ -3,6 +3,7 @@ use std::str::FromStr;
|
||||
|
||||
use dsp_thomann::dsp408::DSP408;
|
||||
use dsp_thomann::dsp408::types;
|
||||
use dsp_thomann::dsp408::types::InputChannel::InA;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let host = IpAddr::from_str("192.168.49.30")?;
|
||||
@@ -22,9 +23,15 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
}
|
||||
println!("DSP connected.");
|
||||
|
||||
dsp.set_mute(types::Channel::Input(types::InputChannel::InA), true)?;
|
||||
// dsp.set_mute(types::Channel::Input(types::InputChannel::InA), true)?;
|
||||
|
||||
dbg!(dsp.state()?);
|
||||
let r = dsp.set_peq_band(types::Channel::Input(InA), types::PEQBand::B1, 3.0, 1400.0, 2.1, types::PEQFilter::LowShelf, false)?;
|
||||
dbg!(r);
|
||||
|
||||
// dbg!(dsp.state()?);
|
||||
|
||||
// let s = &dsp.state()?.current_config.input_states[&InputChannel::InA].peq_chain;
|
||||
// dbg!(s)
|
||||
|
||||
println!("Disconnecting...");
|
||||
dsp.disconnect()?;
|
||||
|
||||
Reference in New Issue
Block a user