MIDI CC sensitivity

When testing my MIDI controller, sometimes a potentiometer can be left in a position that is between 2 possible CC numbers which causes both numbers to alternate and transmit when it should be silent.

What is the best way to stop this while maintaining the highest resolution?

For my MIDI Controller library, I'm using a combination of a single-pole digital filter and hysteresis.

For a modular version that's better documented, see the updated version of the library here: Control-Surface/FilteredAnalog.hpp at 52b35fe1ecf3ee69c4b8ca5e4c8f6f034eeabbb2 · tttapa/Control-Surface · GitHub

Pieter

Thanks Pieter, that looks like what I need. Just need to figure out how to integrate it into my code.

yarsrev:
Just need to figure out how to integrate it into my code.

The easiest way is to just use the library: example
It supports most of the features you need for a simple MIDI Controller.

If you need special features, you can use the class in the second link, and add it to your existing code. Note that you also need the EMA and Hysteresis helper classes, some type definitions from Def/Def.hpp and some constants from Settings/Settings.hpp.

Pieter

I often use:-

newReading = analogRead(pin);
if( abs(newReading - oldReading) >=2) { 
// send new reading
oldReading = newReading;
}

Change the value of 2 to something bigger if you still get jitter.

I would use something like this:

Code:





```
#include <Control_Surface.h>

USBMIDI_Interface midi; // MIDI interface to use

Bank<2> bank; // Bank to change the addresses of the potentiometers
SwitchSelector selector  = {bank, 4}; // Selector to change the bank setting: switch between pin 4 and ground

ManyAddresses::CCPotentiometer<2> potentiometers[] = {
  {bank, A0, {0x07, 0x0B}}, // Bank that selects the address to use, analog pin, list of addresses
  {bank, A1, {0x05, 0x54}},
  {bank, A2, {0x14, 0x15}},
  // …
};

void setupcolor=#000000[/color] {
  // Initialise everything (pin modes, MIDI interface, etc.)
  Control_Surface.begincolor=#000000[/color];
}

void loopcolor=#000000[/color] {
  // Update everything and send MIDI messages if necessary.
  Control_Surface.loopcolor=#000000[/color];
}
```

|

Grumpy_Mike:
I often use:-

newReading = analogRead(pin);

if( abs(newReading - oldReading) >=2) {
// send new reading
oldReading = newReading;
}



Change the value of 2 to something bigger if you still get jitter.

The problem with this approach that it’s very hard to get it perfectly centered or set it to zero, because you always need a difference of 2 from the previous reading. For example, if you move the potentiometer all the way to the left, the MIDI control may stick at one, and you have to move it to the right again and then back to the left to get it to zero.

The problem with this approach that it's very hard to get it perfectly centered or set it to zero ....

Well I have always found it fine for me, especially if you do it on the analogue readings of 0 to 1023, which ofcourse you will scale down to 0 to 127 with a shift to the right three times.

Thanks all, some useful code suggestions here.

Hi all
Still having trouble with this. Mike, wont your suggestion effectively halve the resolution?

Pieter I can’t get your suggestions to work with my code.

See code below, (I didn’t write this, it is hacked together and I don’t really know what I am doing).

#define CC              0xB0

#define NUMBER_OF_ANALOG_INPUTS  16 // NOTE: if you change this value, also change the controllers array, to match the number of inputs and the number of controllers.

#define CHANNEL 1 // Send all messages on channel 1.

/* The list with the corresponding controller numbers: for example, the values of the potentiometer on A0 will be sent as the first controller number in this list, A1 as the second, etc. */
int controllers[] = { 
  0x7, 0x5, 0x14, 0x16, 0x18, 0x1A, 0x1C, 0x1E, 0xE, 0x4A, 0x47, 0x58, 0x49, 0x4B, 0x4C, 0x48, 0x59, 0x54, 0x15, 0x17, 0x19, 0x1B, 0x1D, 0x1F, 0xF, 0x55, 0x56, 0x57, 0x4D, 0x4E, 0x4F, 0x10
};  

int analogVal[NUMBER_OF_ANALOG_INPUTS];  // We declare an array for the values from the analog inputs

int analogOld[NUMBER_OF_ANALOG_INPUTS]; // We declare an array for the previous analog values. 

int shift = 0;

/* The format of the message to send via serial. We create a new data type, that can store 3 bytes at once.  This will be easier to send as MIDI. */
typedef struct {
  uint8_t status;   // first  byte   : status message (NOTE_ON, NOTE_OFF or CC (controlchange) and midi channel (0-15)
  uint8_t data1;    // second byte   : first value (0-127), controller number or note number
  uint8_t data2;    // third  byte   : second value (0-127), controller value or velocity
} 
t_midiMsg;          // We call this data type 't_midiMsg'

void setup() // The setup runs only once, at startup.
{
  pinMode(13, OUTPUT);   // Set pin 13 (the one with the LED) to output
  digitalWrite(13, LOW); // Turn off the LED
  pinMode(11,INPUT_PULLUP);
  for(int i = 0; i < NUMBER_OF_ANALOG_INPUTS; i++){  // We make all values of analogOld -1, so it will always be different from any possible analog reading.
    analogOld[i]=-1;
  }
  Serial.begin(31250);  // Start a serial connection @31250 baud or bits per second on digital pin 0 and 1, this is the connection to the ATmega16U2, which runs the HIDuino MIDI firmware. (31250 baud is the original MIDI speed.)
  delay(1000);           // Wait a second before sending messages, to be sure everything is set up
  digitalWrite(13, HIGH);// Turn on the LED, when the loop is about to start.
}

void loop() // The loop keeps on repeating forever.
{
  if(digitalRead(11) == LOW){
       shift = 16;
        }
      else {
        shift = 0;
        }
  t_midiMsg msg;                                     // create a variable 'msg' of data type 't_midiMsg' we just created
  for(int i = 0; i < NUMBER_OF_ANALOG_INPUTS; i++){  // Repeat this procedure for every analog input.

    analogVal[i] = analogRead(i+A0)/8;               // The resolution of the Arduino's ADC is 10 bit, and the MIDI message has only 7 bits, 10 - 7 = 3, so we divide by 2^3, or 8.
    if(analogVal[i] != analogOld[i]){                // Only send the value, if it is a different value than last time.
      msg.status = CC;                               // Controll Change
      msg.status = msg.status | CHANNEL-1;           // Channels are zero based (0 = ch1, and F = ch16). Bitwise or to add the status message and channel together: 
                                                    /* status     = 0bssss0000 
                                                     * channel    = 0b0000cccc 
                                                     * | ------------------ (bitwise or)
                                                     * msg.status = 0bsssscccc       
                                                     */
      msg.data1   = controllers[i+shift];            // Get the controller number from the array above.
      msg.data2   = analogVal[i];                    // Get the value of the analog input from the analogVal array.
      Serial.write((uint8_t *)&msg, sizeof(msg));    // Send the MIDI message.
      analogOld[i] = analogVal[i];                   // Put the analog values in the array for old analog values, so we can compare the new values with the previous ones.
      delay(10);                                     // Wait for 10ms, so it doesn't flood the computer with MIDI-messages
    }
  }

}

I should add, the code above does work, the switch functions as expected and knobs all send the correct information. It's just sometimes when the controller should be silent, some knobs send cc data due to them being between 2 possible cc numbers.

Mike, wont your suggestion effectively halve the resolution?

No, if you do it before you convert the 10 bits from the A/D to 7 bits for MIDI.

Hi Mike
I get 'newReading' was not declared in this scope - when I insert the code. How do I declare it or is there another way in context of my code?

I get 'newReading' was not declared in this scope

In what code? You don't get it in the code you posted.

I inserted this:

newReading = analogRead(pin);
if( abs(newReading - oldReading) >=2) {
// send new reading
oldReading = newReading;
}

just before the t_midiMsg msg; in the code above.

Do you think it would have been a good idea to tell us that first?

Every variable has to be declared so the compiler knows how to deal with it. You do this by putting a variable type in front of it only on the first time you use it.

If this is done outside of a function then this variable is global in scope, that is any function has access to it. If it is done in a function then the variable is only accessible inside that function, and does not retain its value from one running of a function to the next.

So for that example you should have put

int newReading = analogRead(pin);

If you want it to retain the value then you should use

static int newReading;

At the start of the function that contains it.

Thanks Mike
OK almost there but now it says..

'pin' was not declared in this scope

Here’s what the code looks like now…

#define CC              0xB0

#define NUMBER_OF_ANALOG_INPUTS  16 // NOTE: if you change this value, also change the controllers array, to match the number of inputs and the number of controllers.

#define CHANNEL 1 // Send all messages on channel 1.

/* The list with the corresponding controller numbers: for example, the values of the potentiometer on A0 will be sent as the first controller number in this list, A1 as the second, etc. */
int controllers[] = { 
  0x7, 0x5, 0x14, 0x16, 0x18, 0x1A, 0x1C, 0x1E, 0xE, 0x4A, 0x47, 0x58, 0x49, 0x4B, 0x4C, 0x48, 0x59, 0x54, 0x15, 0x17, 0x19, 0x1B, 0x1D, 0x1F, 0xF, 0x55, 0x56, 0x57, 0x4D, 0x4E, 0x4F, 0x10
};  

