Files
dsp-thomann/src/dsp408/mapper/channel.rs
T

1407 lines
48 KiB
Rust

use strum::IntoEnumIterator;
use crate::{
common::{
errors::{
DecodingError,
MapperError,
ValidationError,
},
validator::{
validate_ascii_padded,
validate_range,
},
},
dsp408::{
protocol::{
constants as protocol_constants,
validators,
},
types,
},
};
use super::{
constants as mapper_constants,
decode_ascii,
encode_ascii,
};
// ------ 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(),
protocol_constants::CHANNEL_NAME_LENGTH,
protocol_constants::CHANNEL_NAME_PADDING,
"Channel name",
)?;
Ok(encode_ascii(name, protocol_constants::CHANNEL_NAME_LENGTH, protocol_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,
mapper_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,
mapper_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,
mapper_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,
mapper_constants::GATE_THRESHOLD_DB_RANGE,
|| format!("Invalid gate threshold: {db}"),
)?;
let db = (db * 2.0).round() / 2.0;
Ok(((db - *mapper_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(*mapper_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 {
(*mapper_constants::EQ_GAIN_DB_RANGE.end()
- *mapper_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,
mapper_constants::EQ_GAIN_DB_RANGE,
|| format!("Invalid EQ gain: {gain}"),
)?;
let gain = eq_quantize(gain);
Ok(
*protocol_constants::EQ_GAIN_RAW_RANGE.start()
+ ((gain - *mapper_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 =
*mapper_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 {
((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 < mapper_constants::GAIN_SPLIT_DB {
(db / mapper_constants::GAIN_LOW_STEP_DB).round() * mapper_constants::GAIN_LOW_STEP_DB
} else {
(db / mapper_constants::GAIN_HIGH_STEP_DB).round() * mapper_constants::GAIN_HIGH_STEP_DB
}
}
pub fn gain_db_to_raw(gain: f32) -> Result<u16, MapperError> {
validate_range(
gain,
mapper_constants::GAIN_DB_RANGE,
|| format!("Invalid channel gain: {gain}"),
)?;
let gain = quantize_gain(gain);
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 - mapper_constants::GAIN_SPLIT_DB) / mapper_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(mapper_constants::GAIN_DB_RANGE.start() + raw_gain as f32 * mapper_constants::GAIN_LOW_STEP_DB)
} else {
Ok(mapper_constants::GAIN_SPLIT_DB + (raw_gain - gain_split_raw()) as f32 * mapper_constants::GAIN_HIGH_STEP_DB)
}
}
// ------ CONTIGUOUS FREQUENCY ------
pub fn frequency_hz_to_raw(hz: f32) -> Result<u16, MapperError> {
validate_range(
hz,
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 / mapper_constants::FREQUENCY_HZ_RANGE.start()).ln() / (mapper_constants::FREQUENCY_HZ_RANGE.end() / mapper_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(mapper_constants::FREQUENCY_HZ_RANGE.start() * (mapper_constants::FREQUENCY_HZ_RANGE.end() / mapper_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,
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 / 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())
}
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(mapper_constants::PEQ_Q_RANGE.start() * (mapper_constants::PEQ_Q_RANGE.end() / mapper_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,
mapper_constants::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,
mapper_constants::THRESHOLD_DB_RANGE,
|| format!("Invalid compressor threshold: {db}"),
)?;
let db = (db * 2.0).round() / 2.0;
Ok(((db - mapper_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(mapper_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 {
((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 < mapper_constants::GAIN_SPLIT_DB {
(db / mapper_constants::GAIN_LOW_STEP_DB).round() * mapper_constants::GAIN_LOW_STEP_DB
} else {
(db / mapper_constants::GAIN_HIGH_STEP_DB).round() * mapper_constants::GAIN_HIGH_STEP_DB
}
}
pub fn matrix_gain_db_to_raw(gain: f32) -> Result<u16, MapperError> {
validate_range(
gain,
mapper_constants::MATRIX_GAIN_DB_RANGE,
|| format!("Invalid matrix gain: {gain}"),
)?;
let gain = quantize_matrix_gain(gain);
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 - mapper_constants::GAIN_SPLIT_DB) / mapper_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(mapper_constants::MATRIX_GAIN_DB_RANGE.start() + raw_gain as f32 * mapper_constants::GAIN_LOW_STEP_DB)
} else {
Ok(mapper_constants::GAIN_SPLIT_DB + (raw_gain - matrix_gain_split_raw()) as f32 * mapper_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) = mapper_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) = mapper_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)))
}
#[cfg(test)]
mod tests {
use super::*;
// ------------------------------------------------------------------
// CHANNEL
// ------------------------------------------------------------------
#[test]
fn channel_round_trip_all_inputs() {
let channels = [
types::InputChannel::InA,
types::InputChannel::InB,
types::InputChannel::InC,
types::InputChannel::InD,
];
for ch in channels {
let raw = input_channel_to_raw(ch).unwrap();
let decoded = raw_to_channel(raw).unwrap();
assert_eq!(decoded, types::Channel::Input(ch));
}
}
#[test]
fn channel_round_trip_all_outputs() {
let channels = [
types::OutputChannel::Out1,
types::OutputChannel::Out2,
types::OutputChannel::Out3,
types::OutputChannel::Out4,
types::OutputChannel::Out5,
types::OutputChannel::Out6,
types::OutputChannel::Out7,
types::OutputChannel::Out8,
];
for ch in channels {
let raw = output_channel_to_raw(ch).unwrap();
let decoded = raw_to_channel(raw).unwrap();
assert_eq!(decoded, types::Channel::Output(ch));
}
}
#[test]
fn channel_specific_raw_values() {
assert_eq!(channel_to_raw(types::Channel::Input(types::InputChannel::InA)).unwrap(), 0x00);
assert_eq!(channel_to_raw(types::Channel::Input(types::InputChannel::InD)).unwrap(), 0x03);
assert_eq!(channel_to_raw(types::Channel::Output(types::OutputChannel::Out1)).unwrap(), 0x04);
assert_eq!(channel_to_raw(types::Channel::Output(types::OutputChannel::Out8)).unwrap(), 0x0B);
}
// ------------------------------------------------------------------
// NAME
// ------------------------------------------------------------------
#[test]
fn channel_name_round_trip() {
let name = "In ABCDE";
let raw = channel_name_to_raw(name).unwrap();
assert_eq!(raw.len(), protocol_constants::CHANNEL_NAME_LENGTH);
let decoded = raw_to_channel_name(&raw).unwrap();
assert_eq!(decoded, name);
}
#[test]
fn channel_name_padding_is_applied() {
let raw = channel_name_to_raw(" Kick ").unwrap();
assert_eq!(raw.len(), protocol_constants::CHANNEL_NAME_LENGTH);
}
// ------------------------------------------------------------------
// ATTACK / RELEASE / HOLD (simple offset-by-one mappings)
// ------------------------------------------------------------------
#[test]
fn attack_ms_mapping() {
assert_eq!(attack_ms_to_raw(1).unwrap(), 0);
assert_eq!(attack_ms_to_raw(999).unwrap(), 998);
assert_eq!(raw_to_attack_ms(0).unwrap(), 1);
assert_eq!(raw_to_attack_ms(998).unwrap(), 999);
}
#[test]
fn attack_ms_round_trip() {
for ms in [1u16, 50, 500, 999] {
let raw = attack_ms_to_raw(ms).unwrap();
assert_eq!(raw_to_attack_ms(raw).unwrap(), ms);
}
}
#[test]
fn release_ms_mapping() {
assert_eq!(release_ms_to_raw(10).unwrap(), 9);
assert_eq!(release_ms_to_raw(3000).unwrap(), 2999);
assert_eq!(raw_to_release_ms(9).unwrap(), 10);
assert_eq!(raw_to_release_ms(2999).unwrap(), 3000);
}
#[test]
fn release_ms_round_trip() {
for ms in [10u16, 100, 1500, 3000] {
let raw = release_ms_to_raw(ms).unwrap();
assert_eq!(raw_to_release_ms(raw).unwrap(), ms);
}
}
#[test]
fn hold_ms_mapping() {
assert_eq!(hold_ms_to_raw(10).unwrap(), 9);
assert_eq!(hold_ms_to_raw(999).unwrap(), 998);
assert_eq!(raw_to_hold_ms(9).unwrap(), 10);
assert_eq!(raw_to_hold_ms(998).unwrap(), 999);
}
#[test]
fn hold_ms_round_trip() {
for ms in [10u16, 250, 999] {
let raw = hold_ms_to_raw(ms).unwrap();
assert_eq!(raw_to_hold_ms(raw).unwrap(), ms);
}
}
// ------------------------------------------------------------------
// GATE THRESHOLD
// ------------------------------------------------------------------
#[test]
fn gate_threshold_bounds() {
assert_eq!(gate_threshold_db_to_raw(-90.0).unwrap(), 0);
assert_eq!(gate_threshold_db_to_raw(0.0).unwrap(), 180); // (0 - -90) * 2
}
#[test]
fn gate_threshold_round_trip() {
for db in [-90.0f32, -45.5, -20.25, 0.0] {
let raw = gate_threshold_db_to_raw(db).unwrap();
let decoded = raw_to_gate_threshold_db(raw).unwrap();
assert!((decoded - (db * 2.0).round() / 2.0).abs() < 1e-4);
}
}
// ------------------------------------------------------------------
// EQ GAIN
// ------------------------------------------------------------------
#[test]
fn eq_gain_round_trip_endpoints() {
for db in [-12.0f32, 0.0, 12.0] {
let raw = eq_gain_db_to_raw(db).unwrap();
let decoded = raw_to_eq_gain(raw).unwrap();
assert!((decoded - db).abs() < 1e-2);
}
}
#[test]
fn eq_gain_round_trip_arbitrary() {
for db in [-7.3f32, 3.9, 11.99] {
let raw = eq_gain_db_to_raw(db).unwrap();
let decoded = raw_to_eq_gain(raw).unwrap();
// allow one quantization step of tolerance
assert!((decoded - db).abs() <= eq_gain_step_db() + 1e-4);
}
}
// ------------------------------------------------------------------
// PEQ BAND
// ------------------------------------------------------------------
#[test]
fn peq_band_output_all_bands() {
let bands = [
types::PEQBand::B1, types::PEQBand::B2, types::PEQBand::B3,
types::PEQBand::B4, types::PEQBand::B5, types::PEQBand::B6,
types::PEQBand::B7, types::PEQBand::B8, types::PEQBand::B9,
];
let out_channel = types::Channel::Output(types::OutputChannel::Out1);
for (i, band) in bands.into_iter().enumerate() {
assert_eq!(peq_band_to_raw(band, out_channel).unwrap(), i as u8);
}
}
#[test]
fn peq_band_input_allowed_bands() {
let bands = [
types::PEQBand::B1, types::PEQBand::B2, types::PEQBand::B3,
types::PEQBand::B4, types::PEQBand::B5, types::PEQBand::B6,
types::PEQBand::B7, types::PEQBand::B8,
];
let in_channel = types::Channel::Input(types::InputChannel::InA);
for (i, band) in bands.into_iter().enumerate() {
assert_eq!(peq_band_to_raw(band, in_channel).unwrap(), i as u8);
}
}
// ------------------------------------------------------------------
// GAIN (channel gain)
// ------------------------------------------------------------------
#[test]
fn gain_round_trip_low_range() {
for db in [-60.0f32, -40.0, -20.5] {
let raw = gain_db_to_raw(db).unwrap();
let decoded = raw_to_gain(raw).unwrap();
assert!((decoded - db).abs() < 1e-3);
}
}
#[test]
fn gain_round_trip_high_range() {
for db in [-19.9f32, 0.0, 12.0] {
let raw = gain_db_to_raw(db).unwrap();
let decoded = raw_to_gain(raw).unwrap();
assert!((decoded - db).abs() < 1e-3);
}
}
#[test]
fn gain_split_boundary_is_low_step() {
let below = gain_db_to_raw(-20.5).unwrap();
let above = gain_db_to_raw(-19.9).unwrap();
assert_eq!(raw_to_gain(below).unwrap(), -20.5);
assert!((raw_to_gain(above).unwrap() - (-19.9)).abs() < 1e-3);
}
// ------------------------------------------------------------------
// CONTIGUOUS FREQUENCY
// ------------------------------------------------------------------
#[test]
fn frequency_round_trip_endpoints() {
for hz in [19.7f32, 20160.0] {
let raw = frequency_hz_to_raw(hz).unwrap();
let decoded = raw_to_frequency_hz(raw).unwrap();
// log scale quantization tolerance: within 1% of range
assert!((decoded - hz).abs() / hz < 0.01);
}
}
#[test]
fn frequency_round_trip_mid() {
let hz = 1000.0f32;
let raw = frequency_hz_to_raw(hz).unwrap();
let decoded = raw_to_frequency_hz(raw).unwrap();
assert!((decoded - hz).abs() / hz < 0.02);
}
// ------------------------------------------------------------------
// DISCRETE FREQUENCY
// ------------------------------------------------------------------
#[test]
fn discrete_frequency_round_trip_all() {
let freqs = [
types::DiscreteFrequency::F20, types::DiscreteFrequency::F25,
types::DiscreteFrequency::F31_5, types::DiscreteFrequency::F40,
types::DiscreteFrequency::F50, types::DiscreteFrequency::F63,
types::DiscreteFrequency::F80, types::DiscreteFrequency::F100,
types::DiscreteFrequency::F125, types::DiscreteFrequency::F160,
types::DiscreteFrequency::F200, types::DiscreteFrequency::F250,
types::DiscreteFrequency::F315, types::DiscreteFrequency::F400,
types::DiscreteFrequency::F500, types::DiscreteFrequency::F630,
types::DiscreteFrequency::F800, types::DiscreteFrequency::F1000,
types::DiscreteFrequency::F1250, types::DiscreteFrequency::F1600,
types::DiscreteFrequency::F2000, types::DiscreteFrequency::F2500,
types::DiscreteFrequency::F3150, types::DiscreteFrequency::F4000,
types::DiscreteFrequency::F5000, types::DiscreteFrequency::F6300,
types::DiscreteFrequency::F8000, types::DiscreteFrequency::F10000,
types::DiscreteFrequency::F12500, types::DiscreteFrequency::F16000,
types::DiscreteFrequency::F20000,
];
for (i, f) in freqs.into_iter().enumerate() {
let raw = frequency_value_to_raw(f).unwrap();
assert_eq!(raw, i as u8);
assert_eq!(raw_to_frequency_value(raw).unwrap(), f);
}
}
// ------------------------------------------------------------------
// CROSSOVER FILTER
// ------------------------------------------------------------------
#[test]
fn crossover_filter_round_trip_all() {
let filters = [
types::CrossoverFilter::Bypass, types::CrossoverFilter::Bw6,
types::CrossoverFilter::Bl6, types::CrossoverFilter::Bw12,
types::CrossoverFilter::Bl12, types::CrossoverFilter::Lk12,
types::CrossoverFilter::Bw18, types::CrossoverFilter::Bl18,
types::CrossoverFilter::Bw24, types::CrossoverFilter::Bl24,
types::CrossoverFilter::Lk24, types::CrossoverFilter::Bw30,
types::CrossoverFilter::Bl30, types::CrossoverFilter::Bw36,
types::CrossoverFilter::Bl36, types::CrossoverFilter::Lk36,
types::CrossoverFilter::Bw42, types::CrossoverFilter::Bl42,
types::CrossoverFilter::Bw48, types::CrossoverFilter::Bl48,
types::CrossoverFilter::Lk48,
];
for (i, f) in filters.into_iter().enumerate() {
let raw = crossover_filter_to_raw(f).unwrap();
assert_eq!(raw, i as u8);
assert_eq!(raw_to_crossover_filter(raw).unwrap(), f);
}
}
// ------------------------------------------------------------------
// PEQ Q
// ------------------------------------------------------------------
#[test]
fn peq_q_round_trip_peak_filter() {
for q in [0.40f32, 1.0, 10.0, 128.0] {
let raw = peq_q_to_raw(q, types::PEQFilter::Peak).unwrap();
let decoded = raw_to_peq_q(raw).unwrap();
// log scale, allow 5% relative tolerance
assert!((decoded - q).abs() / q < 0.05);
}
}
#[test]
fn peq_q_round_trip_shelf_filter() {
for q in [0.40f32, 1.5, 3.0] {
let raw = peq_q_to_raw(q, types::PEQFilter::LowShelf).unwrap();
let decoded = raw_to_peq_q(raw).unwrap();
assert!((decoded - q).abs() / q < 0.05);
}
}
// ------------------------------------------------------------------
// PEQ FILTER
// ------------------------------------------------------------------
#[test]
fn peq_filter_round_trip_all() {
let filters = [
types::PEQFilter::Peak, types::PEQFilter::LowShelf,
types::PEQFilter::HighShelf, types::PEQFilter::Lp6Db,
types::PEQFilter::Lp12Db, types::PEQFilter::Hp6Db,
types::PEQFilter::Hp12Db, types::PEQFilter::AllPass1,
types::PEQFilter::AllPass2,
];
for (i, f) in filters.into_iter().enumerate() {
let raw = peq_filter_to_raw(f).unwrap();
assert_eq!(raw, i as u8);
assert_eq!(raw_to_peq_filter(raw).unwrap(), f);
}
}
// ------------------------------------------------------------------
// RATIO
// ------------------------------------------------------------------
#[test]
fn ratio_round_trip_all() {
let ratios = [
types::Ratio::Ratio1_1, types::Ratio::Ratio1_1_1, types::Ratio::Ratio1_1_3,
types::Ratio::Ratio1_1_5, types::Ratio::Ratio1_1_7, types::Ratio::Ratio1_2,
types::Ratio::Ratio1_2_5, types::Ratio::Ratio1_3, types::Ratio::Ratio1_3_5,
types::Ratio::Ratio1_4, types::Ratio::Ratio1_5, types::Ratio::Ratio1_6,
types::Ratio::Ratio1_8, types::Ratio::Ratio1_10, types::Ratio::Ratio1_20,
types::Ratio::Limit,
];
for (i, r) in ratios.into_iter().enumerate() {
let raw = ratio_to_raw(r).unwrap();
assert_eq!(raw, i as u16);
assert_eq!(raw_to_ratio(raw).unwrap(), r);
}
}
// ------------------------------------------------------------------
// KNEE
// ------------------------------------------------------------------
#[test]
fn knee_is_identity_mapping() {
for db in [0u16, 6, 12] {
let raw = knee_db_to_raw(db).unwrap();
assert_eq!(raw, db);
assert_eq!(raw_to_knee_db(raw).unwrap(), db);
}
}
// ------------------------------------------------------------------
// THRESHOLD
// ------------------------------------------------------------------
#[test]
fn threshold_round_trip() {
for db in [-90.0f32, -35.5, 0.0, 20.0] {
let raw = threshold_db_to_raw(db).unwrap();
let decoded = raw_to_threshold_db(raw).unwrap();
assert!((decoded - (db * 2.0).round() / 2.0).abs() < 1e-4);
}
}
// ------------------------------------------------------------------
// LINK
// ------------------------------------------------------------------
#[test]
fn link_input_channels() {
let source = types::Channel::Input(types::InputChannel::InA);
let destinations = vec![
types::Channel::Input(types::InputChannel::InA),
types::Channel::Input(types::InputChannel::InC),
];
let mask = link_channels_to_raw(source, &destinations).unwrap();
assert_eq!(mask, 0b0000_0101);
let source_raw = channel_to_raw(source).unwrap();
let decoded = raw_to_link_channels(source_raw, mask).unwrap();
assert_eq!(decoded, destinations);
}
#[test]
fn link_output_channels() {
let source = types::Channel::Output(types::OutputChannel::Out2);
let destinations = vec![
types::Channel::Output(types::OutputChannel::Out2),
types::Channel::Output(types::OutputChannel::Out5),
types::Channel::Output(types::OutputChannel::Out8),
];
let mask = link_channels_to_raw(source, &destinations).unwrap();
assert_eq!(mask, (1 << 1) | (1 << 4) | (1 << 7));
let source_raw = channel_to_raw(source).unwrap();
let decoded = raw_to_link_channels(source_raw, mask).unwrap();
assert_eq!(decoded, destinations);
}
// ------------------------------------------------------------------
// MATRIX GAIN
// ------------------------------------------------------------------
#[test]
fn matrix_gain_round_trip() {
for db in [-60.0f32, -30.25, -19.9, 0.0] {
let raw = matrix_gain_db_to_raw(db).unwrap();
let decoded = raw_to_matrix_gain(raw).unwrap();
assert!((decoded - db).abs() < 0.6);
}
}
// ------------------------------------------------------------------
// MATRIX
// ------------------------------------------------------------------
#[test]
fn matrix_round_trip_all_inputs() {
let all = vec![
types::InputChannel::InA,
types::InputChannel::InB,
types::InputChannel::InC,
types::InputChannel::InD,
];
let raw = matrix_to_raw(&all).unwrap();
let mut decoded = raw_to_matrix(raw).unwrap();
decoded.sort_by_key(|c| format!("{:?}", c));
let mut expected = all.clone();
expected.sort_by_key(|c| format!("{:?}", c));
assert_eq!(decoded, expected);
}
#[test]
fn matrix_round_trip_subset() {
let subset = vec![types::InputChannel::InB, types::InputChannel::InD];
let raw = matrix_to_raw(&subset).unwrap();
let decoded = raw_to_matrix(raw).unwrap();
assert_eq!(decoded.len(), 2);
assert!(decoded.contains(&types::InputChannel::InB));
assert!(decoded.contains(&types::InputChannel::InD));
}
#[test]
fn matrix_empty() {
let raw = matrix_to_raw(&[]).unwrap();
assert_eq!(raw, 0);
assert!(raw_to_matrix(raw).unwrap().is_empty());
}
// ------------------------------------------------------------------
// DELAY
// ------------------------------------------------------------------
#[test]
fn delay_round_trip_milliseconds() {
for ms in [0.0f32, 340.0, 680.0] {
let raw = delay_to_raw(ms, types::DelayUnit::Millisecond).unwrap();
let decoded = raw_to_delay(raw, types::DelayUnit::Millisecond).unwrap();
assert!((decoded - ms).abs() < 5.0); // quantized to 8-bit resolution
}
}
#[test]
fn delay_round_trip_meters() {
for m in [0.0f32, 100.0, 233.58] {
let raw = delay_to_raw(m, types::DelayUnit::Meter).unwrap();
let decoded = raw_to_delay(raw, types::DelayUnit::Meter).unwrap();
assert!((decoded - m).abs() < 2.0);
}
}
#[test]
fn delay_round_trip_feet() {
for ft in [0.0f32, 400.0, 766.329] {
let raw = delay_to_raw(ft, types::DelayUnit::Feet).unwrap();
let decoded = raw_to_delay(raw, types::DelayUnit::Feet).unwrap();
assert!((decoded - ft).abs() < 5.0);
}
}
// ------------------------------------------------------------------
// COPY CHANNEL
// ------------------------------------------------------------------
#[test]
fn copy_channel_inputs() {
let source = types::Channel::Input(types::InputChannel::InA);
let dest = types::Channel::Input(types::InputChannel::InC);
let (raw_source, raw_dest) = copy_channel_to_raw(source, dest).unwrap();
assert_eq!(raw_source, 0x00);
assert_eq!(raw_dest, 0x02);
}
#[test]
fn copy_channel_outputs() {
let source = types::Channel::Output(types::OutputChannel::Out1);
let dest = types::Channel::Output(types::OutputChannel::Out8);
let (raw_source, raw_dest) = copy_channel_to_raw(source, dest).unwrap();
assert_eq!(raw_source, 0x04);
assert_eq!(raw_dest, 0x0B);
}
}