model.rs (7537B)
1 //! Device model for the **Scarlett 18i20 3rd Gen** — the config-parameter map and 2 //! the I/O topology, transcribed from the kernel driver's `gen3c` config set and 3 //! `s18i20_gen3_info` descriptor. These are wire facts (offsets/sizes/activation 4 //! codes), the contract the hardware exposes. 5 //! 6 //! Most front-panel controls are a `(offset, size, activate)` triple: write the 7 //! value at `base_offset + channel * byte_width`, then send the `activate` code. 8 9 /// A readable/writable configuration parameter, located by byte `offset` in the 10 /// device data space. `bits` is the on-wire width (1, 8, 16, or 32); `activate` 11 /// is the [`crate::protocol::Scarlett::activate`] code that applies a write. 12 #[derive(Debug, Clone, Copy, PartialEq, Eq)] 13 pub struct Config { 14 pub offset: u16, 15 pub bits: u8, 16 pub activate: u8, 17 } 18 19 impl Config { 20 /// Width in bytes for a `set_data` write (bit-sized params write one byte). 21 pub fn byte_width(&self) -> u32 { 22 match self.bits { 23 1 => 1, 24 n => (n as u32) / 8, 25 } 26 } 27 28 /// Byte offset of element `channel` in a multi-channel parameter (air per 29 /// input, line-out volume per output, …). Bit-packed params (`bits == 1`, 30 /// e.g. phantom) keep the base offset; the caller addresses the group. 31 pub fn channel_offset(&self, channel: u32) -> u32 { 32 if self.bits == 1 { 33 self.offset as u32 + channel // group index, one byte each here 34 } else { 35 self.offset as u32 + channel * self.byte_width() 36 } 37 } 38 } 39 40 /// The gen3c configuration parameters present on the 18i20 g3. Indices are our 41 /// own; the driver's enum order is irrelevant once we carry explicit offsets. 42 #[derive(Debug, Clone, Copy, PartialEq, Eq)] 43 pub enum Param { 44 DimMute, 45 LineOutVolume, 46 MuteSwitch, 47 SwHwSwitch, 48 MasterVolume, 49 LevelSwitch, // Inst/Line per input (2 inputs) 50 PadSwitch, // 10 dB pad per input (8 inputs) 51 AirSwitch, // Air per input (8 inputs) 52 StandaloneSwitch, 53 PhantomSwitch, // 48V per phantom group (2 groups, 4 inputs each) 54 MsdSwitch, 55 PhantomPersistence, 56 MonitorOtherSwitch, 57 MonitorOtherEnable, 58 TalkbackMap, 59 SpdifMode, 60 } 61 62 impl Param { 63 /// The `gen3c` config entry for this parameter (offsets confirmed against 64 /// the kernel driver: `scarlett2_config_set_gen3c`). 65 pub fn config(self) -> Config { 66 use Param::*; 67 let (offset, bits, activate) = match self { 68 DimMute => (0x31, 8, 2), 69 LineOutVolume => (0x34, 16, 1), 70 MuteSwitch => (0x5c, 8, 1), 71 SwHwSwitch => (0x66, 8, 3), 72 MasterVolume => (0x76, 16, 0), // read-only (hardware-controlled) 73 LevelSwitch => (0x7c, 8, 7), 74 PadSwitch => (0x84, 8, 8), 75 AirSwitch => (0x8c, 8, 8), 76 StandaloneSwitch => (0x95, 8, 6), 77 PhantomSwitch => (0x9c, 1, 8), 78 MsdSwitch => (0x9d, 8, 6), 79 PhantomPersistence => (0x9e, 8, 6), 80 MonitorOtherSwitch => (0x9f, 1, 10), 81 MonitorOtherEnable => (0xa0, 1, 10), 82 TalkbackMap => (0xb0, 16, 10), 83 SpdifMode => (0x94, 8, 6), 84 }; 85 Config { offset, bits, activate } 86 } 87 } 88 89 /// A category of physical or virtual signal port. `(inputs, outputs)` counts are 90 /// from the device descriptor — "inputs" are sources into the routing matrix, 91 /// "outputs" are sinks out of it. 92 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 93 pub enum PortType { 94 Analogue, 95 Spdif, 96 Adat, 97 /// The internal mixer matrix. 98 Mix, 99 /// DAW playback/capture channels. 100 Pcm, 101 } 102 103 /// Static topology of the Scarlett 18i20 3rd Gen. 104 #[derive(Debug, Clone, Copy)] 105 pub struct DeviceInfo { 106 pub name: &'static str, 107 pub vid: u16, 108 pub pid: u16, 109 /// Inputs that support Inst/Line switching. 110 pub level_input_count: u8, 111 /// Inputs that support the 10 dB pad. 112 pub pad_input_count: u8, 113 /// Inputs that support Air. 114 pub air_input_count: u8, 115 /// Number of independent 48V phantom groups. 116 pub phantom_count: u8, 117 /// Physical inputs fed by each phantom group. 118 pub inputs_per_phantom: u8, 119 pub has_talkback: bool, 120 pub has_speaker_switching: bool, 121 /// Total metering points returned by GET_METER (sum of the device's 122 /// `meter_map` spans: 8 + 10 + 20 + 2 + 25 on the 18i20 g3). 123 pub meter_count: u16, 124 } 125 126 impl DeviceInfo { 127 /// Number of mixer inputs (signals that can feed a mix bus) — the MIX port 128 /// type's sink count. 129 pub fn mixer_inputs(&self) -> u16 { 130 self.port_count(PortType::Mix).1 as u16 131 } 132 133 /// Number of mix buses (mixer outputs) — the MIX port type's source count. 134 pub fn mix_buses(&self) -> u16 { 135 self.port_count(PortType::Mix).0 as u16 136 } 137 138 /// Total routing destinations (sinks) = sum of every port type's sink count. 139 /// Used as the `count` for reading the full mux/routing table. 140 pub fn mux_dst_count(&self) -> usize { 141 [ 142 PortType::Analogue, 143 PortType::Spdif, 144 PortType::Adat, 145 PortType::Mix, 146 PortType::Pcm, 147 ] 148 .iter() 149 .map(|&t| self.port_count(t).1 as usize) 150 .sum() 151 } 152 153 /// `(sources_into_matrix, sinks_out_of_matrix)` for a port type, transcribed 154 /// from the kernel's `s18i20_gen3_info.port_count[type][IN/OUT]`. Note the 9th 155 /// analogue *source* is the talkback mic. 156 pub fn port_count(&self, ty: PortType) -> (u8, u8) { 157 match ty { 158 PortType::Analogue => (9, 10), 159 PortType::Spdif => (2, 2), 160 PortType::Adat => (8, 8), 161 PortType::Mix => (12, 25), 162 PortType::Pcm => (20, 20), 163 } 164 } 165 } 166 167 /// The Scarlett 18i20 3rd Gen. 168 pub const S18I20_GEN3: DeviceInfo = DeviceInfo { 169 name: "Scarlett 18i20 3rd Gen", 170 vid: 0x1235, 171 pid: 0x8215, 172 level_input_count: 2, 173 pad_input_count: 8, 174 air_input_count: 8, 175 phantom_count: 2, 176 inputs_per_phantom: 4, 177 has_talkback: true, 178 has_speaker_switching: true, 179 meter_count: 65, 180 }; 181 182 #[cfg(test)] 183 mod tests { 184 use super::*; 185 186 #[test] 187 fn air_offsets_step_one_byte_per_input() { 188 let c = Param::AirSwitch.config(); 189 assert_eq!(c.offset, 0x8c); 190 assert_eq!(c.byte_width(), 1); 191 assert_eq!(c.channel_offset(0), 0x8c); 192 assert_eq!(c.channel_offset(7), 0x8c + 7); // 8th input 193 } 194 195 #[test] 196 fn line_out_volume_is_16bit_two_bytes_per_channel() { 197 let c = Param::LineOutVolume.config(); 198 assert_eq!(c.bits, 16); 199 assert_eq!(c.byte_width(), 2); 200 assert_eq!(c.channel_offset(0), 0x34); 201 assert_eq!(c.channel_offset(1), 0x36); // +2 bytes 202 assert_eq!(c.activate, 1); 203 } 204 205 #[test] 206 fn phantom_is_bit_sized_per_group() { 207 let c = Param::PhantomSwitch.config(); 208 assert_eq!(c.bits, 1); 209 assert_eq!(c.byte_width(), 1); 210 assert_eq!(c.channel_offset(0), 0x9c); // group 0 211 assert_eq!(c.channel_offset(1), 0x9d); // group 1 212 assert_eq!(c.activate, 8); 213 } 214 215 #[test] 216 fn descriptor_matches_18i20_topology() { 217 let d = S18I20_GEN3; 218 assert_eq!(d.port_count(PortType::Analogue), (9, 10)); // 9th = talkback 219 assert_eq!(d.port_count(PortType::Pcm), (20, 20)); 220 assert_eq!(d.air_input_count, 8); 221 assert_eq!(d.phantom_count, 2); 222 assert_eq!(d.meter_count, 65); 223 assert!(d.has_talkback); 224 } 225 }