Midi Pitch Bend Issues with Mega

I am having problems with my Arduino Mega midi controller sending proper midi pitch bend commands. Everything else on the controller works fine. It has 37 keys, 12 pots and 8 program select buttons. There is a Spark Fun joystick which outputs 0 to 5 volts in both the X and Y axis. The center points are not exactly 2.5V. I compensated for that on the Y but not yet on the X. Y axis + outputs 0 to 127 cc1 data while Y axis – outputs cc 74 based on the reading of potentiometer 1 also assigned to that value. X is assigned to pitch bend. I need an inverse output compared to the voltage so that 5V = midi value 0 and 0V = 16383.

The center detent of the joystick gives a midi value of oX0132 ( I can compensate for this later). When I move the joystick towards 0 volts, the values steadily increase up to oX3E7F. However, when I move the joystick towards 5 volts, instead of a steady pitch bend down, I get dithers around oX0122 and oX0132 before going to oX0010 then jumping up to oX7F71 then steadily decreasing in value until it bottoms out at oX4032 which means the overall pitch bend down is still higher than center on the joystick.

I have not programmed for the Mega before. I’m guessing there’s an issue with either the handling of midi pitch bend commands or more likely converting the ADC values to the 14bit midi format.

Code is attached. Pitch bend code is lines 114-123,
Any help is appreciated.

type [code]

// Final version: 37-key MIDI keyboard + Program Change + CC switches + 12 Pots + Joystick
#include <MIDI.h>
MIDI_CREATE_DEFAULT_INSTANCE();

const int NUM_KEYS = 37;
const int NUM_PROG_BUTTONS = 8;

const int keyPins[NUM_KEYS] = {
  2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
  14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28,
  29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39
};

const int programChangePins[NUM_PROG_BUTTONS] = {40, 41, 42, 43, 44, 45, 46, 47};

const int PIN_ALL_SOUNDS_OFF = 48;
const int PIN_MONO_POLY = 50;   // swapped
const int PIN_PORTAMENTO = 49;  // swapped

const int baseNote = 36;

bool keyStates[NUM_KEYS];
bool buttonStates[NUM_PROG_BUTTONS];
bool monoPolyState = false;
bool portamentoState = false;

const int NUM_POTS = 12;
const int potPins[NUM_POTS] = {A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11};
const int potCCs[NUM_POTS]  = {74, 71, 73, 72, 91, 92, 93, 7, 5, 94, 95, 10};
int potValues[NUM_POTS];

const int JOY_X_PIN = A14;
const int JOY_Y_PIN = A13;
int prevPitchBend = 8192;
int prevCC1 = -1;
int prevCC74 = -1;

void setup() {
  MIDI.begin(MIDI_CHANNEL_OMNI);
  for (int i = 0; i < NUM_KEYS; i++) {
    pinMode(keyPins[i], INPUT_PULLUP);
    keyStates[i] = false;
  }
  for (int i = 0; i < NUM_PROG_BUTTONS; i++) {
    pinMode(programChangePins[i], INPUT_PULLUP);
    buttonStates[i] = false;
  }
  pinMode(PIN_ALL_SOUNDS_OFF, INPUT_PULLUP);
  pinMode(PIN_MONO_POLY, INPUT_PULLUP);
  pinMode(PIN_PORTAMENTO, INPUT_PULLUP);
}

void loop() {
  readPotentiometers();
  readJoystick();

  for (int i = 0; i < NUM_KEYS; i++) {
    bool pressed = digitalRead(keyPins[i]) == LOW;
    if (pressed != keyStates[i]) {
      keyStates[i] = pressed;
      if (pressed) {
        MIDI.sendNoteOn(baseNote + i, 127, 1);
      } else {
        MIDI.sendNoteOff(baseNote + i, 0, 1);
      }
    }
  }

  for (int i = 0; i < NUM_PROG_BUTTONS; i++) {
    bool pressed = digitalRead(programChangePins[i]) == LOW;
    if (pressed && !buttonStates[i]) {
      buttonStates[i] = true;
      MIDI.sendProgramChange(i, 1);
    } else if (!pressed) {
      buttonStates[i] = false;
    }
  }

  if (digitalRead(PIN_ALL_SOUNDS_OFF) == LOW) {
    MIDI.sendControlChange(120, 0, 1);
    delay(200);
  }

  bool monoPressed = digitalRead(PIN_MONO_POLY) == HIGH;  // reversed logic
  if (monoPressed != monoPolyState) {
    monoPolyState = monoPressed;
    MIDI.sendControlChange(monoPressed ? 127 : 126, monoPressed ? 127 : 0, 1);
  }

  bool portamentoPressed = digitalRead(PIN_PORTAMENTO) == LOW;
  if (portamentoPressed != portamentoState) {
    portamentoState = portamentoPressed;
    MIDI.sendControlChange(65, portamentoPressed ? 127 : 0, 1);
  }
}

