From 605d23f5297879002e8b65a415648c1c332203ac Mon Sep 17 00:00:00 2001 From: LucasDLTG Date: Mon, 20 Jul 2026 23:14:26 +0200 Subject: [PATCH] fix: refactor to avoid circular dependencies --- .readme.md | 1 + src/dsp408/client/dsp408.rs | 415 ++------------------- src/dsp408/client/mod.rs | 3 - src/dsp408/mapper/channel.rs | 128 ++++--- src/dsp408/{client => mapper}/constants.rs | 19 +- src/dsp408/mapper/encoding.rs | 5 +- src/dsp408/mapper/global.rs | 63 ++-- src/dsp408/mapper/mod.rs | 1 + src/dsp408/mapper/response.rs | 35 +- src/dsp408/mod.rs | 7 +- src/dsp408/protocol/commands.rs | 10 +- src/dsp408/protocol/config.rs | 349 +++++++++++++++++ src/dsp408/protocol/constants.rs | 5 + src/dsp408/protocol/frame.rs | 9 +- src/dsp408/protocol/mod.rs | 10 +- src/dsp408/protocol/reader.rs | 39 ++ src/dsp408/protocol/validators.rs | 14 +- src/dsp408/{client => }/types.rs | 0 src/lib.rs | 8 +- src/main.rs | 4 +- 20 files changed, 603 insertions(+), 522 deletions(-) create mode 100644 .readme.md rename src/dsp408/{client => mapper}/constants.rs (74%) create mode 100644 src/dsp408/protocol/config.rs create mode 100644 src/dsp408/protocol/reader.rs rename src/dsp408/{client => }/types.rs (100%) diff --git a/.readme.md b/.readme.md new file mode 100644 index 0000000..61bccd3 --- /dev/null +++ b/.readme.md @@ -0,0 +1 @@ +# verify import order diff --git a/src/dsp408/client/dsp408.rs b/src/dsp408/client/dsp408.rs index 1830df9..ad8d99e 100644 --- a/src/dsp408/client/dsp408.rs +++ b/src/dsp408/client/dsp408.rs @@ -1,16 +1,30 @@ use std::{ - collections::HashMap, io::{Read, Write}, net::{IpAddr, SocketAddr, TcpStream}, sync::{Mutex, OnceLock}, time::Duration, fmt, + collections::HashMap, + fmt, + io::{Read, Write}, + net::{IpAddr, SocketAddr, TcpStream}, + sync::{Mutex, OnceLock}, + time::Duration, }; -use strum::{EnumCount, IntoEnumIterator}; +use strum::IntoEnumIterator; -use crate::common::errors::{DSPError, ValidationError}; -use crate::dsp408::client::types; -use crate::dsp408::client::constants; -use crate::dsp408::protocol::frame::extract_frame; -use crate::dsp408::protocol::commands; -use crate::dsp408::mapper::response::map_response; -use crate::dsp408::mapper; +use crate::{ + common::errors::{DSPError, ValidationError}, + dsp408::{ + mapper::{ + self, + constants::{PRESET_INDEX_MAX, CONFIG_CHUNK_INDEX_MAX}, + response::map_response, + }, + protocol::{ + commands, + config::decode_config, + frame::extract_frame, + }, + types, + }, +}; pub const SOCKET_TIMEOUT: Duration = Duration::from_secs(20); pub const RECV_BUFSIZE: usize = 4096; @@ -231,9 +245,9 @@ impl DSP408 { 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(constants::PRESET_INDEX_MAX); + let mut preset_names = Vec::with_capacity(PRESET_INDEX_MAX); - for i in 0..constants::PRESET_INDEX_MAX { + for i in 0..PRESET_INDEX_MAX { preset_names.push( self.get_preset_name(i + 1)? ); @@ -805,6 +819,7 @@ impl DSP408 { } // Command `0x33` (Set PEQ Band) + #[allow(clippy::too_many_arguments)] pub fn set_peq_band( &mut self, channel: types::Channel, @@ -847,7 +862,7 @@ impl DSP408 { gain: gain_db, frequency: freq_hz, q, - filter_type: filter_type, + filter_type, bypass, }; @@ -1607,12 +1622,12 @@ impl DSP408 { fn read_config_state(&mut self) -> Result { let mut raw = Vec::new(); - for i in 0..=constants::CONFIG_CHUNK_INDEX_MAX { + for i in 0..=CONFIG_CHUNK_INDEX_MAX { let chunk = self.get_config_chunk(i)?; raw.extend_from_slice(&chunk.data); } - let config = build_config_from_chunk(&raw)?; + let config = decode_config(&raw)?; if !self.request_acknowledgement()? { return Err(DSPError::OperationFailed( @@ -1641,375 +1656,3 @@ impl fmt::Debug for DSP408 { .finish() } } - -struct BinaryReader<'a> { - data: &'a [u8], -} - -impl<'a> BinaryReader<'a> { - fn new(data: &'a [u8]) -> Self { - Self { data } - } - - fn pop(&mut self, n: usize) -> Result<&'a [u8], DSPError> { - if self.data.len() < n { - return Err(DSPError::InvalidRequest( - "buffer underflow".into() - )); - } - - let (value, rest) = self.data.split_at(n); - self.data = rest; - - Ok(value) - } - - fn read_u8(&mut self) -> Result { - Ok(self.pop(1)?[0]) - } - - fn read_u16(&mut self) -> Result { - Ok(u16::from_le_bytes( - self.pop(2)?.try_into().unwrap() - )) - } - - fn skip(&mut self, n: usize) -> Result<(), DSPError> { - self.pop(n)?; - Ok(()) - } -} - -fn apply_flag( - r: &mut BinaryReader, - targets: &mut [T], - setter: impl Fn(&mut T, bool), -) -> Result<(), DSPError> { - let bitmap = r.read_u16()?; - - for (index, target) in targets.iter_mut().enumerate() { - setter(target, bitmap & (1 << index) != 0); - } - - Ok(()) -} - -fn read_gate(r: &mut BinaryReader) -> Result { - Ok(types::Gate { - attack: mapper::raw_to_attack_ms(r.read_u16()?)?, - release: mapper::raw_to_release_ms(r.read_u16()?)?, - hold: mapper::raw_to_hold_ms(r.read_u16()?)?, - threshold: mapper::raw_to_gate_threshold_db(r.read_u16()?)?, - }) -} - -fn read_geq( - r: &mut BinaryReader, -) -> Result { - let gains = types::DiscreteFrequency::iter() - .map(|_| { - let raw = r.read_u16()?; - Ok(mapper::raw_to_eq_gain(raw)?) - }) - .collect::, DSPError>>()?; - - Ok(types::GraphicEQ { - gains, - bypass: false, - }) -} - -fn read_peq_chain( - r: &mut BinaryReader, - band_count: usize, -) -> Result { - let bands = (0..band_count) - .map(|_| { - Ok(types::PEQ { - gain: mapper::raw_to_eq_gain(r.read_u16()?)?, - frequency: mapper::raw_to_frequency_hz(r.read_u16()?)?, - q: mapper::raw_to_peq_q(r.read_u8()?)?, - filter_type: mapper::raw_to_peq_filter(r.read_u8()?)?, - bypass: false, - }) - }) - .collect::, DSPError>>()?; - - Ok(types::PEQChain { - bands, - bypass: false, - }) -} - -fn read_crossover( - r: &mut BinaryReader, -) -> Result { - let hp_freq = r.read_u16()?; - let lp_freq = r.read_u16()?; - let hp_filter = r.read_u8()?; - let lp_filter = r.read_u8()?; - Ok(types::CrossoverFilters { - high_pass: types::Crossover { - frequency: mapper::raw_to_frequency_hz(hp_freq)?, - slope: mapper::raw_to_crossover_filter(hp_filter)?, - }, - low_pass: types::Crossover { - frequency: mapper::raw_to_frequency_hz(lp_freq)?, - slope: mapper::raw_to_crossover_filter(lp_filter)?, - }, - }) -} - -fn read_compressor( - r: &mut BinaryReader, -) -> Result { - Ok(types::Compressor { - ratio: mapper::raw_to_ratio(r.read_u16()?)?, - attack: mapper::raw_to_attack_ms(r.read_u16()?)?, - release: mapper::raw_to_release_ms(r.read_u16()?)?, - knee: mapper::raw_to_knee_db(r.read_u16()?)?, - threshold: mapper::raw_to_threshold_db(r.read_u16()?)?, - }) -} - -fn read_limiter( - r: &mut BinaryReader, -) -> Result { - let attack = mapper::raw_to_attack_ms(r.read_u16()?)?; - let release = mapper::raw_to_release_ms(r.read_u16()?)?; - - r.skip(2)?; - - let threshold = mapper::raw_to_threshold_db(r.read_u16()?)?; - - Ok(types::Limiter { - attack, - release, - threshold, - }) -} - -fn build_config_from_chunk(payload: &[u8]) -> Result { - let mut r = BinaryReader::new(payload); - - r.skip(2)?; // flags (unused) - - let _preset_name = mapper::raw_to_preset_name(r.pop(constants::PRESET_NAME_LENGTH)?)?; - - let mut input_channels = vec![types::InputChannelState::default(); types::InputChannel::COUNT]; - - let mut output_channels = vec![types::OutputChannelState::default(); types::OutputChannel::COUNT]; - - let mut input_delays_raw: Vec = Vec::new(); - let mut output_delays_raw: Vec = Vec::new(); - - // ============================ - // INPUT CHANNELS - // ============================ - - for (channel, inp) in types::InputChannel::iter().zip(input_channels.iter_mut()) { - inp.name = mapper::raw_to_channel_name(r.pop(constants::CHANNEL_NAME_LENGTH)?)?; - - inp.gate = read_gate(&mut r)?; - - inp.geq = read_geq(&mut r)?; - - inp.peq_chain = read_peq_chain(&mut r, constants::PEQ_INPUT_BAND_COUNT)?; - - inp.crossover = read_crossover(&mut r)?; - - inp.gain = mapper::raw_to_gain(r.read_u16()?)?; - - inp.phase_inverted = r.read_u8()? != 0; - - r.skip(2)?; // reserved - - input_delays_raw.push(r.read_u8()?); - - r.skip(1)?; - - inp.linked_channels = mapper::raw_to_link_channels(mapper::channel_to_raw(types::Channel::Input(channel))?, r.read_u8()?)?; - } - - // ============================ - // OUTPUT CHANNELS - // ============================ - - for (channel, out) in types::OutputChannel::iter().zip(output_channels.iter_mut()) { - out.name = mapper::raw_to_channel_name(r.pop(constants::CHANNEL_NAME_LENGTH)?)?; - - r.skip(1)?; - - out.matrix_routes = types::MatrixRoutes { - connected: - mapper::raw_to_matrix( - r.read_u8()? - )?, - - gains: (0..4) - .map(|_| { - let raw = r.read_u16()?; - Ok::(mapper::raw_to_matrix_gain(raw)?) - }) - .collect::, _>>()?, - }; - - out.crossover = read_crossover(&mut r)?; - - out.peq_chain = read_peq_chain(&mut r, constants::PEQ_OUTPUT_BAND_COUNT)?; - - out.compressor = read_compressor(&mut r)?; - - out.limiter = read_limiter(&mut r)?; - - out.gain = mapper::raw_to_gain(r.read_u16()?)?; - - out.phase_inverted = r.read_u8()? != 0; - - r.skip(2)?; // reserved - - output_delays_raw.push(r.read_u8()?); - - r.skip(1)?; - - out.linked_channels = mapper::raw_to_link_channels(mapper::channel_to_raw(types::Channel::Output(channel))?, r.read_u8()?)?; - } - - // ============================ - // MUTE FLAGS - // ============================ - - apply_flag( - &mut r, - &mut input_channels, - |ch, value| ch.mute = value, - )?; - - apply_flag( - &mut r, - &mut output_channels, - |ch, value| ch.mute = value, - )?; - - // ============================ - // PEQ BYPASS FLAGS - // ============================ - - for inp in &mut input_channels { - apply_flag( - &mut r, - inp.peq_chain.bands.as_mut_slice(), - |band, value| band.bypass = value, - )?; - } - - for out in &mut output_channels { - apply_flag( - &mut r, - out.peq_chain.bands.as_mut_slice(), - |band, value| band.bypass = value, - )?; - } - - // ============================ - // INPUT SOURCE - // ============================ - - let input_source = types::InputSource { - r#type: mapper::raw_to_input_source( - r.read_u16()? as u8, - )?, - - frequency: mapper::raw_to_frequency_value( - r.read_u16()? as u8, - )?, - }; - - // ============================ - // DELAY UNIT - // ============================ - - let delay_unit = mapper::raw_to_delay_unit( - r.read_u16()? as u8, - )?; - - // ============================ - // APPLY DELAYS - // ============================ - - for (inp, raw_delay) in input_channels.iter_mut().zip(input_delays_raw) - { - inp.delay = mapper::raw_to_delay( - raw_delay, - delay_unit, - )?; - } - - for (out, raw_delay) in output_channels.iter_mut().zip(output_delays_raw) - { - out.delay = mapper::raw_to_delay( - raw_delay, - delay_unit, - )?; - } - - // ============================ - // PEQ / GEQ BYPASS - // ============================ - - let input_peq_bitmap = r.read_u16()?; - - for (index, inp) in input_channels.iter_mut().enumerate() { - let bypass = input_peq_bitmap & (1 << index) != 0; - - for peq in inp.peq_chain.bands.iter_mut() { - peq.bypass = bypass; - } - } - - - let output_peq_bitmap = r.read_u16()?; - - for (index, out) in output_channels.iter_mut().enumerate() { - let bypass = output_peq_bitmap & (1 << index) != 0; - - for peq in out.peq_chain.bands.iter_mut() { - peq.bypass = bypass; - } - } - - let bitmap = r.read_u16()?; - - for (index, inp) in input_channels.iter_mut().enumerate() { - inp.geq.bypass = bitmap & (1 << index) != 0; - } - - // ============================ - // BUILD CHANNEL MAP - // ============================ - - let mut input_states = HashMap::new(); - - for (channel, state) in types::InputChannel::iter().zip(input_channels) { - input_states.insert( - channel, - state, - ); - } - - let mut output_states = HashMap::new(); - - for (channel, state) in types::OutputChannel::iter().zip(output_channels) { - output_states.insert( - channel, - state, - ); - } - - Ok(types::DSPConfigState { - input_states, - output_states, - delay_unit: delay_unit, - input_source: input_source, - }) -} diff --git a/src/dsp408/client/mod.rs b/src/dsp408/client/mod.rs index a53a705..0ea48cb 100644 --- a/src/dsp408/client/mod.rs +++ b/src/dsp408/client/mod.rs @@ -1,5 +1,2 @@ -pub mod constants; -pub mod types; pub mod dsp408; - pub use dsp408::DSP408; \ No newline at end of file diff --git a/src/dsp408/mapper/channel.rs b/src/dsp408/mapper/channel.rs index e9a536f..194411a 100644 --- a/src/dsp408/mapper/channel.rs +++ b/src/dsp408/mapper/channel.rs @@ -1,13 +1,31 @@ use strum::IntoEnumIterator; -use crate::common::validator::{validate_ascii_padded, validate_range}; -use crate::dsp408::client::constants::{self as client_constants}; -use crate::dsp408::protocol::constants as protocol_constants; -use crate::dsp408::protocol::validators; -use crate::common::errors::{MapperError, DecodingError, ValidationError}; -use super::{encode_ascii, decode_ascii}; +use crate::{ + common::{ + errors::{ + DecodingError, + MapperError, + ValidationError, + }, + validator::{ + validate_ascii_padded, + validate_range, + }, + }, + dsp408::{ + protocol::{ + constants as protocol_constants, + validators, + }, + types, + }, +}; -use crate::dsp408::client::types; +use super::{ + constants as mapper_constants, + decode_ascii, + encode_ascii, +}; // ------ CHANNEL ------ @@ -76,12 +94,12 @@ pub fn output_channel_to_raw(channel: types::OutputChannel) -> Result Result, MapperError> { validate_ascii_padded( name.as_bytes(), - client_constants::CHANNEL_NAME_LENGTH, - client_constants::CHANNEL_NAME_PADDING, + protocol_constants::CHANNEL_NAME_LENGTH, + protocol_constants::CHANNEL_NAME_PADDING, "Channel name", )?; - Ok(encode_ascii(name, client_constants::CHANNEL_NAME_LENGTH, client_constants::CHANNEL_NAME_PADDING)?) + Ok(encode_ascii(name, protocol_constants::CHANNEL_NAME_LENGTH, protocol_constants::CHANNEL_NAME_PADDING)?) } pub fn raw_to_channel_name(raw_name: &[u8]) -> Result { @@ -95,7 +113,7 @@ pub fn raw_to_channel_name(raw_name: &[u8]) -> Result { pub fn attack_ms_to_raw(value: u16) -> Result { validate_range( value, - client_constants::ATTACK_MS_RANGE, + mapper_constants::ATTACK_MS_RANGE, || format!("Invalid attack: {:#06x}", value), )?; @@ -113,7 +131,7 @@ pub fn raw_to_attack_ms(value: u16) -> Result { pub fn release_ms_to_raw(value: u16) -> Result { validate_range( value, - client_constants::RELEASE_MS_RANGE, + mapper_constants::RELEASE_MS_RANGE, || format!("Invalid release: {:#06x}", value), )?; @@ -131,7 +149,7 @@ pub fn raw_to_release_ms(value: u16) -> Result { pub fn hold_ms_to_raw(hold: u16) -> Result { validate_range( hold, - client_constants::HOLD_MS_RANGE, + mapper_constants::HOLD_MS_RANGE, || format!("Invalid hold: {:#06x}", hold), )?; @@ -149,19 +167,19 @@ pub fn raw_to_hold_ms(hold: u16) -> Result { pub fn gate_threshold_db_to_raw(db: f32) -> Result { validate_range( db, - client_constants::GATE_THRESHOLD_DB_RANGE, + mapper_constants::GATE_THRESHOLD_DB_RANGE, || format!("Invalid gate threshold: {db}"), )?; let db = (db * 2.0).round() / 2.0; - Ok(((db - *client_constants::GATE_THRESHOLD_DB_RANGE.start()) * 2.0).round() as u16) + Ok(((db - *mapper_constants::GATE_THRESHOLD_DB_RANGE.start()) * 2.0).round() as u16) } pub fn raw_to_gate_threshold_db(value: u16) -> Result { validators::validate_raw_gate_threshold(value)?; - Ok(*client_constants::GATE_THRESHOLD_DB_RANGE.start() + value as f32 / 2.0) + Ok(*mapper_constants::GATE_THRESHOLD_DB_RANGE.start() + value as f32 / 2.0) } // ------ EQ GAIN ------ @@ -172,8 +190,8 @@ fn eq_gain_raw_steps() -> f32 { } fn eq_gain_step_db() -> f32 { - (*client_constants::EQ_GAIN_DB_RANGE.end() - - *client_constants::EQ_GAIN_DB_RANGE.start()) + (*mapper_constants::EQ_GAIN_DB_RANGE.end() + - *mapper_constants::EQ_GAIN_DB_RANGE.start()) / eq_gain_raw_steps() } @@ -184,7 +202,7 @@ fn eq_quantize(db: f32) -> f32 { pub fn eq_gain_db_to_raw(gain: f32) -> Result { validate_range( gain, - client_constants::EQ_GAIN_DB_RANGE, + mapper_constants::EQ_GAIN_DB_RANGE, || format!("Invalid EQ gain: {gain}"), )?; @@ -192,7 +210,7 @@ pub fn eq_gain_db_to_raw(gain: f32) -> Result { Ok( *protocol_constants::EQ_GAIN_RAW_RANGE.start() - + ((gain - *client_constants::EQ_GAIN_DB_RANGE.start()) + + ((gain - *mapper_constants::EQ_GAIN_DB_RANGE.start()) / eq_gain_step_db()) .round() as u16 ) @@ -202,7 +220,7 @@ pub fn raw_to_eq_gain(value: u16) -> Result { validators::validate_raw_eq_gain(value)?; let gain = - *client_constants::EQ_GAIN_DB_RANGE.start() + *mapper_constants::EQ_GAIN_DB_RANGE.start() + (value - *protocol_constants::EQ_GAIN_RAW_RANGE.start()) as f32 * eq_gain_step_db(); @@ -246,30 +264,30 @@ pub fn peq_band_to_raw(band: types::PEQBand, channel: types::Channel) -> Result< // ------ GAIN ------ fn gain_split_raw() -> u16 { - ((client_constants::GAIN_SPLIT_DB - client_constants::GAIN_DB_RANGE.start()) / client_constants::GAIN_LOW_STEP_DB).round() as u16 + ((mapper_constants::GAIN_SPLIT_DB - mapper_constants::GAIN_DB_RANGE.start()) / mapper_constants::GAIN_LOW_STEP_DB).round() as u16 } fn quantize_gain(db: f32) -> f32 { - if db < client_constants::GAIN_SPLIT_DB { - (db / client_constants::GAIN_LOW_STEP_DB).round() * client_constants::GAIN_LOW_STEP_DB + if db < mapper_constants::GAIN_SPLIT_DB { + (db / mapper_constants::GAIN_LOW_STEP_DB).round() * mapper_constants::GAIN_LOW_STEP_DB } else { - (db / client_constants::GAIN_HIGH_STEP_DB).round() * client_constants::GAIN_HIGH_STEP_DB + (db / mapper_constants::GAIN_HIGH_STEP_DB).round() * mapper_constants::GAIN_HIGH_STEP_DB } } pub fn gain_db_to_raw(gain: f32) -> Result { validate_range( gain, - client_constants::GAIN_DB_RANGE, + mapper_constants::GAIN_DB_RANGE, || format!("Invalid channel gain: {gain}"), )?; let gain = quantize_gain(gain); - if gain < client_constants::GAIN_SPLIT_DB { - Ok(((gain - client_constants::GAIN_DB_RANGE.start()) / client_constants::GAIN_LOW_STEP_DB).round() as u16) + if gain < mapper_constants::GAIN_SPLIT_DB { + Ok(((gain - mapper_constants::GAIN_DB_RANGE.start()) / mapper_constants::GAIN_LOW_STEP_DB).round() as u16) } else { - Ok(gain_split_raw() + ((gain - client_constants::GAIN_SPLIT_DB) / client_constants::GAIN_HIGH_STEP_DB).round() as u16) + Ok(gain_split_raw() + ((gain - mapper_constants::GAIN_SPLIT_DB) / mapper_constants::GAIN_HIGH_STEP_DB).round() as u16) } } @@ -277,9 +295,9 @@ pub fn raw_to_gain(raw_gain: u16) -> Result { validators::validate_raw_gain(raw_gain)?; if raw_gain < gain_split_raw() { - Ok(client_constants::GAIN_DB_RANGE.start() + raw_gain as f32 * client_constants::GAIN_LOW_STEP_DB) + Ok(mapper_constants::GAIN_DB_RANGE.start() + raw_gain as f32 * mapper_constants::GAIN_LOW_STEP_DB) } else { - Ok(client_constants::GAIN_SPLIT_DB + (raw_gain - gain_split_raw()) as f32 * client_constants::GAIN_HIGH_STEP_DB) + Ok(mapper_constants::GAIN_SPLIT_DB + (raw_gain - gain_split_raw()) as f32 * mapper_constants::GAIN_HIGH_STEP_DB) } } @@ -289,13 +307,13 @@ pub fn frequency_hz_to_raw(hz: f32) -> Result { validate_range( hz, - client_constants::FREQUENCY_HZ_RANGE, + mapper_constants::FREQUENCY_HZ_RANGE, || format!("Invalid frequency: {hz}"), )?; let device_steps = (protocol_constants::FREQUENCY_RAW_RANGE.end() - protocol_constants::FREQUENCY_RAW_RANGE.start()) as f32; - let raw = ((hz / client_constants::FREQUENCY_HZ_RANGE.start()).ln() / (client_constants::FREQUENCY_HZ_RANGE.end() / client_constants::FREQUENCY_HZ_RANGE.start()).ln() + let raw = ((hz / mapper_constants::FREQUENCY_HZ_RANGE.start()).ln() / (mapper_constants::FREQUENCY_HZ_RANGE.end() / mapper_constants::FREQUENCY_HZ_RANGE.start()).ln() * device_steps) .round(); @@ -308,7 +326,7 @@ pub fn raw_to_frequency_hz(value: u16) -> Result { let device_steps = (protocol_constants::FREQUENCY_RAW_RANGE.end() - protocol_constants::FREQUENCY_RAW_RANGE.start()) as f32; let normalized = (value - protocol_constants::FREQUENCY_RAW_RANGE.start()) as f32 / device_steps; - Ok(client_constants::FREQUENCY_HZ_RANGE.start() * (client_constants::FREQUENCY_HZ_RANGE.end() / client_constants::FREQUENCY_HZ_RANGE.start()).powf(normalized)) + Ok(mapper_constants::FREQUENCY_HZ_RANGE.start() * (mapper_constants::FREQUENCY_HZ_RANGE.end() / mapper_constants::FREQUENCY_HZ_RANGE.start()).powf(normalized)) } // ------ DISCRETE FREQUENCY ------ @@ -481,13 +499,13 @@ pub fn raw_to_crossover_filter(raw: u8) -> Result Result { validate_range( q, - client_constants::peq_q_range(filter), + mapper_constants::peq_q_range(filter), || format!("Invalid PEQ Q: {q}"), )?; let raw_range = (protocol_constants::PEQ_Q_RAW_RANGE.end() - protocol_constants::PEQ_Q_RAW_RANGE.start()) as f32; - Ok(((q / client_constants::PEQ_Q_RANGE.start()).ln() / (client_constants::PEQ_Q_RANGE.end() / client_constants::PEQ_Q_RANGE.start()).ln() * raw_range).round() as u8 + Ok(((q / mapper_constants::PEQ_Q_RANGE.start()).ln() / (mapper_constants::PEQ_Q_RANGE.end() / mapper_constants::PEQ_Q_RANGE.start()).ln() * raw_range).round() as u8 + protocol_constants::PEQ_Q_RAW_RANGE.start()) } @@ -497,7 +515,7 @@ pub fn raw_to_peq_q(value: u8) -> Result { let raw_range = (protocol_constants::PEQ_Q_RAW_RANGE.end() - protocol_constants::PEQ_Q_RAW_RANGE.start()) as f32; let normalized = (value - protocol_constants::PEQ_Q_RAW_RANGE.start()) as f32 / raw_range; - Ok(client_constants::PEQ_Q_RANGE.start() * (client_constants::PEQ_Q_RANGE.end() / client_constants::PEQ_Q_RANGE.start()).powf(normalized)) + Ok(mapper_constants::PEQ_Q_RANGE.start() * (mapper_constants::PEQ_Q_RANGE.end() / mapper_constants::PEQ_Q_RANGE.start()).powf(normalized)) } // ------ FILTER ------ @@ -615,7 +633,7 @@ pub fn raw_to_ratio(value: u16) -> Result { pub fn knee_db_to_raw(db: u16) -> Result { validate_range( db, - client_constants::KNEE_DB_RANGE, + mapper_constants::KNEE_DB_RANGE, || format!("Invalid compressor knee: {db}"), )?; @@ -632,18 +650,18 @@ pub fn raw_to_knee_db(value: u16) -> Result { pub fn threshold_db_to_raw(db: f32) -> Result { validate_range( db, - client_constants::THRESHOLD_DB_RANGE, + mapper_constants::THRESHOLD_DB_RANGE, || format!("Invalid compressor threshold: {db}"), )?; let db = (db * 2.0).round() / 2.0; - Ok(((db - client_constants::THRESHOLD_DB_RANGE.start()) * 2.0).round() as u16) + Ok(((db - mapper_constants::THRESHOLD_DB_RANGE.start()) * 2.0).round() as u16) } pub fn raw_to_threshold_db(value: u16) -> Result { validators::validate_raw_threshold(value)?; - Ok(client_constants::THRESHOLD_DB_RANGE.start() + (value as f32 / 2.0)) + Ok(mapper_constants::THRESHOLD_DB_RANGE.start() + (value as f32 / 2.0)) } // ------ LINK ------ @@ -739,31 +757,31 @@ pub fn raw_to_link_channels(source_raw: u8, destination_mask: u8) -> Result u16 { - ((client_constants::GAIN_SPLIT_DB - client_constants::MATRIX_GAIN_DB_RANGE.start()) / client_constants::GAIN_LOW_STEP_DB).round() as u16 + ((mapper_constants::GAIN_SPLIT_DB - mapper_constants::MATRIX_GAIN_DB_RANGE.start()) / mapper_constants::GAIN_LOW_STEP_DB).round() as u16 } fn quantize_matrix_gain(db: f32) -> f32 { - if db < client_constants::GAIN_SPLIT_DB { - (db / client_constants::GAIN_LOW_STEP_DB).round() * client_constants::GAIN_LOW_STEP_DB + if db < mapper_constants::GAIN_SPLIT_DB { + (db / mapper_constants::GAIN_LOW_STEP_DB).round() * mapper_constants::GAIN_LOW_STEP_DB } else { - (db / client_constants::GAIN_HIGH_STEP_DB).round() * client_constants::GAIN_HIGH_STEP_DB + (db / mapper_constants::GAIN_HIGH_STEP_DB).round() * mapper_constants::GAIN_HIGH_STEP_DB } } pub fn matrix_gain_db_to_raw(gain: f32) -> Result { validate_range( gain, - client_constants::MATRIX_GAIN_DB_RANGE, + mapper_constants::MATRIX_GAIN_DB_RANGE, || format!("Invalid matrix gain: {gain}"), )?; let gain = quantize_matrix_gain(gain); - if gain < client_constants::GAIN_SPLIT_DB { - Ok(((gain - client_constants::MATRIX_GAIN_DB_RANGE.start()) / client_constants::GAIN_LOW_STEP_DB).round() as u16) + if gain < mapper_constants::GAIN_SPLIT_DB { + Ok(((gain - mapper_constants::MATRIX_GAIN_DB_RANGE.start()) / mapper_constants::GAIN_LOW_STEP_DB).round() as u16) } else { Ok(matrix_gain_split_raw() - + ((gain - client_constants::GAIN_SPLIT_DB) / client_constants::GAIN_HIGH_STEP_DB).round() as u16) + + ((gain - mapper_constants::GAIN_SPLIT_DB) / mapper_constants::GAIN_HIGH_STEP_DB).round() as u16) } } @@ -771,9 +789,9 @@ pub fn raw_to_matrix_gain(raw_gain: u16) -> Result { validators::validate_raw_matrix_gain(raw_gain)?; if raw_gain < matrix_gain_split_raw() { - Ok(client_constants::MATRIX_GAIN_DB_RANGE.start() + raw_gain as f32 * client_constants::GAIN_LOW_STEP_DB) + Ok(mapper_constants::MATRIX_GAIN_DB_RANGE.start() + raw_gain as f32 * mapper_constants::GAIN_LOW_STEP_DB) } else { - Ok(client_constants::GAIN_SPLIT_DB + (raw_gain - matrix_gain_split_raw()) as f32 * client_constants::GAIN_HIGH_STEP_DB) + Ok(mapper_constants::GAIN_SPLIT_DB + (raw_gain - matrix_gain_split_raw()) as f32 * mapper_constants::GAIN_HIGH_STEP_DB) } } @@ -815,7 +833,7 @@ fn delay_raw_steps() -> f32 { pub fn delay_to_raw(delay: f32, unit: types::DelayUnit) -> Result { - let (delay_min, delay_max) = client_constants::delay_range(unit); + let (delay_min, delay_max) = mapper_constants::delay_range(unit); validate_range( delay, @@ -832,7 +850,7 @@ pub fn delay_to_raw(delay: f32, unit: types::DelayUnit) -> Result Result { validators::validate_raw_channel_delay(value)?; - let (delay_min, delay_max) = client_constants::delay_range(unit); + let (delay_min, delay_max) = mapper_constants::delay_range(unit); Ok(delay_min + ((value - protocol_constants::DELAY_RAW_RANGE.start()) as f32 / delay_raw_steps()) * (delay_max - delay_min)) } @@ -915,7 +933,7 @@ mod tests { fn channel_name_round_trip() { let name = "In ABCDE"; let raw = channel_name_to_raw(name).unwrap(); - assert_eq!(raw.len(), client_constants::CHANNEL_NAME_LENGTH); + assert_eq!(raw.len(), protocol_constants::CHANNEL_NAME_LENGTH); let decoded = raw_to_channel_name(&raw).unwrap(); assert_eq!(decoded, name); } @@ -923,7 +941,7 @@ mod tests { #[test] fn channel_name_padding_is_applied() { let raw = channel_name_to_raw(" Kick ").unwrap(); - assert_eq!(raw.len(), client_constants::CHANNEL_NAME_LENGTH); + assert_eq!(raw.len(), protocol_constants::CHANNEL_NAME_LENGTH); } // ------------------------------------------------------------------ diff --git a/src/dsp408/client/constants.rs b/src/dsp408/mapper/constants.rs similarity index 74% rename from src/dsp408/client/constants.rs rename to src/dsp408/mapper/constants.rs index 8e2519a..db6878b 100644 --- a/src/dsp408/client/constants.rs +++ b/src/dsp408/mapper/constants.rs @@ -1,17 +1,10 @@ use std::ops::RangeInclusive; -use super::types::{DelayUnit, PEQFilter}; +use crate::dsp408::types::{DelayUnit, PEQFilter}; pub const PRESET_INDEX_MAX: usize = 20; - pub const CONFIG_CHUNK_INDEX_MAX: usize = 28; -pub const PRESET_NAME_LENGTH: usize = 14; -pub const PRESET_NAME_PADDING: u8 = 0x20; - -pub const PASSWORD_LENGTH: usize = 4; -pub const PASSWORD_PADDING: u8 = 0x00; - pub const ATTACK_MS_RANGE: RangeInclusive = 1..=999; pub const RELEASE_MS_RANGE: RangeInclusive = 10..=3000; pub const KNEE_DB_RANGE: RangeInclusive = 0..=12; @@ -21,8 +14,6 @@ pub const FREQUENCY_HZ_RANGE: RangeInclusive = 19.7..=20160.0; pub const EQ_GAIN_DB_RANGE: RangeInclusive = -12.0..=12.0; pub const PEQ_Q_RANGE: RangeInclusive = 0.40..=128.0; -pub const PEQ_INPUT_BAND_COUNT: usize = 8; -pub const PEQ_OUTPUT_BAND_COUNT:usize = 9; pub fn peq_q_range(filter: PEQFilter) -> RangeInclusive { match filter { PEQFilter::Peak @@ -55,15 +46,7 @@ pub fn delay_range(unit: DelayUnit) -> (f32, f32) { } } -pub const CHANNEL_NAME_LENGTH: usize = 8; -pub const CHANNEL_NAME_PADDING: u8 = 0x00; - pub const GATE_THRESHOLD_DB_RANGE: RangeInclusive = -90.0..=0.0; pub const HOLD_MS_RANGE: RangeInclusive = 10..=999; pub const MATRIX_GAIN_DB_RANGE: RangeInclusive = -60.0..=0.0; - -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; \ No newline at end of file diff --git a/src/dsp408/mapper/encoding.rs b/src/dsp408/mapper/encoding.rs index 89624dd..f705e49 100644 --- a/src/dsp408/mapper/encoding.rs +++ b/src/dsp408/mapper/encoding.rs @@ -1,4 +1,7 @@ -use crate::common::errors::{EncodingError, DecodingError}; +use crate::common::errors::{ + DecodingError, + EncodingError, +}; // ------ STRING ------ diff --git a/src/dsp408/mapper/global.rs b/src/dsp408/mapper/global.rs index 226ec6c..38224e1 100644 --- a/src/dsp408/mapper/global.rs +++ b/src/dsp408/mapper/global.rs @@ -1,10 +1,25 @@ -use crate::common::validator::{validate_ascii_padded, validate_range}; -use crate::dsp408::client::constants as client_constants; -use crate::dsp408::client::types; -use crate::dsp408::protocol::validators; -use crate::common::errors::{MapperError, DecodingError}; -use crate::dsp408::protocol::constants as protocol_constants; -use super::{encode_ascii, decode_ascii}; +use crate::{ + common::{ + errors::{DecodingError, MapperError}, + validator::{ + validate_ascii_padded, + validate_range, + }, + }, + dsp408::{ + protocol::{ + constants as protocol_constants, + validators, + }, + types, + }, +}; + +use super::{ + constants, + decode_ascii, + encode_ascii, +}; // ------ DELAY ------ @@ -51,7 +66,7 @@ pub fn raw_to_delay_unit( pub fn preset_index_to_raw(index: usize) -> Result { validate_range( index, - 0..=client_constants::PRESET_INDEX_MAX, + 0..=constants::PRESET_INDEX_MAX, || format!("Invalid preset index {}", index) )?; Ok(index as u8) @@ -60,7 +75,7 @@ pub fn preset_index_to_raw(index: usize) -> Result { pub fn preset_index_without_factory_to_raw(index: usize) -> Result { validate_range( index, - 1..=client_constants::PRESET_INDEX_MAX, + 1..=constants::PRESET_INDEX_MAX, || format!("Invalid preset index {}", index) )?; Ok(index as u8 - 1) @@ -71,7 +86,7 @@ pub fn preset_index_without_factory_to_raw(index: usize) -> Result Result { validate_range( index, - 0..=client_constants::CONFIG_CHUNK_INDEX_MAX, + 0..=constants::CONFIG_CHUNK_INDEX_MAX, || format!("Invalid config index: {:#04x}", index) )?; Ok(index as u8) @@ -82,12 +97,12 @@ pub fn config_chunk_to_raw(index: usize) -> Result { pub fn preset_name_to_raw(name: &str) -> Result, MapperError> { validate_ascii_padded( name.as_bytes(), - client_constants::PRESET_NAME_LENGTH, - client_constants::PRESET_NAME_PADDING, + protocol_constants::PRESET_NAME_LENGTH, + protocol_constants::PRESET_NAME_PADDING, "Preset name" )?; - Ok(encode_ascii(name, client_constants::PRESET_NAME_LENGTH, client_constants::PRESET_NAME_PADDING)?) + Ok(encode_ascii(name, protocol_constants::PRESET_NAME_LENGTH, protocol_constants::PRESET_NAME_PADDING)?) } pub fn raw_to_preset_name(raw_name: &[u8]) -> Result { @@ -139,12 +154,12 @@ pub fn raw_to_input_source(value: u8) -> Result { pub fn password_to_raw(password: &str) -> Result, MapperError> { validate_ascii_padded( password.as_bytes(), - client_constants::PASSWORD_LENGTH, - client_constants::PASSWORD_PADDING, + protocol_constants::PASSWORD_LENGTH, + protocol_constants::PASSWORD_PADDING, "Password" )?; - Ok(encode_ascii(password, client_constants::PASSWORD_LENGTH, client_constants::PASSWORD_PADDING)?) + Ok(encode_ascii(password, protocol_constants::PASSWORD_LENGTH, protocol_constants::PASSWORD_PADDING)?) } #[cfg(test)] @@ -176,8 +191,8 @@ mod tests { fn preset_index_to_raw_bounds() { assert_eq!(preset_index_to_raw(0).unwrap(), 0); assert_eq!( - preset_index_to_raw(client_constants::PRESET_INDEX_MAX).unwrap(), - client_constants::PRESET_INDEX_MAX as u8, + preset_index_to_raw(constants::PRESET_INDEX_MAX).unwrap(), + constants::PRESET_INDEX_MAX as u8, ); } @@ -186,14 +201,14 @@ mod tests { assert_eq!(preset_index_without_factory_to_raw(1).unwrap(), 0); assert_eq!( - preset_index_without_factory_to_raw(client_constants::PRESET_INDEX_MAX).unwrap(), - client_constants::PRESET_INDEX_MAX as u8 - 1, + preset_index_without_factory_to_raw(constants::PRESET_INDEX_MAX).unwrap(), + constants::PRESET_INDEX_MAX as u8 - 1, ); } #[test] fn preset_index_rejects_out_of_range() { - assert!(preset_index_to_raw(client_constants::PRESET_INDEX_MAX + 1).is_err()); + assert!(preset_index_to_raw(constants::PRESET_INDEX_MAX + 1).is_err()); assert!(preset_index_without_factory_to_raw(0).is_err()); } @@ -204,14 +219,14 @@ mod tests { assert_eq!(config_chunk_to_raw(0).unwrap(), 0); assert_eq!( - config_chunk_to_raw(client_constants::CONFIG_CHUNK_INDEX_MAX).unwrap(), - client_constants::CONFIG_CHUNK_INDEX_MAX as u8, + config_chunk_to_raw(constants::CONFIG_CHUNK_INDEX_MAX).unwrap(), + constants::CONFIG_CHUNK_INDEX_MAX as u8, ); } #[test] fn config_chunk_rejects_invalid() { - assert!(config_chunk_to_raw(client_constants::CONFIG_CHUNK_INDEX_MAX + 1).is_err()); + assert!(config_chunk_to_raw(constants::CONFIG_CHUNK_INDEX_MAX + 1).is_err()); } // -------- Preset Name -------- diff --git a/src/dsp408/mapper/mod.rs b/src/dsp408/mapper/mod.rs index e4596d6..99a2b8e 100644 --- a/src/dsp408/mapper/mod.rs +++ b/src/dsp408/mapper/mod.rs @@ -2,6 +2,7 @@ pub mod channel; pub mod encoding; pub mod global; pub mod response; +pub mod constants; pub use channel::*; diff --git a/src/dsp408/mapper/response.rs b/src/dsp408/mapper/response.rs index ff358e5..32d855e 100644 --- a/src/dsp408/mapper/response.rs +++ b/src/dsp408/mapper/response.rs @@ -1,11 +1,32 @@ -use crate::dsp408::client::constants::{METERS_HIGH_STEP, METERS_LOW_STEP, METERS_SPLIT, METERS_SPLIT_LEVEL_DB}; -use crate::dsp408::client::types::{ - Acknowledgement, AuthenticationResult, ConfigChunk, CurrentPreset, DeviceFlags, DeviceInfo, - DeviceResponseType, HandshakeAck, Meters, ModifiedPreset, NotImplemented, PresetName, - UnknownResponse, +use crate::{ + common::errors::ProtocolError, + dsp408::{ + protocol::{ + constants::{ + METERS_HIGH_STEP, + METERS_LOW_STEP, + METERS_SPLIT, + METERS_SPLIT_LEVEL_DB, + }, + frame::Frame, + }, + types::{ + Acknowledgement, + AuthenticationResult, + ConfigChunk, + CurrentPreset, + DeviceFlags, + DeviceInfo, + DeviceResponseType, + HandshakeAck, + Meters, + ModifiedPreset, + NotImplemented, + PresetName, + UnknownResponse, + }, + }, }; -use crate::common::errors::ProtocolError; -use crate::dsp408::protocol::frame::Frame; use super::encoding::{decode_ascii, decode_float16}; diff --git a/src/dsp408/mod.rs b/src/dsp408/mod.rs index 2a7325e..2923a16 100644 --- a/src/dsp408/mod.rs +++ b/src/dsp408/mod.rs @@ -1,5 +1,6 @@ -pub mod client; -pub mod mapper; -pub mod protocol; +mod client; +mod mapper; +pub(crate) mod protocol; +pub mod types; pub use client::DSP408; \ No newline at end of file diff --git a/src/dsp408/protocol/commands.rs b/src/dsp408/protocol/commands.rs index 241f9b5..422a3bf 100644 --- a/src/dsp408/protocol/commands.rs +++ b/src/dsp408/protocol/commands.rs @@ -1,7 +1,9 @@ -use super::frame::{build_frame}; - use crate::common::errors::ValidationError; -use super::validators::*; + +use super::{ + frame::build_frame, + validators::*, +}; fn split_u16(value: u16) -> [u8; 2] { [ @@ -150,7 +152,7 @@ pub fn build_unlock_device( Ok(build_frame( 0x2d, - &password + password )) } diff --git a/src/dsp408/protocol/config.rs b/src/dsp408/protocol/config.rs new file mode 100644 index 0000000..849151b --- /dev/null +++ b/src/dsp408/protocol/config.rs @@ -0,0 +1,349 @@ +use std::collections::HashMap; + +use strum::{EnumCount, IntoEnumIterator}; + +use crate::{ + common::errors::DSPError, + dsp408::{ + mapper, + types, + protocol::{ + constants, + reader::BinaryReader, + }, + }, +}; + +fn apply_flag( + r: &mut BinaryReader, + targets: &mut [T], + setter: impl Fn(&mut T, bool), +) -> Result<(), DSPError> { + let bitmap = r.read_u16()?; + + for (index, target) in targets.iter_mut().enumerate() { + setter(target, bitmap & (1 << index) != 0); + } + + Ok(()) +} + +fn read_gate(r: &mut BinaryReader) -> Result { + Ok(types::Gate { + attack: mapper::raw_to_attack_ms(r.read_u16()?)?, + release: mapper::raw_to_release_ms(r.read_u16()?)?, + hold: mapper::raw_to_hold_ms(r.read_u16()?)?, + threshold: mapper::raw_to_gate_threshold_db(r.read_u16()?)?, + }) +} + +fn read_geq( + r: &mut BinaryReader, +) -> Result { + let gains = types::DiscreteFrequency::iter() + .map(|_| { + let raw = r.read_u16()?; + Ok(mapper::raw_to_eq_gain(raw)?) + }) + .collect::, DSPError>>()?; + + Ok(types::GraphicEQ { + gains, + bypass: false, + }) +} + +fn read_peq_chain( + r: &mut BinaryReader, + band_count: usize, +) -> Result { + let bands = (0..band_count) + .map(|_| { + Ok(types::PEQ { + gain: mapper::raw_to_eq_gain(r.read_u16()?)?, + frequency: mapper::raw_to_frequency_hz(r.read_u16()?)?, + q: mapper::raw_to_peq_q(r.read_u8()?)?, + filter_type: mapper::raw_to_peq_filter(r.read_u8()?)?, + bypass: false, + }) + }) + .collect::, DSPError>>()?; + + Ok(types::PEQChain { + bands, + bypass: false, + }) +} + +fn read_crossover( + r: &mut BinaryReader, +) -> Result { + let hp_freq = r.read_u16()?; + let lp_freq = r.read_u16()?; + let hp_filter = r.read_u8()?; + let lp_filter = r.read_u8()?; + Ok(types::CrossoverFilters { + high_pass: types::Crossover { + frequency: mapper::raw_to_frequency_hz(hp_freq)?, + slope: mapper::raw_to_crossover_filter(hp_filter)?, + }, + low_pass: types::Crossover { + frequency: mapper::raw_to_frequency_hz(lp_freq)?, + slope: mapper::raw_to_crossover_filter(lp_filter)?, + }, + }) +} + +fn read_compressor( + r: &mut BinaryReader, +) -> Result { + Ok(types::Compressor { + ratio: mapper::raw_to_ratio(r.read_u16()?)?, + attack: mapper::raw_to_attack_ms(r.read_u16()?)?, + release: mapper::raw_to_release_ms(r.read_u16()?)?, + knee: mapper::raw_to_knee_db(r.read_u16()?)?, + threshold: mapper::raw_to_threshold_db(r.read_u16()?)?, + }) +} + +fn read_limiter( + r: &mut BinaryReader, +) -> Result { + let attack = mapper::raw_to_attack_ms(r.read_u16()?)?; + let release = mapper::raw_to_release_ms(r.read_u16()?)?; + + r.skip(2)?; + + let threshold = mapper::raw_to_threshold_db(r.read_u16()?)?; + + Ok(types::Limiter { + attack, + release, + threshold, + }) +} + +pub fn decode_config(payload: &[u8]) -> Result { + let mut r = BinaryReader::new(payload); + + r.skip(2)?; // flags (unused) + + let _preset_name = mapper::raw_to_preset_name(r.pop(constants::PRESET_NAME_LENGTH)?)?; + + let mut input_channels = vec![types::InputChannelState::default(); types::InputChannel::COUNT]; + + let mut output_channels = vec![types::OutputChannelState::default(); types::OutputChannel::COUNT]; + + let mut input_delays_raw: Vec = Vec::new(); + let mut output_delays_raw: Vec = Vec::new(); + + // ============================ + // INPUT CHANNELS + // ============================ + + for (channel, inp) in types::InputChannel::iter().zip(input_channels.iter_mut()) { + inp.name = mapper::raw_to_channel_name(r.pop(constants::CHANNEL_NAME_LENGTH)?)?; + + inp.gate = read_gate(&mut r)?; + + inp.geq = read_geq(&mut r)?; + + inp.peq_chain = read_peq_chain(&mut r, constants::PEQ_INPUT_BAND_RANGE.count())?; + + inp.crossover = read_crossover(&mut r)?; + + inp.gain = mapper::raw_to_gain(r.read_u16()?)?; + + inp.phase_inverted = r.read_u8()? != 0; + + r.skip(2)?; // reserved + + input_delays_raw.push(r.read_u8()?); + + r.skip(1)?; + + inp.linked_channels = mapper::raw_to_link_channels(mapper::channel_to_raw(types::Channel::Input(channel))?, r.read_u8()?)?; + } + + // ============================ + // OUTPUT CHANNELS + // ============================ + + for (channel, out) in types::OutputChannel::iter().zip(output_channels.iter_mut()) { + out.name = mapper::raw_to_channel_name(r.pop(constants::CHANNEL_NAME_LENGTH)?)?; + + r.skip(1)?; + + out.matrix_routes = types::MatrixRoutes { + connected: + mapper::raw_to_matrix( + r.read_u8()? + )?, + + gains: (0..4) + .map(|_| { + let raw = r.read_u16()?; + Ok::(mapper::raw_to_matrix_gain(raw)?) + }) + .collect::, _>>()?, + }; + + out.crossover = read_crossover(&mut r)?; + + out.peq_chain = read_peq_chain(&mut r, constants::PEQ_OUTPUT_BAND_RANGE.count())?; + + out.compressor = read_compressor(&mut r)?; + + out.limiter = read_limiter(&mut r)?; + + out.gain = mapper::raw_to_gain(r.read_u16()?)?; + + out.phase_inverted = r.read_u8()? != 0; + + r.skip(2)?; // reserved + + output_delays_raw.push(r.read_u8()?); + + r.skip(1)?; + + out.linked_channels = mapper::raw_to_link_channels(mapper::channel_to_raw(types::Channel::Output(channel))?, r.read_u8()?)?; + } + + // ============================ + // MUTE FLAGS + // ============================ + + apply_flag( + &mut r, + &mut input_channels, + |ch, value| ch.mute = value, + )?; + + apply_flag( + &mut r, + &mut output_channels, + |ch, value| ch.mute = value, + )?; + + // ============================ + // PEQ BYPASS FLAGS + // ============================ + + for inp in &mut input_channels { + apply_flag( + &mut r, + inp.peq_chain.bands.as_mut_slice(), + |band, value| band.bypass = value, + )?; + } + + for out in &mut output_channels { + apply_flag( + &mut r, + out.peq_chain.bands.as_mut_slice(), + |band, value| band.bypass = value, + )?; + } + + // ============================ + // INPUT SOURCE + // ============================ + + let input_source = types::InputSource { + r#type: mapper::raw_to_input_source( + r.read_u16()? as u8, + )?, + + frequency: mapper::raw_to_frequency_value( + r.read_u16()? as u8, + )?, + }; + + // ============================ + // DELAY UNIT + // ============================ + + let delay_unit = mapper::raw_to_delay_unit( + r.read_u16()? as u8, + )?; + + // ============================ + // APPLY DELAYS + // ============================ + + for (inp, raw_delay) in input_channels.iter_mut().zip(input_delays_raw) + { + inp.delay = mapper::raw_to_delay( + raw_delay, + delay_unit, + )?; + } + + for (out, raw_delay) in output_channels.iter_mut().zip(output_delays_raw) + { + out.delay = mapper::raw_to_delay( + raw_delay, + delay_unit, + )?; + } + + // ============================ + // PEQ / GEQ BYPASS + // ============================ + + let input_peq_bitmap = r.read_u16()?; + + for (index, inp) in input_channels.iter_mut().enumerate() { + let bypass = input_peq_bitmap & (1 << index) != 0; + + for peq in inp.peq_chain.bands.iter_mut() { + peq.bypass = bypass; + } + } + + + let output_peq_bitmap = r.read_u16()?; + + for (index, out) in output_channels.iter_mut().enumerate() { + let bypass = output_peq_bitmap & (1 << index) != 0; + + for peq in out.peq_chain.bands.iter_mut() { + peq.bypass = bypass; + } + } + + let bitmap = r.read_u16()?; + + for (index, inp) in input_channels.iter_mut().enumerate() { + inp.geq.bypass = bitmap & (1 << index) != 0; + } + + // ============================ + // BUILD CHANNEL MAP + // ============================ + + let mut input_states = HashMap::new(); + + for (channel, state) in types::InputChannel::iter().zip(input_channels) { + input_states.insert( + channel, + state, + ); + } + + let mut output_states = HashMap::new(); + + for (channel, state) in types::OutputChannel::iter().zip(output_channels) { + output_states.insert( + channel, + state, + ); + } + + Ok(types::DSPConfigState { + input_states, + output_states, + delay_unit, + input_source, + }) +} diff --git a/src/dsp408/protocol/constants.rs b/src/dsp408/protocol/constants.rs index f9a1575..e78860f 100644 --- a/src/dsp408/protocol/constants.rs +++ b/src/dsp408/protocol/constants.rs @@ -77,3 +77,8 @@ pub fn output_link_mask(channel: u8) -> Option { _ => 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; \ No newline at end of file diff --git a/src/dsp408/protocol/frame.rs b/src/dsp408/protocol/frame.rs index 146e09a..e09225d 100644 --- a/src/dsp408/protocol/frame.rs +++ b/src/dsp408/protocol/frame.rs @@ -88,12 +88,9 @@ fn find_footer(data: &[u8]) -> Option { if data.len() < 2 { return None; } - for i in 2..data.len() - 1 { - if data[i] == 0x10 && data[i + 1] == 0x03 { - return Some(i); - } - } - None + + (2..data.len() - 1) + .find(|&i| data[i] == 0x10 && data[i + 1] == 0x03) } #[cfg(test)] diff --git a/src/dsp408/protocol/mod.rs b/src/dsp408/protocol/mod.rs index f0d57ef..6410adf 100644 --- a/src/dsp408/protocol/mod.rs +++ b/src/dsp408/protocol/mod.rs @@ -1,4 +1,6 @@ -pub mod frame; -pub mod commands; -pub mod constants; -pub mod validators; \ No newline at end of file +pub(crate) mod frame; +pub(crate) mod commands; +pub(crate) mod constants; +pub(crate) mod validators; +mod reader; +pub(crate) mod config; \ No newline at end of file diff --git a/src/dsp408/protocol/reader.rs b/src/dsp408/protocol/reader.rs new file mode 100644 index 0000000..d583d09 --- /dev/null +++ b/src/dsp408/protocol/reader.rs @@ -0,0 +1,39 @@ +use crate::common::errors::DSPError; + +pub struct BinaryReader<'a> { + data: &'a [u8], +} + +impl<'a> BinaryReader<'a> { + pub fn new(data: &'a [u8]) -> Self { + Self { data } + } + + pub fn pop(&mut self, n: usize) -> Result<&'a [u8], DSPError> { + if self.data.len() < n { + return Err(DSPError::InvalidRequest( + "buffer underflow".into() + )); + } + + let (value, rest) = self.data.split_at(n); + self.data = rest; + + Ok(value) + } + + pub fn read_u8(&mut self) -> Result { + Ok(self.pop(1)?[0]) + } + + pub fn read_u16(&mut self) -> Result { + Ok(u16::from_le_bytes( + self.pop(2)?.try_into().unwrap() + )) + } + + pub fn skip(&mut self, n: usize) -> Result<(), DSPError> { + self.pop(n)?; + Ok(()) + } +} \ No newline at end of file diff --git a/src/dsp408/protocol/validators.rs b/src/dsp408/protocol/validators.rs index 80a1f6c..0d182c3 100644 --- a/src/dsp408/protocol/validators.rs +++ b/src/dsp408/protocol/validators.rs @@ -1,6 +1,14 @@ -use crate::common::errors::ValidationError; -use crate::common::validator::{validate_ascii_padded, validate_bitmask, validate_range}; -use super::constants::*; +use crate::{ + common::{ + errors::ValidationError, + validator::{ + validate_ascii_padded, + validate_bitmask, + validate_range, + }, + }, + dsp408::protocol::constants::*, +}; pub fn validate_raw_delay_unit(raw_unit: u8) -> Result<(), ValidationError> { validate_range( diff --git a/src/dsp408/client/types.rs b/src/dsp408/types.rs similarity index 100% rename from src/dsp408/client/types.rs rename to src/dsp408/types.rs diff --git a/src/lib.rs b/src/lib.rs index b68df8e..b63c20f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,4 @@ mod common; -mod dsp408; +pub mod dsp408; -pub use common::errors::DSPError; -pub use dsp408::DSP408; -pub mod types { - pub use crate::dsp408::client::types::*; -} \ No newline at end of file +pub use common::errors::DSPError; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 119a15f..19e2620 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,8 @@ use std::net::IpAddr; use std::str::FromStr; -use dsp_thomann::DSP408; -use dsp_thomann::types; +use dsp_thomann::dsp408::DSP408; +use dsp_thomann::dsp408::types; fn main() -> Result<(), Box> {