1use crate::midi::{Cmd, Dir, frame};
16use crate::pack::{pack, unpack};
17
18pub const CHUNK_BYTES: usize = 136;
20pub const UPLOAD_FLOATS: usize = 2048;
22pub const USER_SLOTS: u8 = 10;
24pub const USER_MODEL_BASE: u8 = 30;
26
27#[derive(Clone, Debug, PartialEq, Eq)]
29pub enum UploadError {
30 Slot(u8),
32 Length(usize),
34 NotFinite,
36}
37
38impl std::fmt::Display for UploadError {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 match self {
41 UploadError::Slot(s) => write!(f, "user slot {s} out of range (0-9)"),
42 UploadError::Length(n) => write!(f, "{n} floats, the pedal takes exactly {UPLOAD_FLOATS}"),
43 UploadError::NotFinite => write!(f, "model data contains NaN or infinite values"),
44 }
45 }
46}
47
48impl std::error::Error for UploadError {}
49
50pub fn check(slot: u8, data: &[f32]) -> Result<(), UploadError> {
52 if slot >= USER_SLOTS {
53 return Err(UploadError::Slot(slot));
54 }
55 if data.len() != UPLOAD_FLOATS {
56 return Err(UploadError::Length(data.len()));
57 }
58 if !data.iter().all(|f| f.is_finite()) {
59 return Err(UploadError::NotFinite);
60 }
61 Ok(())
62}
63
64pub mod amp_sub {
66 pub const DATA: u8 = 1;
67 pub const WRITE_INFO: u8 = 3;
68 pub const READ_INFO: u8 = 4;
69 pub const DELETE: u8 = 6;
70}
71
72pub const IR_DATA_SUB: u8 = 0;
74
75#[derive(Clone, Debug, PartialEq, Eq)]
77pub struct Chunk {
78 pub ir_slot: u8,
80 pub slot: u8,
82 pub sub: u8,
83 pub offset: u32,
85 pub total: u32,
87 pub data: Vec<u8>,
88}
89
90impl Chunk {
91 pub fn parse(payload: &[u8]) -> Option<Chunk> {
93 let raw = unpack(payload);
94 let h = raw.get(..12)?;
95 Some(Chunk {
96 ir_slot: h[0],
97 slot: h[1],
98 sub: h[2],
99 offset: u32::from_le_bytes(h[4..8].try_into().ok()?),
100 total: u32::from_le_bytes(h[8..12].try_into().ok()?),
101 data: raw[12..].to_vec(),
102 })
103 }
104
105 fn header(ir_slot: u8, slot: u8, sub: u8, offset: u32, total: u32) -> [u8; 12] {
106 let mut h = [0u8; 12];
107 h[0] = ir_slot;
108 h[1] = slot;
109 h[2] = sub;
110 h[4..8].copy_from_slice(&offset.to_le_bytes());
111 h[8..12].copy_from_slice(&total.to_le_bytes());
112 h
113 }
114
115 pub fn to_packed(&self) -> Vec<u8> {
117 let mut raw = Self::header(self.ir_slot, self.slot, self.sub, self.offset, self.total).to_vec();
118 raw.extend_from_slice(&self.data);
119 pack(&raw)
120 }
121}
122
123pub fn data_frames(cmd: Cmd, slot: u8, sub: u8, data: &[u8]) -> Vec<Vec<u8>> {
126 let total = data.len() as u32;
127 let (ir_slot, slot) = if cmd == Cmd::IrData { (slot, 0) } else { (0, slot) };
128 data.chunks(CHUNK_BYTES)
129 .enumerate()
130 .map(|(i, part)| {
131 let c = Chunk { ir_slot, slot, sub, offset: (i * CHUNK_BYTES) as u32, total, data: part.to_vec() };
132 frame(cmd, Dir::Write, &c.to_packed())
133 })
134 .collect()
135}
136
137pub fn floats_to_bytes(v: &[f32]) -> Vec<u8> {
139 v.iter().flat_map(|f| f.to_le_bytes()).collect()
140}
141
142pub fn bytes_to_floats(b: &[u8]) -> Vec<f32> {
143 b.as_chunks::<4>().0.iter().map(|c| f32::from_le_bytes(*c)).collect()
144}
145
146#[derive(Clone, Debug, Default)]
148pub struct Reassembler {
149 buf: Vec<u8>,
150 filled: usize,
151 slot: Option<u8>,
152}
153
154impl Reassembler {
155 pub fn push(&mut self, c: &Chunk) -> Option<(u8, Vec<f32>)> {
157 let (start, total) = (c.offset as usize, c.total as usize);
158 let slot = c.slot | c.ir_slot;
160 if start == 0 || self.slot != Some(slot) || self.buf.len() != total {
162 self.buf = vec![0; total];
163 self.filled = 0;
164 self.slot = Some(slot);
165 }
166 let end = (start + c.data.len()).min(total);
167 if start >= end {
168 return None;
169 }
170 self.buf[start..end].copy_from_slice(&c.data[..end - start]);
171 self.filled += end - start;
172 (self.filled >= total).then(|| {
173 self.filled = 0;
174 (slot, bytes_to_floats(&self.buf))
175 })
176 }
177}
178
179pub const AMP_INFO_LEN: usize = 128;
180const AMP_NAME_LEN: usize = 32;
181const AMP_FACTOR_AT: usize = 52;
182
183#[derive(Clone, Debug, PartialEq)]
190pub struct UserAmpInfo {
191 pub slot: u8,
192 pub name: String,
193 pub factor: f32,
194 pub raw: [u8; AMP_INFO_LEN],
195}
196
197impl UserAmpInfo {
198 pub fn new(slot: u8, name: &str, factor: f32) -> Self {
200 let mut raw = [0u8; AMP_INFO_LEN];
201 raw[32..36].copy_from_slice(&256u32.to_le_bytes());
202 UserAmpInfo { slot, name: name.to_owned(), factor, raw }
203 }
204
205 pub fn parse(payload: &[u8]) -> Option<Self> {
207 let c = Chunk::parse(payload)?;
208 if c.sub != amp_sub::WRITE_INFO && c.sub != amp_sub::READ_INFO {
209 return None;
210 }
211 let raw: [u8; AMP_INFO_LEN] = c.data.get(..AMP_INFO_LEN)?.try_into().ok()?;
212 Some(UserAmpInfo {
213 slot: c.slot,
214 name: crate::records::ascii(&raw[..AMP_NAME_LEN]),
215 factor: f32::from_le_bytes(raw[AMP_FACTOR_AT..AMP_FACTOR_AT + 4].try_into().ok()?),
216 raw,
217 })
218 }
219
220 pub fn to_raw(&self) -> [u8; AMP_INFO_LEN] {
221 let mut raw = self.raw;
222 raw[..AMP_NAME_LEN].copy_from_slice(&name_field::<AMP_NAME_LEN>(&self.name));
223 raw[AMP_FACTOR_AT..AMP_FACTOR_AT + 4].copy_from_slice(&self.factor.to_le_bytes());
224 raw
225 }
226
227 pub fn write_frame(&self) -> Vec<u8> {
229 let c = Chunk {
230 ir_slot: 0,
231 slot: self.slot,
232 sub: amp_sub::WRITE_INFO,
233 offset: 0,
234 total: AMP_INFO_LEN as u32,
235 data: self.to_raw().to_vec(),
236 };
237 frame(Cmd::UserAmp, Dir::Write, &c.to_packed())
238 }
239}
240
241fn name_field<const N: usize>(name: &str) -> [u8; N] {
243 let mut b = [0u8; N];
244 for (d, c) in b.iter_mut().take(N - 1).zip(name.chars()) {
245 *d = if (' '..='~').contains(&c) { c as u8 } else { b'?' };
246 }
247 b
248}
249
250pub fn user_amp_upload(info: &UserAmpInfo, data: &[f32]) -> Vec<Vec<u8>> {
253 let mut v = vec![info.write_frame()];
254 v.extend(data_frames(Cmd::UserAmp, info.slot, amp_sub::DATA, &floats_to_bytes(data)));
255 v
256}
257
258pub fn user_amp_delete(slot: u8) -> Vec<u8> {
260 let c = Chunk { ir_slot: 0, slot, sub: amp_sub::DELETE, offset: 0, total: 0, data: vec![] };
261 frame(Cmd::UserAmp, Dir::Write, &c.to_packed())
262}
263
264const IR_NAME_LEN: usize = 32;
265
266pub fn user_ir_name(slot: u8, name: &str) -> Vec<u8> {
269 let mut raw = vec![1, 0, 0, 0];
270 raw.extend_from_slice(&name_field::<IR_NAME_LEN>(name));
271 let mut p = vec![slot & 0x7F];
272 p.extend(pack(&raw));
273 frame(Cmd::IrSlot, Dir::Write, &p)
274}
275
276pub fn user_ir_upload(slot: u8, name: &str, data: &[f32]) -> Vec<Vec<u8>> {
281 let mut v = data_frames(Cmd::IrData, slot, IR_DATA_SUB, &floats_to_bytes(data));
282 v.push(user_ir_name(slot, name));
283 v
284}
285
286#[cfg(test)]
287mod tests {
288 use super::*;
289 use crate::midi::{Msg, hex};
290
291 #[test]
292 fn chunking() {
293 let data: Vec<f32> = (0..UPLOAD_FLOATS).map(|i| i as f32 * 0.5).collect();
294 let frames = data_frames(Cmd::UserAmp, 2, amp_sub::DATA, &floats_to_bytes(&data));
295 assert_eq!(frames.len(), 61);
296 let mut r = Reassembler::default();
297 let mut out = None;
298 for f in &frames {
299 let Msg::Nep { cmd: 0x58, dir: 1, payload } = Msg::parse(f) else { panic!() };
300 let c = Chunk::parse(&payload).unwrap();
301 assert_eq!((c.slot, c.sub, c.total), (2, 1, 8192));
302 out = r.push(&c);
303 }
304 assert_eq!(out, Some((2, data)));
305 let Msg::Nep { payload, .. } = Msg::parse(frames.last().unwrap()) else { panic!() };
307 let c = Chunk::parse(&payload).unwrap();
308 assert_eq!((c.offset, c.data.len()), (8160, 32));
309 }
310
311 #[test]
312 fn info_round_trip() {
313 let i = UserAmpInfo::new(2, "Jcm800", 1.0);
314 let Msg::Nep { payload, .. } = Msg::parse(&i.write_frame()) else { panic!() };
315 let p = UserAmpInfo::parse(&payload).unwrap();
316 assert_eq!(p, UserAmpInfo { raw: i.to_raw(), ..i });
317 assert_eq!(hex(&user_amp_delete(0)), "F0 00 21 7A 70 58 01 00 00 18 00 00 00 00 00 00 00 00 00 00 00 F7");
318 }
319
320 #[test]
323 fn rename_is_the_imports_name_record() {
324 let data = vec![0.25f32; UPLOAD_FLOATS];
325 let amp = user_amp_upload(&UserAmpInfo::new(4, "SVT G10 .5", 1.0), &data);
326 assert_eq!(UserAmpInfo::new(4, "SVT G10 .5", 1.0).write_frame(), amp[0]);
327 let ir = user_ir_upload(7, "SVT Neumann .5", &data);
328 assert_eq!(&user_ir_name(7, "SVT Neumann .5"), ir.last().unwrap());
329 let frame = UserAmpInfo::new(0, &"é".repeat(40), 1.0).write_frame();
331 let Msg::Nep { payload, .. } = Msg::parse(&frame) else { panic!() };
332 assert_eq!(UserAmpInfo::parse(&payload).unwrap().name, "?".repeat(31));
333 }
334
335}