void readPotentiometers() {
  for (int i = 0; i < NUM_POTS; i++) {
    int rawValue = analogRead(potPins[i]);
    int midiValue;
    if (potCCs[i] == 10) {
      midiValue = map(rawValue, 0, 1023, 127, 0); // Reverse pan
    } else {
      midiValue = map(rawValue, 0, 1023, 0, 127);
    }
    if (abs(midiValue - potValues[i]) > 1) {
      potValues[i] = midiValue;
      MIDI.sendControlChange(potCCs[i], midiValue, 1);
    }
  }
}

void readJoystick() {
  int xRaw = analogRead(JOY_X_PIN);
  int yRaw = analogRead(JOY_Y_PIN);
  
if(xRaw>10){
  int bend = map(xRaw, 10, 1023,16383, 50);
  bend = constrain(bend, 0, 16383);
  if (abs(bend - prevPitchBend) > 4) {
    MIDI.sendPitchBend(bend, 1);
    prevPitchBend = bend;
  }
}
  float yVolt = yRaw * (5.0 / 1023.0);

  if (yVolt > 2.56) {
    int cc1val = map(yVolt * 100, 256, 495, 0, 127);
    cc1val = constrain(cc1val, 0, 127);
    if (abs(cc1val - prevCC1) > 1) {
      MIDI.sendControlChange(1, cc1val, 1);
      prevCC1 = cc1val;
      prevCC74 = -1;
    }
  } else if (yVolt < 2.54) {
    int cc74val = map(yVolt * 100, 0, 254, 127, 0);
    int baseCC74 = potValues[0];  // A0 value
    cc74val = constrain(cc74val, baseCC74, 127);
    if (abs(cc74val - prevCC74) > 1) {
      MIDI.sendControlChange(74, cc74val, 1);
      prevCC74 = cc74val;
      prevCC1 = -1;
    }
  }
}
[/code]or paste code here

May be you get some erroneous reading as you switch quickly between Analog pins (there is a MUX at the entrance of the ADC).

