14bit MIDI Controller support


#1

There are some synths and maybe some controllers out there that seems to be able to send 14bit MIDI controller values. I do not have such a device and can not make any tests. The Moog Voyager seems to be one of them...

The JUCE MidiMessage standard controller-event supports 7bit values. Think thats how it is defined in the MIDI specifications. Would it be a big hack to support 14bit values too or is something like this already available without parsing MIDI data by myself?


#2

Hi

You must parse it by yourself.

For now I use this code snippet:

 

class MidiNRPN
{
public:
  MidiNRPN() 
    : ctrlnew(false), nrpn_lsb(-1), nrpn_msb(-1), data_lsb(-1), data_msb(-1) {}

  // return true if the message has been filtered
  bool process(const MidiMessage& message)
  {
    if (!message.isController())
      return false;

    int data2 = message.getControllerValue();
    switch (message.getControllerNumber())
    {
    case midi_nrpn_lsb : nrpn_lsb = data2; return true;
    case midi_nrpn_msb : nrpn_msb = data2; return true;
    case midi_rpn_lsb : if (data2 == 127) nrpn_lsb = data_lsb = -1; return true;
    case midi_rpn_msb : if (data2 == 127) nrpn_msb = data_msb = -1; return true;
    case midi_data_lsb :
    case midi_data_msb :
      {
        if (message.getControllerNumber() == midi_data_msb)
        {
          if (nrpn_msb < 0)
            return false;
          data_msb = data2;
        }
        else // midi_data_lsb
        {
          if (nrpn_lsb < 0)
            return false;
          data_lsb = data2;
        }
      
        if (data_lsb >= 0 && data_msb >= 0)
        {
          ctrlnum = (nrpn_msb << 7) | nrpn_lsb;
          ctrlval = (data_msb << 7) | data_lsb;
          data_lsb = data_msb = -1;
          nrpn_msb = nrpn_lsb = -1;
          ctrlnew = true;
        }
        return true;
      }
    default:  return false;
    };
  }

  bool hasNewNrpn()               {bool res = ctrlnew; ctrlnew = false; return res;}
  // results in [0, 16383]
  int getControllerNumber() const {return ctrlnum;}
  int getControllerValue() const  {return ctrlval;}

  static void sendNrpnNow(MidiOutput& midiDevice, int channel, int number, int value)
  {
    midiDevice.sendMessageNow(MidiMessage::controllerEvent(channel, midi_nrpn_msb, (number & 0x3F80) >> 7));
    midiDevice.sendMessageNow(MidiMessage::controllerEvent(channel, midi_nrpn_lsb, number & 0x3F));
    midiDevice.sendMessageNow(MidiMessage::controllerEvent(channel, midi_data_msb, (value & 0x3F80) >> 7));
    midiDevice.sendMessageNow(MidiMessage::controllerEvent(channel, midi_data_lsb, value & 0x3F));
  }

private:
  bool  ctrlnew;
  int   ctrlnum;
  int   ctrlval;

  int nrpn_lsb, nrpn_msb;
  int data_lsb, data_msb;

  enum
  {
    midi_nrpn_lsb         = 98,
    midi_nrpn_msb         = 99,
    midi_rpn_lsb          = 100,
    midi_rpn_msb          = 101,
    midi_data_lsb         = 38,
    midi_data_msb         = 6
  };
};

you could use MidiNRPN persistant context object to process multiple handleIncomingMidiMessage callback.

When returning true, getControllerNumber() and getControllerValue() can be used.

For midi out, use sendNrpnNow().

It could be great having better integration of this in Juce API.

Regards,

 

 


#3

Thanks a lot for the code snipped. I will try it. Would be great to have this integrated in the JUCE API one time.


#4

It looks like JUCE plugins can process 14 bit MIDI messages since somewhere last year by default, but it always returns a controller value in the range from 0..127. Is there any way to get the 14bit high resolution value? Could not find something like this in the MidiMessage class.


#5

With regards to the code snippet above:

check out the MidiRPNDetector and MidiRPNGenerator classes in juce_MidiRPN.h which are written for exactly that purpose.


#6

Thanks for the info. I'm not sure if i undertand that right.. should i get a MIDI Message with a special controller number like 101? I always get the same "real" controller number. Is this already filtered out by the MIDI parser when i do this inside the plugin processor code processing loop? 

Any help is welcome.


#7

Ah, now I get it. You don't want to parse RPN and NRPN messages. You want this:

The first CC message encodes the high 7 bits (128s, MSB) of the value, and the second CC message encodes the low 7 bits (1s, LSB) of the value, and the two CC numbers used are separated by 32. 

So a 14-bit message on CC7 with a value of 522 would look like this:

CC7, value 4, IMMEDIATELY followed by CC39, value 10.

ie: CC7, 4 (x 128, MSB); CC39 (7+32), 10 (x 1, LSB).

4 x 128 + 10 = 522.

(from here: https://cycling74.com/forums/topic/14bit-cc-to-ableton/)

JUCE does not directly support this at the moment. You'll have to do it manually by receiving the MSB and LSB as two separate CCs and combine them when you received both.

For example, MPEInstrument::handleTimbreMSB() and handleTimbreLSB() do exactly that. OK, not quite like the above quote: the slight difference is that it expects the LSB first and then the MSB. Makes more sense to me because otherwise you break backwards compatibility if a device sends the MSB only.


#8

Great, thanks for the information. I didn't see that midi specification feature!