Compare commits
6
Commits
bd5eaa3446
...
af2e7dc602
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af2e7dc602 | ||
|
|
e2fee36cdb | ||
|
|
27821c1866 | ||
|
|
3d94df0b7a | ||
|
|
f6b6beda5e | ||
|
|
a9a49bef89 |
Generated
+63
@@ -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"
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ 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"
|
||||||
@@ -17,3 +19,5 @@ path = "src/lib.rs"
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
hardware-tests = []
|
hardware-tests = []
|
||||||
|
default = []
|
||||||
|
serialization = ["dep:serde", "dep:serde_json"]
|
||||||
|
|||||||
+1
-1
@@ -1868,7 +1868,7 @@ Sine Wave:
|
|||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `Byte 1-36` | 12 meter channels | 3 bytes × 12 | [Enc §8.1 Meter Level](#81-meter-level) |
|
| `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 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**
|
**Example — Query**
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
- Figure out Meter level encoding
|
- Figure out Meter level encoding
|
||||||
- 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
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
|||||||
@@ -122,6 +122,12 @@ pub enum DSPError {
|
|||||||
#[error("operation failed: {0}")]
|
#[error("operation failed: {0}")]
|
||||||
OperationFailed(String),
|
OperationFailed(String),
|
||||||
|
|
||||||
|
#[error("internal error: {0}")]
|
||||||
|
InternalError(String),
|
||||||
|
|
||||||
|
#[error("mutex poisoned: {0}")]
|
||||||
|
MutexPoisoned(String),
|
||||||
|
|
||||||
#[error("I/O error: {0}")]
|
#[error("I/O error: {0}")]
|
||||||
Io(#[from] std::io::Error),
|
Io(#[from] std::io::Error),
|
||||||
|
|
||||||
@@ -134,3 +140,9 @@ pub enum DSPError {
|
|||||||
#[error("frame error: {0}")]
|
#[error("frame error: {0}")]
|
||||||
Frame(#[from] FrameError),
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+172
-96
@@ -3,22 +3,20 @@ use std::{
|
|||||||
fmt,
|
fmt,
|
||||||
io::{Read, Write},
|
io::{Read, Write},
|
||||||
net::{IpAddr, SocketAddr, TcpStream},
|
net::{IpAddr, SocketAddr, TcpStream},
|
||||||
sync::{Mutex, OnceLock},
|
sync::{Arc, Mutex, OnceLock},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use strum::IntoEnumIterator;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
common::errors::{DSPError, ValidationError},
|
common::errors::DSPError,
|
||||||
dsp408::{
|
dsp408::{
|
||||||
mapper::{
|
mapper::{
|
||||||
self,
|
self,
|
||||||
constants::{CONFIG_CHUNK_INDEX_MAX, PRESET_INDEX_MAX},
|
constants::{CONFIG_CHUNK_INDEX_MAX, PRESET_INDEX_MAX, delay_range},
|
||||||
response::map_response,
|
response::map_response,
|
||||||
},
|
},
|
||||||
protocol::{commands, config::decode_config, frame::extract_frame},
|
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 {
|
pub struct DSP408 {
|
||||||
connection: types::DeviceConnection,
|
connection: types::DeviceConnection, // read-only so thread protected
|
||||||
socket: Option<TcpStream>,
|
socket: Arc<Mutex<Option<TcpStream>>>, // thread protected
|
||||||
lock: Mutex<()>,
|
|
||||||
connected: bool,
|
connected: bool,
|
||||||
state: Option<types::DSPState>,
|
state: Option<types::DSPState>,
|
||||||
}
|
}
|
||||||
@@ -62,9 +59,7 @@ impl DSP408 {
|
|||||||
device_id,
|
device_id,
|
||||||
},
|
},
|
||||||
|
|
||||||
socket: None,
|
socket: Arc::new(Mutex::new(None)),
|
||||||
|
|
||||||
lock: Mutex::new(()),
|
|
||||||
|
|
||||||
connected: false,
|
connected: false,
|
||||||
|
|
||||||
@@ -72,19 +67,13 @@ impl DSP408 {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_raw(&mut self, data: &[u8]) -> Result<(), DSPError> {
|
fn send_raw(socket: &mut TcpStream, data: &[u8]) -> Result<(), DSPError> {
|
||||||
let _guard = self.lock.lock().unwrap();
|
|
||||||
|
|
||||||
let socket = self.socket.as_mut().ok_or(DSPError::NotConnected)?;
|
|
||||||
|
|
||||||
socket.write_all(data)?;
|
socket.write_all(data)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn recv_raw(&mut self) -> Result<Vec<u8>, DSPError> {
|
fn recv_raw(socket: &mut TcpStream) -> Result<Vec<u8>, DSPError> {
|
||||||
let socket = self.socket.as_mut().ok_or(DSPError::NotConnected)?;
|
|
||||||
|
|
||||||
let mut buffer = vec![0u8; RECV_BUFSIZE];
|
let mut buffer = vec![0u8; RECV_BUFSIZE];
|
||||||
|
|
||||||
match socket.read(&mut buffer) {
|
match socket.read(&mut buffer) {
|
||||||
@@ -101,10 +90,17 @@ impl DSP408 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_recv(&mut self, command: &[u8]) -> Result<types::DeviceResponseType, DSPError> {
|
pub fn send_recv(&self, command: &[u8]) -> Result<types::DeviceResponseType, DSPError> {
|
||||||
self.send_raw(command)?;
|
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)?;
|
let frame = extract_frame(&raw)?;
|
||||||
|
|
||||||
@@ -117,6 +113,10 @@ impl DSP408 {
|
|||||||
self.connected
|
self.connected
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn connection(&self) -> &types::DeviceConnection {
|
||||||
|
&self.connection
|
||||||
|
}
|
||||||
|
|
||||||
pub fn state(&self) -> Result<&types::DSPState, DSPError> {
|
pub fn state(&self) -> Result<&types::DSPState, DSPError> {
|
||||||
self.state.as_ref().ok_or(DSPError::NotConnected)
|
self.state.as_ref().ok_or(DSPError::NotConnected)
|
||||||
}
|
}
|
||||||
@@ -125,43 +125,7 @@ impl DSP408 {
|
|||||||
self.state.as_mut().ok_or(DSPError::NotConnected)
|
self.state.as_mut().ok_or(DSPError::NotConnected)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- Commands ----------
|
fn initialize_state(&mut self) -> Result<(), DSPError> {
|
||||||
// Command `0x10`
|
|
||||||
pub fn connect(&mut self) -> Result<(), DSPError> {
|
|
||||||
let key = (self.connection.host, self.connection.device_id);
|
|
||||||
|
|
||||||
{
|
|
||||||
let map = instances().lock().unwrap();
|
|
||||||
|
|
||||||
if map.contains_key(&key) {
|
|
||||||
return Err(DSPError::AlreadyConnected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let addr = SocketAddr::new(self.connection.host, self.connection.port);
|
|
||||||
|
|
||||||
let stream = TcpStream::connect_timeout(&addr, SOCKET_TIMEOUT)?;
|
|
||||||
|
|
||||||
stream.set_read_timeout(Some(SOCKET_TIMEOUT))?;
|
|
||||||
|
|
||||||
self.socket = Some(stream);
|
|
||||||
|
|
||||||
let handshake = commands::build_handshake();
|
|
||||||
|
|
||||||
self.send_raw(&handshake)?;
|
|
||||||
|
|
||||||
let start = std::time::Instant::now();
|
|
||||||
|
|
||||||
while start.elapsed() < SOCKET_TIMEOUT {
|
|
||||||
let raw = self.recv_raw()?;
|
|
||||||
let frame = extract_frame(&raw)?;
|
|
||||||
let response = map_response(&frame)?;
|
|
||||||
|
|
||||||
if matches!(response, types::DeviceResponseType::HandshakeAck(_)) {
|
|
||||||
instances().lock().unwrap().insert(key, ());
|
|
||||||
|
|
||||||
self.connected = true;
|
|
||||||
|
|
||||||
let name = self.get_device_name()?;
|
let name = self.get_device_name()?;
|
||||||
let flags = self.get_device_flags()?;
|
let flags = self.get_device_flags()?;
|
||||||
let current_preset = self.get_current_preset()?;
|
let current_preset = self.get_current_preset()?;
|
||||||
@@ -185,28 +149,121 @@ impl DSP408 {
|
|||||||
current_config: state,
|
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()
|
||||||
|
.map_err(|e| DSPError::InternalError(e.to_string()))?;
|
||||||
|
|
||||||
|
if map.contains_key(&key) {
|
||||||
|
return Err(DSPError::AlreadyConnected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let addr = SocketAddr::new(self.connection.host, self.connection.port);
|
||||||
|
|
||||||
|
let stream = TcpStream::connect_timeout(&addr, SOCKET_TIMEOUT)?;
|
||||||
|
|
||||||
|
stream.set_read_timeout(Some(SOCKET_TIMEOUT))?;
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut socket = self
|
||||||
|
.socket
|
||||||
|
.lock()
|
||||||
|
.map_err(|e| DSPError::InternalError(e.to_string()))?;
|
||||||
|
|
||||||
|
*socket = Some(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
let handshake = commands::build_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 = {
|
||||||
|
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(_)) {
|
||||||
|
let result = self.initialize_state();
|
||||||
|
|
||||||
|
if result.is_err() {
|
||||||
|
self.disconnect()?;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
instances()
|
||||||
|
.lock()
|
||||||
|
.map_err(|e| DSPError::InternalError(e.to_string()))?
|
||||||
|
.insert(key, ());
|
||||||
|
|
||||||
|
self.connected = true;
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.disconnect();
|
self.disconnect()?;
|
||||||
|
|
||||||
Err(DSPError::HandshakeFailed)
|
Err(DSPError::HandshakeFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Command `0x11`
|
// Command `0x11`
|
||||||
pub fn disconnect(&mut self) {
|
pub fn disconnect(&mut self) -> Result<(), DSPError> {
|
||||||
let key = (self.connection.host, self.connection.device_id);
|
let key = (self.connection.host, self.connection.device_id);
|
||||||
|
|
||||||
if let Some(mut socket) = self.socket.take() {
|
let mut socket_guard = self.socket.lock()?;
|
||||||
let _ = socket.write_all(&commands::build_disconnect());
|
|
||||||
|
|
||||||
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;
|
self.connected = false;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Command `0x12`
|
// Command `0x12`
|
||||||
@@ -225,7 +282,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)?;
|
||||||
@@ -240,7 +297,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)?;
|
||||||
@@ -268,7 +325,20 @@ impl DSP408 {
|
|||||||
return Ok(false);
|
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)
|
Ok(true)
|
||||||
}
|
}
|
||||||
@@ -348,7 +418,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)?;
|
||||||
@@ -395,8 +465,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)
|
||||||
@@ -426,7 +508,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)?;
|
||||||
@@ -464,7 +546,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)?;
|
||||||
@@ -746,8 +828,12 @@ impl DSP408 {
|
|||||||
|
|
||||||
match channel {
|
match channel {
|
||||||
types::Channel::Input(input) => {
|
types::Channel::Input(input) => {
|
||||||
state.current_config.input_state_mut(input).peq_chain.bands
|
state
|
||||||
[band as usize] = peq;
|
.current_config
|
||||||
|
.input_state_mut(input)
|
||||||
|
.peq_chain
|
||||||
|
.bands
|
||||||
|
.insert(band, peq);
|
||||||
}
|
}
|
||||||
|
|
||||||
types::Channel::Output(output) => {
|
types::Channel::Output(output) => {
|
||||||
@@ -755,7 +841,8 @@ impl DSP408 {
|
|||||||
.current_config
|
.current_config
|
||||||
.output_state_mut(output)
|
.output_state_mut(output)
|
||||||
.peq_chain
|
.peq_chain
|
||||||
.bands[band as usize] = peq;
|
.bands
|
||||||
|
.insert(band, peq);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -883,7 +970,7 @@ impl DSP408 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Command `0x38` (Set Delay)
|
// Command `0x38` (Set Delay)
|
||||||
pub fn set_channel_delay(
|
pub fn set_delay(
|
||||||
&mut self,
|
&mut self,
|
||||||
channel: types::Channel,
|
channel: types::Channel,
|
||||||
delay: f32,
|
delay: f32,
|
||||||
@@ -1286,19 +1373,12 @@ impl DSP408 {
|
|||||||
|
|
||||||
let state = self.state_mut()?;
|
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
|
state
|
||||||
.current_config
|
.current_config
|
||||||
.output_state_mut(output_channel)
|
.output_state_mut(output_channel)
|
||||||
.matrix_routes
|
.matrix_routes
|
||||||
.gains[index] = gain;
|
.gains
|
||||||
|
.insert(input_channel, gain);
|
||||||
|
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
@@ -1334,19 +1414,13 @@ impl DSP408 {
|
|||||||
|
|
||||||
let state = self.state_mut()?;
|
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
|
state
|
||||||
.current_config
|
.current_config
|
||||||
.input_state_mut(input_channel)
|
.input_state_mut(input_channel)
|
||||||
.geq
|
.geq
|
||||||
.gains[index] = gain;
|
.gains
|
||||||
|
.insert(frequency_band, gain);
|
||||||
|
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1413,7 +1487,9 @@ impl DSP408 {
|
|||||||
|
|
||||||
impl Drop for DSP408 {
|
impl Drop for DSP408 {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.disconnect();
|
if self.connected {
|
||||||
|
let _ = self.disconnect();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
common::errors::ProtocolError,
|
common::errors::ProtocolError,
|
||||||
dsp408::{
|
dsp408::{
|
||||||
protocol::{
|
protocol::frame::Frame,
|
||||||
constants::{METERS_HIGH_STEP, METERS_LOW_STEP, METERS_SPLIT, METERS_SPLIT_LEVEL_DB},
|
|
||||||
frame::Frame,
|
|
||||||
},
|
|
||||||
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,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -88,10 +85,6 @@ pub fn map_response(frame: &Frame) -> Result<DeviceResponseType, ProtocolError>
|
|||||||
|
|
||||||
// Command `0x24` (Get Config Chunk)
|
// Command `0x24` (Get Config Chunk)
|
||||||
if cmd == 0x24 {
|
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);
|
let index = payload.first().copied().unwrap_or(0);
|
||||||
|
|
||||||
if index > 0x1c {
|
if index > 0x1c {
|
||||||
@@ -166,27 +159,54 @@ pub fn map_response(frame: &Frame) -> Result<DeviceResponseType, ProtocolError>
|
|||||||
|
|
||||||
// Command `0x40` (Get Level Meters)
|
// Command `0x40` (Get Level Meters)
|
||||||
if cmd == 0x40 {
|
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 {
|
if payload.len() != 38 {
|
||||||
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)
|
let channels: Vec<Meter> = (0..36)
|
||||||
.step_by(3)
|
.step_by(3)
|
||||||
.map(|i| decode_float16(payload[i], payload[i + 1]).unwrap_or_default())
|
.enumerate()
|
||||||
.collect();
|
.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
|
let rms_db = float_to_db(rms);
|
||||||
.into_iter()
|
let peak_db = float_to_db(peak);
|
||||||
.map(|f| {
|
|
||||||
if f < METERS_SPLIT {
|
let clip = if index < 4 {
|
||||||
METERS_SPLIT_LEVEL_DB + (f - METERS_SPLIT) / METERS_LOW_STEP
|
payload[36] & (1 << index) != 0
|
||||||
} else {
|
} 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();
|
.collect();
|
||||||
|
|
||||||
return Ok(DeviceResponseType::Meters(Meters { levels: meters }));
|
return Ok(DeviceResponseType::Meters(Meters { channels }));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(DeviceResponseType::UnknownResponse(UnknownResponse {
|
Ok(DeviceResponseType::UnknownResponse(UnknownResponse {
|
||||||
@@ -415,10 +435,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.rms.is_finite());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
other => panic!("unexpected {:?}", other),
|
other => panic!("unexpected {:?}", other),
|
||||||
|
|||||||
@@ -35,12 +35,14 @@ fn read_gate(r: &mut BinaryReader) -> Result<types::Gate, DSPError> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn read_geq(r: &mut BinaryReader) -> Result<types::GraphicEQ, DSPError> {
|
fn read_geq(r: &mut BinaryReader) -> Result<types::GraphicEQ, DSPError> {
|
||||||
let gains = types::DiscreteFrequency::iter()
|
let mut gains = std::collections::HashMap::new();
|
||||||
.map(|_| {
|
|
||||||
|
for frequency in types::DiscreteFrequency::iter() {
|
||||||
let raw = r.read_u16()?;
|
let raw = r.read_u16()?;
|
||||||
Ok(mapper::raw_to_eq_gain(raw)?)
|
let gain = mapper::raw_to_eq_gain(raw)?;
|
||||||
})
|
|
||||||
.collect::<Result<Vec<_>, DSPError>>()?;
|
gains.insert(frequency, gain);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(types::GraphicEQ {
|
Ok(types::GraphicEQ {
|
||||||
gains,
|
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> {
|
fn read_peq_chain(r: &mut BinaryReader, band_count: usize) -> Result<types::PEQChain, DSPError> {
|
||||||
let bands = (0..band_count)
|
let bands = types::PEQBand::iter()
|
||||||
.map(|_| {
|
.take(band_count)
|
||||||
Ok(types::PEQ {
|
.map(|band| {
|
||||||
|
let peq = types::PEQ {
|
||||||
gain: mapper::raw_to_eq_gain(r.read_u16()?)?,
|
gain: mapper::raw_to_eq_gain(r.read_u16()?)?,
|
||||||
frequency: mapper::raw_to_frequency_hz(r.read_u16()?)?,
|
frequency: mapper::raw_to_frequency_hz(r.read_u16()?)?,
|
||||||
q: mapper::raw_to_peq_q(r.read_u8()?)?,
|
q: mapper::raw_to_peq_q(r.read_u8()?)?,
|
||||||
filter_type: mapper::raw_to_peq_filter(r.read_u8()?)?,
|
filter_type: mapper::raw_to_peq_filter(r.read_u8()?)?,
|
||||||
bypass: false,
|
bypass: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((band, peq))
|
||||||
})
|
})
|
||||||
})
|
.collect::<Result<HashMap<_, _>, DSPError>>()?;
|
||||||
.collect::<Result<Vec<_>, DSPError>>()?;
|
|
||||||
|
|
||||||
Ok(types::PEQChain {
|
Ok(types::PEQChain {
|
||||||
bands,
|
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> {
|
fn read_crossover(r: &mut BinaryReader) -> Result<types::CrossoverFilters, DSPError> {
|
||||||
let hp_freq = r.read_u16()?;
|
let hp_freq = r.read_u16()?;
|
||||||
let lp_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(
|
inp.linked_channels = mapper::raw_to_link_channels(
|
||||||
mapper::channel_to_raw(types::Channel::Input(channel))?,
|
mapper::channel_to_raw(types::Channel::Input(channel))?,
|
||||||
r.read_u8()?,
|
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()) {
|
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)?)?;
|
out.name = mapper::raw_to_channel_name(r.pop(constants::CHANNEL_NAME_LENGTH)?)?;
|
||||||
|
|
||||||
r.skip(1)?;
|
|
||||||
|
|
||||||
out.matrix_routes = types::MatrixRoutes {
|
out.matrix_routes = types::MatrixRoutes {
|
||||||
connected: mapper::raw_to_matrix(r.read_u8()?)?,
|
connected: mapper::raw_to_matrix(r.read_u8()?)?,
|
||||||
|
|
||||||
gains: (0..4)
|
gains: {
|
||||||
.map(|_| {
|
r.skip(1)?;
|
||||||
|
|
||||||
|
[
|
||||||
|
types::InputChannel::InA,
|
||||||
|
types::InputChannel::InB,
|
||||||
|
types::InputChannel::InC,
|
||||||
|
types::InputChannel::InD,
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.map(|input| {
|
||||||
let raw = r.read_u16()?;
|
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)?;
|
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(
|
out.linked_channels = mapper::raw_to_link_channels(
|
||||||
mapper::channel_to_raw(types::Channel::Output(channel))?,
|
mapper::channel_to_raw(types::Channel::Output(channel))?,
|
||||||
r.read_u8()?,
|
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 {
|
for inp in &mut input_channels {
|
||||||
apply_flag(&mut r, inp.peq_chain.bands.as_mut_slice(), |band, value| {
|
apply_peq_bypass(&mut r, &mut inp.peq_chain.bands)?;
|
||||||
band.bypass = value
|
|
||||||
})?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for out in &mut output_channels {
|
for out in &mut output_channels {
|
||||||
apply_flag(&mut r, out.peq_chain.bands.as_mut_slice(), |band, value| {
|
apply_peq_bypass(&mut r, &mut out.peq_chain.bands)?;
|
||||||
band.bypass = value
|
|
||||||
})?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================
|
// ============================
|
||||||
@@ -260,7 +297,7 @@ pub fn decode_config(payload: &[u8]) -> Result<types::DSPConfigState, DSPError>
|
|||||||
for (index, inp) in input_channels.iter_mut().enumerate() {
|
for (index, inp) in input_channels.iter_mut().enumerate() {
|
||||||
let bypass = input_peq_bitmap & (1 << index) != 0;
|
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;
|
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() {
|
for (index, out) in output_channels.iter_mut().enumerate() {
|
||||||
let bypass = output_peq_bitmap & (1 << index) != 0;
|
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;
|
peq.bypass = bypass;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,8 +77,3 @@ pub fn output_link_mask(channel: u8) -> Option<u8> {
|
|||||||
_ => None,
|
_ => 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
@@ -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]
|
||||||
@@ -83,6 +92,7 @@ pub enum CrossoverFilter {
|
|||||||
Bl48,
|
Bl48,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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 PEQBand {
|
pub enum PEQBand {
|
||||||
B1 = 1,
|
B1 = 1,
|
||||||
@@ -96,6 +106,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 +120,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 +156,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 +165,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 +208,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 +219,18 @@ 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 rms: f32,
|
||||||
|
pub peak: 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 +262,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 +271,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 +281,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 +289,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: HashMap<DiscreteFrequency, 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 +306,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: HashMap<InputChannel, 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: HashMap<PEQBand, 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 +342,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 +350,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,
|
||||||
@@ -326,10 +361,11 @@ pub struct InputChannelState {
|
|||||||
pub geq: GraphicEQ,
|
pub geq: GraphicEQ,
|
||||||
pub peq_chain: PEQChain,
|
pub peq_chain: PEQChain,
|
||||||
pub crossover: CrossoverFilters,
|
pub crossover: CrossoverFilters,
|
||||||
pub linked_channels: Vec<Channel>,
|
pub linked_channels: Vec<InputChannel>,
|
||||||
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,
|
||||||
@@ -341,10 +377,11 @@ pub struct OutputChannelState {
|
|||||||
pub gain: f32,
|
pub gain: f32,
|
||||||
pub phase_inverted: bool,
|
pub phase_inverted: bool,
|
||||||
pub delay: f32,
|
pub delay: f32,
|
||||||
pub linked_channels: Vec<Channel>,
|
pub linked_channels: Vec<OutputChannel>,
|
||||||
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,11 +404,11 @@ 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,
|
||||||
pub(crate) flags: DeviceFlags,
|
pub(crate) flags: DeviceFlags,
|
||||||
pub(crate) presets: PresetBank,
|
pub(crate) presets: PresetBank,
|
||||||
|
|
||||||
pub(crate) current_config: DSPConfigState,
|
pub(crate) current_config: DSPConfigState,
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-3
@@ -3,6 +3,7 @@ use std::str::FromStr;
|
|||||||
|
|
||||||
use dsp_thomann::dsp408::DSP408;
|
use dsp_thomann::dsp408::DSP408;
|
||||||
use dsp_thomann::dsp408::types;
|
use dsp_thomann::dsp408::types;
|
||||||
|
use dsp_thomann::dsp408::types::InputChannel::InA;
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let host = IpAddr::from_str("192.168.49.30")?;
|
let host = IpAddr::from_str("192.168.49.30")?;
|
||||||
@@ -22,13 +23,21 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
}
|
}
|
||||||
println!("DSP connected.");
|
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...");
|
println!("Disconnecting...");
|
||||||
dsp.disconnect();
|
dsp.disconnect()?;
|
||||||
println!("Disconnected");
|
println!("Disconnected");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 10020100 27 40 263df7 263dfd 263dfd 263dfd 063d8d 063d63 263dcf 263dfd 263df6 263dcf 263dcf 263dcf 0300 10038b
|
||||||
|
|||||||
Reference in New Issue
Block a user