int analogVal[NUMBER_OF_ANALOG_INPUTS];  // We declare an array for the values from the analog inputs

int analogOld[NUMBER_OF_ANALOG_INPUTS]; // We declare an array for the previous analog values. 

int shift = 0;

/* The format of the message to send via serial. We create a new data type, that can store 3 bytes at once.  This will be easier to send as MIDI. */
typedef struct {
  uint8_t status;   // first  byte   : status message (NOTE_ON, NOTE_OFF or CC (controlchange) and midi channel (0-15)
  uint8_t data1;    // second byte   : first value (0-127), controller number or note number
  uint8_t data2;    // third  byte   : second value (0-127), controller value or velocity
} 
t_midiMsg;          // We call this data type 't_midiMsg'

void setup() // The setup runs only once, at startup.
{
  pinMode(13, OUTPUT);   // Set pin 13 (the one with the LED) to output
  digitalWrite(13, LOW); // Turn off the LED
  pinMode(11,INPUT_PULLUP);
  for(int i = 0; i < NUMBER_OF_ANALOG_INPUTS; i++){  // We make all values of analogOld -1, so it will always be different from any possible analog reading.
    analogOld[i]=-1;
  }
  Serial.begin(31250);  // Start a serial connection @31250 baud or bits per second on digital pin 0 and 1, this is the connection to the ATmega16U2, which runs the HIDuino MIDI firmware. (31250 baud is the original MIDI speed.)
  delay(1000);           // Wait a second before sending messages, to be sure everything is set up
  digitalWrite(13, HIGH);// Turn on the LED, when the loop is about to start.
}

void loop() // The loop keeps on repeating forever.
{
  if(digitalRead(11) == LOW){
       shift = 16;
        }
      else {
        shift = 0;
        }
        int newReading = analogRead(pin);
if( abs(newReading - oldReading) >=2) { 
// send new reading
oldReading = newReading;
}
  t_midiMsg msg;                                     // create a variable 'msg' of data type 't_midiMsg' we just created
  for(int i = 0; i < NUMBER_OF_ANALOG_INPUTS; i++){  // Repeat this procedure for every analog input.

    analogVal[i] = analogRead(i+A0)/8;               // The resolution of the Arduino's ADC is 10 bit, and the MIDI message has only 7 bits, 10 - 7 = 3, so we divide by 2^3, or 8.
    if(analogVal[i] != analogOld[i]){                // Only send the value, if it is a different value than last time.
      msg.status = CC;                               // Controll Change
      msg.status = msg.status | CHANNEL-1;           // Channels are zero based (0 = ch1, and F = ch16). Bitwise or to add the status message and channel together: 
                                                    /* status     = 0bssss0000 
                                                     * channel    = 0b0000cccc 
                                                     * | ------------------ (bitwise or)
                                                     * msg.status = 0bsssscccc       
                                                     */
      msg.data1   = controllers[i+shift];            // Get the controller number from the array above.
      msg.data2   = analogVal[i];                    // Get the value of the analog input from the analogVal array.
      Serial.write((uint8_t *)&msg, sizeof(msg));    // Send the MIDI message.
      analogOld[i] = analogVal[i];                   // Put the analog values in the array for old analog values, so we can compare the new values with the previous ones.
      delay(10);                                     // Wait for 10ms, so it doesn't flood the computer with MIDI-messages
    }
  }

}

You have not defined what you want the value of pin to be, it is not mentioned anywhere else in the code.
So you need to define it and put and set it to a value, or just replace the word "pin" with the number of the analogue input you want to use.

In the mean time please read:-
https://www.arduino.cc/reference/en/language/variables/variable-scope--qualifiers/scope/

Thanks Mike, OK I understand what’s in scope.

I’m still deciphering the code I have hacked together.

I want (pin) to be all analog inputs not just one.

Can I make use of these commands earlier in the code…?

int analogVal[NUMBER_OF_ANALOG_INPUTS];

int analogOld[NUMBER_OF_ANALOG_INPUTS];

Can I make use of these commands earlier in the code...?

No.
You need to specify what number analogue input you want to use. I don’t understand why that particular analogue read is needed.

The code assumes that you have 16 analogue inputs, do you?
If so then you just need a number from 0 to 15 depending on which one you need to use at the time. The place you use the variable pin is not in a loop so I think that is wrong. Later on you use

analogVal[i] = analogRead(i+A0)/8;

This is much closer to what you want although even that line contains two mistakes.
First off as A0 is zero anyway you can just use i and you should not do the divide by 8 until you have decided if you want to send the value or not. So you need to use

analogVal[i] = analogRead(i);
    if(abs(analogVal[i] - analogOld[i]) > 2){

Don’t forget to divide it by 8 before you send it.