14-bit MIDI over USB?

Status
Not open for further replies.

kriista

Member
I'm trying to build a fader to USB-MIDI box that will send 14bit MIDI data using one of these faders:
http://www.ebay.co.uk/itm/Alps-Fade...sistor-Pathway-45mm-Length-60mm-/351026911105

I've done a similar thing sending highByte/lowByte over serial, but not sure how to go about it if trying to stay class-compliant MIDI over USB.

I'll be receiving the data into Max/MSP where I can merge the values into a single one easily, it's more the teensy/arduino code that I'm unsure about.
 
Yes, this can be easily done. Indeed there are multiple ways in the MIDI specification to do it; using the paired set of MSB/LSB MIDI CCs, or by using RPN or NRPN.

The main problem you will find is not the implementtion in Teensy, which is easy, but finding a MIDI implementation to connect to which is good enough that it understands them. There are a lot of lowest common denominator MIDI implementations which don't support 14bit (or do, but throw away 7 bits of it).

Here is one example which I have handy (it uses a second CC to give 14-bit velocity):
Code:
[color=#7E7E7E]/* Try out MIDI CC88 High Resolution Velocity prefix[/color]
[color=#7E7E7E] 2007 addition to allow 14-bit velocity values from 0x0080H to 0x03FFF.[/color]
[color=#7E7E7E] For compatibility with the running-status note-off hack, 0x00 to 0x7F all mean 'note off'[/color]
[color=#7E7E7E] http://www.midi.org/techspecs/ca31.pdf[/color]
[color=#7E7E7E] Chris Lilley 2013[/color]
[color=#7E7E7E] This example code is in the public domain.[/color]
[color=#7E7E7E] */[/color]

#define middleC 60  [color=#7E7E7E]// midi note 60 is C4[/color]

[color=#CC6600]void[/color] sendNoteOnHRV([color=#CC6600]uint32_t[/color] note, [color=#CC6600]uint32_t[/color] velocity, [color=#CC6600]uint32_t[/color] channel) {
  [color=#CC6600]if[/color] (velocity < 256) 
  { 
    [color=#CC6600][b]usbMIDI[/b][/color].[color=#CC6600]sendNoteOn[/color](note, 0, channel); 
  }
  [color=#CC6600]else[/color]
  { 
    [color=#CC6600][b]usbMIDI[/b][/color].[color=#CC6600]sendControlChange[/color](0x058, (velocity & 0x7F),channel);
    [color=#CC6600][b]usbMIDI[/b][/color].[color=#CC6600]sendNoteOn[/color](note, ((velocity >>7) & 0x7F), channel);
  }
}

[color=#CC6600]void[/color] sendNoteOffHRV([color=#CC6600]uint32_t[/color] note, [color=#CC6600]uint32_t[/color] velocity, [color=#CC6600]uint32_t[/color] channel) {
  [color=#CC6600]if[/color] (velocity < 256) 
  { 
    [color=#CC6600][b]usbMIDI[/b][/color].[color=#CC6600]sendNoteOff[/color](note, 0, channel); 
  }
  [color=#CC6600]else[/color]
  { 
    [color=#CC6600][b]usbMIDI[/b][/color].[color=#CC6600]sendControlChange[/color](0x058, (velocity & 0x7F),channel);
    [color=#CC6600][b]usbMIDI[/b][/color].[color=#CC6600]sendNoteOff[/color](note, ((velocity >>7) & 0x7F), channel);
  }
}

[color=#CC6600]void[/color] [color=#CC6600][b]setup[/b][/color](){
}

[color=#CC6600]void[/color] [color=#CC6600][b]loop[/b][/color]() {
  [color=#CC6600]int[/color] i = 0;
  [color=#CC6600]delay[/color](5000);
  [color=#CC6600]for[/color] (i=0; i<= 0x04000; i+=16)
  {
    sendNoteOnHRV(middleC, i, 1);
    [color=#CC6600]delay[/color] (500);
    [color=#CC6600][b]usbMIDI[/b][/color].[color=#CC6600]sendNoteOff[/color](middleC, 0, 1);
    [color=#CC6600]delay[/color] (200);
  }
}
 
Last edited:
Hey Rodrigo, looks like we hangout on the same forums ;) (I'm Nat on Cycling '74)

I can help you with this.
Typically the MSB and LSB are offset by 32 "values" when using controller messages.
so if you use CC1 for the MSB, you would send the LSB on CC33

There's a pretty good explanation and code example here: http://www.midibox.org/dokuwiki/doku.php?id=midi_specification#bit_midi_messages

Let me know if you need more help.

Edit: here's code that I use myself with Max and Teensy: https://github.com/natcl/dmx_midi#using-system-exclusive-messages
 
Last edited:
Here is another example, using NRPN. Two functions are defined, one for 7 bit data and one for high resolution (14bit) data.

Code:
[color=#7E7E7E]/* Demo of sending MIDI Non-Registered Parameter Number (NRPN)[/color]
[color=#7E7E7E] Allows high resolution 14-bit or normal resolution 7-bit values for up[/color]
[color=#7E7E7E] to 2^14 numbered controllers. Controller numbers are implementation [/color]
[color=#7E7E7E] specific (make up your own).[/color]
[color=#7E7E7E] [/color]
[color=#7E7E7E] Documentation:[/color]
[color=#7E7E7E] http://www.midi.org/techspecs/midimessages.php[/color]
[color=#7E7E7E] http://www.philrees.co.uk/nrpnq.htm[/color]
[color=#7E7E7E] http://home.roadrunner.com/~jgglatt/tutr/rpn.htm[/color]
[color=#7E7E7E] [/color]
[color=#7E7E7E] Chris Lilley 2013[/color]
[color=#7E7E7E] This example code is in the public domain.[/color]
[color=#7E7E7E] */[/color]

[color=#7E7E7E]// Fixed values, for demonstration[/color]
[color=#CC6600]uint32_t[/color] channel = 1;          [color=#7E7E7E]// channels are 1 to 16[/color]
[color=#CC6600]uint32_t[/color] param = 123456;   [color=#7E7E7E]// your parameter number here, 0 to 16k[/color]
[color=#CC6600]uint32_t[/color] val = 54321;          [color=#7E7E7E]// your value here, 0 to 16k[/color]
[color=#7E7E7E]// in actual use this would come from some physical controller like an analog value[/color]
[color=#7E7E7E]// or be computed by monitoring an encoder[/color]

[color=#CC6600]void[/color] sendNRPN([color=#CC6600]uint32_t[/color] parameter, [color=#CC6600]uint32_t[/color] value, [color=#CC6600]uint32_t[/color] channel)  {
  [color=#7E7E7E]// Send an arbitrary 7-bit value to an arbitrary 14-bit non-registered parameter[/color]
  [color=#7E7E7E]// Note that param MSB and LSB are shared between RPN and NRPN, so set both at once[/color]
  [color=#7E7E7E]// Also, null out the RPN (and NRPN) active parameter (best practice) after data is sent (as it persists)[/color]

  [color=#7E7E7E]// Control change 99 for NRPN, MSB of param[/color]
  usb_midi_write_packed(0xB00B | (((channel - 1) & 0x0F) << 8)  | ((99 & 0x7F) << 16) | ((parameter & 0x7F) << 24));
  [color=#7E7E7E]// Control change 98 for NRPN, LSB of param[/color]
  usb_midi_write_packed(0xB00B | (((channel - 1) & 0x0F) << 8)  | ((98 & 0x7F) << 16) | (((parameter >>7) & 0x7F) << 24));
  [color=#7E7E7E]// Control change 6 for Data Entry MSB of value[/color]
  usb_midi_write_packed(0xB00B | (((channel - 1) & 0x0F) << 8)  | ((6 & 0x7F) << 16) | ((value & 0x7F) << 24));
  [color=#7E7E7E]// Now null out active parameter by sending null (127, 0x7F) as MSB and LSB[/color]
  [color=#7E7E7E]// Control change 101 for RPN, MSB of param[/color]
  usb_midi_write_packed(0xB00B | (((channel - 1) & 0x0F) << 8)  | ((101 & 0x7F) << 16) | (0x7F << 24));
  [color=#7E7E7E]// Control change 100 for NRPN, LSB of param[/color]
  usb_midi_write_packed(0xB00B | (((channel - 1) & 0x0F) << 8)  | ((100 & 0x7F) << 16) | (0x7F << 24));
}

[color=#CC6600]void[/color] sendNRPNHR([color=#CC6600]uint32_t[/color] parameter, [color=#CC6600]uint32_t[/color] value, [color=#CC6600]uint32_t[/color] channel)  {
  [color=#7E7E7E]// Send an arbitrary 14-bit value to an arbitrary 14-bit non-registered parameter[/color]
  [color=#7E7E7E]// Note that param MSB and LSB are shared between RPN and NRPN, so set both at once[/color]
  [color=#7E7E7E]// Also, null out the RPN (and NRPN) active parameter (best practice) after data is sent (as it persists)[/color]

  [color=#7E7E7E]// Control change 99 for NRPN, MSB of param[/color]
  usb_midi_write_packed(0xB00B | (((channel - 1) & 0x0F) << 8)  | ((99 & 0x7F) << 16) | ((parameter & 0x7F) << 24));
  [color=#7E7E7E]// Control change 98 for NRPN, LSB of param[/color]
  usb_midi_write_packed(0xB00B | (((channel - 1) & 0x0F) << 8)  | ((98 & 0x7F) << 16) | (((parameter >>7) & 0x7F) << 24));
  [color=#7E7E7E]// Control change 6 for Data Entry MSB of value[/color]
  usb_midi_write_packed(0xB00B | (((channel - 1) & 0x0F) << 8)  | ((6 & 0x7F) << 16) | ((value & 0x7F) << 24));
  [color=#7E7E7E]// Control change 38 for Data Entry LSB of param[/color]
  usb_midi_write_packed(0xB00B | (((channel - 1) & 0x0F) << 8)  | ((38 & 0x7F) << 16) | (((value >>7) & 0x7F) << 24));
  [color=#7E7E7E]// Now null out active parameter by sending null (127, 0x7F) as MSB and LSB[/color]
  [color=#7E7E7E]// Control change 101 for RPN, MSB of param[/color]
  usb_midi_write_packed(0xB00B | (((channel - 1) & 0x0F) << 8)  | ((101 & 0x7F) << 16) | (0x7F << 24));
  [color=#7E7E7E]// Control change 100 for NRPN, LSB of param[/color]
  usb_midi_write_packed(0xB00B | (((channel - 1) & 0x0F) << 8)  | ((100 & 0x7F) << 16) | (0x7F << 24));
}

[color=#CC6600]void[/color] [color=#CC6600][b]setup[/b][/color]() {
  [color=#CC6600]delay[/color] (5000); [color=#7E7E7E]// start up your MIDI monitoring now[/color]
  sendNRPNHR(param, val, 1); [color=#7E7E7E]// channel 1[/color]
}

[color=#CC6600]void[/color] [color=#CC6600][b]loop[/b][/color]() {
  [color=#CC6600]delay[/color] (3000); [color=#7E7E7E]// slowly, for testing[/color]
  sendNRPNHR(param, val, 1); [color=#7E7E7E]// channel 1[/color]
}

nrpn-demo.png
 
Hi Nat!

Ok, so if I just want to send a MSB/LSB pair, and then merge them in Max would it be something like this:

Code:
void setup() {
}

void loop() {
  usbMIDI.sendControlChange(1, (highByte(analogRead(A0)), channel);
  usbMIDI.sendControlChange(33, (lowByte(analogRead(A0)), channel);            
}

And then just use [expr ($i1 & 127) | ($i2 << 7)] in Max to put them back together?
 
No, highByte and lowByte won't do it (you are splitting a 14bit value into 2 7bit, not 16 into 2 8's).

The paired CC method has a few disadvantages: its less well supported than NRPN (though that may not matter if you are writing the receiver as well), there are only 32 controls available and most have defined meanings.
 
The receiver will be custom in Max either way, so the 'universality' of it is unimportant.

I also remembered that the ADC isn't 14-bit either way, so not sure if that effects this kind of splitting. So do I need to read the ADC, then prep that range to be split over MIDI?
 
Yes, you read the ADC then split it (like the code I posted splits 'value' in the sendNRPNHR function).

You can use analogReadResolution(bits) to set it to 14.
 
Hello Nantanos, we are working on a controller based on Teensy2 to send midi CC to Pd Extended (with our project La Malinette : http://reso-nance.org/malinette/fr/home).
I try to use your code to improve the resolution of what we curently get (normal 128 values, all details avaibles here : http://reso-nance.org/wiki/projets/malinette-brutbox/accueil)
I get this issues while compiling your NRPN demo code :
warning: left shift count >= width of type [enabled by default]
usb_midi_write_packed(0xB00B | (((channel - 1) & 0x0F) << 8) | ((98 & 0x7F) << 16) | (((parameter >> 7) & 0x7F) << 24));


Can you help ?
thx very much.
 
Status
Not open for further replies.
Back
Top