preset.rs (7075B)
1 //! Presets — capture the device's user-settable state to a serializable snapshot, 2 //! and apply one back. This powers "save/load a configuration" in the TUI and is 3 //! independent of the device's own standalone (NVRAM) save. 4 //! 5 //! What's captured: the per-input switches (Air / Pad / Inst), the 48 V phantom 6 //! groups, the monitor Mute / Dim, and the full mixer matrix. **Not** captured: 7 //! the master monitor level (it's read-only — owned by the hardware knob) and 8 //! routing (edit path isn't validated yet). Applying only writes fields present, 9 //! so older/newer presets degrade gracefully. 10 11 use serde::{Deserialize, Serialize}; 12 13 use crate::controls::{InputState, InputSwitch, MonitorButton, MonitorState}; 14 use crate::matrix::db_to_mixer_value; 15 use crate::protocol::Scarlett; 16 use crate::transport::{Transport, TransportError}; 17 18 /// Snapshot format version, so future readers can adapt. 19 pub const PRESET_VERSION: u32 = 1; 20 21 /// A saved device configuration. 22 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] 23 pub struct Preset { 24 pub version: u32, 25 /// Free-text label shown in the UI. 26 pub name: String, 27 /// Device product id this was captured from (sanity check on load). 28 pub pid: u16, 29 pub air: Vec<bool>, 30 pub pad: Vec<bool>, 31 pub inst: Vec<bool>, 32 pub phantom: Vec<bool>, 33 pub mute: bool, 34 pub dim: bool, 35 /// Mixer matrix as `[bus][input]` in dB. 36 pub mixer: Vec<Vec<f32>>, 37 /// Full routing as a flat `mux[dest_num] = source_num` array (port numbers). 38 /// Empty in older presets; when present, applying restores routing too. 39 #[serde(default)] 40 pub routing: Vec<u16>, 41 } 42 43 impl Preset { 44 /// Build a preset from already-read state (no device I/O). `routing` is the 45 /// flat `mux[dest]=src` port-number array (empty = don't capture routing). 46 pub fn from_state( 47 name: impl Into<String>, 48 pid: u16, 49 inputs: &InputState, 50 monitor: &MonitorState, 51 mixer: &[Vec<f32>], 52 routing: &[u16], 53 ) -> Self { 54 Preset { 55 version: PRESET_VERSION, 56 name: name.into(), 57 pid, 58 air: inputs.air.clone(), 59 pad: inputs.pad.clone(), 60 inst: inputs.inst.clone(), 61 phantom: inputs.phantom.clone(), 62 mute: monitor.mute, 63 dim: monitor.dim, 64 mixer: mixer.to_vec(), 65 routing: routing.to_vec(), 66 } 67 } 68 69 /// Serialize to pretty JSON (human-editable on disk). 70 pub fn to_json(&self) -> String { 71 serde_json::to_string_pretty(self).unwrap_or_default() 72 } 73 74 /// Parse from JSON. 75 pub fn from_json(s: &str) -> Result<Self, serde_json::Error> { 76 serde_json::from_str(s) 77 } 78 } 79 80 impl<T: Transport> Scarlett<T> { 81 /// Apply a preset to the device. Writes each switch + activates, sets phantom 82 /// groups, monitor mute/dim, and every mixer crosspoint. Returns the number 83 /// of device writes performed (useful for a status line). 84 pub fn apply_preset(&mut self, p: &Preset) -> Result<usize, TransportError> { 85 let mut writes = 0; 86 87 for (i, &on) in p.air.iter().enumerate() { 88 self.set_input_switch(InputSwitch::Air, i as u8, on)?; 89 writes += 1; 90 } 91 for (i, &on) in p.pad.iter().enumerate() { 92 self.set_input_switch(InputSwitch::Pad, i as u8, on)?; 93 writes += 1; 94 } 95 for (i, &on) in p.inst.iter().enumerate() { 96 self.set_input_switch(InputSwitch::Inst, i as u8, on)?; 97 writes += 1; 98 } 99 for (g, &on) in p.phantom.iter().enumerate() { 100 self.set_phantom(g as u8, on)?; 101 writes += 1; 102 } 103 104 self.set_monitor_button(MonitorButton::Mute, p.mute)?; 105 self.set_monitor_button(MonitorButton::Dim, p.dim)?; 106 writes += 2; 107 108 for (bus, inputs) in p.mixer.iter().enumerate() { 109 let levels: Vec<u16> = inputs.iter().map(|&db| db_to_mixer_value(db)).collect(); 110 if !levels.is_empty() { 111 self.set_mix(bus as u16, &levels)?; 112 writes += 1; 113 } 114 } 115 116 // Routing last, as one atomic write (the big visible change). 117 if !p.routing.is_empty() { 118 use crate::mux::{mux_assignment_18i20_gen3, MuxState, PORT_COUNT_18I20_GEN3}; 119 let pc = PORT_COUNT_18I20_GEN3; 120 let state = MuxState { pc, mux: p.routing.clone() }; 121 let tables = state.encode_all(&mux_assignment_18i20_gen3()); 122 self.write_routing_tables(&tables)?; 123 writes += 1; 124 } 125 126 Ok(writes) 127 } 128 } 129 130 #[cfg(test)] 131 mod tests { 132 use super::*; 133 use crate::protocol::op; 134 use crate::transport::mock::MockTransport; 135 136 fn sample() -> Preset { 137 Preset { 138 version: PRESET_VERSION, 139 name: "Vocal chain".into(), 140 pid: 0x8215, 141 air: vec![true, false], 142 pad: vec![false], 143 inst: vec![true], 144 phantom: vec![true, false], 145 mute: false, 146 dim: true, 147 mixer: vec![vec![0.0, -6.0]], 148 routing: vec![], 149 } 150 } 151 152 #[test] 153 fn json_round_trips() { 154 let p = sample(); 155 let json = p.to_json(); 156 let back = Preset::from_json(&json).unwrap(); 157 assert_eq!(p, back); 158 assert!(json.contains("\"name\": \"Vocal chain\"")); 159 } 160 161 #[test] 162 fn from_state_captures_fields() { 163 let inputs = InputState { 164 air: vec![true, true], 165 pad: vec![false, false], 166 inst: vec![false, true], 167 phantom: vec![true, false], 168 }; 169 let mon = MonitorState { master_db: -3, mute: true, dim: false }; 170 let mixer = vec![vec![0.0, 0.0]]; 171 let p = Preset::from_state("test", 0x8215, &inputs, &mon, &mixer, &[]); 172 assert_eq!(p.air, vec![true, true]); 173 assert_eq!(p.inst, vec![false, true]); 174 assert!(p.mute); 175 assert_eq!(p.pid, 0x8215); 176 // master volume is intentionally not stored (read-only hardware knob) 177 } 178 179 #[test] 180 fn apply_preset_writes_and_activates() { 181 // apply_preset interleaves set_data/activate/set_mix; an echoing mock 182 // returns a matching empty success for each, so we just assert the count 183 // of high-level writes and that the right commands were emitted. 184 let mut dev = Scarlett::new(MockTransport::echoing()); 185 let writes = dev.apply_preset(&sample()).unwrap(); 186 // 2 air + 1 pad + 1 inst + 2 phantom + 2 monitor + 1 mixer bus = 9 187 assert_eq!(writes, 9); 188 189 let m = dev.into_transport(); 190 let set_mix = m.sent.iter().filter(|(c, _)| *c == op::SET_MIX).count(); 191 let set_data = m.sent.iter().filter(|(c, _)| *c == op::SET_DATA).count(); 192 assert_eq!(set_mix, 1); // one mixer bus 193 // SET_DATA = 2 air + 1 pad + 1 inst + 2 phantom + 2 monitor(mute,dim) = 8 194 // (phantom and monitor buttons are byte writes via set_data too) 195 assert_eq!(set_data, 8); 196 } 197 }