netzstaub

beatz & funkz

Thursday, August 14, 2008

Encoding 8 bit data in MIDI sysex

Another one in the series “write down stuff before I forget it” (I’m notoriously bad at that). When sending patch data over sysex, you pretty quickly encounter the problem of having to encode 8 bit data over the 7 bit values sysex offers you. I use the pretty basic approach of adding an additional byte every 7 bytes to store the MSB of every value. So 7 values look like this when sent over sysex:

(0 MSB7 MSB6 MSB5 MSB4 MSB3 MSB2 MSB1) 
(BYTE1 & 0x7F) (BYTE2 & 0x7F) (BYTE3 & 0x7F) 
(BYTE4 & 0x7F) (BYTE5 & 0x7F) (BYTE6 & 0x7F) 
(BYTE7 & 0x7F) 

The code to encode values in C is:

uint8_t data_to_sysex(uint8_t *data, uint8_t *sysex, uint8_t len) {
  uint8_t retlen = 0;
  uint8_t cnt;
  uint8_t cnt7 = 0;

  sysex[0] = 0;
  for (cnt = 0; cnt < len; cnt++) {
    uint8_t c = data[cnt] & 0x7F;
    uint8_t msb = data[cnt] >> 7;
    sysex[0] |= msb << cnt7;
    sysex[1 + cnt7] = c;

    if (cnt7++ == 6) {
      sysex += 8;
      retlen += 8;
      sysex[0] = 0;
      cnt7 = 0;
    }
  }
  return retlen + cnt7 + (cnt7 != 0 ? 1 : 0);
}

This code assumes the sysex buffer is big enough to hold the values (not much space for safe programming on embedded platforms :). The main loop encodes the msbs of 7 bytes, and every 7 bytes advances 8 bytes in the destination buffer. The final test is there to remove a trailing 0 byte.

The same code in python (used in the ableton controller code):

def data_to_sysex(data):
    sysex = [0]
    idx = 0
    cnt7 = 0

    for x in data:
        c = x & 0x7F
        msb = x >> 7
        sysex[idx] |= msb << cnt7
        sysex += [c]

        if cnt7 == 6:
            idx += 8
            sysex += [0]
            cnt7 = 0
        else:
            cnt7 += 1

    if cnt7 == 0:
        sysex.pop()
        
    return sysex

which basically does exactly the same. I haven’t written python code for almost a year now, so I’m a bit rusty, so maybe there is a more elegant way to code this 🙂

Decoding is the same process in reverse:

uint8_t sysex_to_data(uint8_t *sysex, uint8_t *data, uint8_t len) {
  uint8_t cnt;
  uint8_t cnt2 = 0;
  uint8_t bits = 0;
  for (cnt = 0; cnt < len; cnt++) {
    if ((cnt % 8) == 0) {
      bits = sysex[cnt];
    } else {
      data[cnt2++] = sysex[cnt] | ((bits & 1) << 7);
      bits >>= 1;
    }
  }
  return cnt2;
}

The bits are store at every 8 byte boundary, and the 7 following bytes are restored by shifting through the bits variable. Once you received a sysex packet, you can just decode data like this (this is out of the controller code):

void receive_page_sysex(void) {
  if (sysex_data[4] == curpatch) {
    uint8_t page = sysex_data[5];
    uint8_t type = sysex_data[6];
    sysex_to_data(sysex_data + 7, 
                  get_encoder_page_data(page),
                  sysex_cnt - 7);
    get_encoder_page(page)->type = type;
    current_page->refresh = 1;
    if (page == curpage) {
      set_page(page);
      flash_active = 0;
    }
  }
}

It checks a bit of stuff in the sysex message itself (bytes 4, 5 and 6), and then decodes the data directly into the right buffer. Again, there is no check whatsoever done here, so you can easily overwrite memory by sending the wrong message, but I don’t think there are hackers interested in owning MIDI controllers running around with rogue MIDI devices 🙂

posted by manuel at 9:53 pm  

No Comments »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a comment

Powered by WordPress