Sunday, March 28, 2010

NRPNs Part 3: Sending with Ableton and M4L

I realized that I didn't finish the NRPN saga with Ableton quite yet. We know how to filter out NRPN changes and use the reconstructed 14 bit values, but how do we send an NRPN change back to the device?

We'll use the same method as before, javascript objects, to split up an NRPN change into 4 CC packets to send back out to the device. Here's a snapshot of the Max device with the addition of the javascript object js nrpn_to_cc.js:


We'll go through the path really quickly...

MIDI comes in through midiin and into midiparse which filters different types of MIDI data to each of it's different outlet. Outlet 2 forwards all CC messages, so we connect that directly to js nrpn_filter.js to filter out NRPN messages. Any non-NRPN related CC messages are sent to outlet 0, which are displayed in the two number objects control change ctrl and value. When we receive 2-4 CC changes that represent an NRPN change we forward the index and value out of outlet 1, which are displayed in the two number objects NRPN change index and value.

After we've unpacked the CC data and taken a look at it, it's repacked and send to midiformat inlet 2, which takes a list of two values and turns it into a MIDI CC event. We do the same with the NRPN data but we have to translate it into the correct MIDI values first in js nrpn_to_cc.js, which sends out 4 CC messages for each NRPN change. midiformat then sends a raw MIDI stream to midiout and to a print object which spits out the raw MIDI to the main Max window for debugging.

The code in nrpn_to_cc.js is much simpler than the filter so we'll just start with pseudocode:

When we receive 2 values
    Split value 0 into two 7 bit values
    Send value 0 high 7 bits as a CC change to CC#99
    Send value 0 low 7 bits as a CC change to CC#98
    Split value 1 into two 7 bit values
    Send value 1 high 7 bits as a CC change to CC#38
    Send value 1 low 7 bits as a CC change to CC#6

Pretty simple! Since we already have the values we don't need to do a lot of voodoo to deal with timing. We could handle Running Status here and optimize the MIDI stream quite a bit but for now we'll just make sure we can reconstruct the NRPN packets to send out. The only real interesting part of this code for the budding code junkie is bitwise operators, so we'll go over those quick, give the full code and call it a day.

To get the high and low 7 bits we can either do some convoluted math to convert from base 2 to base 10 or just use bit operators to do it lickity split. Going back to our first example we'll use the value 572:

572 = 0x023C = 0b 00 0010 0011 1100

First we'll get the bottom 7 bits. We use the bit operator AND to grab certain bits from the number and ignore the rest. To demonstrate we'll do a couple bit operations...

First AND against all ones:
00 0010 0011 1100
AND11 1111 1111 1111
=00 0010 0011 1100

And second AND against all zeros:
00 0010 0011 1100
AND00 0000 0000 0000
=00 0000 0000 0000
Well now that we know if we AND a 1 to our original value, we get exactly what we put in. If we AND a 0 to the value, we always get 0. So what we want is to get the original value for the low 7 bits and ignore all the rest.This is called a bitmask.

So we'll do this for the lower 7 bits:
00 0010 0011 11000x023C
AND00 0000 0111 11110x007F
=00 0000 0011 11000x003C

 And for the higher 7 bits:
00 0010 0011 11000x023C
AND11 1111 1000 00000x3F80
=00 0010 0000 00000x0200
So now we have the lower 7 bits exactly where we want them, but the higher 7 bits aren't in the right place. We need to move them over 7 places to get it in the right position. Luckily there's an operator perfectly suited for this... >> 7 moves all the bits over by 7 places:

0b00 0010 0000 0000 >> 7 = 0b00 0000 0000 0001
0x0200 >> 7 = 0x0001

Now everything is ready to send out! Here's the code for the javascript object:

// MIDI CC constants
const CC_DATA_MSB = 6;
const CC_DATA_LSB = 38;
const CC_NRPN_LSB = 98;
const CC_NRPN_MSB = 99;

// inlets and outlets
inlets = 1;
outlets = 1;

// global variables and arrays
var nrpn_index;            // Save last known rpn index
var data;
var output;                // Up to 8 Bytes to send

// Maxobj variables for scripting
var nrpn_index_lsb;
var nrpn_index_msb;
var data_lsb;
var data_msb;

// methods start here

// list - expects an RPN 14 bit Index + 14 bit Value
function list(val)
{
    if(arguments.length==2)
    {
        nrpn_index = arguments[0];
        data = arguments[1];

        nrpn_index_lsb = nrpn_index & 0x007F;
        nrpn_index_msb = (nrpn_index & 0x3F8) >> 7 ;
        data_lsb = data & 0x007F;
        data_msb = (data & 0x3F8) >> 7;
       
        outlet(0, CC_NRPN_MSB, nrpn_index_msb);
        outlet(0, CC_NRPN_LSB, nrpn_index_lsb);
        outlet(0, CC_DATA_MSB, data_msb);
        outlet(0, CC_DATA_LSB, data_lsb);
    }
}

3 comments:

  1. thanks for these js objects. just what i needed :)

    ReplyDelete
  2. Hiya chris,
    I left a message on gearslutz about being interested in doing some of this. I mentioned there that i was using Java to do all my midi in and out. I only just start exploring the midi api and i found a bug with Mac and midi - when ableton is open accessing the underlying midi system crashes the java part of the app. To get around this i was using some 3rd party java API's to access the midi system. This bypasses ableton and hence allows Sysex. I would be interested in your thoughts on this and if this would be a good way to go (bypassing ableton for the sysex and possibly nrpn). I spose it does give you the flexibility to choose to send midi to ableton or out via the midi system.

    cheers
    kle

    ReplyDelete
  3. data_msb = (data & 0x3F8) >> 7;

    A zero is missing: (data & 0x3F80), without it it'll be only capable of 1024 as the highest value.

    ReplyDelete