Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 8 of 8

Thread: Teensy 3.1 USB_MIDI NRPN Support

  1. #1

    Teensy 3.1 USB_MIDI NRPN Support

    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!

  2. #2
    Senior Member
    Join Date
    Jun 2013
    Location
    Montréal
    Posts
    467
    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.

  3. #3
    Senior Member
    Join Date
    Nov 2012
    Location
    Boston, MA, USA
    Posts
    1,112
    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:
    /* Demo of sending MIDI Non-Registered Parameter Number (NRPN)
     Allows high resolution 14-bit or normal resolution 7-bit values for up
     to 2^14 numbered controllers. Controller numbers are implementation 
     specific (make up your own).
    
     Documentation:
     http://www.midi.org/techspecs/midimessages.php
     http://www.philrees.co.uk/nrpnq.htm
     http://home.roadrunner.com/~jgglatt/tutr/rpn.htm
    
     Chris Lilley 2014
     This example code is in the public domain.
     */
    
    // Fixed values, for demonstration
    uint32_t channel = 1;          // channels are 1 to 16
    uint32_t param = 123456;   // your parameter number here, 0 to 16k
    uint32_t val = 54321;          // your value here, 0 to 16k
    // in actual use this would come from some physical controller like an analog value
    // or be computed by monitoring an encoder
    
    void sendNRPN(uint32_t parameter, uint32_t value, uint32_t channel)  {
      // Send an arbitrary 7-bit value to an arbitrary 14-bit non-registered parameter
      // Note that param MSB and LSB are shared between RPN and NRPN, so set both at once
      // Also, null out the RPN (and NRPN) active parameter (best practice) after data is sent (as it persists)
    
      // Control change 99 for NRPN, MSB of param
      usb_midi_write_packed(0xB00B | (((channel - 1) & 0x0F) << 8)  | ((99 & 0x7F) << 16) | ((parameter & 0x7F) << 24));
      // Control change 98 for NRPN, LSB of param
      usb_midi_write_packed(0xB00B | (((channel - 1) & 0x0F) << 8)  | ((98 & 0x7F) << 16) | (((parameter >>7) & 0x7F) << 24));
      // Control change 6 for Data Entry MSB of value
      usb_midi_write_packed(0xB00B | (((channel - 1) & 0x0F) << 8)  | ((6 & 0x7F) << 16) | ((value & 0x7F) << 24));
      // Now null out active parameter by sending null (127, 0x7F) as MSB and LSB
      // Control change 101 for RPN, MSB of param
      usb_midi_write_packed(0xB00B | (((channel - 1) & 0x0F) << 8)  | ((101 & 0x7F) << 16) | (0x7F << 24));
      // Control change 100 for NRPN, LSB of param
      usb_midi_write_packed(0xB00B | (((channel - 1) & 0x0F) << 8)  | ((100 & 0x7F) << 16) | (0x7F << 24));
    }
    
    void sendNRPNHR(uint32_t parameter, uint32_t value, uint32_t channel)  {
      // Send an arbitrary 14-bit value to an arbitrary 14-bit non-registered parameter
      // Note that param MSB and LSB are shared between RPN and NRPN, so set both at once
      // Also, null out the RPN (and NRPN) active parameter (best practice) after data is sent (as it persists)
    
      // Control change 99 for NRPN, MSB of param
      usb_midi_write_packed(0xB00B | (((channel - 1) & 0x0F) << 8)  | ((99 & 0x7F) << 16) | ((parameter & 0x7F) << 24));
      // Control change 98 for NRPN, LSB of param
      usb_midi_write_packed(0xB00B | (((channel - 1) & 0x0F) << 8)  | ((98 & 0x7F) << 16) | (((parameter >>7) & 0x7F) << 24));
      // Control change 6 for Data Entry MSB of value
      usb_midi_write_packed(0xB00B | (((channel - 1) & 0x0F) << 8)  | ((6 & 0x7F) << 16) | ((value & 0x7F) << 24));
      // Control change 38 for Data Entry LSB of param
      usb_midi_write_packed(0xB00B | (((channel - 1) & 0x0F) << 8)  | ((38 & 0x7F) << 16) | (((value >>7) & 0x7F) << 24));
      // Now null out active parameter by sending null (127, 0x7F) as MSB and LSB
      // Control change 101 for RPN, MSB of param
      usb_midi_write_packed(0xB00B | (((channel - 1) & 0x0F) << 8)  | ((101 & 0x7F) << 16) | (0x7F << 24));
      // Control change 100 for NRPN, LSB of param
      usb_midi_write_packed(0xB00B | (((channel - 1) & 0x0F) << 8)  | ((100 & 0x7F) << 16) | (0x7F << 24));
    }
    
    void setup() {
      delay (5000); // start up your MIDI monitoring now
      sendNRPNHR(param, val, 1); // channel 1
    }
    
    void loop() {
      delay (3000); // slowly, for testing
      sendNRPNHR(param, val, 1); // channel 1
    }

  4. #4
    Junior Member
    Join Date
    Dec 2019
    Posts
    2

    Post

    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.

  5. #5
    Junior Member
    Join Date
    Dec 2019
    Posts
    2
    Oh and is there a documentation of the usb_midi_write_packed() function anywhere? I can't find one.

  6. #6
    Senior Member
    Join Date
    Nov 2017
    Location
    Belgium
    Posts
    214
    NRPN & RPN are fully supported by the 3 midi libs.
    Have a look at Examples/Teensy/USB_MIDI/TransmitEverything.ino

  7. #7
    Quote Originally Posted by Dumdumdrum View Post
    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);
            }

  8. #8
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    23,977
    Quote Originally Posted by Dumdumdrum View Post
    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.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •