1use crate::catalog::Module;
4use crate::pack::{pack, unpack};
5
6pub const PRESET_LEN: usize = 110;
7pub const PRESET_COUNT: usize = 99;
8
9#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
11pub struct ModuleState {
12 pub on: bool,
13 pub model: u8,
14 pub params: [u8; 12],
15}
16
17impl ModuleState {
18 pub fn set_head(&mut self, head: u8) {
19 self.on = head & 0x40 == 0;
20 self.model = head & 0x3F;
21 }
22
23 pub fn head(&self) -> u8 {
24 (if self.on { 0 } else { 0x40 }) | (self.model & 0x3F)
25 }
26}
27
28#[derive(Clone, Debug, PartialEq, Eq)]
30pub struct Preset {
31 pub index: u8,
32 pub modules: [ModuleState; 8],
34 pub volume: u8,
35 pub chain: [u8; 7],
37 pub name: String,
38 pub raw: [u8; PRESET_LEN],
39}
40
41const PARAM_LAYOUT: [(usize, usize); 8] = [
43 (25, 26), (32, 33), (41, 42), (61, 62), (66, 67), (73, 74), (20, 21), (48, 49), ];
52
53impl Default for Preset {
54 fn default() -> Self {
55 Preset {
56 index: 0,
57 modules: Default::default(),
58 volume: 80,
59 chain: DEFAULT_CHAIN,
60 name: String::new(),
61 raw: [0; PRESET_LEN],
62 }
63 }
64}
65
66pub const DEFAULT_CHAIN: [u8; 7] = [0, 1, 2, 7, 3, 4, 5];
67
68impl Preset {
69 pub fn from_packed(midi: &[u8]) -> Option<Preset> {
71 Preset::from_raw(&unpack(midi))
72 }
73
74 pub fn from_raw(b: &[u8]) -> Option<Preset> {
75 let raw: [u8; PRESET_LEN] = b.get(..PRESET_LEN)?.try_into().ok()?;
76 let mut p = Preset { index: raw[0], raw, ..Default::default() };
77 for m in Module::EFFECTS {
78 let i = m.index();
79 let st = &mut p.modules[i];
80 st.set_head(raw[12 + i]);
81 let (_, first) = PARAM_LAYOUT[i];
82 let n = m.param_ccs().map(|x| x.1 as usize).unwrap_or(0);
83 st.params[..n].copy_from_slice(&raw[first..first + n]);
84 }
85 p.volume = raw[78];
86 p.chain.copy_from_slice(&raw[83..90]);
87 if p.chain.iter().any(|c| *c > 7) {
88 p.chain = DEFAULT_CHAIN;
89 }
90 p.name = ascii(&raw[94..110]);
91 Some(p)
92 }
93
94 pub fn to_raw(&self) -> [u8; PRESET_LEN] {
97 let mut raw = self.raw;
98 raw[0] = self.index;
99 for m in Module::EFFECTS {
100 let i = m.index();
101 let st = &self.modules[i];
102 if raw[12 + i] & 0x3F != st.model {
103 raw[PARAM_LAYOUT[i].0] = param_count(m, st.model);
104 }
105 raw[12 + i] = st.head();
106 let (_, first) = PARAM_LAYOUT[i];
107 let n = m.param_ccs().map(|x| x.1 as usize).unwrap_or(0);
108 raw[first..first + n].copy_from_slice(&st.params[..n]);
109 }
110 raw[78] = self.volume;
111 raw[83..90].copy_from_slice(&self.chain);
112 raw[94..110].copy_from_slice(&name_bytes(&self.name));
113 raw
114 }
115
116 pub fn to_packed(&self) -> Vec<u8> {
118 pack(&self.to_raw())
119 }
120
121 pub fn module(&self, m: Module) -> Option<&ModuleState> {
122 self.modules.get(m.index())
123 }
124
125 pub fn module_mut(&mut self, m: Module) -> Option<&mut ModuleState> {
126 self.modules.get_mut(m.index())
127 }
128
129 pub fn same_sound(&self, other: &Preset) -> bool {
131 self.volume == other.volume
132 && self.chain == other.chain
133 && Module::EFFECTS.into_iter().all(|m| {
134 let (a, b) = (&self.modules[m.index()], &other.modules[m.index()]);
135 a.on == b.on
136 && a.model == b.model
137 && m.params(a.model)
138 .iter()
139 .all(|p| a.params[p.slot as usize] == b.params[p.slot as usize])
140 })
141 }
142}
143
144pub const SETTINGS_LEN: usize = 17;
145
146pub fn param_count(m: Module, model: u8) -> u8 {
149 match m {
150 Module::Cab => 5,
151 _ => m.params(model).len() as u8,
152 }
153}
154
155pub fn name_bytes(name: &str) -> [u8; 16] {
157 let mut b = [0u8; 16];
158 for (d, c) in b.iter_mut().zip(name.chars()) {
159 *d = if (' '..='~').contains(&c) { c as u8 } else { b'?' };
160 }
161 b
162}
163
164#[derive(Clone, Debug, Default, PartialEq, Eq)]
166pub struct Settings {
167 pub input_trim: u8,
169 pub eq_bass: u8,
170 pub eq_mid: u8,
171 pub eq_treble: u8,
172 pub wireless: bool,
173 pub loopback: bool,
174 pub raw: Vec<u8>,
175}
176
177impl Settings {
178 pub fn from_packed(midi: &[u8]) -> Option<Settings> {
179 let raw = unpack(midi);
180 if raw.len() < 11 {
181 return None;
182 }
183 Some(Settings {
184 input_trim: raw[1],
185 eq_bass: raw[4],
186 eq_mid: raw[5],
187 eq_treble: raw[6],
188 wireless: raw[9] != 0,
189 loopback: raw[10] != 0,
190 raw,
191 })
192 }
193
194 pub fn to_raw(&self) -> Vec<u8> {
196 let mut raw = self.raw.clone();
197 if raw.len() < SETTINGS_LEN {
198 raw.resize(SETTINGS_LEN, 0);
199 }
200 raw[1] = self.input_trim.min(20);
201 raw[4] = self.eq_bass.min(100);
202 raw[5] = self.eq_mid.min(100);
203 raw[6] = self.eq_treble.min(100);
204 raw[9] = self.wireless as u8;
205 raw[10] = self.loopback as u8;
206 raw
207 }
208
209 pub fn to_packed(&self) -> Vec<u8> {
210 pack(&self.to_raw())
211 }
212
213 pub fn input_trim_db(&self) -> i16 {
214 self.input_trim as i16 - 10
215 }
216}
217
218pub fn parse_ir_slot(payload: &[u8]) -> Option<(u8, String)> {
220 let (&slot, rest) = payload.split_first()?;
221 let raw = unpack(rest);
222 Some((slot, ascii(raw.get(4..)?)))
223}
224
225pub fn parse_user_amp(payload: &[u8]) -> Option<(u8, String)> {
228 let i = crate::upload::UserAmpInfo::parse(payload)?;
229 (crate::upload::Chunk::parse(payload)?.sub == crate::upload::amp_sub::READ_INFO).then_some((i.slot, i.name))
230}
231
232pub fn u14(hi: u8, lo: u8) -> u16 {
234 ((hi as u16 & 0x7F) << 7) | (lo as u16 & 0x7F)
235}
236
237pub fn ascii(b: &[u8]) -> String {
239 b.iter()
240 .take_while(|c| **c != 0)
241 .map(|&c| if (0x20..0x7F).contains(&c) { c as char } else { '?' })
242 .collect::<String>()
243 .trim_end()
244 .to_owned()
245}
246
247#[cfg(test)]
248mod tests {
249 use super::*;
250 use crate::pack::pack;
251
252 #[test]
253 fn preset_decode() {
254 let mut raw = [0u8; PRESET_LEN];
255 raw[0] = 4;
256 raw[13] = 0x48; raw[33] = 64; raw[78] = 80;
259 raw[83..90].copy_from_slice(&DEFAULT_CHAIN);
260 raw[94..99].copy_from_slice(b"Hello");
261 let p = Preset::from_packed(&pack(&raw)).unwrap();
262 assert_eq!(p.index, 4);
263 assert!(!p.modules[1].on);
264 assert_eq!(p.modules[1].model, 8);
265 assert_eq!(p.modules[1].params[0], 64);
266 assert_eq!(p.name, "Hello");
267 }
268
269 #[test]
270 fn settings_factory() {
271 let raw = [0x00, 0x0A, 0x1E, 0x0A, 0x32, 0x32, 0x32, 0x32, 0x32, 1, 1, 0x3C, 0xB4, 0, 0, 0, 0];
272 let s = Settings::from_packed(&pack(&raw)).unwrap();
273 assert_eq!(s.input_trim_db(), 0);
274 assert_eq!(s.eq_mid, 50);
275 assert!(s.wireless && s.loopback);
276 assert_eq!(s.to_raw(), raw);
277 let mut s2 = s.clone();
278 s2.input_trim = 13;
279 s2.loopback = false;
280 let r = Settings::from_packed(&s2.to_packed()).unwrap();
281 assert_eq!((r.input_trim, r.loopback, r.raw[12]), (13, false, 0xB4));
282 }
283
284 #[test]
285 fn preset_encode_patches_fields() {
286 let mut raw = [0u8; PRESET_LEN];
287 raw[8] = 0x62;
288 raw[12] = 0x05; raw[25] = 3;
290 raw[83..90].copy_from_slice(&DEFAULT_CHAIN);
291 let mut p = Preset::from_raw(&raw).unwrap();
292 assert_eq!(p.to_raw(), raw);
293 p.index = 9;
294 p.modules[0].model = 3; p.modules[0].on = false;
296 p.modules[0].params[3] = 77;
297 p.name = "Lead ünicode name too long".into();
298 let r = p.to_raw();
299 assert_eq!((r[0], r[8], r[12], r[25], r[29]), (9, 0x62, 0x43, 4, 77));
300 assert_eq!(&r[94..110], b"Lead ?nicode nam");
301 assert_eq!(Preset::from_packed(&p.to_packed()).unwrap().to_raw(), r);
302 }
303}