//! JamJum JP-Mini wire protocol, reverse-engineered from KuSuite 5.3 (see jpmini.py). //! //! Packet: 78 59 //! SysEx: F0 F7 pub const SLOTS: usize = 16; const PAYLOAD_SLOTS: usize = 18; const CMD_PAD_RGB: u8 = 0x51; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Rgb(pub u8, pub u8, pub u8); impl Rgb { pub const OFF: Rgb = Rgb(0, 0, 0); } pub type Grid = [Rgb; SLOTS]; fn packet(cmd: u8, payload: &[u8]) -> Vec { let n = payload.len() as u32; let mut p = vec![0x78, 0x59, cmd, n as u8, (n >> 8) as u8, (n >> 16) as u8]; p.extend_from_slice(payload); p.push(!payload.iter().fold(0u8, |a, &b| a.wrapping_add(b))); p } fn pack7(data: &[u8]) -> Vec { let mut out = Vec::with_capacity(data.len() * 8 / 7 + 1); let (mut acc, mut bits) = (0u32, 0u32); for &b in data { acc |= u32::from(b) << bits; bits += 8; while bits >= 7 { out.push((acc & 0x7F) as u8); acc >>= 7; bits -= 7; } } if bits > 0 { out.push((acc & 0x7F) as u8); } out } /// Complete SysEx message setting all 16 pads of the bank currently displayed. pub fn pad_colors_sysex(grid: &Grid) -> Vec { let mut payload = Vec::with_capacity(1 + 3 * PAYLOAD_SLOTS); payload.push(0xFF); for &Rgb(r, g, b) in grid { payload.extend_from_slice(&[g, r, b]); // the device expects G, R, B } payload.resize(1 + 3 * PAYLOAD_SLOTS, 0); let mut msg = vec![0xF0]; msg.extend(pack7(&packet(CMD_PAD_RGB, &payload))); msg.push(0xF7); msg } #[cfg(test)] mod tests { use super::*; fn hex(s: &str) -> Vec { s.split_whitespace() .map(|b| u8::from_str_radix(b, 16).unwrap()) .collect() } #[test] fn pack7_matches_reference() { assert_eq!( pack7(&[0x78, 0x59, 0xFF, 0x00, 0x80]), vec![0x78, 0x32, 0x7D, 0x07, 0x00, 0x10] ); } #[test] fn all_off_matches_reference() { let expected = hex( "F0 78 32 45 3A 03 00 40 7F 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 \ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 \ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 F7", ); assert_eq!(pad_colors_sysex(&[Rgb::OFF; SLOTS]), expected); } #[test] fn mixed_grid_matches_reference() { let mut grid = [Rgb::OFF; SLOTS]; grid[0] = Rgb(0, 0, 255); grid[1] = Rgb(40, 40, 40); grid[15] = Rgb(255, 100, 0); let expected = hex( "F0 78 32 45 3A 03 00 40 7F 00 00 7C 47 02 05 0A 00 00 00 00 00 00 00 00 00 00 00 00 \ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 \ 00 00 00 00 20 76 1F 00 00 00 00 00 00 00 40 09 F7", ); assert_eq!(pad_colors_sysex(&grid), expected); } }