Get midi values to print on an i2c screen with control surface

So as a part of a project im doing im using the control surface to act as simply a MIDI to usb adapter with a seperate I2C 16x2 LCD screen that im am trying to use to dispaly the note value and velocity of incoming notes from the serial midi on channel 1.

The isssue im having is i dont know what function I could extract those values from so that I can send those to the LCD.

this is roughly what i had tried to do based on a sketch that parts of the code came from but

#include <Arduino.h>             // Include standard arduino library
#include <Control_Surface.h>    // Include the Control Surface library
#include <MIDIUSB.h>            // Include the USB MIDI library
#include <LiquidCrystal_I2C.h>  // Include the LCD I2C Library


LiquidCrystal_I2C lcd(0x3F, 16, 2);

USBMIDI_Interface midiusb;
HardwareSerialMIDI_Interface midiser = {Serial1, MIDI_BAUD};

// Create a MIDI pipe factory to connect the MIDI interfaces to eachother and to the Control Surface
MIDI_PipeFactory<5> pipes;

// creates a list of midi notes to be displayd on the lcd
String lcdNotes[12] = {"C","C#/Db","D","D#/Eb","E","F","F#/Gb","G","G#/Ab","A","A#/Bb","B"};
int noteNo;
int velVal;
bool screenUpdate;

// Custom MIDI callback that prints incoming messages.
struct MyMIDI_Callbacks : MIDI_Callbacks {

  // Callback for channel messages (notes, control change, pitch bend, etc.).
  void onChannelMessage(Parsing_MIDI_Interface &midi) override {
    ChannelMessage cm = midi.getChannelMessage();

    if (cm.header == 0x90) {
      noteNo = cm.data1;
      velVal = cm.data2;
      screenUpdate = true;
    }
    else if (cm.header == 0x80) {
      noteNo = 0;
      velVal = 0;
      screenUpdate = false;
    }
    // forward channel mesages without changes
    midiusb.send(cm);
  }


  // Callback for system exclusive messages
  void onSysExMessage(Parsing_MIDI_Interface &midi) override {
    SysExMessage se = midi.getSysExMessage();

    // forward system exclusive messages without changes
    midiusb.send(se);
  }
 
  // Callback for real-time messages
  void onRealTimeMessage(Parsing_MIDI_Interface &midi) override {
    RealTimeMessage rt = midi.getRealTimeMessage();

    // forward real-time messages without changes
    midiusb.send(rt);
  }
} callbacks; 


// setup code
void setup() {
  Serial.begin(115200);

  // forward MIDI USB to MIDI serial
  midiusb >> pipes >> midiser;
  // forward MIDI serial to MIDI USB
  midiser >> pipes >> midiusb;
  // send control suface messages only to MIDI USB
  Control_Surface >> pipes >> midiusb;

  // connect both MIDI USB and serial to control surface
  midiser >> pipes >> Control_Surface;
  midiusb >> pipes >> Control_Surface;

  // initialize Control Surface _after_ connecting the interfaces
  Control_Surface.begin();
  
  // set up lcd
  lcd.begin();
  lcd.backlight();

  lcd.setCursor(0,0);
  lcd.print("Note: ");
  lcd.setCursor(0,1);
  lcd.print("velocity: ");
}

void screenClear() {
  lcd.setCursor(6,0);
  lcd.print("    ");
  lcd.setCursor(10,1);
  lcd.print("    ");
}

void screenNewVals() {
  lcd.setCursor(6,0);
  lcd.print(noteNo);
  lcd.setCursor(10,1);
  lcd.print(velVal);
}

// main processing loop
void loop() {
  if (screenUpdate == true) {
    screenNewVals();
  }
  else if (screenUpdate == false) {
    screenClear();
  }

  
  Control_Surface.loop();
}

however the only time that data gets written to the display is on the startup section and values do update so if anyone has any sugestions on what im doing wrong I would be glad to hear them.

Chears.

A bool is either true or false so no need for the second if().
The logic seems a bit off. You should only update the screen when a new note arrives, not every time through loop() since you never reset screenUpdate. The decision to print the note value or clear should be based on noteNo == 0 or not. It seems like this might be better

#include <Arduino.h>             // Include standard arduino library
#include <Control_Surface.h>    // Include the Control Surface library
#include <MIDIUSB.h>            // Include the USB MIDI library
#include <LiquidCrystal_I2C.h>  // Include the LCD I2C Library


LiquidCrystal_I2C lcd(0x3F, 16, 2);

USBMIDI_Interface midiusb;
HardwareSerialMIDI_Interface midiser = {Serial1, MIDI_BAUD};

// Create a MIDI pipe factory to connect the MIDI interfaces to eachother and to the Control Surface
MIDI_PipeFactory<5> pipes;

// creates a list of midi notes to be displayd on the lcd
String lcdNotes[12] = {"C", "C#/Db", "D", "D#/Eb", "E", "F", "F#/Gb", "G", "G#/Ab", "A", "A#/Bb", "B"};
int noteNo;
int velVal;
bool screenUpdate;

// Custom MIDI callback that prints incoming messages.
struct MyMIDI_Callbacks : MIDI_Callbacks {

  // Callback for channel messages (notes, control change, pitch bend, etc.).
  void onChannelMessage(Parsing_MIDI_Interface &midi) override {
    ChannelMessage cm = midi.getChannelMessage();

    if (cm.header == 0x90) {
      noteNo = cm.data1;
      velVal = cm.data2;
      screenUpdate = true;
    }
    else if (cm.header == 0x80) {
      noteNo = 0;
      velVal = 0;
      screenUpdate = true;
    }
    // forward channel mesages without changes
    midiusb.send(cm);
  }


  // Callback for system exclusive messages
  void onSysExMessage(Parsing_MIDI_Interface &midi) override {
    SysExMessage se = midi.getSysExMessage();

    // forward system exclusive messages without changes
    midiusb.send(se);
  }

  // Callback for real-time messages
  void onRealTimeMessage(Parsing_MIDI_Interface &midi) override {
    RealTimeMessage rt = midi.getRealTimeMessage();

    // forward real-time messages without changes
    midiusb.send(rt);
  }
} callbacks;


// setup code
void setup() {
  Serial.begin(115200);

  // forward MIDI USB to MIDI serial
  midiusb >> pipes >> midiser;
  // forward MIDI serial to MIDI USB
  midiser >> pipes >> midiusb;
  // send control suface messages only to MIDI USB
  Control_Surface >> pipes >> midiusb;

  // connect both MIDI USB and serial to control surface
  midiser >> pipes >> Control_Surface;
  midiusb >> pipes >> Control_Surface;

  // initialize Control Surface _after_ connecting the interfaces
  Control_Surface.begin();

  // set up lcd
  lcd.begin();
  lcd.backlight();

  lcd.setCursor(0, 0);
  lcd.print("Note: ");
  lcd.setCursor(0, 1);
  lcd.print("velocity: ");
}

void screenNewVals() {
  if ( noteNo ) {
    lcd.setCursor(6, 0);
    lcd.print(noteNo);
    lcd.setCursor(10, 1);
    lcd.print(velVal);
  }
  else {
    lcd.setCursor(6, 0);
    lcd.print("    ");
    lcd.setCursor(10, 1);
    lcd.print("    ");
  }
}

// main processing loop
void loop() {
  if (screenUpdate == true) {
    screenNewVals();
    screenUpdate = false;
  }
  Control_Surface.loop();
}

the update screen function whilst not writtten well isnt the issue that im having but i apreciate your help there.

the issue is that i cant find what function i need to call to get the data to send to the function

this callback function here that i am trying to use to set global variables based on sections within it doesnt seem to send them to global but to local which i then cant read.

struct MyMIDI_Callbacks : MIDI_Callbacks {

