Midi Controller sending PC messages - HELP

Hello everybody!

I am completely new to the work with Arduinos. I learned a bit of BASIC programming long time ago in high school, that’s all!

My project:

As a guitarist I want to integrate a midi controller into one of my electric guitars containing 10 switches. 9 (#2..10) of them shall send Program Change messages from 1-9, the last one (#16) should either modify the PCs to 11-19 or send a bank change command alternating between bank 0 and 1.

I got myself a Pro Micro ATmega32U4 Micro USB Board. And I started with a sketch auto-coded by Gustavo Silveira’s ARDUINO MIDI CONTROLLER CODE GENERATOR. Unfortunately the original sketch only sends Midi notes from buttons.

So my question is: How can I modify the sketch so send PC messages?

See line: // Send Program Change -- HELP!

Greetings from Germany

Hans


SKETCH:

// GOAL: MidiController with 10 Buttons
// 9 of these 10 Buttons send Program Changes, 1 Button (#16) alternates PCs between from 1-9 and from 11-19

#include <MIDIUSB.h>
#include <MIDIUSB_Defs.h>


/*
  BASED ON: Made by Gustavo Silveira, 2023.
  - This Sketch reads the Arduino's digital and analog ports and send midi notes and midi control change
  MODIFIED to ProgramChanges

*/

/////////////////////////////////////////////
// Choosing your board
// Define your board, choose:


#define ATMEGA32U4 1  // put here the uC you are using, like in the lines above followed by "1", like "ATMEGA328 1", "DEBUG 1", etc.

/////////////////////////////////////////////
// Are you using buttons?
#define USING_BUTTONS 1  // comment if not using buttons


/////////////////////////////////////////////
// BUTTONS
#ifdef USING_BUTTONS

const int N_BUTTONS = 10;                                //  total numbers of buttons
const int BUTTON_ARDUINO_PIN[10] = {2, 3, 4, 5, 6, 7, 8, 9, 10, 16};  // pins of each button connected straight to the Arduino

int buttonCState[N_BUTTONS] = {};  // stores the button current value
int buttonPState[N_BUTTONS] = {};  // stores the button previous value


// debounce
unsigned long lastDebounceTime[N_BUTTONS] = { 0 };  // the last time the output pin was toggled
unsigned long debounceDelay = 50;                   // the debounce time; increase if the output flickers

#endif


/////////////////////////////////////////////
// MIDI
byte midiCh = 0;  // MIDI channel to be used - start with 1 for MIDI.h lib or 0 for MIDIUSB lib
//byte note = 36;   // Lowest note to be used
//byte cc = 1;      // Lowest MIDI CC to be used


/////////////////////////////////////////////
// SETUP
void setup() {


  Serial.begin(115200);  //


#ifdef USING_BUTTONS
  // Buttons
  // Initialize buttons with pull up resistors
  for (int i = 0; i < N_BUTTONS; i++) {
    pinMode(BUTTON_ARDUINO_PIN[i], INPUT_PULLUP);
  }



#endif


}

/////////////////////////////////////////////
// LOOP
void loop() {

#ifdef USING_BUTTONS
  buttons();
#endif

}

/////////////////////////////////////////////
// BUTTONS
#ifdef USING_BUTTONS

void buttons() {

  for (int i = 0; i < N_BUTTONS; i++) {

    buttonCState[i] = digitalRead(BUTTON_ARDUINO_PIN[i]);  // read pins from arduino


    if ((millis() - lastDebounceTime[i]) > debounceDelay) {

      if (buttonPState[i] != buttonCState[i]) {
        lastDebounceTime[i] = millis();

        if (buttonCState[i] == LOW) {

          // Send Program Change -- HELP!

          programchange???;  // channel, note, velocity

          MidiUSB.flush();



        } else {


        }
        buttonPState[i] = buttonCState[i];
      }
    }
  }
}

#endif

/////////////////////////////////////////////


The MIDIUSB library only deals with low-level USB transport and descriptors. If you use a higher-level MIDI library like Control Surface, you can simply use the following:

#include <Control_Surface.h>
 
USBMIDI_Interface midi;
 
void setup() {
    midi.begin();
    // Send a program change message
    MIDIAddress program {MIDI_PC::Harpsichord, Channel_9};
    // or replace MIDI_PC::Harpsichord by any number in [0, 127]
    midi.sendProgramChange(program);
}
 
void loop() {
    midi.update(); // discard incoming messages
}

See the following resources:

If you want to send a MIDI USB packet directly, you'll need to study §4 of https://www.usb.org/sites/default/files/midi10.pdf

For example:

uint8_t program = 3;
midiEventPacket_t packet {0x0C, 0xC0, program, 0x00};
MidiUSB.sendMIDI(packet);
MidiUSB.flush();
1 Like

Thanks for this comprehensive answer and information!

Hans

Answering here because my German isn't great:
The MIDI status byte for channel voice messages consists of a low nibble (4 bits) representing the channel number, and a high nibble indicating the type of the message.
The first byte of a MIDI USB packet consists of a low nibble indicating the type of the message, and a high nibble representing the cable number.

For channel voice messages, the message type nibble in the status byte and the MIDI USB packet is the same, e.g. 0xC for program change. For example, the status byte for a program change message on channel 4 would be 0xC3, and the first byte of the USB packet for such a message on cable 5 would be 0x4C, with the full packet looking like this:

midiEventPacket_t packet {0x4C, 0xC3, program, 0x00};
1 Like

Hello again!

It was a great feeling, when the arduino sent PC messages to my music program (Gig Performer btw.).

BUT one thing I don't manage and I don't know why: I want to use pin #16 (= internal button #9) as a sort of "momentary shift" command.
By that I mean: when I pressed this button once, the next PC message shall be increased by 10! So I would have an upper register to control another 9 patches in GigPerformer (where the guitar synths will be).
I used the variable "shift" which should be set to 10 when button #9 (pin16) will be pressed. Only the NEXT button pressing shall be increased by 10.

Here's the code:


```cpp
#include <Control_Surface.h>
#include <MIDIUSB.h>
#include <MIDIUSB_Defs.h>

USBMIDI_Interface midi;

/*
  BASED ON: Made by Gustavo Silveira, 2023.
  - This Sketch reads the Arduino's digital and analog ports and sends (MODIFIED to) ProgramChanges.

*/

/////////////////////////////////////////////
// Choosing your board
// Define your board, choose:


#define ATMEGA32U4 1  // put here the uC you are using, like in the lines above followed by "1", like "ATMEGA328 1", "DEBUG 1", etc.

/////////////////////////////////////////////
// Are you using buttons?
#define USING_BUTTONS 1  // comment if not using buttons


/////////////////////////////////////////////
// BUTTONS


const int N_BUTTONS = 10;                                //  total numbers of buttons
const int BUTTON_ARDUINO_PIN[10] = {2, 3, 4, 5, 6, 7, 8, 9, 10, 16};  // pins of each button connected straight to the Arduino

int buttonCState[N_BUTTONS] = {};  // stores the button current value
int buttonPState[N_BUTTONS] = {};  // stores the button previous value

int button10CState[N_BUTTONS] = {};  // stores the button current value
int button10PState[N_BUTTONS] = {};  // stores the button previous value

// debounce
unsigned long lastDebounceTime[N_BUTTONS] = { 0 };  // the last time the output pin was toggled
unsigned long debounceDelay = 50;                   // the debounce time; increase if the output flickers




/////////////////////////////////////////////
// MIDI
byte midiCh = 0;  // MIDI channel to be used - start with 1 for MIDI.h lib or 0 for MIDIUSB lib



/////////////////////////////////////////////
// SETUP
void setup() {

midi.begin();

Serial.begin(115200);  //



  // Buttons
  // Initialize buttons with pull up resistors
  for (int i = 0; i < N_BUTTONS; i++) {
    pinMode(BUTTON_ARDUINO_PIN[i], INPUT_PULLUP);
  }






}

/////////////////////////////////////////////
// LOOP
void loop() {


  buttons();


}

/////////////////////////////////////////////
// BUTTONS


void buttons() {
 int shift;
  if (digitalRead(BUTTON_ARDUINO_PIN[9]) == LOW)  {
    int shift = 10; 
    }  
 
  for (int i = 0; i < N_BUTTONS-1; i++) {

    buttonCState[i] = digitalRead(BUTTON_ARDUINO_PIN[i]);  // read pins from arduino


    if ((millis() - lastDebounceTime[i]) > debounceDelay) {

      if (buttonPState[i] != buttonCState[i]) {
        lastDebounceTime[i] = millis();

        if (buttonCState[i] == LOW)  {

      
          // Send a program change message
          int p = i + shift + 1;
          MIDIAddress program {p, Channel_1};
        
         midi.sendProgramChange(program);

         



        } else {


        }
        buttonPState[i] = buttonCState[i];
        
      }
    }
  }
}



/////////////////////////////////////////////


I'm thankful for your insights.

Hans

This creates two variables with the name shift, which is not what you want.

Try this instead:

  int shift = 0;
  if (digitalRead(BUTTON_ARDUINO_PIN[9]) == LOW)  {
    shift = 10; 
  }

Or equivalently:

int shift = digitalRead(BUTTON_ARDUINO_PIN[9]) == LOW ? 10 : 0;

I didn't look at the other parts in detail, there may be other problems hiding there.

1 Like

Hm, still doesn't work! I think the problem is, that in the loop "buttons" the variable shift always gets resetted at its beginning, even there is no active other button pressed. The reset of "shift" to zero should only happen, when there is actually a PC message send!
So the reset "shift = 0" should be after the midi-message gets send, right?

My problem is:
It seems I can't get the variable "shift" into the "scope" in which the PC message gets send.

Error: 'shift' was not declared in this scope

Here again the whole code:

#include <Control_Surface.h>
#include <MIDIUSB.h>
#include <MIDIUSB_Defs.h>

USBMIDI_Interface midi;

/*
  BASED ON: Made by Gustavo Silveira, 2023.
  - This Sketch reads the Arduino's digital and analog ports and send midi notes and midi control change
  MODIFIED to ProgramChanges

*/

/////////////////////////////////////////////
// Choosing your board
// Define your board, choose:


#define ATMEGA32U4 1  // put here the uC you are using, like in the lines above followed by "1", like "ATMEGA328 1", "DEBUG 1", etc.

/////////////////////////////////////////////
// Are you using buttons?
#define USING_BUTTONS 1  // comment if not using buttons


/////////////////////////////////////////////
// BUTTONS


const int N_BUTTONS = 10;                                //  total numbers of buttons
const int BUTTON_ARDUINO_PIN[10] = {2, 3, 4, 5, 6, 7, 8, 9, 10, 16};  // pins of each button connected straight to the Arduino

int buttonCState[N_BUTTONS] = {};  // stores the button current value
int buttonPState[N_BUTTONS] = {};  // stores the button previous value

int button10CState[N_BUTTONS] = {};  // stores the button current value
int button10PState[N_BUTTONS] = {};  // stores the button previous value

// debounce
unsigned long lastDebounceTime[N_BUTTONS] = { 0 };  // the last time the output pin was toggled
unsigned long debounceDelay = 50;                   // the debounce time; increase if the output flickers




/////////////////////////////////////////////
// MIDI
byte midiCh = 0;  // MIDI channel to be used - start with 1 for MIDI.h lib or 0 for MIDIUSB lib



/////////////////////////////////////////////
// SETUP
void setup() {

midi.begin();

Serial.begin(115200);  //



  // Buttons
  // Initialize buttons with pull up resistors
  for (int i = 0; i < N_BUTTONS; i++) {
    pinMode(BUTTON_ARDUINO_PIN[i], INPUT_PULLUP);
  }






}

/////////////////////////////////////////////
// LOOP
void loop() {


  buttons();


}

/////////////////////////////////////////////
// BUTTONS


void buttons() {
 

  for (int i = 0; i < N_BUTTONS-1; i++) {

        if (digitalRead(BUTTON_ARDUINO_PIN[9]) == LOW)  {
        int shift = 10; }

    buttonCState[i] = digitalRead(BUTTON_ARDUINO_PIN[i]);  // read pins from arduino


    if ((millis() - lastDebounceTime[i]) > debounceDelay) {

      if (buttonPState[i] != buttonCState[i]) {
        lastDebounceTime[i] = millis();

        if (buttonCState[i] == LOW)  {

      
          // Send a program change message
          int p = i + shift + 1;
          MIDIAddress program {p, Channel_1};
        
         midi.sendProgramChange(program);
          shift = 0;

        } else {


        }
        buttonPState[i] = buttonCState[i];
        
      }
    }
  }
}



/////////////////////////////////////////////


Do you want the shift to apply only while the button is pressed, or should it toggle between shifted and not shifted by a momentary press?

Neither! It should be "on hold" for the next ONE PC message to increase it by 10.
So inreal life I would press button#9 and then one of the other nine buttons, which gets addressed to the upper register so to speak!

I guess the other options would be easier. To hold the "shift" button like on a computer keyboard would prob. work with a if ... && function? And toggling - I don't know right now.

You could do something like this:

#include <Control_Surface.h>

USBMIDI_Interface midi;

Button pc_buttons[] {
  2, 3, 4, 5, 6, 7, 8, 9, 10,
};
Button bank_switch = 16;
uint8_t offset = 0;

void setup() {
  midi.begin();
  for (auto &b : pc_buttons)
    b.begin();
  bank_switch.begin();
}

void loop() {
  midi.update();
  if (bank_switch.update() == Button::Falling)
    offset = 9;
  for (auto &b : pc_buttons) {
    if (b.update() == Button::Falling) {
      uint8_t index = &b - pc_buttons;
      midi.sendProgramChange(index + 1 + offset);
      offset = 0;
    }
  }
}
1 Like

First of all a big thank you for your help!

I have no clue what your code would do and how! :joy:

I'll check it tomorrow! Here it is 1:20am! :sleeping_bed:

I also thought about splitting my presets in GigPerformer and switching from A (guitar sound) to B (guitar synth) by sending a CC message. So far I would like to have a solution which sets the first 9 patches as default because the guitar sound is more important.

Another possibility would be to create two banks and put the guitar sounds in bank 0 and the synths in bank 1. But the I would have to switch the bank back when coming from a synth sound to a guitar sound.

Thanks again!

Hans

I am completely flashed!
IT WORKS!

This is the shortest sketch I have seen or used so far in relation to my project! Magic!

Thanks SO MUCH, Pieter! I just recogneized you also created the library "surface". Great!
Programming is really a world on its own! On the other hand the time one as a musician puts in technical and IT stuff is quite extensive. I guess I should practice guitar again! :sweat_smile:

Greetings from Southern Germany

Hans

1 Like

Glad to hear! Let me know if anything about the code is unclear.

1 Like

Hello again!

I tried to add two buttons sending CC messages:
Button on pin 14: CC24, 127, CH4 (turning on a flanger effect)
Button on pin 15: CC26, 127, CH4 (turning on a distortion/overdrive effect)

Unfortunately there are error messages. Would be nice, if you checked the sketch:


```cpp
#include <Control_Surface.h>

USBMIDI_Interface midi;

#define ATMEGA32U4 1

Button pc_buttons[] {
  2, 3, 4, 5, 6, 7, 8, 9, 10,
};
Button bank_switch = 16;
uint8_t offset = 0;
Button flanger_switch = 14;
Button OD_switch = 15;


void setup() {
  midi.begin();
  for (auto &b : pc_buttons)
    b.begin();
  bank_switch.begin();
  flanger_switch.begin();
  OD_switch.begin();

}

void loop() {
  midi.update();
  if (bank_switch.update() == Button::Falling)
    offset = 9;


  for (auto &b : pc_buttons) {
    if (b.update() == Button::Falling) {
      uint8_t index = &b - pc_buttons;
      midi.sendProgramChange(index + 1 + offset);
      offset = 0;
    }
  }

  if (flanger_switch.update() == Button::Falling)
  midi.sendControlChange(int 24, int 127, int 4);

  if (OD_switch.update() == Button::Falling)
  midi.sendControlChange(int 26, int 127, int 4);
}




**Error messages:**

D:\hg-hg\Documents\Arduino\Arduino_guitar_2-add_flanger_OD\Arduino_guitar_2-add_flanger_OD.ino: In function 'void loop()':
D:\hg-hg\Documents\Arduino\Arduino_guitar_2-add_flanger_OD\Arduino_guitar_2-add_flanger_OD.ino:41:26: error: expected primary-expression before 'int'
midi.sendControlChange(int 24, int 127, int 4);
^~~
D:\hg-hg\Documents\Arduino\Arduino_guitar_2-add_flanger_OD\Arduino_guitar_2-add_flanger_OD.ino:41:34: error: expected primary-expression before 'int'
midi.sendControlChange(int 24, int 127, int 4);
^~~
D:\hg-hg\Documents\Arduino\Arduino_guitar_2-add_flanger_OD\Arduino_guitar_2-add_flanger_OD.ino:41:43: error: expected primary-expression before 'int'
midi.sendControlChange(int 24, int 127, int 4);
^~~
D:\hg-hg\Documents\Arduino\Arduino_guitar_2-add_flanger_OD\Arduino_guitar_2-add_flanger_OD.ino:44:26: error: expected primary-expression before 'int'
midi.sendControlChange(int 26, int 127, int 4);
^~~
D:\hg-hg\Documents\Arduino\Arduino_guitar_2-add_flanger_OD\Arduino_guitar_2-add_flanger_OD.ino:44:34: error: expected primary-expression before 'int'
midi.sendControlChange(int 26, int 127, int 4);
^~~
D:\hg-hg\Documents\Arduino\Arduino_guitar_2-add_flanger_OD\Arduino_guitar_2-add_flanger_OD.ino:44:43: error: expected primary-expression before 'int'
midi.sendControlChange(int 26, int 127, int 4);
^~~

exit status 1

Compilation error: expected primary-expression before 'int'


This is not valid C++ syntax. I'd recommend having a look at https://learncpp.com and Control Surface: Send-All-MIDI-Messages.ino.

1 Like

Hi!

So, I got it! The final some lines by myself, for the rest I thank PieterP for his coding.

Again the functionality of the project:

Sending messages to Gig Performer, a vst host programm which (in my case) hosts presets for guitar amp and effect simulations. I also included a guitar synth program (MidiGuitar2).

The midi usb controller sends 9 PC by 9 buttons, one button serves to initiate a kind of one-time-offset to another 9 presets. Two more buttons are there to switch on-off a flanger and an overdrive (here the latching function is provided by Gig Performer).

It is pretty fascinating what this little thing can do at an incredible cost.

Greetings and happy New Year,

Hans

CODE:


```cpp
#include <Control_Surface.h>   // Bibliotheken einlesen
#include "MIDIUSB.h"

USBMIDI_Interface midi;

#define ATMEGA32U4 1

Button pc_buttons[] {
  2, 3, 4, 5, 6, 7, 8, 9, 10,   // ergibt intern 1 bis 9
};
Button bank_switch = 16;        // erhoeht einmalig die naechste Programmnummer um 10 (offset)
uint8_t offset = 0;
Button flanger_switch = 14;     // Zuordnung Switch zu pin
Button OD_switch = 15;
byte (MIDI_CH) = 0;             // Def Midi Channel (1)

void setup() {
  midi.begin();
  for (auto &b : pc_buttons)    // auto debounct die Buttons und zaehlt sie durch
    b.begin();                  // alle Buttons sind mit der Button-Funktion verknuepft
  bank_switch.begin();
  flanger_switch.begin();
  OD_switch.begin();

}

void loop() {
  midi.update();
  if (bank_switch.update() == Button::Falling)   // Button::Falling = Button ist gedrueckt
    offset = 10;


  for (auto &b : pc_buttons) {                   // zaehlt die Buttons durch
    if (b.update() == Button::Falling) {
      uint8_t index = &b - pc_buttons;
      midi.sendProgramChange(index + 1 + offset);  // sendet PC mit Nummer um 1 und evtl. um 10 erhoeht 
      offset = 0;                                  // der offset wird zurueckgesetzt -> nur 1x angewandt
    }
  }

///////////////////////////////////////////////////////
// FLANGER auf Button pin 14 + OD auf Button pin 15

  if (flanger_switch.update() == Button::Falling)  {
     midi.sendControlChange(24, 127);              // 24=Controller Nr Flanger, 127 = 0n)
 }
   if (OD_switch.update() == Button::Falling)  {
     midi.sendControlChange(26, 127);               // 26=Controller Nr OD, 127 = 0n)
 }
}



1 Like

One more question:

In this sketch the inputs are not defined with "pullup". Is this included in the "Button"-macro?

You'll find the answer in the documentation: Control Surface: Button Class Reference

void begin()

Initialize (enable the internal pull-up resistor).

Definition at line 9 of file Button.cpp.

(Side note: Button is not a macro, but a class)

1 Like

FINISHED!

Now the controller is built and fixed to one of my guitars (can be removed without remains).

It is not perfect, but I love it and I think it has turned out beautifully!

Some more QUESTIONS to the sketch, which I couldn't find the answer in the documentation to surface.h:

Parts of the sketch:

void setup() {
  midi.begin();
  for (auto &b : pc_buttons)    // ???
    b.begin();                  // ???
  bank_switch.begin();
  flanger_switch.begin();
  OD_switch.begin();

}

void loop() {
  midi.update();
  if (bank_switch.update() == Button::Falling)   // Button::Falling = Button ist gedrueckt
    offset = 10;


  for (auto &b : pc_buttons) {                   // ???
    if (b.update() == Button::Falling) {         // ???
      uint8_t index = &b - pc_buttons;           // ???
      midi.sendProgramChange(index + 1 + offset);  // sendet PC mit Nummer um 1 und evtl. um 10 erhoeht 
      offset = 0;                                  // der offset wird zurueckgesetzt -> nur 1x angewandt
    }
  }

I marked the lines in which I don't understand things with question marks.
What does "auto" mean? Is it part of CC++ or of the surface-library?
What does this single "b" mean? Is it a variable? But I don't see it defined,

I am still a beginner, so be gentle! :wink:

Greetings,

HANS