2274 lines
72 KiB
Rust
2274 lines
72 KiB
Rust
use std::{
|
|
collections::HashMap,
|
|
fmt,
|
|
io::{Read, Write},
|
|
net::{IpAddr, SocketAddr, TcpStream},
|
|
sync::{Arc, Mutex, OnceLock},
|
|
time::Duration,
|
|
};
|
|
|
|
use crate::{
|
|
common::errors::DSPError,
|
|
dsp408::{
|
|
client::default::{
|
|
default_dsp_state, default_input_meters, default_output_meters, random_input_meters,
|
|
random_output_meters,
|
|
},
|
|
mapper::{
|
|
self,
|
|
constants::{CONFIG_CHUNK_INDEX_MAX, PRESET_INDEX_MAX, delay_range},
|
|
response::map_response,
|
|
},
|
|
protocol::{commands, config::decode_config, frame::extract_frame},
|
|
types::{self, ConnectionStatus::Disconnected, DSPMode},
|
|
},
|
|
};
|
|
|
|
pub const SOCKET_TIMEOUT: Duration = Duration::from_secs(20);
|
|
pub const RECV_BUFSIZE: usize = 4096;
|
|
|
|
static INSTANCES: OnceLock<Mutex<HashMap<(IpAddr, u8), ()>>> = OnceLock::new();
|
|
|
|
fn instances() -> &'static Mutex<HashMap<(IpAddr, u8), ()>> {
|
|
INSTANCES.get_or_init(|| Mutex::new(HashMap::new()))
|
|
}
|
|
|
|
pub struct DSP408 {
|
|
connection: types::DeviceConnection,
|
|
socket: Arc<Mutex<Option<TcpStream>>>,
|
|
mode: DSPMode,
|
|
state: types::DSPState,
|
|
input_meters: types::InputMeters,
|
|
output_meters: types::OutputMeters,
|
|
}
|
|
|
|
impl DSP408 {
|
|
pub fn new(host: IpAddr, port: u16, device_id: u8) -> Result<Self, DSPError> {
|
|
if matches!(host, IpAddr::V6(_)) {
|
|
return Err(DSPError::InvalidHost("DSP doesn't support IPv6"));
|
|
}
|
|
|
|
if port == 0 {
|
|
return Err(DSPError::InvalidPort("Port must be between 1 and 65535"));
|
|
}
|
|
|
|
if device_id == 0 || device_id > 254 {
|
|
return Err(DSPError::InvalidDeviceId(
|
|
"Device ID must be between 1 and 254",
|
|
));
|
|
}
|
|
|
|
Ok(Self {
|
|
mode: types::DSPMode::Normal,
|
|
|
|
connection: types::DeviceConnection {
|
|
host,
|
|
port,
|
|
device_id,
|
|
},
|
|
|
|
socket: Arc::new(Mutex::new(None)),
|
|
|
|
state: default_dsp_state(),
|
|
|
|
input_meters: default_input_meters(),
|
|
output_meters: default_output_meters(),
|
|
})
|
|
}
|
|
|
|
pub fn new_demo() -> Self {
|
|
Self {
|
|
mode: types::DSPMode::Demo,
|
|
|
|
connection: types::DeviceConnection {
|
|
host: IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED),
|
|
port: 0,
|
|
device_id: 1,
|
|
},
|
|
|
|
socket: Arc::new(Mutex::new(None)),
|
|
|
|
state: default_dsp_state(),
|
|
|
|
input_meters: default_input_meters(),
|
|
output_meters: default_output_meters(),
|
|
}
|
|
}
|
|
|
|
fn send_raw(socket: &mut TcpStream, data: &[u8]) -> Result<(), DSPError> {
|
|
socket.write_all(data)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn recv_raw(socket: &mut TcpStream) -> Result<Vec<u8>, DSPError> {
|
|
let mut buffer = vec![0u8; RECV_BUFSIZE];
|
|
|
|
match socket.read(&mut buffer) {
|
|
Ok(0) => Err(DSPError::ConnectionClosed),
|
|
|
|
Ok(size) => {
|
|
buffer.truncate(size);
|
|
Ok(buffer)
|
|
}
|
|
|
|
Err(e) if e.kind() == std::io::ErrorKind::TimedOut => Err(DSPError::Timeout),
|
|
|
|
Err(e) => Err(DSPError::Io(e)),
|
|
}
|
|
}
|
|
|
|
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 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 response = map_response(&frame)?;
|
|
|
|
Ok(response)
|
|
}
|
|
|
|
pub fn mode(&self) -> types::DSPMode {
|
|
self.mode
|
|
}
|
|
|
|
pub fn connected(&self) -> bool {
|
|
self.state.connection == types::ConnectionStatus::Connected
|
|
}
|
|
|
|
pub fn demo(&self) -> bool {
|
|
self.mode == types::DSPMode::Demo
|
|
}
|
|
|
|
pub fn state(&self) -> &types::DSPState {
|
|
&self.state
|
|
}
|
|
|
|
pub fn input_meters(&self) -> &types::InputMeters {
|
|
&self.input_meters
|
|
}
|
|
|
|
pub fn output_meters(&self) -> &types::OutputMeters {
|
|
&self.output_meters
|
|
}
|
|
|
|
fn state_mut(&mut self) -> &mut types::DSPState {
|
|
&mut self.state
|
|
}
|
|
|
|
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 = types::DSPState {
|
|
name,
|
|
flags,
|
|
presets: types::PresetBank {
|
|
current_index: current_preset as usize,
|
|
names: preset_names,
|
|
modified: modified.modified,
|
|
},
|
|
current_config: state,
|
|
connection: Disconnected,
|
|
};
|
|
|
|
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> {
|
|
if self.mode == types::DSPMode::Demo {
|
|
self.state.connection = types::ConnectionStatus::Connected;
|
|
return Ok(());
|
|
}
|
|
|
|
if self.state.connection == types::ConnectionStatus::Connected {
|
|
return Err(DSPError::AlreadyConnected);
|
|
}
|
|
|
|
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(_)) {
|
|
if let Err(error) = self.initialize_state() {
|
|
let _ = self.disconnect();
|
|
return Err(error);
|
|
}
|
|
|
|
self.state.connection = types::ConnectionStatus::Connected;
|
|
|
|
instances()
|
|
.lock()
|
|
.map_err(|e| DSPError::InternalError(e.to_string()))?
|
|
.insert(key, ());
|
|
|
|
return Ok(());
|
|
}
|
|
}
|
|
|
|
self.disconnect()?;
|
|
|
|
Err(DSPError::HandshakeFailed)
|
|
}
|
|
|
|
// Command `0x11`
|
|
pub fn disconnect(&mut self) -> Result<(), DSPError> {
|
|
if self.mode == types::DSPMode::Demo {
|
|
self.state.connection = types::ConnectionStatus::Disconnected;
|
|
return Ok(());
|
|
}
|
|
|
|
let key = (self.connection.host, self.connection.device_id);
|
|
|
|
let mut socket_guard = self
|
|
.socket
|
|
.lock()
|
|
.map_err(|e| DSPError::MutexPoisoned(e.to_string()))?;
|
|
|
|
if let Some(mut socket) = socket_guard.take() {
|
|
let _ = socket.write_all(&commands::build_disconnect());
|
|
let _ = socket.shutdown(std::net::Shutdown::Both);
|
|
}
|
|
|
|
instances()
|
|
.lock()
|
|
.map_err(|e| DSPError::MutexPoisoned(e.to_string()))?
|
|
.remove(&key);
|
|
|
|
self.state.connection = types::ConnectionStatus::Disconnected;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// Command `0x12`
|
|
fn request_acknowledgement(&mut self) -> Result<bool, DSPError> {
|
|
let request = commands::build_acknowledgement();
|
|
|
|
let resp = self.send_recv(&request)?;
|
|
|
|
match resp {
|
|
types::DeviceResponseType::Acknowledgement(ack) => Ok(ack.success),
|
|
other => Err(DSPError::UnexpectedResponse(format!(
|
|
"Expected Acknowledgement, got {:?}",
|
|
other
|
|
))),
|
|
}
|
|
}
|
|
|
|
// Command `0x13` (Device Name)
|
|
fn get_device_name(&mut self) -> Result<String, DSPError> {
|
|
let request = commands::build_request_device_name();
|
|
|
|
let resp = self.send_recv(&request)?;
|
|
|
|
match resp {
|
|
types::DeviceResponseType::DeviceInfo(info) => Ok(info.name),
|
|
other => Err(DSPError::UnexpectedResponse(format!(
|
|
"Expected DeviceInfo, got {:?}",
|
|
other
|
|
))),
|
|
}
|
|
}
|
|
|
|
// Command `0x14` (Current Preset Index)
|
|
fn get_current_preset(&mut self) -> Result<u8, DSPError> {
|
|
let request = commands::build_get_current_preset();
|
|
|
|
let resp = self.send_recv(&request)?;
|
|
|
|
match resp {
|
|
types::DeviceResponseType::CurrentPreset(preset) => Ok(preset.index),
|
|
other => Err(DSPError::UnexpectedResponse(format!(
|
|
"Expected CurrentPreset, got {:?}",
|
|
other
|
|
))),
|
|
}
|
|
}
|
|
|
|
// Command `0x15` (Set Delay Unit)
|
|
pub fn set_delay_unit(&mut self, unit: types::DelayUnit) -> Result<bool, DSPError> {
|
|
let raw_unit = mapper::delay_unit_to_raw(unit)?;
|
|
|
|
match self.state.connection {
|
|
types::ConnectionStatus::Connected => {
|
|
if self.mode == types::DSPMode::Normal {
|
|
let request = commands::build_set_delay_unit(raw_unit)?;
|
|
|
|
let resp = self.send_recv(&request)?;
|
|
|
|
match resp {
|
|
types::DeviceResponseType::Acknowledgement(ack) => {
|
|
if !ack.success {
|
|
return Ok(false);
|
|
}
|
|
}
|
|
|
|
other => {
|
|
return Err(DSPError::UnexpectedResponse(format!(
|
|
"Expected Acknowledgement, got {:?}",
|
|
other
|
|
)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
types::ConnectionStatus::Disconnected => {
|
|
return Err(DSPError::NotConnected);
|
|
}
|
|
}
|
|
|
|
// Apply the state change in both Connected and Demo.
|
|
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)
|
|
}
|
|
|
|
// Command `0x20` (Recall)
|
|
pub fn recall_preset(&mut self, preset_index: usize) -> Result<bool, DSPError> {
|
|
let raw_index = mapper::preset_index_to_raw(preset_index)?;
|
|
|
|
match self.state.connection {
|
|
types::ConnectionStatus::Connected => {
|
|
if self.mode == types::DSPMode::Normal {
|
|
let request = commands::build_recall(raw_index)?;
|
|
|
|
let resp = self.send_recv(&request)?;
|
|
|
|
match resp {
|
|
types::DeviceResponseType::Acknowledgement(ack) => {
|
|
if !ack.success {
|
|
return Ok(false);
|
|
}
|
|
|
|
let config = self.read_config_state()?;
|
|
self.state_mut().current_config = config;
|
|
}
|
|
|
|
other => {
|
|
return Err(DSPError::UnexpectedResponse(format!(
|
|
"Expected Acknowledgement, got {:?}",
|
|
other
|
|
)));
|
|
}
|
|
}
|
|
} else if self.mode == types::DSPMode::Demo {
|
|
self.state = default_dsp_state();
|
|
}
|
|
}
|
|
types::ConnectionStatus::Disconnected => {
|
|
return Err(DSPError::NotConnected);
|
|
}
|
|
}
|
|
|
|
self.state_mut().presets.current_index = preset_index;
|
|
|
|
Ok(true)
|
|
}
|
|
|
|
// Command `0x21` (Store Preset)
|
|
pub fn store_current_configuration(
|
|
&mut self,
|
|
preset_index: usize,
|
|
force: bool,
|
|
) -> Result<bool, DSPError> {
|
|
let raw_index = mapper::preset_index_to_raw(preset_index)?;
|
|
|
|
if preset_index == 0 && !force {
|
|
return Ok(false);
|
|
}
|
|
|
|
match self.state.connection {
|
|
types::ConnectionStatus::Connected => {
|
|
if self.mode == types::DSPMode::Normal {
|
|
let request = commands::build_store_current_configuration(raw_index)?;
|
|
|
|
let resp = self.send_recv(&request)?;
|
|
|
|
match resp {
|
|
types::DeviceResponseType::Acknowledgement(ack) => {
|
|
if !ack.success {
|
|
return Ok(false);
|
|
}
|
|
}
|
|
|
|
other => {
|
|
return Err(DSPError::UnexpectedResponse(format!(
|
|
"Expected Acknowledgement, got {:?}",
|
|
other
|
|
)));
|
|
}
|
|
}
|
|
|
|
if !self.request_acknowledgement()? {
|
|
return Err(DSPError::OperationFailed(
|
|
"Store Configuration failed".into(),
|
|
));
|
|
}
|
|
}
|
|
}
|
|
types::ConnectionStatus::Disconnected => {
|
|
return Err(DSPError::NotConnected);
|
|
}
|
|
}
|
|
|
|
self.state_mut().presets.modified[preset_index] = true;
|
|
|
|
Ok(true)
|
|
}
|
|
|
|
// Command `0x22` (Get Preset Modification Status)
|
|
fn get_preset_modification_status(&mut self) -> Result<types::ModifiedPreset, DSPError> {
|
|
let request = commands::build_preset_modification_status();
|
|
|
|
let resp = self.send_recv(&request)?;
|
|
|
|
match resp {
|
|
types::DeviceResponseType::ModifiedPreset(status) => Ok(status),
|
|
other => Err(DSPError::UnexpectedResponse(format!(
|
|
"Expected ModifiedPreset, got {:?}",
|
|
other
|
|
))),
|
|
}
|
|
}
|
|
|
|
// Command `0x23` (Factory Reset)
|
|
pub fn factory_reset(&mut self, force: bool) -> Result<bool, DSPError> {
|
|
if !force {
|
|
return Ok(false);
|
|
}
|
|
|
|
match self.state.connection {
|
|
types::ConnectionStatus::Connected => {
|
|
if self.mode == types::DSPMode::Normal {
|
|
let request = commands::build_factory_reset();
|
|
|
|
let resp = self.send_recv(&request)?;
|
|
|
|
match resp {
|
|
types::DeviceResponseType::Acknowledgement(ack) => {
|
|
if !ack.success {
|
|
return Ok(false);
|
|
}
|
|
}
|
|
|
|
other => {
|
|
return Err(DSPError::UnexpectedResponse(format!(
|
|
"Expected Acknowledgement, got {:?}",
|
|
other
|
|
)));
|
|
}
|
|
}
|
|
} else if self.mode == types::DSPMode::Demo {
|
|
self.state = default_dsp_state();
|
|
}
|
|
}
|
|
|
|
types::ConnectionStatus::Disconnected => {
|
|
return Err(DSPError::NotConnected);
|
|
}
|
|
}
|
|
|
|
Ok(true)
|
|
}
|
|
|
|
// Command `0x26` (Store Preset Name)
|
|
pub fn set_current_preset_name(&mut self, name: &str) -> Result<bool, DSPError> {
|
|
let raw_name = mapper::preset_name_to_raw(name)?;
|
|
|
|
match self.state.connection {
|
|
types::ConnectionStatus::Connected => {
|
|
if self.mode == types::DSPMode::Normal {
|
|
let request = commands::build_store_current_preset_name(&raw_name)?;
|
|
|
|
let resp = self.send_recv(&request)?;
|
|
|
|
match resp {
|
|
types::DeviceResponseType::Acknowledgement(ack) => {
|
|
if !ack.success {
|
|
return Ok(false);
|
|
}
|
|
}
|
|
|
|
other => {
|
|
return Err(DSPError::UnexpectedResponse(format!(
|
|
"Expected Acknowledgement, got {:?}",
|
|
other
|
|
)));
|
|
}
|
|
}
|
|
|
|
let current_index = self.state().presets.current_index;
|
|
|
|
if !self.store_current_configuration(current_index, true)? {
|
|
return Ok(false);
|
|
}
|
|
|
|
if !self.request_acknowledgement()? {
|
|
return Ok(false);
|
|
}
|
|
}
|
|
}
|
|
types::ConnectionStatus::Disconnected => {
|
|
return Err(DSPError::NotConnected);
|
|
}
|
|
}
|
|
|
|
// Shared authoritative state update.
|
|
let current_index = self.state().presets.current_index;
|
|
let name = mapper::raw_to_preset_name(&raw_name)?;
|
|
|
|
self.state_mut().presets.names[current_index - 1] = name;
|
|
|
|
Ok(true)
|
|
}
|
|
|
|
// Command `0x27 - 0x24` (Get Config Chunk)
|
|
fn get_config_chunk(&mut self, index: usize) -> Result<types::ConfigChunk, DSPError> {
|
|
let raw_index = mapper::config_chunk_to_raw(index)?;
|
|
|
|
let request = commands::build_config_chunk(raw_index)?;
|
|
|
|
let resp = self.send_recv(&request)?;
|
|
|
|
match resp {
|
|
types::DeviceResponseType::ConfigChunk(config) => Ok(config),
|
|
other => Err(DSPError::UnexpectedResponse(format!(
|
|
"Expected ConfigChunk, got {:?}",
|
|
other
|
|
))),
|
|
}
|
|
}
|
|
|
|
// Command `0x29` (Preset Name)
|
|
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)?;
|
|
|
|
let resp = self.send_recv(&request)?;
|
|
|
|
match resp {
|
|
types::DeviceResponseType::PresetName(name) => Ok(name.name),
|
|
other => Err(DSPError::UnexpectedResponse(format!(
|
|
"Expected PresetName, got {:?}",
|
|
other
|
|
))),
|
|
}
|
|
}
|
|
|
|
fn copy_channel_state(
|
|
&mut self,
|
|
source: types::Channel,
|
|
destination: types::Channel,
|
|
) -> Result<(), DSPError> {
|
|
match (source, destination) {
|
|
(types::Channel::Input(src), types::Channel::Input(dst)) => {
|
|
let src_state = self
|
|
.state
|
|
.current_config
|
|
.input_states
|
|
.get(&src)
|
|
.cloned()
|
|
.ok_or_else(|| DSPError::InternalError("missing input channel state".into()))?;
|
|
|
|
self.state
|
|
.current_config
|
|
.input_states
|
|
.insert(dst, src_state);
|
|
}
|
|
|
|
(types::Channel::Output(src), types::Channel::Output(dst)) => {
|
|
let src_state = self
|
|
.state
|
|
.current_config
|
|
.output_states
|
|
.get(&src)
|
|
.cloned()
|
|
.ok_or_else(|| {
|
|
DSPError::InternalError("missing output channel state".into())
|
|
})?;
|
|
|
|
self.state
|
|
.current_config
|
|
.output_states
|
|
.insert(dst, src_state);
|
|
}
|
|
|
|
_ => {
|
|
return Err(DSPError::InternalError(
|
|
"copy_channel requires source and destination to be the same channel kind"
|
|
.into(),
|
|
));
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// Command `0x2A` (Copy Channel)
|
|
pub fn copy_channel(
|
|
&mut self,
|
|
channel_1: types::Channel,
|
|
channel_2: types::Channel,
|
|
) -> Result<bool, DSPError> {
|
|
let (raw_channel_1, raw_channel_2) = mapper::copy_channel_to_raw(channel_1, channel_2)?;
|
|
|
|
match self.state.connection {
|
|
types::ConnectionStatus::Connected => {
|
|
if self.mode == types::DSPMode::Normal {
|
|
let request = commands::build_copy_channel(raw_channel_1, raw_channel_2)?;
|
|
|
|
let resp = self.send_recv(&request)?;
|
|
|
|
match resp {
|
|
types::DeviceResponseType::Acknowledgement(ack) => {
|
|
if !ack.success {
|
|
return Ok(false);
|
|
}
|
|
}
|
|
|
|
other => {
|
|
return Err(DSPError::UnexpectedResponse(format!(
|
|
"Expected Acknowledgement, got {:?}",
|
|
other
|
|
)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
types::ConnectionStatus::Disconnected => {
|
|
return Err(DSPError::NotConnected);
|
|
}
|
|
}
|
|
|
|
self.copy_channel_state(channel_1, channel_2)?;
|
|
|
|
Ok(true)
|
|
}
|
|
|
|
// Command `0x2C` (Device Flags)
|
|
fn get_device_flags(&mut self) -> Result<types::DeviceFlags, DSPError> {
|
|
let request = commands::build_get_device_flags();
|
|
|
|
let resp = self.send_recv(&request)?;
|
|
|
|
match resp {
|
|
types::DeviceResponseType::DeviceFlags(flags) => Ok(flags),
|
|
other => Err(DSPError::UnexpectedResponse(format!(
|
|
"Expected DeviceFlags, got {:?}",
|
|
other
|
|
))),
|
|
}
|
|
}
|
|
|
|
// Command `0x2D` (Unlock Device)
|
|
pub fn unlock_device(&mut self, password: &str) -> Result<bool, DSPError> {
|
|
let raw_password = mapper::password_to_raw(password)?;
|
|
|
|
match self.state.connection {
|
|
types::ConnectionStatus::Connected => {
|
|
if self.mode == types::DSPMode::Normal {
|
|
let request = commands::build_unlock_device(&raw_password)?;
|
|
|
|
let resp = self.send_recv(&request)?;
|
|
|
|
match resp {
|
|
types::DeviceResponseType::AuthenticationResult(result) => {
|
|
if !result.success {
|
|
return Ok(false);
|
|
}
|
|
}
|
|
|
|
other => {
|
|
return Err(DSPError::UnexpectedResponse(format!(
|
|
"Expected AuthenticationResult, got {:?}",
|
|
other
|
|
)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
types::ConnectionStatus::Disconnected => {
|
|
return Err(DSPError::NotConnected);
|
|
}
|
|
}
|
|
|
|
self.state_mut().flags.is_locked = false;
|
|
|
|
Ok(true)
|
|
}
|
|
|
|
// Command `0x2F` (Lock Device)
|
|
pub fn lock_device(&mut self, password: &str) -> Result<bool, DSPError> {
|
|
let raw_password = mapper::password_to_raw(password)?;
|
|
|
|
match self.state.connection {
|
|
types::ConnectionStatus::Connected => {
|
|
if self.mode == types::DSPMode::Normal {
|
|
let request = commands::build_lock_device(&raw_password)?;
|
|
|
|
let resp = self.send_recv(&request)?;
|
|
|
|
match resp {
|
|
types::DeviceResponseType::Acknowledgement(ack) => {
|
|
if !ack.success {
|
|
return Ok(false);
|
|
}
|
|
}
|
|
|
|
other => {
|
|
return Err(DSPError::UnexpectedResponse(format!(
|
|
"Expected Acknowledgement, got {:?}",
|
|
other
|
|
)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
types::ConnectionStatus::Disconnected => {
|
|
return Err(DSPError::NotConnected);
|
|
}
|
|
}
|
|
|
|
self.state_mut().flags.is_locked = true;
|
|
|
|
self.disconnect()?;
|
|
|
|
Ok(true)
|
|
}
|
|
|
|
// Command `0x30` (Set Compressor)
|
|
pub fn set_compressor(
|
|
&mut self,
|
|
channel: types::OutputChannel,
|
|
ratio: types::Ratio,
|
|
attack: u16,
|
|
release: u16,
|
|
knee: u16,
|
|
threshold: f32,
|
|
) -> Result<bool, DSPError> {
|
|
let raw_channel = mapper::output_channel_to_raw(channel)?;
|
|
let raw_ratio = mapper::ratio_to_raw(ratio)?;
|
|
let raw_attack = mapper::attack_ms_to_raw(attack)?;
|
|
let raw_release = mapper::release_ms_to_raw(release)?;
|
|
let raw_knee = mapper::knee_db_to_raw(knee)?;
|
|
let raw_threshold = mapper::threshold_db_to_raw(threshold)?;
|
|
|
|
match self.state.connection {
|
|
types::ConnectionStatus::Connected => {
|
|
if self.mode == types::DSPMode::Normal {
|
|
let request = commands::build_set_compressor(
|
|
raw_channel,
|
|
raw_ratio,
|
|
raw_attack,
|
|
raw_release,
|
|
raw_knee,
|
|
raw_threshold,
|
|
)?;
|
|
|
|
let resp = self.send_recv(&request)?;
|
|
|
|
match resp {
|
|
types::DeviceResponseType::Acknowledgement(ack) => {
|
|
if !ack.success {
|
|
return Ok(false);
|
|
}
|
|
}
|
|
|
|
other => {
|
|
return Err(DSPError::UnexpectedResponse(format!(
|
|
"Expected Acknowledgement, got {:?}",
|
|
other
|
|
)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
types::ConnectionStatus::Disconnected => {
|
|
return Err(DSPError::NotConnected);
|
|
}
|
|
}
|
|
|
|
// Shared authoritative state update.
|
|
self.state_mut()
|
|
.current_config
|
|
.output_state_mut(channel)
|
|
.compressor = types::Compressor {
|
|
threshold: mapper::raw_to_threshold_db(raw_threshold)?,
|
|
ratio: mapper::raw_to_ratio(raw_ratio)?,
|
|
attack: mapper::raw_to_attack_ms(raw_attack)?,
|
|
release: mapper::raw_to_release_ms(raw_release)?,
|
|
knee: mapper::raw_to_knee_db(raw_knee)?,
|
|
};
|
|
|
|
Ok(true)
|
|
}
|
|
|
|
// Command `0x31` (Set Low-Pass Filter)
|
|
pub fn set_low_pass(
|
|
&mut self,
|
|
channel: types::Channel,
|
|
freq: f32,
|
|
filter_type: types::CrossoverFilter,
|
|
) -> Result<bool, DSPError> {
|
|
let raw_channel = mapper::channel_to_raw(channel)?;
|
|
let raw_freq = mapper::frequency_hz_to_raw(freq)?;
|
|
let raw_filter = mapper::crossover_filter_to_raw(filter_type)?;
|
|
|
|
match self.state.connection {
|
|
types::ConnectionStatus::Connected => {
|
|
if self.mode == types::DSPMode::Normal {
|
|
let request = commands::build_low_pass(raw_channel, raw_freq, raw_filter)?;
|
|
|
|
let resp = self.send_recv(&request)?;
|
|
|
|
match resp {
|
|
types::DeviceResponseType::Acknowledgement(ack) => {
|
|
if !ack.success {
|
|
return Ok(false);
|
|
}
|
|
}
|
|
|
|
other => {
|
|
return Err(DSPError::UnexpectedResponse(format!(
|
|
"Expected Acknowledgement, got {:?}",
|
|
other
|
|
)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
types::ConnectionStatus::Disconnected => {
|
|
return Err(DSPError::NotConnected);
|
|
}
|
|
}
|
|
|
|
let state = self.state_mut();
|
|
|
|
match channel {
|
|
types::Channel::Input(input) => {
|
|
state
|
|
.current_config
|
|
.input_state_mut(input)
|
|
.crossover
|
|
.low_pass = types::Crossover {
|
|
frequency: mapper::raw_to_frequency_hz(raw_freq)?,
|
|
slope: mapper::raw_to_crossover_filter(raw_filter)?,
|
|
};
|
|
}
|
|
|
|
types::Channel::Output(output) => {
|
|
state
|
|
.current_config
|
|
.output_state_mut(output)
|
|
.crossover
|
|
.low_pass = types::Crossover {
|
|
frequency: mapper::raw_to_frequency_hz(raw_freq)?,
|
|
slope: mapper::raw_to_crossover_filter(raw_filter)?,
|
|
};
|
|
}
|
|
}
|
|
|
|
Ok(true)
|
|
}
|
|
|
|
// Command `0x32` (Set High-Pass Filter)
|
|
pub fn set_high_pass(
|
|
&mut self,
|
|
channel: types::Channel,
|
|
freq: f32,
|
|
filter_type: types::CrossoverFilter,
|
|
) -> Result<bool, DSPError> {
|
|
let raw_channel = mapper::channel_to_raw(channel)?;
|
|
let raw_freq = mapper::frequency_hz_to_raw(freq)?;
|
|
let raw_filter = mapper::crossover_filter_to_raw(filter_type)?;
|
|
|
|
match self.state.connection {
|
|
types::ConnectionStatus::Connected => {
|
|
if self.mode == types::DSPMode::Normal {
|
|
let request = commands::build_high_pass(raw_channel, raw_freq, raw_filter)?;
|
|
|
|
let resp = self.send_recv(&request)?;
|
|
|
|
match resp {
|
|
types::DeviceResponseType::Acknowledgement(ack) => {
|
|
if !ack.success {
|
|
return Ok(false);
|
|
}
|
|
}
|
|
|
|
other => {
|
|
return Err(DSPError::UnexpectedResponse(format!(
|
|
"Expected Acknowledgement, got {:?}",
|
|
other
|
|
)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
types::ConnectionStatus::Disconnected => {
|
|
return Err(DSPError::NotConnected);
|
|
}
|
|
}
|
|
|
|
let state = self.state_mut();
|
|
|
|
match channel {
|
|
types::Channel::Input(input) => {
|
|
state
|
|
.current_config
|
|
.input_state_mut(input)
|
|
.crossover
|
|
.high_pass = types::Crossover {
|
|
frequency: mapper::raw_to_frequency_hz(raw_freq)?,
|
|
slope: mapper::raw_to_crossover_filter(raw_filter)?,
|
|
};
|
|
}
|
|
|
|
types::Channel::Output(output) => {
|
|
state
|
|
.current_config
|
|
.output_state_mut(output)
|
|
.crossover
|
|
.high_pass = types::Crossover {
|
|
frequency: mapper::raw_to_frequency_hz(raw_freq)?,
|
|
slope: mapper::raw_to_crossover_filter(raw_filter)?,
|
|
};
|
|
}
|
|
}
|
|
|
|
Ok(true)
|
|
}
|
|
|
|
// Command `0x33` (Set PEQ Band)
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub fn set_peq_band(
|
|
&mut self,
|
|
channel: types::Channel,
|
|
band: types::PEQBand,
|
|
gain_db: f32,
|
|
freq_hz: f32,
|
|
q: f32,
|
|
filter_type: types::PEQFilter,
|
|
bypass: bool,
|
|
) -> Result<bool, DSPError> {
|
|
let raw_channel = mapper::channel_to_raw(channel)?;
|
|
let raw_band = mapper::peq_band_to_raw(band, channel)?;
|
|
let raw_gain = mapper::eq_gain_db_to_raw(gain_db)?;
|
|
let raw_frequency = mapper::frequency_hz_to_raw(freq_hz)?;
|
|
let raw_q = mapper::peq_q_to_raw(q, filter_type)?;
|
|
let raw_filter = mapper::peq_filter_to_raw(filter_type)?;
|
|
|
|
match self.state.connection {
|
|
types::ConnectionStatus::Connected => {
|
|
if self.mode == types::DSPMode::Normal {
|
|
let request = commands::build_peq_band(
|
|
raw_channel,
|
|
raw_band,
|
|
raw_gain,
|
|
raw_frequency,
|
|
raw_q,
|
|
raw_filter,
|
|
bypass as u8,
|
|
)?;
|
|
|
|
let resp = self.send_recv(&request)?;
|
|
|
|
match resp {
|
|
types::DeviceResponseType::Acknowledgement(ack) => {
|
|
if !ack.success {
|
|
return Ok(false);
|
|
}
|
|
}
|
|
|
|
other => {
|
|
return Err(DSPError::UnexpectedResponse(format!(
|
|
"Expected Acknowledgement, got {:?}",
|
|
other
|
|
)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
types::ConnectionStatus::Disconnected => {
|
|
return Err(DSPError::NotConnected);
|
|
}
|
|
}
|
|
|
|
// Shared authoritative state update (Connected + Demo).
|
|
let state = self.state_mut();
|
|
|
|
let peq = types::PEQ {
|
|
gain: mapper::raw_to_eq_gain(raw_gain)?,
|
|
frequency: mapper::raw_to_frequency_hz(raw_frequency)?,
|
|
q: mapper::raw_to_peq_q(raw_q)?,
|
|
filter_type: mapper::raw_to_peq_filter(raw_filter)?,
|
|
bypass,
|
|
};
|
|
|
|
match channel {
|
|
types::Channel::Input(input) => {
|
|
state
|
|
.current_config
|
|
.input_state_mut(input)
|
|
.peq_chain
|
|
.bands
|
|
.insert(band, peq);
|
|
}
|
|
|
|
types::Channel::Output(output) => {
|
|
state
|
|
.current_config
|
|
.output_state_mut(output)
|
|
.peq_chain
|
|
.bands
|
|
.insert(band, peq);
|
|
}
|
|
}
|
|
|
|
Ok(true)
|
|
}
|
|
|
|
// Command `0x34` (Set Gain)
|
|
pub fn set_channel_gain(&mut self, channel: types::Channel, db: f32) -> Result<bool, DSPError> {
|
|
let raw_channel = mapper::channel_to_raw(channel)?;
|
|
let raw_db = mapper::gain_db_to_raw(db)?;
|
|
|
|
match self.state.connection {
|
|
types::ConnectionStatus::Connected => {
|
|
if self.mode == types::DSPMode::Normal {
|
|
let request = commands::build_gain(raw_channel, raw_db)?;
|
|
|
|
let resp = self.send_recv(&request)?;
|
|
|
|
match resp {
|
|
types::DeviceResponseType::Acknowledgement(ack) => {
|
|
if !ack.success {
|
|
return Ok(false);
|
|
}
|
|
}
|
|
|
|
other => {
|
|
return Err(DSPError::UnexpectedResponse(format!(
|
|
"Expected Acknowledgement, got {:?}",
|
|
other
|
|
)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
types::ConnectionStatus::Disconnected => {
|
|
return Err(DSPError::NotConnected);
|
|
}
|
|
}
|
|
|
|
let state = self.state_mut();
|
|
|
|
match channel {
|
|
types::Channel::Input(input) => {
|
|
state.current_config.input_state_mut(input).gain = mapper::raw_to_gain(raw_db)?;
|
|
}
|
|
|
|
types::Channel::Output(output) => {
|
|
state.current_config.output_state_mut(output).gain = mapper::raw_to_gain(raw_db)?;
|
|
}
|
|
}
|
|
|
|
Ok(true)
|
|
}
|
|
|
|
// Command `0x35` (Set Mute)
|
|
pub fn set_mute(&mut self, channel: types::Channel, muted: bool) -> Result<bool, DSPError> {
|
|
let raw_channel = mapper::channel_to_raw(channel)?;
|
|
|
|
match self.state.connection {
|
|
types::ConnectionStatus::Connected => {
|
|
if self.mode == types::DSPMode::Normal {
|
|
let request = commands::build_mute(raw_channel, muted as u8)?;
|
|
|
|
let resp = self.send_recv(&request)?;
|
|
|
|
match resp {
|
|
types::DeviceResponseType::Acknowledgement(ack) => {
|
|
if !ack.success {
|
|
return Ok(false);
|
|
}
|
|
}
|
|
|
|
other => {
|
|
return Err(DSPError::UnexpectedResponse(format!(
|
|
"Expected Acknowledgement, got {:?}",
|
|
other
|
|
)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
types::ConnectionStatus::Disconnected => {
|
|
return Err(DSPError::NotConnected);
|
|
}
|
|
}
|
|
|
|
let state = self.state_mut();
|
|
|
|
match channel {
|
|
types::Channel::Input(input) => {
|
|
state.current_config.input_state_mut(input).mute = muted;
|
|
}
|
|
|
|
types::Channel::Output(output) => {
|
|
state.current_config.output_state_mut(output).mute = muted;
|
|
}
|
|
}
|
|
|
|
Ok(true)
|
|
}
|
|
|
|
// Command `0x36` (Invert Gain)
|
|
pub fn set_channel_inverse_gain(
|
|
&mut self,
|
|
channel: types::Channel,
|
|
inverted: bool,
|
|
) -> Result<bool, DSPError> {
|
|
let raw_channel = mapper::channel_to_raw(channel)?;
|
|
|
|
match self.state.connection {
|
|
types::ConnectionStatus::Connected => {
|
|
if self.mode == types::DSPMode::Normal {
|
|
let request = commands::build_inverted_gain(raw_channel, inverted as u8)?;
|
|
|
|
let resp = self.send_recv(&request)?;
|
|
|
|
match resp {
|
|
types::DeviceResponseType::Acknowledgement(ack) => {
|
|
if !ack.success {
|
|
return Ok(false);
|
|
}
|
|
}
|
|
|
|
other => {
|
|
return Err(DSPError::UnexpectedResponse(format!(
|
|
"Expected Acknowledgement, got {:?}",
|
|
other
|
|
)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
types::ConnectionStatus::Disconnected => {
|
|
return Err(DSPError::NotConnected);
|
|
}
|
|
}
|
|
|
|
let state = self.state_mut();
|
|
|
|
match channel {
|
|
types::Channel::Input(input) => {
|
|
state.current_config.input_state_mut(input).phase_inverted = inverted;
|
|
}
|
|
|
|
types::Channel::Output(output) => {
|
|
state.current_config.output_state_mut(output).phase_inverted = inverted;
|
|
}
|
|
}
|
|
|
|
Ok(true)
|
|
}
|
|
|
|
// Command `0x38` (Set Delay)
|
|
pub fn set_delay(
|
|
&mut self,
|
|
channel: types::Channel,
|
|
delay: f32,
|
|
unit: types::DelayUnit,
|
|
) -> Result<bool, DSPError> {
|
|
let raw_channel = mapper::channel_to_raw(channel)?;
|
|
let raw_delay = mapper::delay_to_raw(delay, unit)?;
|
|
|
|
match self.state.connection {
|
|
types::ConnectionStatus::Connected => {
|
|
if self.mode == types::DSPMode::Normal {
|
|
let request = commands::build_set_delay(raw_channel, raw_delay)?;
|
|
|
|
let resp = self.send_recv(&request)?;
|
|
|
|
match resp {
|
|
types::DeviceResponseType::Acknowledgement(ack) => {
|
|
if !ack.success {
|
|
return Ok(false);
|
|
}
|
|
}
|
|
|
|
other => {
|
|
return Err(DSPError::UnexpectedResponse(format!(
|
|
"Expected Acknowledgement, got {:?}",
|
|
other
|
|
)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
types::ConnectionStatus::Disconnected => {
|
|
return Err(DSPError::NotConnected);
|
|
}
|
|
}
|
|
|
|
let state = self.state_mut();
|
|
let delay = mapper::raw_to_delay(raw_delay, unit)?;
|
|
|
|
match channel {
|
|
types::Channel::Input(input) => {
|
|
state.current_config.input_state_mut(input).delay = delay;
|
|
}
|
|
|
|
types::Channel::Output(output) => {
|
|
state.current_config.output_state_mut(output).delay = delay;
|
|
}
|
|
}
|
|
|
|
Ok(true)
|
|
}
|
|
|
|
// Command `0x39` (Set Input Source)
|
|
pub fn set_input_source(&mut self, input_source: types::InputSource) -> Result<bool, DSPError> {
|
|
let raw_source = mapper::input_source_to_raw(input_source.r#type)?;
|
|
let raw_frequency = mapper::frequency_value_to_raw(input_source.frequency)?;
|
|
|
|
match self.state.connection {
|
|
types::ConnectionStatus::Connected => {
|
|
if self.mode == types::DSPMode::Normal {
|
|
let request = commands::build_set_input_source(raw_source, raw_frequency)?;
|
|
|
|
let resp = self.send_recv(&request)?;
|
|
|
|
match resp {
|
|
types::DeviceResponseType::Acknowledgement(ack) => {
|
|
if !ack.success {
|
|
return Ok(false);
|
|
}
|
|
}
|
|
|
|
other => {
|
|
return Err(DSPError::UnexpectedResponse(format!(
|
|
"Expected Acknowledgement, got {:?}",
|
|
other
|
|
)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
types::ConnectionStatus::Disconnected => {
|
|
return Err(DSPError::NotConnected);
|
|
}
|
|
}
|
|
|
|
self.state_mut().current_config.input_source = types::InputSource {
|
|
r#type: mapper::raw_to_input_source(raw_source)?,
|
|
frequency: mapper::raw_to_frequency_value(raw_frequency)?,
|
|
};
|
|
|
|
Ok(true)
|
|
}
|
|
|
|
// Command `0x3A` (Matrix Routing)
|
|
pub fn set_matrix_routing(
|
|
&mut self,
|
|
output: types::OutputChannel,
|
|
inputs: Vec<types::InputChannel>,
|
|
) -> Result<bool, DSPError> {
|
|
let raw_output = mapper::output_channel_to_raw(output)?;
|
|
let raw_inputs = mapper::matrix_to_raw(&inputs)?;
|
|
|
|
match self.state.connection {
|
|
types::ConnectionStatus::Connected => {
|
|
if self.mode == types::DSPMode::Normal {
|
|
let request = commands::build_matrix_routing(raw_output, raw_inputs)?;
|
|
|
|
let resp = self.send_recv(&request)?;
|
|
|
|
match resp {
|
|
types::DeviceResponseType::Acknowledgement(ack) => {
|
|
if !ack.success {
|
|
return Ok(false);
|
|
}
|
|
}
|
|
|
|
other => {
|
|
return Err(DSPError::UnexpectedResponse(format!(
|
|
"Expected Acknowledgement, got {:?}",
|
|
other
|
|
)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
types::ConnectionStatus::Disconnected => {
|
|
return Err(DSPError::NotConnected);
|
|
}
|
|
}
|
|
|
|
self.state_mut()
|
|
.current_config
|
|
.output_state_mut(output)
|
|
.matrix_routes
|
|
.connected = inputs;
|
|
|
|
Ok(true)
|
|
}
|
|
|
|
// Command `0x3B` (Link Channel)
|
|
pub fn link_channels(
|
|
&mut self,
|
|
source: types::Channel,
|
|
destinations: Vec<types::Channel>,
|
|
) -> Result<bool, DSPError> {
|
|
let raw_source = mapper::channel_to_raw(source)?;
|
|
let bitmask = mapper::link_channels_to_raw(source, &destinations)?;
|
|
|
|
// Copy source configuration to each destination first. This runs in
|
|
// both Connected and Demo mode (copy_channel handles that dispatch
|
|
// itself), and returns NotConnected for us if we're Disconnected.
|
|
for destination in &destinations {
|
|
if *destination == source {
|
|
continue;
|
|
}
|
|
|
|
if !self.copy_channel(source, *destination)? {
|
|
return Ok(false);
|
|
}
|
|
}
|
|
|
|
match self.state.connection {
|
|
types::ConnectionStatus::Connected => {
|
|
if self.mode == types::DSPMode::Normal {
|
|
// Link source
|
|
let request = commands::build_link_channel(raw_source, bitmask)?;
|
|
|
|
let resp = self.send_recv(&request)?;
|
|
|
|
match resp {
|
|
types::DeviceResponseType::Acknowledgement(ack) => {
|
|
if !ack.success {
|
|
return Ok(false);
|
|
}
|
|
}
|
|
|
|
other => {
|
|
return Err(DSPError::UnexpectedResponse(format!(
|
|
"Expected Acknowledgement, got {:?}",
|
|
other
|
|
)));
|
|
}
|
|
}
|
|
|
|
// Clear destination links
|
|
for destination in &destinations {
|
|
if *destination == source {
|
|
continue;
|
|
}
|
|
|
|
let raw_destination = mapper::channel_to_raw(*destination)?;
|
|
|
|
let request = commands::build_link_channel(raw_destination, 0x00)?;
|
|
|
|
let resp = self.send_recv(&request)?;
|
|
|
|
match resp {
|
|
types::DeviceResponseType::Acknowledgement(ack) => {
|
|
if !ack.success {
|
|
return Ok(false);
|
|
}
|
|
}
|
|
|
|
other => {
|
|
return Err(DSPError::UnexpectedResponse(format!(
|
|
"Expected Acknowledgement, got {:?}",
|
|
other
|
|
)));
|
|
}
|
|
}
|
|
}
|
|
|
|
if !self.request_acknowledgement()? {
|
|
return Ok(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
types::ConnectionStatus::Disconnected => {
|
|
return Err(DSPError::NotConnected);
|
|
}
|
|
}
|
|
|
|
let state = self.state_mut();
|
|
|
|
match source {
|
|
types::Channel::Input(s) => {
|
|
let input_destinations: Vec<types::InputChannel> = destinations
|
|
.iter()
|
|
.filter_map(|destination| match destination {
|
|
types::Channel::Input(input) => Some(*input),
|
|
types::Channel::Output(_) => None,
|
|
})
|
|
.collect();
|
|
|
|
let states = &mut state.current_config.input_states;
|
|
|
|
// Remove the source and destinations from all existing links.
|
|
for channel_state in states.values_mut() {
|
|
channel_state
|
|
.linked_channels
|
|
.retain(|channel| *channel != s && !input_destinations.contains(channel));
|
|
}
|
|
|
|
// Link source -> destinations.
|
|
states.get_mut(&s).unwrap().linked_channels = input_destinations;
|
|
}
|
|
|
|
types::Channel::Output(s) => {
|
|
let output_destinations: Vec<types::OutputChannel> = destinations
|
|
.iter()
|
|
.filter_map(|destination| match destination {
|
|
types::Channel::Output(output) => Some(*output),
|
|
types::Channel::Input(_) => None,
|
|
})
|
|
.collect();
|
|
|
|
let states = &mut state.current_config.output_states;
|
|
|
|
// Remove the source and destinations from all existing links.
|
|
for channel_state in states.values_mut() {
|
|
channel_state
|
|
.linked_channels
|
|
.retain(|channel| *channel != s && !output_destinations.contains(channel));
|
|
}
|
|
|
|
// Link source -> destinations.
|
|
states.get_mut(&s).unwrap().linked_channels = output_destinations;
|
|
}
|
|
}
|
|
|
|
Ok(true)
|
|
}
|
|
|
|
// Command `0x3C` (Bypass PEQ)
|
|
pub fn set_bypass_peq(
|
|
&mut self,
|
|
channel: types::Channel,
|
|
bypass: bool,
|
|
) -> Result<bool, DSPError> {
|
|
let raw_channel = mapper::channel_to_raw(channel)?;
|
|
|
|
match self.state.connection {
|
|
types::ConnectionStatus::Connected => {
|
|
if self.mode == types::DSPMode::Normal {
|
|
let request = commands::build_bypass_peq(raw_channel, bypass as u8)?;
|
|
|
|
let resp = self.send_recv(&request)?;
|
|
|
|
match resp {
|
|
types::DeviceResponseType::Acknowledgement(ack) => {
|
|
if !ack.success {
|
|
return Ok(false);
|
|
}
|
|
}
|
|
|
|
other => {
|
|
return Err(DSPError::UnexpectedResponse(format!(
|
|
"Expected Acknowledgement, got {:?}",
|
|
other
|
|
)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
types::ConnectionStatus::Disconnected => {
|
|
return Err(DSPError::NotConnected);
|
|
}
|
|
}
|
|
|
|
let state = self.state_mut();
|
|
|
|
match channel {
|
|
types::Channel::Input(input) => {
|
|
state.current_config.input_state_mut(input).peq_chain.bypass = bypass;
|
|
}
|
|
|
|
types::Channel::Output(output) => {
|
|
state
|
|
.current_config
|
|
.output_state_mut(output)
|
|
.peq_chain
|
|
.bypass = bypass;
|
|
}
|
|
}
|
|
|
|
Ok(true)
|
|
}
|
|
|
|
// Command `0x3D` (Set Channel Name)
|
|
pub fn set_channel_name(
|
|
&mut self,
|
|
channel: types::Channel,
|
|
name: &str,
|
|
) -> Result<bool, DSPError> {
|
|
let raw_channel = mapper::channel_to_raw(channel)?;
|
|
let raw_name = mapper::channel_name_to_raw(name)?;
|
|
|
|
match self.state.connection {
|
|
types::ConnectionStatus::Connected => {
|
|
if self.mode == types::DSPMode::Normal {
|
|
let request = commands::build_store_channel_name(raw_channel, &raw_name)?;
|
|
|
|
let resp = self.send_recv(&request)?;
|
|
|
|
match resp {
|
|
types::DeviceResponseType::Acknowledgement(ack) => {
|
|
if !ack.success {
|
|
return Ok(false);
|
|
}
|
|
}
|
|
|
|
other => {
|
|
return Err(DSPError::UnexpectedResponse(format!(
|
|
"Expected Acknowledgement, got {:?}",
|
|
other
|
|
)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
types::ConnectionStatus::Disconnected => {
|
|
return Err(DSPError::NotConnected);
|
|
}
|
|
}
|
|
|
|
let state = self.state_mut();
|
|
|
|
match channel {
|
|
types::Channel::Input(input) => {
|
|
state.current_config.input_state_mut(input).name =
|
|
mapper::raw_to_channel_name(&raw_name)?;
|
|
}
|
|
|
|
types::Channel::Output(output) => {
|
|
state.current_config.output_state_mut(output).name =
|
|
mapper::raw_to_channel_name(&raw_name)?;
|
|
}
|
|
}
|
|
|
|
Ok(true)
|
|
}
|
|
|
|
// Command `0x3E` (Set Gate Parameters)
|
|
pub fn set_gate(
|
|
&mut self,
|
|
channel: types::InputChannel,
|
|
threshold: f32,
|
|
attack: u16,
|
|
hold: u16,
|
|
release: u16,
|
|
) -> Result<bool, DSPError> {
|
|
let raw_channel = mapper::input_channel_to_raw(channel)?;
|
|
let raw_threshold = mapper::gate_threshold_db_to_raw(threshold)?;
|
|
let raw_attack = mapper::attack_ms_to_raw(attack)?;
|
|
let raw_hold = mapper::hold_ms_to_raw(hold)?;
|
|
let raw_release = mapper::release_ms_to_raw(release)?;
|
|
|
|
match self.state.connection {
|
|
types::ConnectionStatus::Connected => {
|
|
if self.mode == types::DSPMode::Normal {
|
|
let request = commands::build_set_gate(
|
|
raw_channel,
|
|
raw_threshold,
|
|
raw_attack,
|
|
raw_hold,
|
|
raw_release,
|
|
)?;
|
|
|
|
let resp = self.send_recv(&request)?;
|
|
|
|
match resp {
|
|
types::DeviceResponseType::Acknowledgement(ack) => {
|
|
if !ack.success {
|
|
return Ok(false);
|
|
}
|
|
}
|
|
|
|
other => {
|
|
return Err(DSPError::UnexpectedResponse(format!(
|
|
"Expected Acknowledgement, got {:?}",
|
|
other
|
|
)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
types::ConnectionStatus::Disconnected => {
|
|
return Err(DSPError::NotConnected);
|
|
}
|
|
}
|
|
|
|
self.state_mut()
|
|
.current_config
|
|
.input_state_mut(channel)
|
|
.gate = types::Gate {
|
|
attack: mapper::raw_to_attack_ms(raw_attack)?,
|
|
release: mapper::raw_to_release_ms(raw_release)?,
|
|
hold: mapper::raw_to_hold_ms(raw_hold)?,
|
|
threshold: mapper::raw_to_gate_threshold_db(raw_threshold)?,
|
|
};
|
|
|
|
Ok(true)
|
|
}
|
|
|
|
// Command `0x3F` (Set Output Limiter)
|
|
pub fn set_limiter(
|
|
&mut self,
|
|
channel: types::OutputChannel,
|
|
threshold: f32,
|
|
attack: u16,
|
|
release: u16,
|
|
) -> Result<bool, DSPError> {
|
|
let raw_channel = mapper::output_channel_to_raw(channel)?;
|
|
let raw_threshold = mapper::threshold_db_to_raw(threshold)?;
|
|
let raw_attack = mapper::attack_ms_to_raw(attack)?;
|
|
let raw_release = mapper::release_ms_to_raw(release)?;
|
|
|
|
match self.state.connection {
|
|
types::ConnectionStatus::Connected => {
|
|
if self.mode == types::DSPMode::Normal {
|
|
let request = commands::build_set_limiter(
|
|
raw_channel,
|
|
raw_threshold,
|
|
raw_attack,
|
|
raw_release,
|
|
)?;
|
|
|
|
let resp = self.send_recv(&request)?;
|
|
|
|
match resp {
|
|
types::DeviceResponseType::Acknowledgement(ack) => {
|
|
if !ack.success {
|
|
return Ok(false);
|
|
}
|
|
}
|
|
|
|
other => {
|
|
return Err(DSPError::UnexpectedResponse(format!(
|
|
"Expected Acknowledgement, got {:?}",
|
|
other
|
|
)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
types::ConnectionStatus::Disconnected => {
|
|
return Err(DSPError::NotConnected);
|
|
}
|
|
}
|
|
|
|
self.state_mut()
|
|
.current_config
|
|
.output_state_mut(channel)
|
|
.limiter = types::Limiter {
|
|
threshold: mapper::raw_to_threshold_db(raw_threshold)?,
|
|
attack: mapper::raw_to_attack_ms(raw_attack)?,
|
|
release: mapper::raw_to_release_ms(raw_release)?,
|
|
};
|
|
|
|
Ok(true)
|
|
}
|
|
|
|
// Command `0x40` (Get Meter Level)
|
|
pub fn get_meter_levels(
|
|
&mut self,
|
|
) -> Result<(types::InputMeters, types::OutputMeters), DSPError> {
|
|
match self.state.connection {
|
|
types::ConnectionStatus::Connected => {
|
|
if self.mode == types::DSPMode::Demo {
|
|
self.input_meters = random_input_meters();
|
|
self.output_meters = random_output_meters();
|
|
|
|
return Ok((self.input_meters.clone(), self.output_meters.clone()));
|
|
}
|
|
|
|
let request = commands::build_get_meter_levels();
|
|
|
|
let resp = self.send_recv(&request)?;
|
|
|
|
match resp {
|
|
types::DeviceResponseType::Meters(inputs, outputs) => {
|
|
self.input_meters = inputs.clone();
|
|
self.output_meters = outputs.clone();
|
|
Ok((inputs, outputs))
|
|
}
|
|
|
|
other => Err(DSPError::UnexpectedResponse(format!(
|
|
"Expected Meters, got {:?}",
|
|
other
|
|
))),
|
|
}
|
|
}
|
|
|
|
types::ConnectionStatus::Disconnected => Err(DSPError::NotConnected),
|
|
}
|
|
}
|
|
|
|
// Command `0x41` (Set Matrix Gain)
|
|
pub fn set_matrix_gain(
|
|
&mut self,
|
|
input_channel: types::InputChannel,
|
|
output_channel: types::OutputChannel,
|
|
gain: f32,
|
|
) -> Result<bool, DSPError> {
|
|
let raw_input_channel = mapper::input_channel_to_raw(input_channel)?;
|
|
let raw_output_channel = mapper::output_channel_to_raw(output_channel)?;
|
|
let raw_gain = mapper::matrix_gain_db_to_raw(gain)?;
|
|
|
|
match self.state.connection {
|
|
types::ConnectionStatus::Connected => {
|
|
if self.mode == types::DSPMode::Normal {
|
|
let request = commands::build_matrix_gain(
|
|
raw_input_channel,
|
|
raw_output_channel,
|
|
raw_gain,
|
|
)?;
|
|
|
|
let resp = self.send_recv(&request)?;
|
|
|
|
match resp {
|
|
types::DeviceResponseType::Acknowledgement(ack) => {
|
|
if !ack.success {
|
|
return Ok(false);
|
|
}
|
|
}
|
|
|
|
other => {
|
|
return Err(DSPError::UnexpectedResponse(format!(
|
|
"Expected Acknowledgement, got {:?}",
|
|
other
|
|
)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
types::ConnectionStatus::Disconnected => {
|
|
return Err(DSPError::NotConnected);
|
|
}
|
|
}
|
|
|
|
self.state_mut()
|
|
.current_config
|
|
.output_state_mut(output_channel)
|
|
.matrix_routes
|
|
.gains
|
|
.insert(input_channel, mapper::raw_to_matrix_gain(raw_gain)?);
|
|
|
|
Ok(true)
|
|
}
|
|
|
|
// Command `0x48` (Set GEQ Band)
|
|
pub fn set_geq_band(
|
|
&mut self,
|
|
input_channel: types::InputChannel,
|
|
frequency_band: types::DiscreteFrequency,
|
|
gain: f32,
|
|
) -> Result<bool, DSPError> {
|
|
let raw_input_channel = mapper::input_channel_to_raw(input_channel)?;
|
|
let raw_frequency_band = mapper::frequency_value_to_raw(frequency_band)?;
|
|
let raw_gain = mapper::eq_gain_db_to_raw(gain)?;
|
|
|
|
match self.state.connection {
|
|
types::ConnectionStatus::Connected => {
|
|
if self.mode == types::DSPMode::Normal {
|
|
let request = commands::build_set_geq_band(
|
|
raw_input_channel,
|
|
raw_frequency_band,
|
|
raw_gain,
|
|
)?;
|
|
|
|
let resp = self.send_recv(&request)?;
|
|
|
|
match resp {
|
|
types::DeviceResponseType::Acknowledgement(ack) => {
|
|
if !ack.success {
|
|
return Ok(false);
|
|
}
|
|
}
|
|
|
|
other => {
|
|
return Err(DSPError::UnexpectedResponse(format!(
|
|
"Expected Acknowledgement, got {:?}",
|
|
other
|
|
)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
types::ConnectionStatus::Disconnected => {
|
|
return Err(DSPError::NotConnected);
|
|
}
|
|
}
|
|
|
|
self.state_mut()
|
|
.current_config
|
|
.input_state_mut(input_channel)
|
|
.geq
|
|
.gains
|
|
.insert(
|
|
mapper::raw_to_frequency_value(raw_frequency_band)?,
|
|
mapper::raw_to_eq_gain(raw_gain)?,
|
|
);
|
|
|
|
Ok(true)
|
|
}
|
|
|
|
// Command `0x49` (Set GEQ Bypass)
|
|
pub fn set_geq_bypass(
|
|
&mut self,
|
|
input_channel: types::InputChannel,
|
|
bypass: bool,
|
|
) -> Result<bool, DSPError> {
|
|
let raw_input_channel = mapper::input_channel_to_raw(input_channel)?;
|
|
|
|
match self.state.connection {
|
|
types::ConnectionStatus::Connected => {
|
|
if self.mode == types::DSPMode::Normal {
|
|
let request = commands::build_set_geq_bypass(raw_input_channel, bypass as u8)?;
|
|
|
|
let resp = self.send_recv(&request)?;
|
|
|
|
match resp {
|
|
types::DeviceResponseType::Acknowledgement(ack) => {
|
|
if !ack.success {
|
|
return Ok(false);
|
|
}
|
|
}
|
|
|
|
other => {
|
|
return Err(DSPError::UnexpectedResponse(format!(
|
|
"Expected Acknowledgement, got {:?}",
|
|
other
|
|
)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
types::ConnectionStatus::Disconnected => {
|
|
return Err(DSPError::NotConnected);
|
|
}
|
|
}
|
|
|
|
self.state_mut()
|
|
.current_config
|
|
.input_state_mut(input_channel)
|
|
.geq
|
|
.bypass = bypass;
|
|
|
|
Ok(true)
|
|
}
|
|
|
|
fn read_config_state(&mut self) -> Result<types::DSPConfigState, DSPError> {
|
|
let mut raw = Vec::new();
|
|
|
|
for i in 0..=CONFIG_CHUNK_INDEX_MAX {
|
|
let chunk = self.get_config_chunk(i)?;
|
|
raw.extend_from_slice(&chunk.data);
|
|
}
|
|
|
|
let config = decode_config(&raw)?;
|
|
|
|
if !self.request_acknowledgement()? {
|
|
return Err(DSPError::OperationFailed("DSP initialization".into()));
|
|
}
|
|
|
|
Ok(config)
|
|
}
|
|
}
|
|
|
|
impl Drop for DSP408 {
|
|
fn drop(&mut self) {
|
|
if self.connected() {
|
|
let _ = self.disconnect();
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for DSP408 {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
f.debug_struct("DSP408")
|
|
.field("host", &self.connection.host)
|
|
.field("port", &self.connection.port)
|
|
.field("device_id", &self.connection.device_id)
|
|
.field("connected", &self.connected())
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "hardware-tests")]
|
|
#[test]
|
|
fn full_command_sweep_against_real_hardware() {
|
|
use std::sync::Once;
|
|
use std::{env, str::FromStr};
|
|
|
|
static INIT: Once = Once::new();
|
|
|
|
fn init_test_env() {
|
|
INIT.call_once(|| {
|
|
dotenvy::dotenv().ok();
|
|
});
|
|
}
|
|
|
|
fn dsp_host() -> IpAddr {
|
|
IpAddr::from_str(&env::var("DSP_HOST").unwrap_or_else(|_| "192.168.49.30".into()))
|
|
.expect("DSP_HOST must be a valid IP")
|
|
}
|
|
|
|
fn dsp_port() -> u16 {
|
|
env::var("DSP_PORT")
|
|
.ok()
|
|
.and_then(|v| v.parse().ok())
|
|
.unwrap_or(9761)
|
|
}
|
|
|
|
fn dsp_device_id() -> u8 {
|
|
env::var("DSP_DEVICE_ID")
|
|
.ok()
|
|
.and_then(|v| v.parse().ok())
|
|
.unwrap_or(1)
|
|
}
|
|
|
|
fn destructive_allowed() -> bool {
|
|
matches!(
|
|
env::var("DSP_ALLOW_DESTRUCTIVE")
|
|
.unwrap_or_default()
|
|
.as_str(),
|
|
"1" | "true" | "yes"
|
|
)
|
|
}
|
|
|
|
init_test_env();
|
|
|
|
let host = dsp_host();
|
|
let port = dsp_port();
|
|
let device_id = dsp_device_id();
|
|
|
|
let mut dsp = DSP408::new(host, port, device_id).expect("invalid connection params");
|
|
|
|
// ---------- Connect ----------
|
|
dsp.connect().expect("connect() should succeed");
|
|
assert!(
|
|
dsp.connected(),
|
|
"device should report connected after connect()"
|
|
);
|
|
|
|
{
|
|
let state = dsp.state();
|
|
|
|
assert!(!state.name.is_empty(), "device name should be non-empty");
|
|
assert!(
|
|
!state.presets.names.is_empty(),
|
|
"preset name list should be populated"
|
|
);
|
|
assert!(
|
|
!state.current_config.input_states.is_empty(),
|
|
"input map should be populated"
|
|
);
|
|
assert!(
|
|
!state.current_config.output_states.is_empty(),
|
|
"output map should be populated"
|
|
);
|
|
}
|
|
|
|
// ---------- Delay unit ----------
|
|
let ok = dsp
|
|
.set_delay_unit(types::DelayUnit::Millisecond)
|
|
.expect("set_delay_unit");
|
|
assert!(ok);
|
|
assert_eq!(
|
|
dsp.state().current_config.delay_unit,
|
|
types::DelayUnit::Millisecond
|
|
);
|
|
|
|
// ---------- Input channel exercise ----------
|
|
let input = types::InputChannel::InA;
|
|
let in_ch = types::Channel::Input(input);
|
|
|
|
let ok = dsp.set_mute(in_ch, true).expect("set_mute");
|
|
assert!(ok);
|
|
assert!(dsp.state().current_config.input_states[&input].mute);
|
|
|
|
let ok = dsp.set_mute(in_ch, false).expect("unmute");
|
|
assert!(ok);
|
|
assert!(!dsp.state().current_config.input_states[&input].mute);
|
|
|
|
let ok = dsp.set_channel_gain(in_ch, -3.0).expect("set_channel_gain");
|
|
assert!(ok);
|
|
assert!((dsp.state().current_config.input_states[&input].gain - (-3.0)).abs() < 0.01);
|
|
|
|
let ok = dsp
|
|
.set_channel_inverse_gain(in_ch, true)
|
|
.expect("set_channel_inverse_gain");
|
|
assert!(ok);
|
|
assert!(dsp.state().current_config.input_states[&input].phase_inverted);
|
|
dsp.set_channel_inverse_gain(in_ch, false)
|
|
.expect("revert phase");
|
|
|
|
let ok = dsp
|
|
.set_delay(in_ch, 1.5, types::DelayUnit::Millisecond)
|
|
.expect("set_delay");
|
|
assert!(ok);
|
|
|
|
let ok = dsp
|
|
.set_channel_name(in_ch, "Test InA")
|
|
.expect("set_channel_name");
|
|
assert!(ok);
|
|
assert_eq!(
|
|
dsp.state().current_config.input_states[&input].name,
|
|
"Test InA"
|
|
);
|
|
|
|
let ok = dsp
|
|
.set_low_pass(in_ch, 120.0, types::CrossoverFilter::Bw24)
|
|
.expect("set_low_pass");
|
|
assert!(ok);
|
|
|
|
let ok = dsp
|
|
.set_high_pass(in_ch, 80.0, types::CrossoverFilter::Bw24)
|
|
.expect("set_high_pass");
|
|
assert!(ok);
|
|
|
|
let ok = dsp
|
|
.set_peq_band(
|
|
in_ch,
|
|
types::PEQBand::B1,
|
|
2.0,
|
|
1000.0,
|
|
1.0,
|
|
types::PEQFilter::Peak,
|
|
false,
|
|
)
|
|
.expect("set_peq_band");
|
|
assert!(ok);
|
|
|
|
let ok = dsp.set_bypass_peq(in_ch, true).expect("set_bypass_peq");
|
|
assert!(ok);
|
|
dsp.set_bypass_peq(in_ch, false).expect("un-bypass peq");
|
|
|
|
let ok = dsp.set_gate(input, -40.0, 5, 100, 200).expect("set_gate");
|
|
assert!(ok);
|
|
|
|
let ok = dsp
|
|
.set_geq_band(input, types::DiscreteFrequency::F100, 1.0)
|
|
.expect("set_geq_band");
|
|
assert!(ok);
|
|
|
|
let ok = dsp.set_geq_bypass(input, true).expect("set_geq_bypass");
|
|
assert!(ok);
|
|
dsp.set_geq_bypass(input, false).expect("un-bypass geq");
|
|
|
|
let ok = dsp
|
|
.set_input_source(types::InputSource {
|
|
r#type: types::InputType::Analog, // adjust to your enum
|
|
frequency: types::DiscreteFrequency::F1000, // adjust to your enum
|
|
})
|
|
.expect("set_input_source");
|
|
assert!(ok);
|
|
|
|
// // ---------- Output channel exercise ----------
|
|
let output = types::OutputChannel::Out1;
|
|
let out_ch = types::Channel::Output(output);
|
|
|
|
let ok = dsp
|
|
.set_compressor(output, types::Ratio::Limit, 5, 50, 2, -12.0)
|
|
.expect("set_compressor");
|
|
assert!(ok);
|
|
|
|
let ok = dsp.set_limiter(output, -3.0, 1, 50).expect("set_limiter");
|
|
assert!(ok);
|
|
|
|
let ok = dsp
|
|
.set_matrix_gain(input, output, -6.0)
|
|
.expect("set_matrix_gain");
|
|
assert!(ok);
|
|
|
|
let ok = dsp
|
|
.set_matrix_routing(output, vec![input])
|
|
.expect("set_matrix_routing");
|
|
assert!(ok);
|
|
assert_eq!(
|
|
dsp.state().current_config.output_states[&output]
|
|
.matrix_routes
|
|
.connected,
|
|
vec![input]
|
|
);
|
|
|
|
// // ---------- Copy / link channels ----------
|
|
let other_output = types::OutputChannel::Out2;
|
|
let ok = dsp
|
|
.copy_channel(out_ch, types::Channel::Output(other_output))
|
|
.expect("copy_channel");
|
|
assert!(ok);
|
|
|
|
let ok = dsp
|
|
.link_channels(out_ch, vec![types::Channel::Output(other_output)])
|
|
.expect("link_channels");
|
|
assert!(ok);
|
|
|
|
// // ---------- Preset name / modification status ----------
|
|
let ok = dsp
|
|
.set_current_preset_name("Integration Te")
|
|
.expect("set_current_preset_name");
|
|
assert!(ok);
|
|
|
|
let status = dsp
|
|
.get_preset_modification_status()
|
|
.expect("get_preset_modification_status");
|
|
println!("Preset modification status: {:?}", status);
|
|
|
|
let name = dsp.get_preset_name(1).expect("get_preset_name");
|
|
println!("Preset 1 name: {}", name);
|
|
|
|
// // ---------- Meters ----------
|
|
let meters = dsp.get_meter_levels().expect("get_meter_levels");
|
|
println!("Meters: {:?}", meters);
|
|
|
|
// // ---------- Destructive ops (opt-in only) ----------
|
|
if destructive_allowed() {
|
|
let ok = dsp
|
|
.store_current_configuration(1, false)
|
|
.expect("store_current_configuration");
|
|
assert!(ok);
|
|
assert!(dsp.state().presets.modified[1]);
|
|
|
|
let ok = dsp.recall_preset(1).expect("recall_preset");
|
|
assert!(ok);
|
|
assert_eq!(dsp.state().presets.current_index, 1);
|
|
|
|
// factory_reset intentionally left commented out — wipes the unit.
|
|
// let ok = dsp.factory_reset(true).expect("factory_reset");
|
|
// assert!(ok);
|
|
} else {
|
|
println!("Skipping destructive ops (set DSP_ALLOW_DESTRUCTIVE=1 to enable)");
|
|
}
|
|
|
|
// ---------- Disconnect ----------
|
|
assert!(dsp.disconnect().is_ok());
|
|
assert!(!dsp.connected(), "device should report disconnected");
|
|
}
|