Initial commit

This commit is contained in:
2026-07-20 19:29:57 +02:00
commit ad7da5f7ee
23 changed files with 5271 additions and 0 deletions
+858
View File
@@ -0,0 +1,858 @@
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::dsp408::client::types;
// ------ CHANNEL ------
fn channel_to_raw_value(channel: types::Channel) -> u8 {
match channel {
types::Channel::Input(types::InputChannel::InA) => 0x00,
types::Channel::Input(types::InputChannel::InB) => 0x01,
types::Channel::Input(types::InputChannel::InC) => 0x02,
types::Channel::Input(types::InputChannel::InD) => 0x03,
types::Channel::Output(types::OutputChannel::Out1) => 0x04,
types::Channel::Output(types::OutputChannel::Out2) => 0x05,
types::Channel::Output(types::OutputChannel::Out3) => 0x06,
types::Channel::Output(types::OutputChannel::Out4) => 0x07,
types::Channel::Output(types::OutputChannel::Out5) => 0x08,
types::Channel::Output(types::OutputChannel::Out6) => 0x09,
types::Channel::Output(types::OutputChannel::Out7) => 0x0A,
types::Channel::Output(types::OutputChannel::Out8) => 0x0B,
}
}
fn raw_to_channel_value(value: u8) -> Option<types::Channel> {
match value {
0x00 => Some(types::Channel::Input(types::InputChannel::InA)),
0x01 => Some(types::Channel::Input(types::InputChannel::InB)),
0x02 => Some(types::Channel::Input(types::InputChannel::InC)),
0x03 => Some(types::Channel::Input(types::InputChannel::InD)),
0x04 => Some(types::Channel::Output(types::OutputChannel::Out1)),
0x05 => Some(types::Channel::Output(types::OutputChannel::Out2)),
0x06 => Some(types::Channel::Output(types::OutputChannel::Out3)),
0x07 => Some(types::Channel::Output(types::OutputChannel::Out4)),
0x08 => Some(types::Channel::Output(types::OutputChannel::Out5)),
0x09 => Some(types::Channel::Output(types::OutputChannel::Out6)),
0x0A => Some(types::Channel::Output(types::OutputChannel::Out7)),
0x0B => Some(types::Channel::Output(types::OutputChannel::Out8)),
_ => None,
}
}
pub fn channel_to_raw(channel: types::Channel) -> Result<u8, MapperError> {
Ok(channel_to_raw_value(channel))
}
pub fn raw_to_channel(value: u8) -> Result<types::Channel, MapperError> {
validators::validate_raw_channel(value)?;
raw_to_channel_value(value)
.ok_or_else(|| {
MapperError::Decoding(
DecodingError::new(
format!("Unknown channel value: {:#04x}", value)
)
)
})
}
pub fn input_channel_to_raw(channel: types::InputChannel) -> Result<u8, MapperError> {
Ok(channel_to_raw_value(types::Channel::Input(channel)))
}
pub fn output_channel_to_raw(channel: types::OutputChannel) -> Result<u8, MapperError> {
Ok(channel_to_raw_value(types::Channel::Output(channel)))
}
// ------ NAME ------
pub fn channel_name_to_raw(name: &str) -> Result<Vec<u8>, MapperError> {
validate_ascii_padded(
name.as_bytes(),
client_constants::CHANNEL_NAME_LENGTH,
client_constants::CHANNEL_NAME_PADDING,
"Channel name",
)?;
Ok(encode_ascii(name, client_constants::CHANNEL_NAME_LENGTH, client_constants::CHANNEL_NAME_PADDING)?)
}
pub fn raw_to_channel_name(raw_name: &[u8]) -> Result<String, MapperError> {
validators::validate_raw_channel_name(raw_name)?;
Ok(decode_ascii(raw_name, true)?)
}
// ------ ATTACK ------
pub fn attack_ms_to_raw(value: u16) -> Result<u16, MapperError> {
validate_range(
value,
client_constants::ATTACK_MS_RANGE,
|| format!("Invalid attack: {:#06x}", value),
)?;
Ok(value - 1)
}
pub fn raw_to_attack_ms(value: u16) -> Result<u16, MapperError> {
validators::validate_raw_attack(value)?;
Ok(value + 1)
}
// ------ RELEASE ------
pub fn release_ms_to_raw(value: u16) -> Result<u16, MapperError> {
validate_range(
value,
client_constants::RELEASE_MS_RANGE,
|| format!("Invalid release: {:#06x}", value),
)?;
Ok(value - 1)
}
pub fn raw_to_release_ms(value: u16) -> Result<u16, MapperError> {
validators::validate_raw_release(value)?;
Ok(value + 1)
}
// ------ HOLD ------
pub fn hold_ms_to_raw(hold: u16) -> Result<u16, MapperError> {
validate_range(
hold,
client_constants::HOLD_MS_RANGE,
|| format!("Invalid hold: {:#06x}", hold),
)?;
Ok(hold - 1)
}
pub fn raw_to_hold_ms(hold: u16) -> Result<u16, MapperError> {
validators::validate_raw_hold(hold)?;
Ok(hold + 1)
}
// ------ GATE THRESHOLD ------
pub fn gate_threshold_db_to_raw(db: f32) -> Result<u16, MapperError> {
validate_range(
db,
client_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)
}
pub fn raw_to_gate_threshold_db(value: u16) -> Result<f32, MapperError> {
validators::validate_raw_gate_threshold(value)?;
Ok(*client_constants::GATE_THRESHOLD_DB_RANGE.start() + value as f32 / 2.0)
}
// ------ EQ GAIN ------
fn eq_gain_raw_steps() -> f32 {
(*protocol_constants::EQ_GAIN_RAW_RANGE.end()
- *protocol_constants::EQ_GAIN_RAW_RANGE.start()) as f32
}
fn eq_gain_step_db() -> f32 {
(*client_constants::EQ_GAIN_DB_RANGE.end()
- *client_constants::EQ_GAIN_DB_RANGE.start())
/ eq_gain_raw_steps()
}
fn eq_quantize(db: f32) -> f32 {
(db / eq_gain_step_db()).round() * eq_gain_step_db()
}
pub fn eq_gain_db_to_raw(gain: f32) -> Result<u16, MapperError> {
validate_range(
gain,
client_constants::EQ_GAIN_DB_RANGE,
|| format!("Invalid EQ gain: {gain}"),
)?;
let gain = eq_quantize(gain);
Ok(
*protocol_constants::EQ_GAIN_RAW_RANGE.start()
+ ((gain - *client_constants::EQ_GAIN_DB_RANGE.start())
/ eq_gain_step_db())
.round() as u16
)
}
pub fn raw_to_eq_gain(value: u16) -> Result<f32, MapperError> {
validators::validate_raw_eq_gain(value)?;
let gain =
*client_constants::EQ_GAIN_DB_RANGE.start()
+ (value - *protocol_constants::EQ_GAIN_RAW_RANGE.start()) as f32
* eq_gain_step_db();
Ok(eq_quantize(gain))
}
// ------ PEQ BAND ------
fn peq_band_to_raw_value(band: types::PEQBand) -> u8 {
match band {
types::PEQBand::B1 => 0,
types::PEQBand::B2 => 1,
types::PEQBand::B3 => 2,
types::PEQBand::B4 => 3,
types::PEQBand::B5 => 4,
types::PEQBand::B6 => 5,
types::PEQBand::B7 => 6,
types::PEQBand::B8 => 7,
types::PEQBand::B9 => 8,
}
}
pub fn peq_band_to_raw(band: types::PEQBand, channel: types::Channel) -> Result<u8, MapperError> {
match channel {
types::Channel::Input(_) => {
if matches!(band, types::PEQBand::B9) {
return Err(MapperError::Validation(ValidationError::Value(
"PEQ Band B9 is not available on input channels".to_string()
)));
}
}
types::Channel::Output(_) => {
// All PEQ bands are allowed
}
}
Ok(peq_band_to_raw_value(band))
}
// ------ 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
}
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
} else {
(db / client_constants::GAIN_HIGH_STEP_DB).round() * client_constants::GAIN_HIGH_STEP_DB
}
}
pub fn gain_db_to_raw(gain: f32) -> Result<u16, MapperError> {
validate_range(
gain,
client_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)
} else {
Ok(gain_split_raw() + ((gain - client_constants::GAIN_SPLIT_DB) / client_constants::GAIN_HIGH_STEP_DB).round() as u16)
}
}
pub fn raw_to_gain(raw_gain: u16) -> Result<f32, MapperError> {
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)
} else {
Ok(client_constants::GAIN_SPLIT_DB + (raw_gain - gain_split_raw()) as f32 * client_constants::GAIN_HIGH_STEP_DB)
}
}
// ------ CONTIGUOUS FREQUENCY ------
pub fn frequency_hz_to_raw(hz: f32) -> Result<u16, MapperError> {
validate_range(
hz,
client_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()
* device_steps)
.round();
Ok(protocol_constants::FREQUENCY_RAW_RANGE.start() + raw as u16)
}
pub fn raw_to_frequency_hz(value: u16) -> Result<f32, MapperError> {
validators::validate_raw_frequency(value)?;
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))
}
// ------ DISCRETE FREQUENCY ------
fn discrete_frequency_to_raw_value(frequency: types::DiscreteFrequency) -> u8 {
match frequency {
types::DiscreteFrequency::F20 => 0,
types::DiscreteFrequency::F25 => 1,
types::DiscreteFrequency::F31_5 => 2,
types::DiscreteFrequency::F40 => 3,
types::DiscreteFrequency::F50 => 4,
types::DiscreteFrequency::F63 => 5,
types::DiscreteFrequency::F80 => 6,
types::DiscreteFrequency::F100 => 7,
types::DiscreteFrequency::F125 => 8,
types::DiscreteFrequency::F160 => 9,
types::DiscreteFrequency::F200 => 10,
types::DiscreteFrequency::F250 => 11,
types::DiscreteFrequency::F315 => 12,
types::DiscreteFrequency::F400 => 13,
types::DiscreteFrequency::F500 => 14,
types::DiscreteFrequency::F630 => 15,
types::DiscreteFrequency::F800 => 16,
types::DiscreteFrequency::F1000 => 17,
types::DiscreteFrequency::F1250 => 18,
types::DiscreteFrequency::F1600 => 19,
types::DiscreteFrequency::F2000 => 20,
types::DiscreteFrequency::F2500 => 21,
types::DiscreteFrequency::F3150 => 22,
types::DiscreteFrequency::F4000 => 23,
types::DiscreteFrequency::F5000 => 24,
types::DiscreteFrequency::F6300 => 25,
types::DiscreteFrequency::F8000 => 26,
types::DiscreteFrequency::F10000 => 27,
types::DiscreteFrequency::F12500 => 28,
types::DiscreteFrequency::F16000 => 29,
types::DiscreteFrequency::F20000 => 30,
}
}
fn raw_to_discrete_frequency_value(raw: u8) -> Option<types::DiscreteFrequency> {
Some(match raw {
0 => types::DiscreteFrequency::F20,
1 => types::DiscreteFrequency::F25,
2 => types::DiscreteFrequency::F31_5,
3 => types::DiscreteFrequency::F40,
4 => types::DiscreteFrequency::F50,
5 => types::DiscreteFrequency::F63,
6 => types::DiscreteFrequency::F80,
7 => types::DiscreteFrequency::F100,
8 => types::DiscreteFrequency::F125,
9 => types::DiscreteFrequency::F160,
10 => types::DiscreteFrequency::F200,
11 => types::DiscreteFrequency::F250,
12 => types::DiscreteFrequency::F315,
13 => types::DiscreteFrequency::F400,
14 => types::DiscreteFrequency::F500,
15 => types::DiscreteFrequency::F630,
16 => types::DiscreteFrequency::F800,
17 => types::DiscreteFrequency::F1000,
18 => types::DiscreteFrequency::F1250,
19 => types::DiscreteFrequency::F1600,
20 => types::DiscreteFrequency::F2000,
21 => types::DiscreteFrequency::F2500,
22 => types::DiscreteFrequency::F3150,
23 => types::DiscreteFrequency::F4000,
24 => types::DiscreteFrequency::F5000,
25 => types::DiscreteFrequency::F6300,
26 => types::DiscreteFrequency::F8000,
27 => types::DiscreteFrequency::F10000,
28 => types::DiscreteFrequency::F12500,
29 => types::DiscreteFrequency::F16000,
30 => types::DiscreteFrequency::F20000,
_ => return None,
})
}
pub fn frequency_value_to_raw(frequency: types::DiscreteFrequency) -> Result<u8, MapperError> {
Ok(discrete_frequency_to_raw_value(frequency))
}
pub fn raw_to_frequency_value(frequency: u8) -> Result<types::DiscreteFrequency, MapperError> {
validators::validate_raw_discrete_frequency(frequency)?;
raw_to_discrete_frequency_value(frequency)
.ok_or_else(|| {
MapperError::Decoding(
DecodingError::new(
format!("Unknown frequency value: {:#04x}", frequency)
)
)
})
}
// ------ CROSSOVER ------
fn crossover_filter_to_raw_value(filter: types::CrossoverFilter) -> u8 {
match filter {
types::CrossoverFilter::Bypass => 0,
types::CrossoverFilter::Bw6 => 1,
types::CrossoverFilter::Bl6 => 2,
types::CrossoverFilter::Bw12 => 3,
types::CrossoverFilter::Bl12 => 4,
types::CrossoverFilter::Lk12 => 5,
types::CrossoverFilter::Bw18 => 6,
types::CrossoverFilter::Bl18 => 7,
types::CrossoverFilter::Bw24 => 8,
types::CrossoverFilter::Bl24 => 9,
types::CrossoverFilter::Lk24 => 10,
types::CrossoverFilter::Bw30 => 11,
types::CrossoverFilter::Bl30 => 12,
types::CrossoverFilter::Bw36 => 13,
types::CrossoverFilter::Bl36 => 14,
types::CrossoverFilter::Lk36 => 15,
types::CrossoverFilter::Bw42 => 16,
types::CrossoverFilter::Bl42 => 17,
types::CrossoverFilter::Bw48 => 18,
types::CrossoverFilter::Bl48 => 19,
types::CrossoverFilter::Lk48 => 20,
}
}
fn raw_to_crossover_filter_value(raw: u8) -> Option<types::CrossoverFilter> {
Some(match raw {
0 => types::CrossoverFilter::Bypass,
1 => types::CrossoverFilter::Bw6,
2 => types::CrossoverFilter::Bl6,
3 => types::CrossoverFilter::Bw12,
4 => types::CrossoverFilter::Bl12,
5 => types::CrossoverFilter::Lk12,
6 => types::CrossoverFilter::Bw18,
7 => types::CrossoverFilter::Bl18,
8 => types::CrossoverFilter::Bw24,
9 => types::CrossoverFilter::Bl24,
10 => types::CrossoverFilter::Lk24,
11 => types::CrossoverFilter::Bw30,
12 => types::CrossoverFilter::Bl30,
13 => types::CrossoverFilter::Bw36,
14 => types::CrossoverFilter::Bl36,
15 => types::CrossoverFilter::Lk36,
16 => types::CrossoverFilter::Bw42,
17 => types::CrossoverFilter::Bl42,
18 => types::CrossoverFilter::Bw48,
19 => types::CrossoverFilter::Bl48,
20 => types::CrossoverFilter::Lk48,
_ => return None,
})
}
pub fn crossover_filter_to_raw(filter: types::CrossoverFilter) -> Result<u8, MapperError> {
Ok(crossover_filter_to_raw_value(filter))
}
pub fn raw_to_crossover_filter(raw: u8) -> Result<types::CrossoverFilter, MapperError> {
validators::validate_raw_crossover_filter(raw)?;
raw_to_crossover_filter_value(raw)
.ok_or_else(|| {
MapperError::Decoding(
DecodingError::new(
format!("Unknown crossover filter value: {:#04x}", raw)
)
)
})
}
// ------ PEQ Q ------
pub fn peq_q_to_raw(q: f32, filter: types::PEQFilter) -> Result<u8, MapperError> {
validate_range(
q,
client_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
+ protocol_constants::PEQ_Q_RAW_RANGE.start())
}
pub fn raw_to_peq_q(value: u8) -> Result<f32, MapperError> {
validators::validate_raw_peq_q(value)?;
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))
}
// ------ FILTER ------
fn peq_filter_to_raw_value(filter: types::PEQFilter) -> u8 {
match filter {
types::PEQFilter::Peak => 0,
types::PEQFilter::LowShelf => 1,
types::PEQFilter::HighShelf => 2,
types::PEQFilter::Lp6Db => 3,
types::PEQFilter::Lp12Db => 4,
types::PEQFilter::Hp6Db => 5,
types::PEQFilter::Hp12Db => 6,
types::PEQFilter::AllPass1 => 7,
types::PEQFilter::AllPass2 => 8,
}
}
fn raw_to_peq_filter_value(value: u8) -> Option<types::PEQFilter> {
Some(match value {
0 => types::PEQFilter::Peak,
1 => types::PEQFilter::LowShelf,
2 => types::PEQFilter::HighShelf,
3 => types::PEQFilter::Lp6Db,
4 => types::PEQFilter::Lp12Db,
5 => types::PEQFilter::Hp6Db,
6 => types::PEQFilter::Hp12Db,
7 => types::PEQFilter::AllPass1,
8 => types::PEQFilter::AllPass2,
_ => return None,
})
}
pub fn peq_filter_to_raw(filter: types::PEQFilter) -> Result<u8, MapperError> {
Ok(peq_filter_to_raw_value(filter))
}
pub fn raw_to_peq_filter(value: u8) -> Result<types::PEQFilter, MapperError> {
validators::validate_raw_peq_filter(value)?;
raw_to_peq_filter_value(value)
.ok_or_else(|| {
MapperError::Decoding(
DecodingError::new(
format!("Unknown PEQ filter value: {:#04x}", value)
)
)
})
}
// ------ RATIO ------
fn ratio_to_raw_value(ratio: types::Ratio) -> u16 {
match ratio {
types::Ratio::Ratio1_1 => 0,
types::Ratio::Ratio1_1_1 => 1,
types::Ratio::Ratio1_1_3 => 2,
types::Ratio::Ratio1_1_5 => 3,
types::Ratio::Ratio1_1_7 => 4,
types::Ratio::Ratio1_2 => 5,
types::Ratio::Ratio1_2_5 => 6,
types::Ratio::Ratio1_3 => 7,
types::Ratio::Ratio1_3_5 => 8,
types::Ratio::Ratio1_4 => 9,
types::Ratio::Ratio1_5 => 10,
types::Ratio::Ratio1_6 => 11,
types::Ratio::Ratio1_8 => 12,
types::Ratio::Ratio1_10 => 13,
types::Ratio::Ratio1_20 => 14,
types::Ratio::Limit => 15,
}
}
fn raw_to_ratio_value(value: u16) -> Option<types::Ratio> {
Some(match value {
0 => types::Ratio::Ratio1_1,
1 => types::Ratio::Ratio1_1_1,
2 => types::Ratio::Ratio1_1_3,
3 => types::Ratio::Ratio1_1_5,
4 => types::Ratio::Ratio1_1_7,
5 => types::Ratio::Ratio1_2,
6 => types::Ratio::Ratio1_2_5,
7 => types::Ratio::Ratio1_3,
8 => types::Ratio::Ratio1_3_5,
9 => types::Ratio::Ratio1_4,
10 => types::Ratio::Ratio1_5,
11 => types::Ratio::Ratio1_6,
12 => types::Ratio::Ratio1_8,
13 => types::Ratio::Ratio1_10,
14 => types::Ratio::Ratio1_20,
15 => types::Ratio::Limit,
_ => return None,
})
}
pub fn ratio_to_raw(ratio: types::Ratio) -> Result<u16, MapperError> {
Ok(ratio_to_raw_value(ratio))
}
pub fn raw_to_ratio(value: u16) -> Result<types::Ratio, MapperError> {
validators::validate_raw_compressor_ratio(value)?;
raw_to_ratio_value(value)
.ok_or_else(|| {
MapperError::Decoding(
DecodingError::new(
format!("Unknown ratio value: {:#04x}", value)
)
)
})
}
// ------ KNEE ------
pub fn knee_db_to_raw(db: u16) -> Result<u16, MapperError> {
validate_range(
db,
client_constants::COMPRESSOR_KNEE_DB_RANGE,
|| format!("Invalid compressor knee: {db}"),
)?;
Ok(db)
}
pub fn raw_to_knee_db(value: u16) -> Result<u16, MapperError> {
validators::validate_raw_compressor_knee(value)?;
Ok(value)
}
// ------ THRESHOLD ------
pub fn threshold_db_to_raw(db: f32) -> Result<u16, MapperError> {
validate_range(
db,
client_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)
}
pub fn raw_to_threshold_db(value: u16) -> Result<f32, MapperError> {
validators::validate_raw_threshold(value)?;
Ok(client_constants::THRESHOLD_DB_RANGE.start() + (value as f32 / 2.0))
}
// ------ LINK ------
pub fn link_channels_to_raw(
source: types::Channel,
destinations: &[types::Channel],
) -> Result<u8, MapperError> {
let mut mask = 0u8;
match source {
types::Channel::Input(source_channel) => {
for destination in destinations {
let types::Channel::Input(destination_channel) = destination else {
return Err(MapperError::Validation(
ValidationError::value(
"Input channel cannot link to output channel"
),
));
};
if *destination_channel < source_channel {
return Err(MapperError::Validation(
ValidationError::value(format!(
"{:?} cannot link to previous channel {:?}",
source_channel,
destination_channel
)),
));
}
mask |= 1 << (*destination_channel as u8);
}
}
types::Channel::Output(source_channel) => {
for destination in destinations {
let types::Channel::Output(destination_channel) = destination else {
return Err(MapperError::Validation(
ValidationError::value(
"Output channel cannot link to input channel"
),
));
};
if *destination_channel < source_channel {
return Err(MapperError::Validation(
ValidationError::value(format!(
"{:?} cannot link to previous channel {:?}",
source_channel,
destination_channel
)),
));
}
mask |= 1 << (*destination_channel as u8);
}
}
}
Ok(mask)
}
pub fn raw_to_link_channels(source_raw: u8, destination_mask: u8) -> Result<Vec<types::Channel>, MapperError> {
validators::validate_raw_link_channel(source_raw, destination_mask)?;
let source = raw_to_channel(source_raw)?;
let mut destinations = Vec::new();
match source {
types::Channel::Input(_) => {
for (index, channel) in types::InputChannel::iter().enumerate() {
if destination_mask & (1 << index) != 0 {
destinations.push(types::Channel::Input(channel));
}
}
}
types::Channel::Output(_) => {
for (index, channel) in types::OutputChannel::iter().enumerate() {
if destination_mask & (1 << index) != 0 {
destinations.push(types::Channel::Output(channel));
}
}
}
}
Ok(destinations)
}
// ------ MATRIX GAIN ------
fn matrix_gain_split_raw() -> u16 {
((client_constants::GAIN_SPLIT_DB - client_constants::MATRIX_GAIN_DB_RANGE.start()) / client_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
} else {
(db / client_constants::GAIN_HIGH_STEP_DB).round() * client_constants::GAIN_HIGH_STEP_DB
}
}
pub fn matrix_gain_db_to_raw(gain: f32) -> Result<u16, MapperError> {
validate_range(
gain,
client_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)
} else {
Ok(matrix_gain_split_raw()
+ ((gain - client_constants::GAIN_SPLIT_DB) / client_constants::GAIN_HIGH_STEP_DB).round() as u16)
}
}
pub fn raw_to_matrix_gain(raw_gain: u16) -> Result<f32, MapperError> {
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)
} else {
Ok(client_constants::GAIN_SPLIT_DB + (raw_gain - matrix_gain_split_raw()) as f32 * client_constants::GAIN_HIGH_STEP_DB)
}
}
// ------ MATRIX ------
fn matrix_input_mask(channel: types::InputChannel) -> u8 {
match channel {
types::InputChannel::InA => protocol_constants::MATRIX_INPUT_A_MASK,
types::InputChannel::InB => protocol_constants::MATRIX_INPUT_B_MASK,
types::InputChannel::InC => protocol_constants::MATRIX_INPUT_C_MASK,
types::InputChannel::InD => protocol_constants::MATRIX_INPUT_D_MASK,
}
}
pub fn matrix_to_raw(input_channels: &[types::InputChannel]) -> Result<u8, MapperError> {
let mut bitmask = 0x00;
for &channel in input_channels {
bitmask |= matrix_input_mask(channel);
}
Ok(bitmask)
}
pub fn raw_to_matrix(raw: u8) -> Result<Vec<types::InputChannel>, MapperError> {
validators::validate_raw_matrix_bitmask(raw)?;
Ok([types::InputChannel::InA, types::InputChannel::InB, types::InputChannel::InC, types::InputChannel::InD]
.into_iter()
.filter(|&c| raw & matrix_input_mask(c) != 0)
.collect())
}
// ------ DELAY ------
fn delay_raw_steps() -> f32 {
(protocol_constants::DELAY_RAW_RANGE.end() - protocol_constants::DELAY_RAW_RANGE.start()) as f32
}
pub fn delay_to_raw(delay: f32, unit: types::DelayUnit) -> Result<u8, MapperError> {
let (delay_min, delay_max) = client_constants::delay_range(unit);
validate_range(
delay,
delay_min..=delay_max,
|| format!("Invalid channel delay: {delay}")
)?;
let raw = *protocol_constants::DELAY_RAW_RANGE.start() as f32
+ ((delay - delay_min) / (delay_max - delay_min) * delay_raw_steps()).round();
Ok(raw as u8)
}
pub fn raw_to_delay(value: u8, unit: types::DelayUnit) -> Result<f32, MapperError> {
validators::validate_raw_channel_delay(value)?;
let (delay_min, delay_max) = client_constants::delay_range(unit);
Ok(delay_min + ((value - protocol_constants::DELAY_RAW_RANGE.start()) as f32 / delay_raw_steps()) * (delay_max - delay_min))
}
// ------ COPY CHANNEL ------
pub fn copy_channel_to_raw(source: types::Channel, destination: types::Channel) -> Result<(u8, u8), MapperError> {
if source == destination {
return Err(ValidationError::Value("Source channel and destination channel can't be the same".to_string()).into())
}
match (&source, &destination) {
(types::Channel::Input(_), types::Channel::Output(_))
| (types::Channel::Output(_), types::Channel::Input(_)) => {
return Err(ValidationError::Value(
"Channels need to be both inputs or outputs".to_string()).into(),
);
}
_ => {}
}
Ok((channel_to_raw_value(source), channel_to_raw_value(destination)))
}