Compare commits

...
6 Commits
Author SHA1 Message Date
LucasDLTG af2e7dc602 feat: adapt to real usage
CI / Rust CI (push) Failing after 1m3s
2026-09-06 12:29:45 +02:00
LucasDLTG e2fee36cdb feat: rms + peak meters 2026-08-01 12:45:58 +02:00
LucasDLTG 27821c1866 fix: remove warnings 2026-07-31 14:03:52 +02:00
LucasDLTG 3d94df0b7a feat: decode meters clip indicators 2026-07-31 13:54:48 +02:00
LucasDLTG f6b6beda5e fix: prevent race condition on socket + remove panicking code 2026-07-21 23:38:35 +02:00
LucasDLTG a9a49bef89 fix: thread lock in tcp send/receive 2026-07-21 14:47:47 +02:00
16 changed files with 2846 additions and 2591 deletions
Generated
+63
View File
@@ -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"
+4
View File
@@ -10,6 +10,8 @@ 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"
@@ -17,3 +19,5 @@ path = "src/lib.rs"
[features]
hardware-tests = []
default = []
serialization = ["dep:serde", "dep:serde_json"]
+1 -1
View File
@@ -1868,7 +1868,7 @@ Sine Wave:
|---|---|---|---|
| `Byte 1-36` | 12 meter channels | 3 bytes × 12 | [Enc §8.1 Meter Level](#81-meter-level) |
| `Byte 37` | Input clip indicator | 1 byte bitmask | Same OR bitmask pattern as [Enc §4.1](#41-matrix-input-mask) |
| `Byte 38` | Output clip indicator | 1 byte bitmask | Same OR bitmask pattern as [Enc §4.1](#41-matrix-input-mask) |
| `Byte 38` | Output limit indicator | 1 byte bitmask | Same OR bitmask pattern as [Enc §4.1](#41-matrix-input-mask) |
**Example — Query**
+2
View File
@@ -13,6 +13,8 @@
- Figure out Meter level encoding
- 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.
+12
View File
@@ -122,6 +122,12 @@ pub enum DSPError {
#[error("operation failed: {0}")]
OperationFailed(String),
#[error("internal error: {0}")]
InternalError(String),
#[error("mutex poisoned: {0}")]
MutexPoisoned(String),
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
@@ -134,3 +140,9 @@ pub enum DSPError {
#[error("frame error: {0}")]
Frame(#[from] FrameError),
}
impl<T> From<std::sync::PoisonError<T>> for DSPError {
fn from(err: std::sync::PoisonError<T>) -> Self {
DSPError::MutexPoisoned(err.to_string())
}
}
+161 -85
View File
@@ -3,22 +3,20 @@ use std::{
fmt,
io::{Read, Write},
net::{IpAddr, SocketAddr, TcpStream},
sync::{Mutex, OnceLock},
sync::{Arc, Mutex, OnceLock},
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},
},
};
@@ -32,9 +30,8 @@ fn instances() -> &'static Mutex<HashMap<(IpAddr, u8), ()>> {
}
pub struct DSP408 {
connection: types::DeviceConnection,
socket: Option<TcpStream>,
lock: Mutex<()>,
connection: types::DeviceConnection, // read-only so thread protected
socket: Arc<Mutex<Option<TcpStream>>>, // thread protected
connected: bool,
state: Option<types::DSPState>,
}
@@ -62,9 +59,7 @@ impl DSP408 {
device_id,
},
socket: None,
lock: Mutex::new(()),
socket: Arc::new(Mutex::new(None)),
connected: false,
@@ -72,19 +67,13 @@ impl DSP408 {
})
}
fn send_raw(&mut self, data: &[u8]) -> Result<(), DSPError> {
let _guard = self.lock.lock().unwrap();
let socket = self.socket.as_mut().ok_or(DSPError::NotConnected)?;
fn send_raw(socket: &mut TcpStream, data: &[u8]) -> Result<(), DSPError> {
socket.write_all(data)?;
Ok(())
}
fn recv_raw(&mut self) -> Result<Vec<u8>, DSPError> {
let socket = self.socket.as_mut().ok_or(DSPError::NotConnected)?;
fn recv_raw(socket: &mut TcpStream) -> Result<Vec<u8>, DSPError> {
let mut buffer = vec![0u8; RECV_BUFSIZE];
match socket.read(&mut buffer) {
@@ -101,10 +90,17 @@ impl DSP408 {
}
}
pub fn send_recv(&mut self, command: &[u8]) -> Result<types::DeviceResponseType, DSPError> {
self.send_raw(command)?;
pub fn send_recv(&self, command: &[u8]) -> Result<types::DeviceResponseType, DSPError> {
let mut socket_guard = self
.socket
.lock()
.map_err(|e| DSPError::MutexPoisoned(e.to_string()))?;
let raw = self.recv_raw()?;
let socket = socket_guard.as_mut().ok_or(DSPError::NotConnected)?;
Self::send_raw(socket, command)?;
let raw = Self::recv_raw(socket)?;
let frame = extract_frame(&raw)?;
@@ -117,6 +113,10 @@ impl DSP408 {
self.connected
}
pub fn connection(&self) -> &types::DeviceConnection {
&self.connection
}
pub fn state(&self) -> Result<&types::DSPState, DSPError> {
self.state.as_ref().ok_or(DSPError::NotConnected)
}
@@ -125,13 +125,49 @@ impl DSP408 {
self.state.as_mut().ok_or(DSPError::NotConnected)
}
fn initialize_state(&mut self) -> Result<(), DSPError> {
let name = self.get_device_name()?;
let flags = self.get_device_flags()?;
let current_preset = self.get_current_preset()?;
let modified = self.get_preset_modification_status()?;
let mut preset_names = Vec::with_capacity(PRESET_INDEX_MAX);
for i in 0..PRESET_INDEX_MAX {
preset_names.push(self.get_preset_name(i + 1)?);
}
let state = self.read_config_state()?;
self.state = Some(types::DSPState {
name,
flags,
presets: types::PresetBank {
current_index: current_preset as usize,
names: preset_names,
modified: modified.modified,
},
current_config: state,
});
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> {
let key = (self.connection.host, self.connection.device_id);
{
let map = instances().lock().unwrap();
let map = instances()
.lock()
.map_err(|e| DSPError::InternalError(e.to_string()))?;
if map.contains_key(&key) {
return Err(DSPError::AlreadyConnected);
@@ -144,69 +180,90 @@ impl DSP408 {
stream.set_read_timeout(Some(SOCKET_TIMEOUT))?;
self.socket = Some(stream);
{
let mut socket = self
.socket
.lock()
.map_err(|e| DSPError::InternalError(e.to_string()))?;
*socket = Some(stream);
}
let handshake = commands::build_handshake();
self.send_raw(&handshake)?;
{
let mut socket = self
.socket
.lock()
.map_err(|e| DSPError::InternalError(e.to_string()))?;
let socket = socket.as_mut().ok_or(DSPError::NotConnected)?;
Self::send_raw(socket, &handshake)?;
}
let start = std::time::Instant::now();
while start.elapsed() < SOCKET_TIMEOUT {
let raw = self.recv_raw()?;
let raw = {
let mut socket = self
.socket
.lock()
.map_err(|e| DSPError::InternalError(e.to_string()))?;
let socket = socket.as_mut().ok_or(DSPError::NotConnected)?;
Self::recv_raw(socket)?
};
let frame = extract_frame(&raw)?;
let response = map_response(&frame)?;
if matches!(response, types::DeviceResponseType::HandshakeAck(_)) {
instances().lock().unwrap().insert(key, ());
let result = self.initialize_state();
self.connected = true;
let name = self.get_device_name()?;
let flags = self.get_device_flags()?;
let current_preset = self.get_current_preset()?;
let modified = self.get_preset_modification_status()?;
let mut preset_names = Vec::with_capacity(PRESET_INDEX_MAX);
for i in 0..PRESET_INDEX_MAX {
preset_names.push(self.get_preset_name(i + 1)?);
if result.is_err() {
self.disconnect()?;
return result;
}
let state = self.read_config_state()?;
instances()
.lock()
.map_err(|e| DSPError::InternalError(e.to_string()))?
.insert(key, ());
self.state = Some(types::DSPState {
name,
flags,
presets: types::PresetBank {
current_index: current_preset as usize,
names: preset_names,
modified: modified.modified,
},
current_config: state,
});
self.connected = true;
return Ok(());
}
}
self.disconnect();
self.disconnect()?;
Err(DSPError::HandshakeFailed)
}
// Command `0x11`
pub fn disconnect(&mut self) {
pub fn disconnect(&mut self) -> Result<(), DSPError> {
let key = (self.connection.host, self.connection.device_id);
if let Some(mut socket) = self.socket.take() {
let _ = socket.write_all(&commands::build_disconnect());
let mut socket_guard = self.socket.lock()?;
let _ = socket.shutdown(std::net::Shutdown::Both);
if let Some(mut socket) = socket_guard.take() {
socket
.write_all(&commands::build_disconnect())
.map_err(DSPError::Io)?;
socket
.shutdown(std::net::Shutdown::Both)
.map_err(DSPError::Io)?;
}
instances().lock().unwrap().remove(&key);
instances().lock()?.remove(&key);
self.connected = false;
Ok(())
}
// Command `0x12`
@@ -225,7 +282,7 @@ impl DSP408 {
}
// 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 resp = self.send_recv(&request)?;
@@ -240,7 +297,7 @@ impl DSP408 {
}
// 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 resp = self.send_recv(&request)?;
@@ -268,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)
}
@@ -348,7 +418,7 @@ impl DSP408 {
}
// 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 resp = self.send_recv(&request)?;
@@ -395,8 +465,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)
@@ -426,7 +508,7 @@ impl DSP408 {
}
// 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 request = commands::build_get_preset_name(raw_index)?;
@@ -464,7 +546,7 @@ impl DSP408 {
}
// 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 resp = self.send_recv(&request)?;
@@ -746,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) => {
@@ -755,7 +841,8 @@ impl DSP408 {
.current_config
.output_state_mut(output)
.peq_chain
.bands[band as usize] = peq;
.bands
.insert(band, peq);
}
}
@@ -883,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,
@@ -1286,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)
}
@@ -1334,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)
}
@@ -1413,7 +1487,9 @@ impl DSP408 {
impl Drop for DSP408 {
fn drop(&mut self) {
self.disconnect();
if self.connected {
let _ = self.disconnect();
}
}
}
+43 -23
View File
@@ -1,14 +1,11 @@
use crate::{
common::errors::ProtocolError,
dsp408::{
protocol::{
constants::{METERS_HIGH_STEP, METERS_LOW_STEP, METERS_SPLIT, METERS_SPLIT_LEVEL_DB},
frame::Frame,
},
protocol::frame::Frame,
types::{
Acknowledgement, AuthenticationResult, ConfigChunk, CurrentPreset, DeviceFlags,
DeviceInfo, DeviceResponseType, HandshakeAck, Meters, ModifiedPreset, NotImplemented,
PresetName, UnknownResponse,
DeviceInfo, DeviceResponseType, HandshakeAck, Meter, Meters, ModifiedPreset,
NotImplemented, PresetName, UnknownResponse,
},
},
};
@@ -88,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 {
@@ -166,27 +159,54 @@ pub fn map_response(frame: &Frame) -> Result<DeviceResponseType, ProtocolError>
// Command `0x40` (Get Level Meters)
if cmd == 0x40 {
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 {
return Err(ProtocolError::new("Not enough bytes for meter levels"));
}
let floats: Vec<f32> = (0..36)
let channels: Vec<Meter> = (0..36)
.step_by(3)
.map(|i| decode_float16(payload[i], payload[i + 1]).unwrap_or_default())
.collect();
.enumerate()
.map(|(index, i)| {
let rms = decode_float16(payload[i], payload[i + 1]).unwrap_or_default();
let peak = decode_float16(payload[i + 2], payload[i + 1]).unwrap_or_default();
let meters: Vec<f32> = floats
.into_iter()
.map(|f| {
if f < METERS_SPLIT {
METERS_SPLIT_LEVEL_DB + (f - METERS_SPLIT) / METERS_LOW_STEP
let rms_db = float_to_db(rms);
let peak_db = float_to_db(peak);
let clip = if index < 4 {
payload[36] & (1 << index) != 0
} else {
METERS_SPLIT_LEVEL_DB + (f - METERS_SPLIT) / METERS_HIGH_STEP
payload[37] & (1 << (index - 4)) != 0
};
Meter {
rms: rms_db,
peak: peak_db,
clip,
}
})
.collect();
return Ok(DeviceResponseType::Meters(Meters { levels: meters }));
return Ok(DeviceResponseType::Meters(Meters { channels }));
}
Ok(DeviceResponseType::UnknownResponse(UnknownResponse {
@@ -415,10 +435,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.rms.is_finite());
}
}
other => panic!("unexpected {:?}", other),
+64 -27
View File
@@ -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;
}
}
-5
View File
@@ -77,8 +77,3 @@ pub fn output_link_mask(channel: u8) -> Option<u8> {
_ => None,
}
}
pub const METERS_SPLIT: f32 = 1.0;
pub const METERS_SPLIT_LEVEL_DB: f32 = -18.0;
pub const METERS_LOW_STEP: f32 = 0.0104;
pub const METERS_HIGH_STEP: f32 = 0.0208;
+45 -8
View File
@@ -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]
@@ -83,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,
@@ -96,6 +106,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 +120,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 +156,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 +165,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 +208,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 +219,18 @@ pub struct AuthenticationResult {
pub success: bool,
}
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Meter {
pub rms: f32,
pub peak: f32,
pub clip: bool,
}
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq)]
pub struct Meters {
pub levels: Vec<f32>,
pub channels: Vec<Meter>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -238,7 +262,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 +271,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 +281,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 +289,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<f32>,
pub gains: HashMap<DiscreteFrequency, f32>,
pub bypass: bool,
}
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PEQ {
pub gain: f32,
@@ -278,30 +306,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<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,
}
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq)]
pub struct PresetBank {
pub current_index: usize,
@@ -309,6 +342,7 @@ pub struct PresetBank {
pub modified: Vec<bool>,
}
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct DeviceConnection {
pub host: IpAddr,
@@ -316,6 +350,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,
@@ -326,10 +361,11 @@ 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,
}
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Default, PartialEq)]
pub struct OutputChannelState {
pub name: String,
@@ -341,10 +377,11 @@ 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,
}
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq)]
pub struct DSPConfigState {
pub(crate) input_states: HashMap<InputChannel, InputChannelState>,
@@ -367,11 +404,11 @@ impl DSPConfigState {
}
}
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq)]
pub struct DSPState {
pub(crate) name: String,
pub(crate) flags: DeviceFlags,
pub(crate) presets: PresetBank,
pub(crate) current_config: DSPConfigState,
}
+12 -3
View File
@@ -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,13 +23,21 @@ 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();
dsp.disconnect()?;
println!("Disconnected");
Ok(())
}
// 10020100 27 40 263df7 263dfd 263dfd 263dfd 063d8d 063d63 263dcf 263dfd 263df6 263dcf 263dcf 263dcf 0300 10038b