Try doubling the read to give time to the ADC to adjust to the changed input.

  for (int i = 0; i < NUM_POTS; i++) {
    int rawValue = analogRead(potPins[i]); // throw away value
    rawValue = analogRead(potPins[i]); // the one you keep

The first call reroute the mux to the right analog pin and reads the value but under certain conditions you might still get influence from the charge that was there from the previous pin. Reading twice gives usually enough time to adjust to the real voltage on that pin.

The same might apply here

Side note:
You’ll get faster if you don’t think in terms of voltage and floating point and just use the raw value. You have a map() later anyway.

Don’t do that and compare with 2.56 or 2.54, look at what raw value leads to these voltages and compare with that. Then in the map() call, give the raw value range and keep the same target values - you’ll also save a floating point multiplication by 100 and keep all the math in integers maths which is faster.

OK let's sort out your use of hexadecimal notation.
A hexadecimal value starts off with a zero followed by a letter X.

If you write 0X0122, you are actually defining a number that has no part in MIDI because it is way too big. It looks like you have put a decimal number 122 and are expecting what? I don't know.

The biggest HEX number you can have with MIDI is 0XFF.

Those values - as I understood - are the raw output of the joystick axis mapped into 16 bits - not the midi commands

Not sure why OP is using this weird hex notation and you are right it needs clarification …

Hard to tell from the description, but it sounds almost like you have a bad potentiometer in the joystick, maybe just dirty internal contacts, or possibly bad wiring. Might be easier to diagnose if you write a simple sketch that continuously reads and prints the raw values from the joystick.

  int xRaw = analogRead(JOY_X_PIN);
  int yRaw = analogRead(JOY_Y_PIN);

  if (xRaw > 10) {
    int bend = map(xRaw, 10, 1023, 16383, 50);
    bend = constrain(bend, 0, 16383);
    if (abs(bend - prevPitchBend) > 4) {
      MIDI.sendPitchBend(bend, 1);
      prevPitchBend = bend;
    }
  }

The test for

(abs(bend - prevPitchBend) > 4)

could be simplified to

 (bend != prevPitchBend) 

because the 0-1023 input is scaled to 0-16383, giving steps of 16. No difference between bend and prevPitchBend will ever be less than a multiple of 16 unless the two are exactly equal.

I would drop the entire map() and constrain() and use the following which inverts the input from 0-1023 to 1023-0, then shifts 4 bits right to scale to 16383-0.

  int bend = (1023 - xRaw) << 4;

Thanks for responses so far. Just to clarify a few points:

  1. Sorry for my weird hex format. I deal with vision issues and it's my little way of making life easier. I won't use it in this forum again.
  2. We're dealing with midi pitch bend data which is 14 bits (7bits+7 bits) which is decimal 0 to 16383 or 0X0000 to 0X3FFF
  3. ADC is only 10 bit so raw output of ADC is decimal 0 to 1023.
  4. I'm using MidiOx to evaluate the controller's output and it seems to have issues displaying pitch bend data. For instance, a known good pitch bend output from another controller reads min 0X0000, max 0X7F40 with a center point of 0X4000. That max value should obviously not exist. Midi Ox doesn't display a single 14 bit value but rather two 7 bit values. I'm guessing how it breaks down the 14 bit value allows for the 0X7F40 to occur.

0x7F40 is 32576 - way above what you can represent on 14 bits

I don't know why you call that a good pitch bend output. the data sent to MidiOx is probably wrong.

Midi uses a 14-bit value for the pitch bend. It is sent in a somewhat unconventional format, as two bytes each containing 7 bits of the value, with the most significant bit of each byte always being zero.
For the value 0x7F40, drop the most significant bit of each byte, and combine the remaining 7 bits of each byte to produce 0x3FC0 = 16320. A value of 16383 should show up as 0x7F7F in Midi OX.

Which Midi library are you using? I've compiled your code using MIDI_Library by Francois Best, from the IDE's library manager, and the sendPitchBend() function from that library takes an integer value from -8192 to +8191, with 0 being the value when the joystick is centered. That could explain the odd jump in values you are seeing.

if I want to send 0x3FC0 why isn't the LSB 0xC0 and the MSB 0x3F ?

Are you saying the MSb is set to 1 on purpose and should be ignored? Why isn't it set on the LSB?

david_2018: I tried your suggestions and they gave me the same midi pitch bend output. I do like your code as it's cleaner.

Also, I have tried several different pots with each end tied to 0V and 5V, A14 tied to center-tap with the same results. I too suspected a bad joystick but that doesn't seem to be the case.

J-M-L:
That is exactly my point: MidiOx cannot be fully trusted for accurate pitch bend data. I can only use it for relative purposes. MidiOx has 2 data fields. In normal cc use, the first data field is for controller number and the second field is for 0-127 value. In the case of pitch bend, MidiOx somehow breaks the 14 bit pitch bend data into two 7bit fields in weird way. For instance, the joystick center is decimal 64 0 which would be 0X 40 00. Minimum position is decimal 0 64 which is 0X 00 40 but the max position is decimal 112 40 which shows as 0X 70 28. This exceeds 0X3FFF but it kind of makes sense broken down the way MidiOx displays pitch bend data.

Musically, a full max joystick X axis position gives me a whole step bend up. While bending down, the pitch goes up a minor third then bends down a half step at the full minimum position. Which means the final pitch down is actually a whole step up from the original note!

Try the following modification to your code:

void readJoystick() {
  int xRaw = analogRead(JOY_X_PIN);
  int yRaw = analogRead(JOY_Y_PIN);

  if (xRaw > 10) {
    int bend = map(xRaw, 0, 1023, MIDI_PITCHBEND_MAX, MIDI_PITCHBEND_MIN);
    if (bend != prevPitchBend) {
      MIDI.sendPitchBend(bend, 1);
      prevPitchBend = bend;
    }
  }

david_2018:
Those last modifications worked perfectly! Yea! Thank-you so much!

For everyone trying to follow the issues with MidiOx. Here's an example breakdown. david_2018 obviously gets this, but for the rest of us:

let's say the midi pitch bend value is 11111110011111. In hex, it would obviously be 0X3F9F. But MidiOx breaks it down into 1111111 and 0011111.
Then it displays either 0X7F and 0X1F or decimal 127 and 31, depending how the output display is chosen. You can't just annex the two numbers together to get the real hex or decimal number like I was doing initially above.

There is no issue with Midi-OX. It is displaying the midi bytes exactly as they were transmitted by whatever midi device is connected. And that device transmitted the bytes in exactly the format specified by the MIDI protocol.

No it doesn't. It will report EXACTLY what it received. It is the sending midi device/controller which, in this case, would send 0X7F and 0X1F and that is what Midi-OX would report.

Pete
See Summary of MIDI messages which describes how MIDI messages, including pitch bend, are formatted.

Here are the details of the sendPitchBend() function from the MIDI_Library library.
The pitch bend value is a signed integer from -8192 to +8191, with 0 being no pitch change.
The original code posted in this discussion was incorrectly using a value from 0 to 16383 for the pitch bend, resulting in erroneous data being sent when the value was above 8191. A bit confusing because of the upper two bits being ignored, and the numbers being broken into two 7-bit unsigned integers.


/*! \brief Send a Pitch Bend message using a signed integer value.
 \param inPitchValue  The amount of bend to send (in a signed integer format),
 between MIDI_PITCHBEND_MIN and MIDI_PITCHBEND_MAX,
 center value is 0.
 \param inChannel     The channel on which the message will be sent (1 to 16).
 */
template<class Transport, class Settings, class Platform>
void MidiInterface<Transport, Settings, Platform>::sendPitchBend(int inPitchValue,
                                                        Channel inChannel)
{
    const unsigned bend = unsigned(inPitchValue - int(MIDI_PITCHBEND_MIN));
    send(PitchBend, (bend & 0x7f), (bend >> 7) & 0x7f, inChannel);
}
#define MIDI_PITCHBEND_MIN      -8192
#define MIDI_PITCHBEND_MAX      8191

No it doesn't. A lot of MIDI commands that require a 14 bit number have the Most significant 7 bits at the base address, the least significant 7 bits are stored 32 messages away from this.

For your specific example of pitch bend, the data entry is at position 1 in the mode change table, this is the most significant 7 bits, with the least significant 7 bits being stored at position 33 of the control table.

You might think this is weird, but it is how MIDI works.

These two extracts from the control table show you haw this works:-


and