feat: decode meters clip indicators

This commit is contained in:
2026-07-31 13:54:48 +02:00
parent f6b6beda5e
commit 3d94df0b7a
6 changed files with 158 additions and 20 deletions
Generated
+63
View File
@@ -13,6 +13,8 @@ name = "dsp_thomann"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"dotenvy", "dotenvy",
"serde",
"serde_json",
"strum", "strum",
"strum_macros", "strum_macros",
"thiserror", "thiserror",
@@ -24,6 +26,18 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "memchr"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.106" version = "1.0.106"
@@ -42,6 +56,49 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "serde"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.2",
]
[[package]]
name = "serde_json"
version = "1.0.151"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
dependencies = [
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]] [[package]]
name = "strum" name = "strum"
version = "0.28.0" version = "0.28.0"
@@ -107,3 +164,9 @@ name = "unicode-ident"
version = "1.0.24" version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "zmij"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
+5 -1
View File
@@ -10,10 +10,14 @@ dotenvy = "0.15"
thiserror = "2.0.19" thiserror = "2.0.19"
strum = "0.28.0" strum = "0.28.0"
strum_macros = "0.28.0" strum_macros = "0.28.0"
serde = { version = "1", features = ["derive"], optional = true }
serde_json = { version = "1", optional = true }
[lib] [lib]
name = "dsp_thomann" name = "dsp_thomann"
path = "src/lib.rs" path = "src/lib.rs"
[features] [features]
hardware-tests = [] hardware-tests = []
default = []
serialization = ["dep:serde", "dep:serde_json"]
+1
View File
@@ -14,6 +14,7 @@
- Full test of client (Improve existing one) - Full test of client (Improve existing one)
- Write correct readme.md - Write correct readme.md
- Make client dsp408 thread safe - Make client dsp408 thread safe
- Absolutely, send command to avoid tcp keep alive, that make the dsp crash
Rust library for remote LAN control of Thomann DSP408 digital signal processors. Rust library for remote LAN control of Thomann DSP408 digital signal processors.
+18 -6
View File
@@ -277,7 +277,7 @@ impl DSP408 {
} }
// Command `0x13` (Device Name) // Command `0x13` (Device Name)
pub fn get_device_name(&mut self) -> Result<String, DSPError> { fn get_device_name(&mut self) -> Result<String, DSPError> {
let request = commands::build_request_device_name(); let request = commands::build_request_device_name();
let resp = self.send_recv(&request)?; let resp = self.send_recv(&request)?;
@@ -292,7 +292,7 @@ impl DSP408 {
} }
// Command `0x14` (Current Preset Index) // Command `0x14` (Current Preset Index)
pub fn get_current_preset(&mut self) -> Result<u8, DSPError> { fn get_current_preset(&mut self) -> Result<u8, DSPError> {
let request = commands::build_get_current_preset(); let request = commands::build_get_current_preset();
let resp = self.send_recv(&request)?; let resp = self.send_recv(&request)?;
@@ -400,7 +400,7 @@ impl DSP408 {
} }
// Command `0x22` (Get Preset Modification Status) // Command `0x22` (Get Preset Modification Status)
pub fn get_preset_modification_status(&mut self) -> Result<types::ModifiedPreset, DSPError> { fn get_preset_modification_status(&mut self) -> Result<types::ModifiedPreset, DSPError> {
let request = commands::build_preset_modification_status(); let request = commands::build_preset_modification_status();
let resp = self.send_recv(&request)?; let resp = self.send_recv(&request)?;
@@ -447,8 +447,20 @@ impl DSP408 {
return Ok(false); return Ok(false);
} }
let state = self.state_mut()?; let current_index = {
let state = self.state()?;
state.presets.current_index
};
if !self.store_current_configuration(current_index, true)? {
return Ok(false);
}
if !self.request_acknowledgement()? {
return Ok(false);
}
let state = self.state_mut()?;
state.presets.names[state.presets.current_index] = name.to_string(); state.presets.names[state.presets.current_index] = name.to_string();
Ok(true) Ok(true)
@@ -478,7 +490,7 @@ impl DSP408 {
} }
// Command `0x29` (Preset Name) // Command `0x29` (Preset Name)
pub fn get_preset_name(&mut self, preset_index: usize) -> Result<String, DSPError> { fn get_preset_name(&mut self, preset_index: usize) -> Result<String, DSPError> {
let raw_index = mapper::preset_index_without_factory_to_raw(preset_index)?; let raw_index = mapper::preset_index_without_factory_to_raw(preset_index)?;
let request = commands::build_get_preset_name(raw_index)?; let request = commands::build_get_preset_name(raw_index)?;
@@ -516,7 +528,7 @@ impl DSP408 {
} }
// Command `0x2C` (Device Flags) // Command `0x2C` (Device Flags)
pub fn get_device_flags(&mut self) -> Result<types::DeviceFlags, DSPError> { fn get_device_flags(&mut self) -> Result<types::DeviceFlags, DSPError> {
let request = commands::build_get_device_flags(); let request = commands::build_get_device_flags();
let resp = self.send_recv(&request)?; let resp = self.send_recv(&request)?;
+33 -11
View File
@@ -7,8 +7,8 @@ use crate::{
}, },
types::{ types::{
Acknowledgement, AuthenticationResult, ConfigChunk, CurrentPreset, DeviceFlags, Acknowledgement, AuthenticationResult, ConfigChunk, CurrentPreset, DeviceFlags,
DeviceInfo, DeviceResponseType, HandshakeAck, Meters, ModifiedPreset, NotImplemented, DeviceInfo, DeviceResponseType, HandshakeAck, Meter, Meters, ModifiedPreset,
PresetName, UnknownResponse, NotImplemented, PresetName, UnknownResponse,
}, },
}, },
}; };
@@ -170,13 +170,10 @@ pub fn map_response(frame: &Frame) -> Result<DeviceResponseType, ProtocolError>
return Err(ProtocolError::new("Not enough bytes for meter levels")); return Err(ProtocolError::new("Not enough bytes for meter levels"));
} }
let floats: Vec<f32> = (0..36) // Bytes 0..36 = 12 × 3-byte meter levels
let levels: Vec<f32> = (0..36)
.step_by(3) .step_by(3)
.map(|i| decode_float16(payload[i], payload[i + 1]).unwrap_or_default()) .map(|i| decode_float16(payload[i], payload[i + 1]).unwrap_or_default())
.collect();
let meters: Vec<f32> = floats
.into_iter()
.map(|f| { .map(|f| {
if f < METERS_SPLIT { if f < METERS_SPLIT {
METERS_SPLIT_LEVEL_DB + (f - METERS_SPLIT) / METERS_LOW_STEP METERS_SPLIT_LEVEL_DB + (f - METERS_SPLIT) / METERS_LOW_STEP
@@ -186,7 +183,30 @@ pub fn map_response(frame: &Frame) -> Result<DeviceResponseType, ProtocolError>
}) })
.collect(); .collect();
return Ok(DeviceResponseType::Meters(Meters { levels: meters })); // Byte 36 = input clip bitmask
let input_clip_mask = payload[36];
// Byte 37 = output clip bitmask
let output_clip_mask = payload[37];
let channels = levels
.into_iter()
.enumerate()
.map(|(index, level)| {
let clip = if index < 4 {
// InA..InD
input_clip_mask & (1 << index) != 0
} else {
// Out1..Out8
let output_index = index - 4;
output_clip_mask & (1 << output_index) != 0
};
Meter { level, clip }
})
.collect();
return Ok(DeviceResponseType::Meters(Meters { channels }));
} }
Ok(DeviceResponseType::UnknownResponse(UnknownResponse { Ok(DeviceResponseType::UnknownResponse(UnknownResponse {
@@ -197,6 +217,8 @@ pub fn map_response(frame: &Frame) -> Result<DeviceResponseType, ProtocolError>
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::sync::mpsc::channel;
use super::*; use super::*;
fn frame(command: u8, payload: Vec<u8>) -> Frame { fn frame(command: u8, payload: Vec<u8>) -> Frame {
@@ -415,10 +437,10 @@ mod tests {
match result { match result {
DeviceResponseType::Meters(m) => { DeviceResponseType::Meters(m) => {
assert_eq!(m.levels.len(), 12); assert_eq!(m.channels.len(), 12);
for level in m.levels { for channel in m.channels {
assert!(level.is_finite()); assert!(channel.level.is_finite());
} }
} }
other => panic!("unexpected {:?}", other), other => panic!("unexpected {:?}", other),
+38 -2
View File
@@ -1,8 +1,12 @@
use std::{collections::HashMap, net::IpAddr}; use std::{collections::HashMap, net::IpAddr};
use strum_macros::{EnumCount, EnumIter}; use strum_macros::{EnumCount, EnumIter};
#[cfg(feature = "serialization")]
use serde::{Deserialize, Serialize};
// ------ Enums ------ // ------ Enums ------
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DelayUnit { pub enum DelayUnit {
Millisecond, Millisecond,
@@ -10,6 +14,7 @@ pub enum DelayUnit {
Feet, Feet,
} }
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter, EnumCount, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter, EnumCount, PartialOrd, Ord)]
pub enum InputChannel { pub enum InputChannel {
InA, InA,
@@ -18,6 +23,7 @@ pub enum InputChannel {
InD, InD,
} }
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter, EnumCount, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter, EnumCount, PartialOrd, Ord)]
pub enum OutputChannel { pub enum OutputChannel {
Out1, Out1,
@@ -30,12 +36,14 @@ pub enum OutputChannel {
Out8, Out8,
} }
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Channel { pub enum Channel {
Input(InputChannel), Input(InputChannel),
Output(OutputChannel), Output(OutputChannel),
} }
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Ratio { pub enum Ratio {
#[default] #[default]
@@ -57,6 +65,7 @@ pub enum Ratio {
Limit, Limit,
} }
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum CrossoverFilter { pub enum CrossoverFilter {
#[default] #[default]
@@ -96,6 +105,7 @@ pub enum PEQBand {
B9, B9,
} }
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PEQFilter { pub enum PEQFilter {
Peak, Peak,
@@ -109,6 +119,7 @@ pub enum PEQFilter {
AllPass2, AllPass2,
} }
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter)]
pub enum DiscreteFrequency { pub enum DiscreteFrequency {
F20, F20,
@@ -144,6 +155,7 @@ pub enum DiscreteFrequency {
F20000, F20000,
} }
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum InputType { pub enum InputType {
Analog, Analog,
@@ -152,6 +164,7 @@ pub enum InputType {
SineWave, SineWave,
} }
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub struct InputSource { pub struct InputSource {
pub r#type: InputType, pub r#type: InputType,
@@ -194,6 +207,7 @@ pub struct PresetName {
pub name: String, pub name: String,
} }
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DeviceFlags { pub struct DeviceFlags {
pub is_locked: bool, pub is_locked: bool,
@@ -204,9 +218,17 @@ pub struct AuthenticationResult {
pub success: bool, pub success: bool,
} }
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Meter {
pub level: f32,
pub clip: bool,
}
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct Meters { pub struct Meters {
pub levels: Vec<f32>, pub channels: Vec<Meter>,
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
@@ -238,7 +260,7 @@ pub enum DeviceResponseType {
} }
// ------ State ------ // ------ State ------
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Default)] #[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct Gate { pub struct Gate {
pub attack: u16, pub attack: u16,
@@ -247,6 +269,7 @@ pub struct Gate {
pub threshold: f32, pub threshold: f32,
} }
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Default)] #[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct Compressor { pub struct Compressor {
pub threshold: f32, pub threshold: f32,
@@ -256,6 +279,7 @@ pub struct Compressor {
pub knee: u16, pub knee: u16,
} }
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Default)] #[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct Limiter { pub struct Limiter {
pub threshold: f32, pub threshold: f32,
@@ -263,12 +287,14 @@ pub struct Limiter {
pub release: u16, pub release: u16,
} }
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Default)] #[derive(Debug, Clone, PartialEq, Default)]
pub struct GraphicEQ { pub struct GraphicEQ {
pub gains: Vec<f32>, pub gains: Vec<f32>,
pub bypass: bool, pub bypass: bool,
} }
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub struct PEQ { pub struct PEQ {
pub gain: f32, pub gain: f32,
@@ -278,30 +304,35 @@ pub struct PEQ {
pub bypass: bool, pub bypass: bool,
} }
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Default)] #[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct CrossoverFilters { pub struct CrossoverFilters {
pub high_pass: Crossover, pub high_pass: Crossover,
pub low_pass: Crossover, pub low_pass: Crossover,
} }
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Default)] #[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct Crossover { pub struct Crossover {
pub frequency: f32, pub frequency: f32,
pub slope: CrossoverFilter, pub slope: CrossoverFilter,
} }
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Default)] #[derive(Debug, Clone, PartialEq, Default)]
pub struct MatrixRoutes { pub struct MatrixRoutes {
pub connected: Vec<InputChannel>, pub connected: Vec<InputChannel>,
pub gains: Vec<f32>, pub gains: Vec<f32>,
} }
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Default)] #[derive(Debug, Clone, PartialEq, Default)]
pub struct PEQChain { pub struct PEQChain {
pub bands: Vec<PEQ>, pub bands: Vec<PEQ>,
pub bypass: bool, pub bypass: bool,
} }
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct PresetBank { pub struct PresetBank {
pub current_index: usize, pub current_index: usize,
@@ -309,6 +340,7 @@ pub struct PresetBank {
pub modified: Vec<bool>, pub modified: Vec<bool>,
} }
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct DeviceConnection { pub struct DeviceConnection {
pub host: IpAddr, pub host: IpAddr,
@@ -316,6 +348,7 @@ pub struct DeviceConnection {
pub device_id: u8, pub device_id: u8,
} }
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Default)] #[derive(Debug, Clone, PartialEq, Default)]
pub struct InputChannelState { pub struct InputChannelState {
pub name: String, pub name: String,
@@ -330,6 +363,7 @@ pub struct InputChannelState {
pub mute: bool, pub mute: bool,
} }
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Default, PartialEq)] #[derive(Debug, Clone, Default, PartialEq)]
pub struct OutputChannelState { pub struct OutputChannelState {
pub name: String, pub name: String,
@@ -345,6 +379,7 @@ pub struct OutputChannelState {
pub mute: bool, pub mute: bool,
} }
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct DSPConfigState { pub struct DSPConfigState {
pub(crate) input_states: HashMap<InputChannel, InputChannelState>, pub(crate) input_states: HashMap<InputChannel, InputChannelState>,
@@ -367,6 +402,7 @@ impl DSPConfigState {
} }
} }
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct DSPState { pub struct DSPState {
pub(crate) name: String, pub(crate) name: String,