A small library wich uses another library

Hello, I'm completely new at C++ and I'm trying to translate a MIDI pedalboard software by me in a more elegant one and to learn.

I know that this code is actually a mess but I'm stuck in the middle of the process.
maybe I have to replace some byte type in .cpp and .h files in char or unsigned char and I already tried but it didn't solve my problem.

By the way, this is the error I get:

Compiling 'myMIDIcontroller' for 'Arduino Uno'
 
cc1hOuIW.ltrans0.ltrans.o*: In function global constructors keyed to 65535_0_myMIDIcontroller.cpp.o.1915

Error linking for board Arduino Uno
   (.text.startup+0x142): undefined reference to MIDIButton::MIDIButton(int, unsigned char, unsigned char, unsigned char)
   (.text.startup+0x152): undefined reference to MIDIButton::MIDIButton(int, unsigned char, unsigned char, unsigned char)
Build failed for project 'myMIDIcontroller'
 
cc1hOuIW.ltrans0.ltrans.o*: In function loop
myMIDIcontroller.ino:29: undefined reference to MIDIButton  listener()
myMIDIcontroller.ino:30: undefined reference to MIDIButton  listener()
 
collect2.exe*: error: ld returned 1 exit status

myMIDIcontroller.ino

#include "MIDIButton.h"
#include <MIDI.h>

MIDI_CREATE_DEFAULT_INSTANCE();

// digital pin 2 has a pushbutton attached to it. Give it a name:
#define btn1pin 2
#define btn2pin 3

const int debounceTime = 5; //debounce time in milliseconds
bool btnState;
bool btnLast;
byte ccValue;

MIDIButton btn1(btn1pin, 0xB0, 0x14, 0);
MIDIButton btn2(btn1pin, 0xB0, 0x15, 0);

// the setup routine runs once when you press reset:
void setup() {
  // initialize serial communication at 9600 bits per second:
  //Serial.begin(115200);
  //  Set MIDI baud rate:
  MIDI.begin(1);
  //Serial.begin(31250);
  // make the pushbutton's pin an input:
  }

// the loop routine runs over and over again forever:
void loop() {
	btn1.listener();
	btn2.listener();
	
/*	
  // read the input pin:
  btnState = digitalRead(btn1pin);
  if (btnState != btnLast) {
    delay(debounceTime);
    if (btnState == LOW && btnLast == HIGH) {
      // print out the state of the button:
      ccToggle(0x14, ccValue);
    } else {
      // print out the state of the button:
      //Serial.println(btnState);
    }
    btnLast = btnState;
  } 
*/
}

MIDIButton.h

/*
  MIDIButton.h - Library for button behavior.
  Created by Luca Troncone, 13th May 2020.
  Released into the public domain.
*/

#ifndef MIDIButton_h
#define MIDIButton_h

//#include <arduino.h>
#include <MIDI.h>

class MIDIButton
{
  public:
    MIDIButton(int pin, byte command, byte ccNumber, byte value);
    void listener();    
  protected:
  private:
    const int _pin;
    byte _ccNumber, _ccValue;
    bool btnState, btnLast;
	void ccToggle(byte _ccNumber, byte _ccValue);
};

#endif

MIDIButton.cpp

/*
  MIDIButton.cpp - Library for button behavior.
  Created by Luca Troncone, 13th May 2020.
  Released into the public domain.
*/

//#include <arduino.h>
#include <MIDI.h>
#include "MIDIButton.h"

#define debounceTime 5

MIDI_CREATE_DEFAULT_INSTANCE();
MIDIButton::MIDIButton(int pin, byte command, byte ccNumber, byte value)
{
	pinMode(pin, INPUT);
	int _pin = pin;
	byte _command = command;
	byte _ccNumber = ccNumber;
	byte _ccValue = value;
}

void MIDIButton::listener(){
	btnState = digitalRead(_pin);
	if (btnState != btnLast) {
		delay(debounceTime);
		if (btnState == LOW && btnLast == HIGH) {
			// button press
			ccToggle(_ccNumber, _ccValue);
			} else {
			// button release 
		}
		btnLast = btnState;
	}
}

void MIDIButton::ccToggle(byte ccNumber, byte ccValue)
{
	if (ccValue == 0) {
		ccValue = 127;
		} else {
		ccValue=0;
	}
	//MIDI.sendControlChange(this->_ccNumber, this->_ccValue)
}

