Go Down

Topic: MIDI CC sensitivity (Read 979 times) previous topic - next topic

yarsrev

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?


PieterP

#1
Feb 08, 2019, 10:19 am Last Edit: Feb 08, 2019, 10:26 am by PieterP
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: https://github.com/tttapa/Control-Surface/blob/52b35fe1ecf3ee69c4b8ca5e4c8f6f034eeabbb2/src/Hardware/FilteredAnalog.hpp

Pieter

yarsrev

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

PieterP

#3
Feb 08, 2019, 11:42 am Last Edit: Feb 08, 2019, 11:43 am by PieterP
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

Grumpy_Mike

#4
Feb 08, 2019, 03:54 pm Last Edit: Feb 08, 2019, 03:55 pm by Grumpy_Mike
I often use:-
Code: [Select]
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.

PieterP

#5
Feb 08, 2019, 05:52 pm Last Edit: Feb 08, 2019, 05:52 pm by PieterP
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 setup() {
  // Initialise everything (pin modes, MIDI interface, etc.)
  Control_Surface.begin();
}

void loop() {
  // Update everything and send MIDI messages if necessary.
  Control_Surface.loop();
}

I often use:-
Code: [Select]
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.

Grumpy_Mike

Quote
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.

yarsrev

Thanks all, some useful code suggestions here.

yarsrev

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).

Code: [Select]

#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
    }
  }

}

yarsrev

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.

Grumpy_Mike

Quote
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.


yarsrev

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?

Grumpy_Mike

Quote
I get 'newReading' was not declared in this scope
In what code? You don't get it in the code you posted.

yarsrev

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.

Grumpy_Mike

#14
Mar 12, 2019, 10:49 am Last Edit: Mar 12, 2019, 10:53 am by Grumpy_Mike
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
Code: [Select]
int newReading = analogRead(pin);

If you want it to retain the value then you should use
Code: [Select]
static int newReading;
At the start of the function that contains it.

Go Up