diff --git a/Cargo.lock b/Cargo.lock index 2d48b33..49cc4a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,6 +13,8 @@ name = "dsp_thomann" version = "0.1.0" dependencies = [ "dotenvy", + "serde", + "serde_json", "strum", "strum_macros", "thiserror", @@ -24,6 +26,18 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "proc-macro2" version = "1.0.106" @@ -42,6 +56,49 @@ dependencies = [ "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]] name = "strum" version = "0.28.0" @@ -107,3 +164,9 @@ name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/Cargo.toml b/Cargo.toml index f09719d..a06f970 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,10 +10,14 @@ dotenvy = "0.15" thiserror = "2.0.19" strum = "0.28.0" strum_macros = "0.28.0" +serde = { version = "1", features = ["derive"], optional = true } +serde_json = { version = "1", optional = true } [lib] name = "dsp_thomann" path = "src/lib.rs" [features] -hardware-tests = [] \ No newline at end of file +hardware-tests = [] +default = [] +serialization = ["dep:serde", "dep:serde_json"] diff --git a/readme.md b/readme.md index 2c41f5d..4aad18c 100644 --- a/readme.md +++ b/readme.md @@ -14,6 +14,7 @@ - Full test of client (Improve existing one) - Write correct readme.md - 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. diff --git a/src/dsp408/client/dsp408.rs b/src/dsp408/client/dsp408.rs index 66841f7..7297962 100644 --- a/src/dsp408/client/dsp408.rs +++ b/src/dsp408/client/dsp408.rs @@ -277,7 +277,7 @@ impl DSP408 { } // Command `0x13` (Device Name) - pub fn get_device_name(&mut self) -> Result { + fn get_device_name(&mut self) -> Result { let request = commands::build_request_device_name(); let resp = self.send_recv(&request)?; @@ -292,7 +292,7 @@ impl DSP408 { } // Command `0x14` (Current Preset Index) - pub fn get_current_preset(&mut self) -> Result { + fn get_current_preset(&mut self) -> Result { let request = commands::build_get_current_preset(); let resp = self.send_recv(&request)?; @@ -400,7 +400,7 @@ impl DSP408 { } // Command `0x22` (Get Preset Modification Status) - pub fn get_preset_modification_status(&mut self) -> Result { + fn get_preset_modification_status(&mut self) -> Result { let request = commands::build_preset_modification_status(); let resp = self.send_recv(&request)?; @@ -447,8 +447,20 @@ impl DSP408 { 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(); Ok(true) @@ -478,7 +490,7 @@ impl DSP408 { } // Command `0x29` (Preset Name) - pub fn get_preset_name(&mut self, preset_index: usize) -> Result { + fn get_preset_name(&mut self, preset_index: usize) -> Result { let raw_index = mapper::preset_index_without_factory_to_raw(preset_index)?; let request = commands::build_get_preset_name(raw_index)?; @@ -516,7 +528,7 @@ impl DSP408 { } // Command `0x2C` (Device Flags) - pub fn get_device_flags(&mut self) -> Result { + fn get_device_flags(&mut self) -> Result { let request = commands::build_get_device_flags(); let resp = self.send_recv(&request)?; diff --git a/src/dsp408/mapper/response.rs b/src/dsp408/mapper/response.rs index c3dae5f..68a719f 100644 --- a/src/dsp408/mapper/response.rs +++ b/src/dsp408/mapper/response.rs @@ -7,8 +7,8 @@ use crate::{ }, types::{ Acknowledgement, AuthenticationResult, ConfigChunk, CurrentPreset, DeviceFlags, - DeviceInfo, DeviceResponseType, HandshakeAck, Meters, ModifiedPreset, NotImplemented, - PresetName, UnknownResponse, + DeviceInfo, DeviceResponseType, HandshakeAck, Meter, Meters, ModifiedPreset, + NotImplemented, PresetName, UnknownResponse, }, }, }; @@ -170,13 +170,10 @@ pub fn map_response(frame: &Frame) -> Result return Err(ProtocolError::new("Not enough bytes for meter levels")); } - let floats: Vec = (0..36) + // Bytes 0..36 = 12 × 3-byte meter levels + let levels: Vec = (0..36) .step_by(3) .map(|i| decode_float16(payload[i], payload[i + 1]).unwrap_or_default()) - .collect(); - - let meters: Vec = floats - .into_iter() .map(|f| { if f < METERS_SPLIT { METERS_SPLIT_LEVEL_DB + (f - METERS_SPLIT) / METERS_LOW_STEP @@ -186,7 +183,30 @@ pub fn map_response(frame: &Frame) -> Result }) .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 { @@ -197,6 +217,8 @@ pub fn map_response(frame: &Frame) -> Result #[cfg(test)] mod tests { + use std::sync::mpsc::channel; + use super::*; fn frame(command: u8, payload: Vec) -> Frame { @@ -415,10 +437,10 @@ mod tests { match result { DeviceResponseType::Meters(m) => { - assert_eq!(m.levels.len(), 12); + assert_eq!(m.channels.len(), 12); - for level in m.levels { - assert!(level.is_finite()); + for channel in m.channels { + assert!(channel.level.is_finite()); } } other => panic!("unexpected {:?}", other), diff --git a/src/dsp408/types.rs b/src/dsp408/types.rs index 773dcbe..7559bbe 100644 --- a/src/dsp408/types.rs +++ b/src/dsp408/types.rs @@ -1,8 +1,12 @@ use std::{collections::HashMap, net::IpAddr}; use strum_macros::{EnumCount, EnumIter}; +#[cfg(feature = "serialization")] +use serde::{Deserialize, Serialize}; + // ------ Enums ------ +#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum DelayUnit { Millisecond, @@ -10,6 +14,7 @@ pub enum DelayUnit { Feet, } +#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter, EnumCount, PartialOrd, Ord)] pub enum InputChannel { InA, @@ -18,6 +23,7 @@ pub enum InputChannel { InD, } +#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter, EnumCount, PartialOrd, Ord)] pub enum OutputChannel { Out1, @@ -30,12 +36,14 @@ pub enum OutputChannel { Out8, } +#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum Channel { Input(InputChannel), Output(OutputChannel), } +#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub enum Ratio { #[default] @@ -57,6 +65,7 @@ pub enum Ratio { Limit, } +#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub enum CrossoverFilter { #[default] @@ -96,6 +105,7 @@ pub enum PEQBand { B9, } +#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum PEQFilter { Peak, @@ -109,6 +119,7 @@ pub enum PEQFilter { AllPass2, } +#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter)] pub enum DiscreteFrequency { F20, @@ -144,6 +155,7 @@ pub enum DiscreteFrequency { F20000, } +#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum InputType { Analog, @@ -152,6 +164,7 @@ pub enum InputType { SineWave, } +#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] #[derive(Debug, Clone, Copy, PartialEq)] pub struct InputSource { pub r#type: InputType, @@ -194,6 +207,7 @@ pub struct PresetName { pub name: String, } +#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct DeviceFlags { pub is_locked: bool, @@ -204,9 +218,17 @@ pub struct AuthenticationResult { 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)] pub struct Meters { - pub levels: Vec, + pub channels: Vec, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -238,7 +260,7 @@ pub enum DeviceResponseType { } // ------ State ------ - +#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] #[derive(Debug, Clone, Copy, PartialEq, Default)] pub struct Gate { pub attack: u16, @@ -247,6 +269,7 @@ pub struct Gate { pub threshold: f32, } +#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] #[derive(Debug, Clone, Copy, PartialEq, Default)] pub struct Compressor { pub threshold: f32, @@ -256,6 +279,7 @@ pub struct Compressor { pub knee: u16, } +#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] #[derive(Debug, Clone, Copy, PartialEq, Default)] pub struct Limiter { pub threshold: f32, @@ -263,12 +287,14 @@ pub struct Limiter { pub release: u16, } +#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq, Default)] pub struct GraphicEQ { pub gains: Vec, pub bypass: bool, } +#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] #[derive(Debug, Clone, Copy, PartialEq)] pub struct PEQ { pub gain: f32, @@ -278,30 +304,35 @@ pub struct PEQ { pub bypass: bool, } +#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] #[derive(Debug, Clone, Copy, PartialEq, Default)] pub struct CrossoverFilters { pub high_pass: Crossover, pub low_pass: Crossover, } +#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] #[derive(Debug, Clone, Copy, PartialEq, Default)] pub struct Crossover { pub frequency: f32, pub slope: CrossoverFilter, } +#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq, Default)] pub struct MatrixRoutes { pub connected: Vec, pub gains: Vec, } +#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq, Default)] pub struct PEQChain { pub bands: Vec, pub bypass: bool, } +#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq)] pub struct PresetBank { pub current_index: usize, @@ -309,6 +340,7 @@ pub struct PresetBank { pub modified: Vec, } +#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct DeviceConnection { pub host: IpAddr, @@ -316,6 +348,7 @@ pub struct DeviceConnection { pub device_id: u8, } +#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq, Default)] pub struct InputChannelState { pub name: String, @@ -330,6 +363,7 @@ pub struct InputChannelState { pub mute: bool, } +#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] #[derive(Debug, Clone, Default, PartialEq)] pub struct OutputChannelState { pub name: String, @@ -345,6 +379,7 @@ pub struct OutputChannelState { pub mute: bool, } +#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq)] pub struct DSPConfigState { pub(crate) input_states: HashMap, @@ -367,6 +402,7 @@ impl DSPConfigState { } } +#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq)] pub struct DSPState { pub(crate) name: String,