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 { 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 { Ok(channel_to_raw_value(channel)) } pub fn raw_to_channel(value: u8) -> Result { 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 { Ok(channel_to_raw_value(types::Channel::Input(channel))) } pub fn output_channel_to_raw(channel: types::OutputChannel) -> Result { Ok(channel_to_raw_value(types::Channel::Output(channel))) } // ------ NAME ------ pub fn channel_name_to_raw(name: &str) -> Result, 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 { validators::validate_raw_channel_name(raw_name)?; Ok(decode_ascii(raw_name, true)?) } // ------ ATTACK ------ pub fn attack_ms_to_raw(value: u16) -> Result { 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 { validators::validate_raw_attack(value)?; Ok(value + 1) } // ------ RELEASE ------ pub fn release_ms_to_raw(value: u16) -> Result { 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 { validators::validate_raw_release(value)?; Ok(value + 1) } // ------ HOLD ------ pub fn hold_ms_to_raw(hold: u16) -> Result { 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 { validators::validate_raw_hold(hold)?; Ok(hold + 1) } // ------ GATE THRESHOLD ------ pub fn gate_threshold_db_to_raw(db: f32) -> Result { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { Ok(discrete_frequency_to_raw_value(frequency)) } pub fn raw_to_frequency_value(frequency: u8) -> Result { 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 { 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 { Ok(crossover_filter_to_raw_value(filter)) } pub fn raw_to_crossover_filter(raw: u8) -> Result { 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 { 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 { 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 { 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 { Ok(peq_filter_to_raw_value(filter)) } pub fn raw_to_peq_filter(value: u8) -> Result { 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 { 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 { Ok(ratio_to_raw_value(ratio)) } pub fn raw_to_ratio(value: u16) -> Result { 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 { validate_range( db, mapper_constants::KNEE_DB_RANGE, || format!("Invalid compressor knee: {db}"), )?; Ok(db) } pub fn raw_to_knee_db(value: u16) -> Result { validators::validate_raw_compressor_knee(value)?; Ok(value) } // ------ THRESHOLD ------ pub fn threshold_db_to_raw(db: f32) -> Result { 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 { 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 { 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, 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 { 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 { 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 { let mut bitmask = 0x00; for &channel in input_channels { bitmask |= matrix_input_mask(channel); } Ok(bitmask) } pub fn raw_to_matrix(raw: u8) -> Result, 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 { 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 { 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); } }