Control Surface new-input
MIDI Control Surface library for Arduino


This is an example that demonstrates how to extend the library using your own Bankable MIDI Output Elements. The example declares a MIDI Output Element that sends MIDI Note events when a push button is pressed or released. It can be banked to change the MIDI address. It's a simplified version of the Bankable::NoteButton class.

To understand this example, you need to understand the Bank.ino and the Custom-MIDI-Output-Element.ino examples first.

AVR, AVR USB, Due, Nano 33 IoT, Nano 33 BLE, Teensy 3.x, ESP32


The internal pull-up resistors for the buttons will be enabled automatically.



Select the Arduino as a custom MIDI controller in your DAW, and use the MIDI learn option to assign the button to a function.

Written by PieterP, 2020-09-29

* @brief A class for momentary buttons and switches that send MIDI events.
* The button is debounced, and the internal pull-up resistor is enabled.
* @see Bankable::NoteButton
* @see Button
class MyNoteButton : public MIDIOutputElement {
* @brief Create a new MyNoteButton object on the given pin, with the
* given address and velocity.
* @param bankConfig
* Specifies the bank that determines the address of this button, as
* well as the bank type. The bank type determines whether to change
* the address (note number), the MIDI channel, or the MIDI USB
* cable number.
* @param pin
* The digital input pin to read from.
* The internal pull-up resistor will be enabled.
* @param address
* The MIDI address to send to.
* @param velocity
* The MIDI note velocity [0, 127].
MyNoteButton(OutputBankConfig<> bankConfig, pin_t pin,
MIDIAddress baseAddress, uint8_t velocity)
: address(bankConfig, baseAddress), button(pin), velocity(velocity) {}
// Initialize: enable the pull-up resistor for the button.
// This method is called once by `Control_Surface.begin()`.
void begin() final override { button.begin(); }
// Update: read the button and send MIDI messages when appropriate.
// This method is called continuously by `Control_Surface.loop()`.
void update() final override {
AH::Button::State state = button.update(); // Read the button
if (state == AH::Button::Falling) { // if pressed
// Don't allow changing the bank setting as long as the button is pressed:
Control_Surface.sendNoteOn(address.getActiveAddress(), velocity);
} else if (state == AH::Button::Rising) { // if released
Control_Surface.sendNoteOff(address.getActiveAddress(), velocity);
// Button is released, so the bank setting can be changed again.
AH::Button button;
uint8_t velocity;
* Compare this class with the class in the Custom-MIDI-Output-Element.ino
* example.
* You'll notice that the constructor argument `bankConfig` was added to specify
* the bank to use.
* The `address` member variable is no longer of type `MIDIAddress`, it's now of
* type `Bankable::SingleAddress`. This is a bank-dependent address. The actual
* `MIDIAddress` that's selected by the bank can be retrieved by calling
* `address.getActiveAddress()`.
* Knowing this, you should be able to understand most of the `update` method,
* it's almost the same as the Custom-MIDI-Output-Element.ino example, except
* for the calls to `lock()` and `unlock()`.
* To understand why these are necessary, consider what happens in the following
* example without locking:
* 1. The first bank is selected. This means that `address.getActiveAddress()`
* will return note C2.
* 2. The user presses the button, a Note On event is sent for note C2.
* 3. The button is still pressed, and the user selects the second bank:
* `address.getActiveAddress()` will now return note C3.
* 4. The user releases the button, a Note Off event is sent for note C3.
* This is bad! The note C2 will keep on playing indefinitely, because we
* never sent a Note Off event for that note.
* To fix this, we have to make sure that the Note Off events we send are always
* sent to the same address as the previous Note On event. The address cannot
* change in between Note On and Note Off events.
* That's exactly what the `address.lock()` method does: it freezes the address.
* After locking, `address.getActiveAddress()` will always return the same
* address, until `address.unlock()` is called. When the address is unlocked,
* `address.getActiveAddress()` will just return the address determined by the
* selected bank.
// :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: //
// Instantiate a MIDI over USB interface.
// Instantiate four Banks, with twelve tracks per bank (12 semitones = 1 octave).
Bank<4> bank(12);
// Instantiate a Bank selector to control which one of the four Banks is active.
bank, // Bank to manage
6, // push button pin
// Instantiate a MyNoteButton object
MyNoteButton button {
{bank, BankType::CHANGE_ADDRESS}, // bank changes the note number (address)
5, // Push button on pin 5
{MIDI_Notes::C(2), CHANNEL_1}, // Base address: Note C2 on MIDI channel 1
0x7F, // Maximum velocity
void setup() {
Control_Surface.begin(); // Initialize Control Surface (calls button.begin())
void loop() {
Control_Surface.loop(); // Update the Control Surface (calls button.update())
Change the offset of the address (i.e.
Definition: BankConfig.hpp:17
constexpr Channel CHANNEL_1
Definition: Channel.hpp:118
The main header file that includes all Control-Surface header files.
Control_Surface_ & Control_Surface
A predefined instance of the Control Surface to use in the Arduino sketches.
A class for reading and debouncing buttons and switches.
Definition: Button.hpp:18
An enumeration of the different states a button can be in.
Definition: Button.hpp:53
@ Rising
Input went from low to high (0,1)
Definition: Button.hpp:57
@ Falling
Input went from high to low (1,0)
Definition: Button.hpp:56
A super class for object that have to be updated regularly.
Definition: Updatable.hpp:173
virtual void update()=0
Update this updatable.
virtual void begin()=0
Initialize this updatable.
A class that groups Bankable MIDI Output Elements and Bankable MIDI Input Elements,...
Definition: Bank.hpp:91
void begin()
Initialize the Control_Surface.
void loop()
Update all MIDI elements, send MIDI events and read MIDI input.
Selector with one button that increments the selection.
A type-safe utility class for saving a MIDI address consisting of a 7-bit address,...
void sendNoteOn(MIDIAddress address, uint8_t velocity)
Send a MIDI Note On event.
Definition: MIDI_Sender.ipp:20
void sendNoteOff(MIDIAddress address, uint8_t velocity)
Send a MIDI Note Off event.
Definition: MIDI_Sender.ipp:31
A class for MIDI interfaces sending MIDI messages over a USB MIDI connection.
uint16_t pin_t
The type for Arduino pins (and ExtendedIOElement pins).
constexpr Note C
C (Do)
Definition: Notes.hpp:40