Teensy 3.1 USB_MIDI NRPN Support

Status
Not open for further replies.

syso2342

Well-known member
Hi,

I am wondering if somebody was already working on NRPN support for 14-Bit Values over MIDI
instead of the normal 7-Bit Values as CC Messages.

I am building an digitally controlled analog synthesizer and need 14-Bit Values for more
precision for some parameters.

PS: My synthesizer schematics and all project files are planned to be released after
completion of the project!
 
You can do it using CCs, from wikipedia:
Unlike other MIDI controllers (such as velocity, modulation, volume, etc.), NRPNs require more than one piece of controller data to be sent. First, controller 99 - NRPN Most Significant Byte (MSB) - followed by 98 - NRPN Least Significant Byte (LSB) sent as a pair specify the parameter to be changed. Controller 6 then sets the value of the parameter in question. Controller 38 may optionally then be sent as a fine adjustment to the value set by controller 6.
 
I was working on this a bit before my summer vacation (just back and catching up).

Here is some sample code with functions to send NRPN.

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 2014[/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]
}
 
Hi Nantonos,

I got the code to work by swapping the MSB for the LSB of the parameter and the value. But I don't quite undertstand what it sends there.

My functioning code now looks like this:

usb_midi_write_packed(0xB00B | (((channel - 1) & 0x0F) << 8) | ((99 & 0x7F) << 16) | (((parameter >> 7) & 0x7F) << 24)); // Control change 99 for NRPN, MSB of param
usb_midi_write_packed(0xB00B | (((channel - 1) & 0x0F) << 8) | ((98 & 0x7F) << 16) | ((parameter & 0x7F) << 24)); // Control change 98 for NRPN, LSB of param
usb_midi_write_packed(0xB00B | (((channel - 1) & 0x0F) << 8) | ((6 & 0x7F) << 16) | (((value >> 7) & 0x7F) << 24)); // Control change 6 for Data Entry MSB of value
usb_midi_write_packed(0xB00B | (((channel - 1) & 0x0F) << 8) | ((38 & 0x7F) << 16) | ((value & 0x7F) << 24)); // Control change 38 for Data Entry LSB of value

// Now null out active parameter by sending null (127, 0x7F) as MSB and LSB
usb_midi_write_packed(0xB00B | (((channel - 1) & 0x0F) << 8) | ((101 & 0x7F) << 16) | (0x7F << 24)); // Control change 101 for RPN, MSB of param
usb_midi_write_packed(0xB00B | (((channel - 1) & 0x0F) << 8) | ((100 & 0x7F) << 16) | (0x7F << 24)); // Control change 100 for NRPN, LSB of param

Looking at the first message, the CC99 for parameter = 1000 and channel = 12 would be this then:

111 01100011 10111011 00001011

What I can read from that is this:

111 is the MSB of our parameter number 1000. 1000 in binary being 1111101000

01100011 - 0 indicating a data byte, 1100011 being the Controller number 99

10111011 - 1 indicating the status byte, 011 telling it's a CC message, 1011 channel 12 (12-1 in binary being 1011)

So if this is right so far, what does the last byte 00001011 do?
And why do the to the databytes (111 being 00000111) come before the status byte?


I would be really happy to hear from you despite the age of this thread. :cool:
 
NRPN & RPN are fully supported by the 3 midi libs.
Have a look at Examples/Teensy/USB_MIDI/TransmitEverything.ino
 
Oh and is there a documentation of the usb_midi_write_packed() function anywhere? I can't find one.

Just analyzed this code this morning, it just sends a 32 bit value to usb and that's what is called by the usb midi send(...) method and also all sysex sending code.
I personally would not use this low level API that could change in future, and would probably use send() instead.
When you use directly this c function you have to format the quad-byte value and now you reveal the endian-ness. Again, that could change with different architecture and it would be best to use the standard API if possible.
EDIT: why not using beginNrpn(), endRpn(), sendRpnValue() directly?

See in usb_midi.h:
Code:
	void beginRpn(uint16_t number, uint8_t channel, uint8_t cable=0) __attribute__((always_inline)) {
		sendControlChange(101, number >> 7, channel, cable);
		sendControlChange(100, number, channel, cable);
	}
	void sendRpnValue(uint16_t value, uint8_t channel, uint8_t cable=0) __attribute__((always_inline)) {
		sendControlChange(6, value >> 7, channel, cable);
		sendControlChange(38, value, channel, cable);
	}
	void sendRpnIncrement(uint8_t amount, uint8_t channel, uint8_t cable=0) __attribute__((always_inline)) {
		sendControlChange(96, amount, channel, cable);
	}
	void sendRpnDecrement(uint8_t amount, uint8_t channel, uint8_t cable=0) __attribute__((always_inline)) {
		sendControlChange(97, amount, channel, cable);
	}
	void endRpn(uint8_t channel, uint8_t cable=0) __attribute__((always_inline)) {
		sendControlChange(101, 0x7F, channel, cable);
		sendControlChange(100, 0x7F, channel, cable);
	}
	void beginNrpn(uint16_t number, uint8_t channel, uint8_t cable=0) __attribute__((always_inline)) {
		sendControlChange(99, number >> 7, channel, cable);
		sendControlChange(98, number, channel, cable);
	}
	void sendNrpnValue(uint16_t value, uint8_t channel, uint8_t cable=0) __attribute__((always_inline)) {
		sendControlChange(6, value >> 7, channel, cable);
		sendControlChange(38, value, channel, cable);
        }
 
Oh and is there a documentation of the usb_midi_write_packed() function anywhere? I can't find one.

Yes. It's documented in the USB MIDI class spec ("Universal Serial Bus Device Class Definition for MIDI Devices", release 1.0, Nov 1, 1999):

https://www.usb.org/sites/default/files/midi10.pdf

Look on page 16 at Figure 8. In that figure, even though byte 0 is shown on the left, it's the LSB in the 32 bit word usb_midi_write_packed() takes as input.
 
Status
Not open for further replies.
Back
Top