Why this happens? At the moment I keep all the files in the same folder... this is my firs experience with an Arduino project with more than a file.
Please explain in the easiest possible way due to my poor english and C++ knowledge.
Every help will be appretiated!

First of all, go to the preferences in the Arduino IDE, and enable all compiler warnings.
Then recompile your sketch and inspect the warnings. Almost all of them are actually bugs in your code.

/tmp/arduino_build_256593/sketch/MIDIButton.cpp: In constructor 'MIDIButton::MIDIButton(int, byte, byte, byte)':
/tmp/arduino_build_256593/sketch/MIDIButton.cpp:15:1: warning: uninitialized const member in 'const int' [-fpermissive]
 MIDIButton::MIDIButton(int pin, byte command, byte ccNumber, byte value)
 ^~~~~~~~~~
In file included from /tmp/arduino_build_256593/sketch/MIDIButton.cpp:10:0:
/tmp/arduino_build_256593/sketch/MIDIButton.h:21:15: note: 'const int MIDIButton::_pin' should be initialized
     const int _pin;
               ^~~~
/tmp/arduino_build_256593/sketch/MIDIButton.cpp:18:7: warning: unused variable '_pin' [-Wunused-variable]
   int _pin = pin;
       ^~~~
/tmp/arduino_build_256593/sketch/MIDIButton.cpp:19:8: warning: unused variable '_command' [-Wunused-variable]
   byte _command = command;
        ^~~~~~~~
/tmp/arduino_build_256593/sketch/MIDIButton.cpp:20:8: warning: unused variable '_ccNumber' [-Wunused-variable]
   byte _ccNumber = ccNumber;
        ^~~~~~~~~
/tmp/arduino_build_256593/sketch/MIDIButton.cpp:21:8: warning: unused variable '_ccValue' [-Wunused-variable]
   byte _ccValue = value;
        ^~~~~~~~

The linker error you're getting seems to indicate that the MIDIButton.cpp file isn't actually being compiled. Is it in the same folder as your sketch? Did you use the correct .cpp extension?

Your constructor doesn't initialize the member variables of your class, it just creates local copies of the constructor parameters and then discards them. These are all the unused variable warnings.
The warning above that (uninitialized const member) indicates that you didn't initialize the const member MIDIButton::_pin. You have to do that using an initializer list.
Why did you prefix all member variables with an underscore? It's not necessary.

The header file you need to include in MIDIButton.cpp is Arduino.h, not arduino.h.

Don't use pinMode and other hardware-related functions in the constructor, add a separate "begin" method that you call in the setup function. When the constructor of your class runs, the Arduino core hasn't initialized the hardware yet.

A more serious problem is that due to the way the MIDI library handles initialization, you cannot use the "MIDI" object from different translation units (from different source files, e.g. your main sketch and MIDIButton.cpp).
You could instantiate the necessary objects yourself in a header file and declare them "extern". Then define them in a single implementation file. You can then include the header file from all implementation files where you need to use "MIDI".


That being said, the Control Surface library should support everything you're trying to do:

