1pub const MFR: [u8; 3] = [0x00, 0x21, 0x7A];
4pub const UID: u8 = 0x70;
5
6pub const PEDAL_CH: u8 = 0;
8pub const HOST_CH: u8 = 1;
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
11pub enum Cmd {
12 IrSlot = 0x09,
13 Preset = 0x0B,
14 EditBuffer = 0x0C,
15 IrData = 0x10,
16 PresetName = 0x12,
17 System = 0x14,
18 CurState = 0x15,
19 Loop = 0x18,
20 Drum = 0x19,
21 UserAmp = 0x58,
22 Tuner = 0x6F,
23 Notify = 0x7E,
24}
25
26impl Cmd {
27 pub fn from_u8(b: u8) -> Option<Cmd> {
28 use Cmd::*;
29 Some(match b {
30 0x09 => IrSlot,
31 0x0B => Preset,
32 0x0C => EditBuffer,
33 0x10 => IrData,
34 0x12 => PresetName,
35 0x14 => System,
36 0x15 => CurState,
37 0x18 => Loop,
38 0x19 => Drum,
39 0x58 => UserAmp,
40 0x6F => Tuner,
41 0x7E => Notify,
42 _ => return None,
43 })
44 }
45
46 pub fn label(b: u8) -> &'static str {
47 match b {
48 0x09 => "user IR slot",
49 0x0B => "preset",
50 0x0C => "edit buffer",
51 0x0D => "module link",
52 0x10 => "IR data",
53 0x12 => "preset name",
54 0x14 => "system settings",
55 0x15 => "current state",
56 0x16 => "delete IR",
57 0x18 => "looper",
58 0x19 => "drum",
59 0x1B => "bluetooth",
60 0x58 => "user amp (NAM)",
61 0x6F => "tuner",
62 0x7E => "notify",
63 _ => "unknown",
64 }
65 }
66}
67
68#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
69pub enum Dir {
70 Read = 0,
71 Write = 1,
72 Reply = 2,
73 Ack = 3,
74}
75
76impl Dir {
77 pub fn label(b: u8) -> &'static str {
78 match b {
79 0 => "read",
80 1 => "write",
81 2 => "reply",
82 3 => "ack",
83 _ => "?",
84 }
85 }
86}
87
88pub fn frame(cmd: Cmd, dir: Dir, payload: &[u8]) -> Vec<u8> {
90 let mut v = Vec::with_capacity(payload.len() + 8);
91 v.push(0xF0);
92 v.extend_from_slice(&MFR);
93 v.push(UID);
94 v.push(cmd as u8);
95 v.push(dir as u8);
96 debug_assert!(payload.iter().all(|b| *b < 0x80));
97 v.extend_from_slice(payload);
98 v.push(0xF7);
99 v
100}
101
102pub fn short_request(kind: u8) -> Vec<u8> {
104 vec![0xF0, MFR[0], MFR[1], MFR[2], kind, 0xF7]
105}
106
107pub mod req {
109 use super::*;
110 use crate::pack::pack;
111
112 pub fn version() -> Vec<u8> {
113 short_request(0x00)
114 }
115 pub fn product() -> Vec<u8> {
116 short_request(0x60)
117 }
118 pub fn settings() -> Vec<u8> {
119 frame(Cmd::System, Dir::Read, &[])
120 }
121 pub fn cur_state() -> Vec<u8> {
122 frame(Cmd::CurState, Dir::Read, &[])
123 }
124 pub fn drum() -> Vec<u8> {
125 frame(Cmd::Drum, Dir::Read, &[])
126 }
127 pub fn looper() -> Vec<u8> {
128 frame(Cmd::Loop, Dir::Read, &[])
129 }
130 pub fn tuner() -> Vec<u8> {
131 frame(Cmd::Tuner, Dir::Read, &[])
132 }
133 pub fn edit_buffer() -> Vec<u8> {
137 frame(Cmd::EditBuffer, Dir::Read, &[0; 14])
138 }
139 pub fn ir_slot(slot: u8) -> Vec<u8> {
140 frame(Cmd::IrSlot, Dir::Read, &[slot])
141 }
142 pub fn preset(index: u8) -> Vec<u8> {
143 let mut p = vec![0u8; 14];
144 p[0] = index & 0x7F;
145 frame(Cmd::Preset, Dir::Read, &p)
146 }
147 pub fn user_amp(slot: u8) -> Vec<u8> {
148 let mut hdr = [0u8; 12];
149 hdr[1] = slot;
150 hdr[2] = 4; frame(Cmd::UserAmp, Dir::Read, &pack(&hdr))
152 }
153}
154
155pub mod host {
158 use super::*;
159 use crate::catalog::Module;
160 use crate::records::{Preset, Settings, name_bytes};
161
162 pub fn cc(cc: u8, val: u8) -> Vec<u8> {
163 vec![0xB0 | HOST_CH, cc & 0x7F, val & 0x7F]
164 }
165
166 pub fn program(index: u8) -> Vec<u8> {
168 vec![0xC0 | HOST_CH, index & 0x7F]
169 }
170
171 pub fn head(m: Module, model: u8, on: bool) -> Option<Vec<u8>> {
173 Some(cc(m.head_cc()?, (if on { 0 } else { 0x40 }) | (model & 0x3F)))
174 }
175
176 pub fn param(m: Module, slot: usize, val: u8) -> Option<Vec<u8>> {
178 let (first, n) = m.param_ccs()?;
179 (slot < n as usize).then(|| cc(first + slot as u8, val))
180 }
181
182 pub fn volume(v: u8) -> Vec<u8> {
183 cc(0x3A, v.min(100))
184 }
185
186 pub fn page(m: Module) -> Vec<u8> {
188 cc(0x3B, m as u8)
189 }
190
191 pub const LOOP_REC_PLAY_DUB: u8 = 0x01;
192 pub const LOOP_STOP: u8 = 0x02;
193 pub const LOOP_CLEAR: u8 = 0x04;
194 pub const LOOP_UNDO_REDO: u8 = 0x08;
195
196 pub fn looper_button(b: u8) -> Vec<u8> {
197 cc(0x44, b)
198 }
199
200 pub fn preset_name(source_slot: u8, name: &str) -> Vec<u8> {
203 let mut p = Vec::with_capacity(17);
204 p.push(source_slot & 0x7F);
205 p.extend(name_bytes(name).iter().map(|b| b & 0x7F));
206 frame(Cmd::PresetName, Dir::Write, &p)
207 }
208
209 pub fn preset_write(preset: &Preset) -> Vec<u8> {
211 frame(Cmd::Preset, Dir::Write, &preset.to_packed())
212 }
213
214 pub fn save_preset(source_slot: u8, preset: &Preset) -> Vec<Vec<u8>> {
216 vec![preset_name(source_slot, &preset.name), preset_write(preset), program(preset.index)]
217 }
218
219 pub fn settings_write(s: &Settings) -> Vec<u8> {
220 frame(Cmd::System, Dir::Write, &s.to_packed())
221 }
222}
223
224#[derive(Clone, Debug, PartialEq, Eq)]
226pub enum Msg {
227 Cc { ch: u8, cc: u8, val: u8 },
228 Pc { ch: u8, prog: u8 },
229 Nep { cmd: u8, dir: u8, payload: Vec<u8> },
231 Notify { what: u8, payload: Vec<u8> },
233 Short { kind: u8, payload: Vec<u8> },
235 Other(Vec<u8>),
237}
238
239impl Msg {
240 pub fn parse(b: &[u8]) -> Msg {
241 let Some(&s) = b.first() else {
242 return Msg::Other(vec![]);
243 };
244 match s & 0xF0 {
245 0xB0 if b.len() >= 3 => return Msg::Cc { ch: s & 0x0F, cc: b[1], val: b[2] },
246 0xC0 if b.len() >= 2 => return Msg::Pc { ch: s & 0x0F, prog: b[1] },
247 _ => {}
248 }
249 if s != 0xF0 || b.len() < 6 || b[1..4] != MFR {
250 return Msg::Other(b.to_vec());
251 }
252 let end = if b.last() == Some(&0xF7) { b.len() - 1 } else { b.len() };
253 let body = &b[4..end];
254 if body[0] != UID || body.len() < 3 {
255 return Msg::Short { kind: body[0], payload: body[1..].to_vec() };
256 }
257 let (cmd, dir, payload) = (body[1], body[2], &body[3..]);
258 if cmd == Cmd::Notify as u8 && payload.len() >= 2 {
259 return Msg::Notify { what: payload[0], payload: payload[2..].to_vec() };
260 }
261 Msg::Nep { cmd, dir, payload: payload.to_vec() }
262 }
263}
264
265#[derive(Default)]
267pub struct Assembler {
268 buf: Vec<u8>,
269}
270
271impl Assembler {
272 pub fn push(&mut self, data: &[u8]) -> Vec<Vec<u8>> {
273 self.buf.extend_from_slice(data);
274 let mut out = vec![];
275 loop {
276 let start = self.buf.iter().position(|b| *b >= 0x80).unwrap_or(self.buf.len());
278 self.buf.drain(..start);
279 let Some(&s) = self.buf.first() else { break };
280 let len = if s == 0xF0 {
281 match self.buf.iter().position(|b| *b == 0xF7) {
282 Some(e) => e + 1,
283 None => break,
284 }
285 } else if s >= 0xF8 {
286 1
287 } else {
288 let need = match s & 0xF0 {
289 0xC0 | 0xD0 => 2,
290 0xF0 => 1,
291 _ => 3,
292 };
293 if self.buf.len() < need {
294 break;
295 }
296 match self.buf[1..need].iter().position(|b| *b >= 0x80) {
298 Some(p) => p + 1,
299 None => need,
300 }
301 };
302 out.push(self.buf.drain(..len).collect());
303 }
304 out
305 }
306}
307
308pub fn hex(b: &[u8]) -> String {
309 let mut s = String::with_capacity(b.len() * 3);
310 for (i, x) in b.iter().enumerate() {
311 if i > 0 {
312 s.push(' ');
313 }
314 s.push_str(&format!("{x:02X}"));
315 }
316 s
317}
318
319#[cfg(test)]
320mod tests {
321 use super::*;
322
323 #[test]
324 fn parse_basic() {
325 assert_eq!(Msg::parse(&[0xB0, 0x12, 0x40]), Msg::Cc { ch: 0, cc: 0x12, val: 0x40 });
326 assert_eq!(Msg::parse(&[0xC1, 4]), Msg::Pc { ch: 1, prog: 4 });
327 assert_eq!(
328 Msg::parse(&[0xF0, 0, 0x21, 0x7A, 0x70, 0x7E, 0x02, 0x19, 0x02, 1, 2, 3, 0, 0, 0x78, 0xF7]),
329 Msg::Notify { what: 0x19, payload: vec![1, 2, 3, 0, 0, 0x78] }
330 );
331 assert_eq!(
332 Msg::parse(&req::settings()),
333 Msg::Nep { cmd: 0x14, dir: 0, payload: vec![] }
334 );
335 }
336
337 #[test]
338 fn assembler_splits_and_joins() {
339 let mut a = Assembler::default();
340 assert!(a.push(&[0xF0, 0x00, 0x21]).is_empty());
341 let m = a.push(&[0x7A, 0x00, 0xF7, 0xB0, 0x01, 0x08, 0xC0]);
342 assert_eq!(m, vec![vec![0xF0, 0, 0x21, 0x7A, 0, 0xF7], vec![0xB0, 1, 8]]);
343 assert_eq!(a.push(&[0x05]), vec![vec![0xC0, 5]]);
344 }
345
346 #[test]
347 fn host_commands() {
348 use crate::catalog::Module;
349 assert_eq!(host::head(Module::Amp, 8, false), Some(vec![0xB1, 0x01, 0x48]));
350 assert_eq!(host::param(Module::OdFx, 1, 66), Some(vec![0xB1, 0x0D, 66]));
351 assert_eq!(host::param(Module::Gate, 2, 1), None);
352 assert_eq!(host::head(Module::Loop, 0, true), None);
353 assert_eq!(host::program(84), vec![0xC1, 0x54]);
354 assert_eq!(host::page(Module::Gate), vec![0xB1, 0x3B, 0x06]);
355 assert_eq!(
357 hex(&host::preset_name(0x44, "4Ooz!")),
358 "F0 00 21 7A 70 12 01 44 34 4F 6F 7A 21 00 00 00 00 00 00 00 00 00 00 00 F7"
359 );
360 }
361
362 #[test]
363 fn user_amp_request_matches_capture() {
364 assert_eq!(
365 hex(&req::user_amp(1)),
366 "F0 00 21 7A 70 58 00 00 02 10 00 00 00 00 00 00 00 00 00 00 00 F7"
367 );
368 }
369}