  // Callback for channel messages (notes, control change, pitch bend, etc.).
  void onChannelMessage(Parsing_MIDI_Interface &midi) override {
    ChannelMessage cm = midi.getChannelMessage();

    if (cm.header == 0x90) {
      noteNo = cm.data1;
      velVal = cm.data2;
      screenUpdate = true;
    }
    else if (cm.header == 0x80) {
      noteNo = 0;
      velVal = 0;
      screenUpdate = false;
    }
    // forward channel mesages without changes
    midiusb.send(cm);
  }
} callbacks;

im sure that there is a function within the control surfae library that i can use to give me the values that im looking for in a way that i can read them but i dont know what it is

That function most certainly sets those global variables. Have you tried putting some debug print

statements in there to prove the function is being called?

You create a variable named callbacks, but it is never used, you never set it as the callbacks for any of the MIDI interfaces in your code.

Please see Control Surface: MIDI Tutorial (note that this is the documentation for the master branch, so you might have to upgrade if you have an old version, which appears to be the case given that you're using the old MIDI_Callbacks API).

Either use the MIDI pipes to forward MIDI from one interface to another, or use the callbacks, but not both.

yeah the old callbacks api is from the code that i orignaly coppied and tried to modify which had used the callbacks to stop the all notes off command and i had tried to hyjack that.

I'll look at the new api and see if i can get it working from there.

thanks

so i tried the tutorial documentaion that you linked to

#include <Control_Surface.h>
 
// Instantiate a MIDI over USB interface
USBMIDI_Interface midi;
 
// Custom MIDI callback that prints incoming MIDI channel messages.
struct MyMIDI_Callbacks : MIDI_Callbacks {
 
    // Callback for channel messages (notes, control change, pitch bend, etc.).
    void onChannelMessage(MIDI_Interface &, ChannelMessage msg) override {
        // Print the message
        Serial << F("Received: ") << hex << msg.header << ' ' << msg.data1;
        if (msg.hasTwoDataBytes()) 
            Serial << ' ' << msg.data2;
        Serial << dec << endl;
    }
 
} callback; // Instantiate a callback
 
void setup() {
    Serial.begin(115200);        // For printing the messages
    midi.begin();                // Initialize the MIDI interface
    midi.setCallbacks(callback); // Attach the custom callback
}
 
void loop() {
    midi.update(); // Continuously dispatch incoming MIDI input to the callbacks
}

hoever I kept getting this error

src\main.cpp:11:10: error: 'void MyMIDI_Callbacks::onChannelMessage(CS::MIDI_Interface&, CS::ChannelMessage)' marked 'override', but does not override
     void onChannelMessage(MIDI_Interface &, ChannelMessage msg) override {
          ^~~~~~~~~~~~~~~~

im using the latest version of the libary 1.2.0-4 that Platform IO lets me add to the project folder.

Like I said, the documentation is for the master branch, not for 1.2.0.
I think you should be able to install it using

pio lib install https://github.com/tttapa/Control-Surface.git#master

If you really want to, you could use the 1.2.0 version, but then you have to consult the correct documentation: Control Surface: MIDI-Input.ino
(There is no "MIDI tutorial" for version 1.2.0.)

ah ok thank you i hadn't realised that.

it now all seems to work using the setMIDIInptCallbacks(channelMessageCallback) comand and using the callback to set the lcd.

it still seems to hang but that might just be becuase i dont have the lcd attached to the L2C bus at the moment.

I modified the code and this works on my end. You may want to give it a try.

#include <Control_Surface.h>    // Include the Control Surface library
#include <LiquidCrystal_I2C.h>  // Include the LCD I2C Library

LiquidCrystal_I2C lcd(0x3F, 16, 2);


USBMIDI_Interface midi;

String lcdNotes[12] = {"C","C#/Db","D","D#/Eb","E","F","F#/Gb","G","G#/Ab","A","A#/Bb","B"};
int noteNo;
int velVal;
bool screenUpdate;

// Custom MIDI callback that prints incoming messages.
struct MyMIDI_Callbacks : MIDI_Callbacks {
  // Callback for channel messages (notes, control change, pitch bend, etc.).
  void onChannelMessage(MIDI_Interface &, ChannelMessage msg)  {
      if (msg.header >> 4 == 0x09) {
      noteNo = msg.data1;
      velVal = msg.data2;
      screenUpdate = true;
    }
    else if (msg.header >> 4 == 0x08) {
      noteNo = 0;
      velVal = 0;
      screenUpdate = false;
    }
//    midi.send(msg);
  }

} callback; // Instantiate a callback
void setup() {
  // Set display type as 16 char, 2 rows
  Control_Surface.begin();									  
  lcd.init();
  lcd.backlight();
  lcd.setCursor(0,0);
  lcd.print("Note: ");
  lcd.setCursor(0,1);
  lcd.print("velocity: ");
  
  midi.setCallbacks(callback); // Attach the custom callback
  Serial.begin(115200);
}

void screenClear() {
  lcd.setCursor(6,0);
  lcd.print("     ");
  lcd.setCursor(10,1);
  lcd.print("    ");
}

void screenNewVals() {
  lcd.setCursor(6,0);
  lcd.print(lcdNotes[noteNo % 12]);
  lcd.setCursor(10,1);
  lcd.print(velVal);
}

void loop() {

//  midi.update(); // Continuously handle MIDI input
  if (screenUpdate == true) {
    screenNewVals();
  }
  else if (screenUpdate == false) {
    screenClear();
  }
  Control_Surface.loop();  
}

So in the end i ended up something that works based off your sugestions here and i thank you.

Compleated code for anyone who is wondering is here.

#include <Control_Surface.h>
#include <LiquidCrystal_I2C.h>

USBMIDI_Interface usbmidi;
HardwareSerialMIDI_Interface midiser = {Serial1, MIDI_BAUD};
LiquidCrystal_I2C lcd(0x3F, 20, 4);

BidirectionalMIDI_PipeFactory<2> pipes;
MIDI_Pipe throughPipe;

bool screenUpdate = false;
int noteNo;
int velVal;
String lcdNotes[12] = {"C","C#/Db","D","D#/Eb","E","F","F#/Gb","G","G#/Ab","A","A#/Bb","B"};


bool channelMessageCallback(ChannelMessage cm) {
  Serial << F("Channel message: ") << hex                   //
         << cm.header << ' ' << cm.data1 << ' ' << cm.data2 //
         << dec << F(" on cable ") << cm.cable.getOneBased() << endl;
    if (cm.header >> 4 == 0x09) {
        noteNo = cm.data1;
        velVal = cm.data2;
        screenUpdate = true;
    }
    else if (cm.header >> 4 == 0x08) {
        screenUpdate = true;
        noteNo = 130;        
    }
  return true; // Return true to indicate that handling is done,
               // and Control_Surface shouldn't handle it anymore.
               // If you want Control_Surface to handle it as well,
               // return false.
}

void screenNewVals() {
  if (noteNo < 128) {
    lcd.setCursor(6, 0);
    lcd.print(lcdNotes[noteNo % 12]);
    lcd.setCursor(10, 1);
    lcd.print(velVal);
  }
  else {
    lcd.setCursor(6, 0);
    lcd.print("     ");
    lcd.setCursor(10, 1);
    lcd.print("    ");
  }
}


void setup() {
  Serial.begin(115200);
  // Setup LCD
  lcd.init();
  lcd.backlight();
  // setup basic info on startup
  lcd.setCursor(0,0);
  lcd.print("Note: ");
  lcd.setCursor(0,1);
  lcd.print("velocity: ");

  Control_Surface | pipes | usbmidi;
  Control_Surface | pipes | midiser;
  midiser >> throughPipe >> usbmidi;

  Control_Surface.begin();
  Control_Surface.setMIDIInputCallbacks(channelMessageCallback,   //
                                        nullptr,                  //
                                        nullptr,                  //
                                        nullptr);                 //
}

void loop() {
  if (screenUpdate == true) {
    screenNewVals();
    screenUpdate = false;
  }
  Control_Surface.loop();
}

once again thanks for peoples help with this.

1 Like