[color=#5e6d03]#include[/color] [color=#434f54]<[/color][b][color=#d35400]Control_Surface[/color][/b][color=#434f54].[/color][color=#000000]h[/color][color=#434f54]>[/color] [color=#434f54]// Include the Control Surface library[/color]
 
[color=#434f54]// Instantiate a MIDI interface to use (on pins 0 and 1)[/color]
[b][color=#d35400]HardwareSerialMIDI_Interface[/color][/b] [color=#00979c]midi[/color] [color=#434f54]=[/color] [b][color=#d35400]Serial[/color][/b][color=#000000];[/color]
 
[color=#434f54]// Instantiate an array of CCButtonLatched objects[/color]
[b][color=#d35400]CCButtonLatched[/color][/b] [color=#000000]buttons[/color][color=#000000][[/color][color=#000000]][/color] [color=#434f54]=[/color] [color=#000000]{[/color]
  [color=#000000]{[/color] [color=#434f54]// first button:[/color]
    [color=#000000]2[/color][color=#434f54],[/color]    [color=#434f54]// Push button between this pin and ground[/color]
    [color=#000000]0x14[/color][color=#434f54],[/color] [color=#434f54]// MIDI controller number[/color]
  [color=#000000]}[/color][color=#434f54],[/color]
  [color=#000000]{[/color] [color=#434f54]// second button:[/color]
    [color=#000000]3[/color][color=#434f54],[/color]
    [color=#000000]0x15[/color][color=#434f54],[/color]
  [color=#000000]}[/color]
[color=#000000]}[/color][color=#000000];[/color]
 
[color=#00979c]void[/color] [color=#5e6d03]setup[/color][color=#000000]([/color][color=#000000])[/color] [color=#000000]{[/color]
  [b][color=#d35400]Control_Surface[/color][/b][color=#434f54].[/color][color=#d35400]begin[/color][color=#000000]([/color][color=#000000])[/color][color=#000000];[/color] [color=#434f54]// Initialize Control Surface[/color]
[color=#000000]}[/color]
 
[color=#00979c]void[/color] [color=#5e6d03]loop[/color][color=#000000]([/color][color=#000000])[/color] [color=#000000]{[/color]
  [b][color=#d35400]Control_Surface[/color][/b][color=#434f54].[/color][color=#5e6d03]loop[/color][color=#000000]([/color][color=#000000])[/color][color=#000000];[/color] [color=#434f54]// Update the Control Surface[/color]
[color=#000000]}[/color]

If you want to make your own MIDIButton class, you can look at this example, which does almost exactly that: Custom-MIDI-Output-Element.ino

But that might not be necessary at all, because there are many different MIDI Elements available out of the box:
MIDI Output Elements

Pieter

Thanks a lot for your exhaustive answer! :slight_smile:

This is the complete message from the Arduino IDE compiler, but I'm used to code with Atmel Studio with VisualMicro because it has Debug feature.

I attached a compiler log.

What is wrong with my constructor? at this time I converted every byte in unsigned char but don't know if matters or not. I've read that in C++ is used to put underscore before private variables...

About the pinMode in the constructor, I've seen in someonelse' sketch (may be in the examples from MIDI_library). Anyway I'll put it in a begin method as you suggested.

I have problems understanding what you say here:

A more serious problem is that due to the way the MIDI library handles initialization, you cannot use the "MIDI" object from different translation units (from different source files, e.g. your main sketch and MIDIButton.cpp).
You could instantiate the necessary objects yourself in a header file and declare them "extern". Then define them in a single implementation file. You can then include the header file from all implementation files where you need to use "MIDI".

Can you show me an example or a guideline/template or how you would modify my code to resolve this problem?

I decided to write my library for 2 reasons: the 1st is that I want buttons with toggle or press/hold/release features and I didn't find a MIDI library with that; the 2nd is to learn to write classes and libraries.

I've discovered many MIDI libraries, but this Control Surface looks interesting: my first goal at the moment is to let the pedalboard work, than learning.

I would exploit of your kindness with one more question: Imagine this scenario where my ccToggle code can send 0 or 127 to activate/deactivate. As Arduino start and every time I switch to another patch/instrument, the ccValue variable can be different from the one in the instrument. How can I avoid this? I've read something about bulk dump, but I don't think it is really what I need. (Hope I explained well).

Thank you, I really appretiated your help.

Compiler log.txt (11.2 KB)

Tronky:
What is wrong with my constructor?

Your constructor should initialize the member variables. It doesn't do that, it creates local variables, initializes those, and don't do anything with the member variables.

MIDIButton::MIDIButton(int pin, byte command, byte ccNumber, byte value) {
  int _pin = pin;
  // ↑ by writing a typename here, you're declaring a new local variable
  // it shadows the member variable with the same name

To assign a value to the member variable "_pin", you need something like this:

MIDIButton::MIDIButton(int pin, byte command, byte ccNumber, byte value) {
  _pin = pin;

However, this is bad practice, and it won't work for const members, for example.

The correct way to initialize the member variables is to use a member initializer list. See this thread for more information, I don't have time to explain it from scratch here.

MIDIButton::MIDIButton(int pin, byte command, byte ccNumber, byte value)
  : _pin(pin), _command(command), _ccNumber(ccNumber), _ccValue(value) // ← initializer list
{} // ← the body is empty now

As mentioned below, there's no reason for the underscores, you can have constructor parameters and member variables with the same name:

class MIDIButton {
  public:
    MIDIButton(int pin, byte command, byte ccNumber, byte value);
  private:
    const int pin;
    byte command, ccNumber, ccValue;
    bool btnState, btnLast;
};

MIDIButton::MIDIButton(int pin, byte command, byte ccNumber, byte value)
  : pin(pin), command(command), ccNumber(ccNumber), ccValue(value) // ← initializer list
{} // ← the body is empty now

Tronky:
at this time I converted every byte in unsigned char but don't know if matters or not.

No, it doesn't matter. What does matter is showing intent in your code:
The CC number is a number, it cannot be negative, and it can be stored using 8 bits. Therefore you should use the type uint8_t.
In many implementations, this type is equivalent to unsigned char, but this clearly shows the wrong intent: the CC number stores a number, not a character.

Tronky:
I've read that in C++ is used to put underscore before private variables...

You don't need any underscores, it is meaningless to the compiler. You indicate that something is a private member variable by using the private keyword in your class definition. The name of the variable doesn't matter, and has no effect on whether a variable is public or private.

Some style guides use a trailing underscore for private members. Underscores as a prefix are uncommon, because underscores followed by capital letters are reserved identifiers.
I'd argue that the underscore is unnecessary: if you need it to indicate that something is a member variable, your code is most likely not clear enough. On top of that, almost all decent editors and IDEs will indicate what variables are members using colors or icons. Sadly, the Arduino IDE is a terrible IDE, and it doesn't do any of that.
Anyway, most of it comes down to personal preference, pick a clear convention and stick to it.

Tronky:
I have problems understanding what you say here:

A more serious problem is that due to the way the MIDI library handles initialization, you cannot use the "MIDI" object from different translation units (from different source files, e.g. your main sketch and MIDIButton.cpp).

You could instantiate the necessary objects yourself in a header file and declare them "extern". Then define them in a single implementation file. You can then include the header file from all implementation files where you need to use "MIDI".




Can you show me an example or a guideline/template or how you would modify my code to resolve this problem?

Make sure you understand this first: c++ - How do I use extern to share variables between source files? - Stack Overflow
It's very important to understand the difference between a declaration and a definition.

With the MIDI library, it's more involved because they hide their declarations behind the MIDI_CREATE_DEFAULT_INSTANCE macro. You cannot use that macro if you want to share the MIDI variable between files, you'll have to look at how the macro is defined, and use that code directly: arduino_midi_library/src/serialMIDI.h at 80dfb8333c79882d6eb86e811523b67a75e920b5 · FortySevenEffects/arduino_midi_library · GitHub

Tronky:
I decided to write my library for 2 reasons: the 1st is that I want buttons with toggle or press/hold/release features and I didn't find a MIDI library with that; the 2nd is to learn to write classes and libraries.

Control Surface supports this toggle function out of the box if you use the latched variants, as shown in the example I posted earlier. (latched switch == toggle switch)
For detecting long presses, you can simply use the Button::stableTime() method, in combination with the Custom MIDI output element example I linked to in my previous reply. When the button state is Button::Pressed, Button::stableTime() returns the number of milliseconds the button has been pressed for.

Tronky:
I would exploit of your kindness with one more question: Imagine this scenario where my ccToggle code can send 0 or 127 to activate/deactivate. As Arduino start and every time I switch to another patch/instrument, the ccValue variable can be different from the one in the instrument. How can I avoid this? I've read something about bulk dump, but I don't think it is really what I need. (Hope I explained well).

You'll need to listen to the MIDI data that your audio software sends to the Arduino. You can inspect this using a MIDI monitor, or using the Reverse Engineering example

You can listen for incoming values using the CCValue class.

Thank you for the time you spend replying me. I already tried Surface Control and I like how it works. If I had another arduino board I'd like to begin using it to see how many controls I need and wich. I already ordered a pro micro because I'd like to have USB and 5pins MIDI interface and smaller dimension. At the moment I own only 5 stomp momentary buttons and I will order others soon, but I'd like use one of them as a shift to double the features as I had 8 buttons. Do you think it will be easy with Control Surface library?

Tronky:
At the moment I own only 5 stomp momentary buttons and I will order others soon, but I'd like use one of them as a shift to double the features as I had 8 buttons. Do you think it will be easy with Control Surface library?

Yes, this should be easy using the Bankable module: Bank.ino

Hello, I've written this simple code with 2 buttons (at the moment): one is a CC toggle and the other is a bank selector. My Arduino is connected via 5 pin Midi port to an USB midi interface and when I push the CC button, it flashes the red led (output led) and when I push the bank selector button it doesn't. I think is the right behavior, but the software I used to test if CC toggles work, seems not receiving midi CC messages anymore. What's wrong with my code?

#include <Control_Surface.h>

HardwareSerialMIDI_Interface midi = Serial;


Bank<2> bank(1);

SwitchSelector selector = {bank, 3};

Bankable::CCButtonLatched<2> buttons[] = {
  {{bank, BankType::CHANGE_ADDRESS}, 2, 0x14},
};

 


void setup() {
  Control_Surface.begin(); // Initialize Control Surface
}
 
void loop() {
  Control_Surface.loop(); // Update the Control Surface
}

In the future I'd like to add some leds to show which bank is active. Can you suggest the best way to do it? (I have some difficulties to understand how to read the library guide. Think due to poor C++ knowledge).

Thank you very much

The code you posted works fine for me. What software are you using? Have you tried using a MIDI monitor on your computer, or using the MIDI Debug mode?

When the push button on pin 3 is not pressed, the push button on pin 2 will send on CC #20, and when the button on pin 3 is pressed, the button on pin 2 will send on CC #21 (because the bank adds 1 to the address). Does that match your expectation?

Tronky:
In the future I'd like to add some leds to show which bank is active. Can you suggest the best way to do it?

If you go to Modules > Selectors > Selectors LEDs in the documentation, you'll see that there's a version of SwitchSelector with LED feedback.

Tronky:
(I have some difficulties to understand how to read the library guide. Think due to poor C++ knowledge).

What parts do you struggle with specifically?

PieterP:
When the push button on pin 3 is not pressed, the push button on pin 2 will send on CC #20, and when the button on pin 3 is pressed, the button on pin 2 will send on CC #21 (because the bank adds 1 to the address). Does that match your expectation?

Yes it is exactly what I want except for bank shifter: I want it to act as a toggle (because now I have just 1 button for switching banks, but in the future, I'm going to design more banks with "next" and "previous" buttons... I already have seen that there is a dedicated Selector but it needed 2 buttons.. actually I have 1). Anyway I'm using IL Host Modular. No I've not yet tried MIDI debug mode, I'll do!
About the library guide: the most difficulty for me is to find the options I can use to... I don't eveno know how to explain... for example it has been hard to discover which "word" use to define BankType. I've seen it in the example in which every pot changes midi volume on different channels, but I needed that bank changes the CC address, not channel...
As always: thanks for your help! :slight_smile:

Tronky:
Yes it is exactly what I want except for bank shifter: I want it to act as a toggle (because now I have just 1 button for switching banks, but in the future, I'm going to design more banks with "next" and "previous" buttons... I already have seen that there is a dedicated Selector but it needed 2 buttons.. actually I have 1).

For toggling between two banks, you can use IncrementSelector<2>. IncrementSelecter cycles through all N banks when you press the button, so if you have only two banks, it has the same effect as toggling between them. I should probably add an alias for it, though.

Tronky:
About the library guide: the most difficulty for me is to find the options I can use to... I don't eveno know how to explain... for example it has been hard to discover which "word" use to define BankType. I've seen it in the example in which every pot changes midi volume on different channels, but I needed that bank changes the CC address, not channel...

Yeah I can see why that's difficult, the library has a lot of features, it's very hard to showcase them all without being overwhelming. If you have any ideas on how to make it clearer, feel free to let me know!

The BankType::CHANGE_ADDRESS is actually mentioned in the comments of the Bank.ino example I linked to earlier.

Alternatively, you can find the alternatives by clicking on "BankType::CHANGE_CHANNEL" in the code on the example page. The code snippets in the documentation contain links to all relevant documentation pages of everything that's used in the code.

If you have absolutely no idea where to start, if all you had to go on was the type name Bankable::CCButtonLatched, for example, you could start by using the search bar in the top right to search for "CCButtonLatched", then click the result, and go to the documentation for the constructor. From there on, you can click through to the arguments, as explained here:
Banks with Mux · Issue #143 · tttapa/Control-Surface · GitHub
The nested brace initialization is often a bit confusing to beginning C++ programmers, but once you know how it works, it's really straightforward, and it's used everywhere, so you really need to know it before you can properly use the library.

To get an overview of all available features of the library, the modules is the best place to start.

Hello, I've just finish the script with a bank selector with a led indicator. Now I'm asking myself if there is a way to change cc value by steps; for example to lead a 3 way selector with one pushbutton so that every time i push the button value will be 0->63->127.
Luca.

This is not supported out of the box, but you can easily write such a MIDI element yourself. See the Custom-MIDI-Output-Element.ino example.

Try to get it working without banks first, then you can add the bankable capabilities. You can use Bankable::MIDIButtonLatched as an example.