This is an old version of the documentation. View the latest version here.
Control Surface  1.0.0
MIDI Control Surface library for Arduino
Getting Started

Installation

Instructions on how to install the library and its dependencies can be found on the page Installation.

First Output: Creating a Basic MIDI Controller Sketch

1. Include the library

Include the library so that you have access to all the classes and functions.

2. Instantiate a MIDI Interface

If you want to send out or receive MIDI messages, you have to define at least one MIDI interface. If you don't do that, you'll get an error when calling Control_Surface.begin().

There are many different MIDI interfaces to choose from:

  • USBMIDI_Interface: On boards that support MIDI over USB natively, this will do exactly what you'd expect. You just have to plug it into your computer, and it shows up as a MIDI device. On Arduinos without native USB capabilities (e.g. UNO or MEGA), you have to use custom firmware for the ATmega16U2.
  • HardwareSerialMIDI_Interface: This interface will send and receive MIDI messages over a hardware UART. You can use it for MIDI over a 5-pin DIN connector, for example.
  • HairlessMIDI_Interface: If you have an Arduino without native USB support, an easy way to get the MIDI messages from your Arduino to your computer is the Hairless MIDI<->Serial Bridge. In that case, you can use this MIDI interface. The default baud rate is 115200 symbols per second.
  • BluetoothMIDI_Interface: If you have an ESP32, you can send and receive MIDI messages over Bluetooth Low Energy. This interface is still very much experimental, but it's pretty cool. If you know more about the MIDI BLE protocol, feel free to suggest some improvements.
  • USBDebugMIDI_Interface: Debugging MIDI Controllers can be very cumbersome. There are MIDI monitors available, but you have to reconnect every time you upload a new sketch, and sending MIDI to the Arduino is not always easy.
    This interface is designed to help you with that. It prints outgoing MIDI messages to the Serial port in a readable format, and it allows you to enter hexadecimal MIDI messages (as text) in the Serial monitor (e.g. 90 3C 7F to turn on middle C).

A complete overview of the available MIDI interfaces can be found here.

For now, we'll use the USBMIDI_Interface because it's probably the one you'll use in your final program.

You can give the interface any name you want, here, I'll be very original and choose midi. It doesn't matter, and you don't need to use it afterwards, just defining the interface is enough, the Control Surface library will automatically detect and use it.

Note: some interfaces require additional parameters, for example, the USBDebugMIDI_Interface needs to know the baud rate.
In that case, you can instantiate it as follows:
USBDebugMIDI_Interface midi = {115200};

3. Add Extended Input/Output elements (optional)

If your MIDI Controller requires many in- or outputs, you'll run out of IO pins really quickly. A solution is to use multiplexers or shift registers.
The Control Surface Library supports both of these options, and makes it easy to support other types of IO expanders in the future.

An overview of Extended Input/Output elements can be found here.

In this example, we'll use an 8-channel CD74HC4051 analog multiplexer. This allows us to read eight analog inputs using just one analog pin on the Arduino, at the cost of only three digital output pins.

Each of the eight analog inputs of the multiplexer can be connected to the wiper of one potentiometer.

We'll connect the three address lines of the multiplexer (S0, S1 and S2) to digital pins 3, 4 and 5. The output of the multiplexer goes to analog pin A0. Connect the enable pin (Ē) to ground.

CD74HC4051 mux = { A0, {3, 4, 5} };

4. Add MIDI Control Elements

Now, we can specify the objects that read the input of the potentiometers and send out MIDI events accordingly.

Again, I'll refer to the overview of MIDI Output Elements here.

Let's define a single potentiometer on pin A1 that sends out MIDI Control Change events.
In the documentation, you'll find that the first argument for the CCPotentiometer constructor is the analog pin number, and the second is the MIDI address.
The MIDI address is a structure that consists of an address number, the MIDI channel, and the cable number.
In this case, the address number is the controller number, which is a number from 0 to 119. The MIDI channel is a channel from CHANNEL_1 until CHANNEL_16. We'll ignore the cable number for now, if you don't specifically set it, it'll just use the default cable.

For the MIDI controller numbers, you can use the predefined constants, or you can just use a number.

In our case, we don't want a single potentiometer, we want eight. It's much easier to define them in an array.
Also note how we state that it should use the pins of the multiplexer we defined in the previous step.

Note: The first pin is pin(0), not pin(1).

Note: Some other MIDI Control Elements might not need an address number, or they might not need a channel. In that case, you just leave out the optional parts that you don't need.
For example, a PBPotentiometer doesn't need an address number, just a channel, so you can instantiate it as follows:

PBPotentiometer potentiometer = { A1, CHANNEL_9 };

5. Initialize the Control Surface

There's a lot to be done in the setup: The MIDI interface has to be initialized, all pins must be set to the correct mode, etc.
Luckily, the Control Surface Library handles almost all of it for you.

void setup() {
}

Note: If you didn't define a MIDI interface, Control_Surface.begin() will raise an error and the on-board LED will start blinking.
Other errors will also be indicated this way.
If you don't know why this happens, you should enable debug information in the Control_Surface/src/Settings/Settings.h file, and inspect the output in the Serial Monitor.
Someday, I will add a "Troubleshooting" page. For now, if you have any problems, just open an issue on GitHub to remind me.

6. Continuously Update the Control Surface

Now that everything is set up, you can just update the Control Surface forever. It will refresh all inputs and send the appropriate MIDI messages if any of the inputs change.

void loop() {
}

Note: Using blocking code like delays will interfere with the Control Surface, so try to use Blink Without Delay techniques instead.

The finished sketch

That's it!
Now you can just upload the sketch to your Arduino, open up your favorite audio software, map the potentiometers, and start playing!

// Include the library
// Instantiate a MIDI Interface to use
// Instantiate an analog multiplexer
CD74HC4051 mux = {
A0, // Analog input pin
{3, 4, 5} // Address pins S0, S1, S2
};
// Create an array of potentiometers that send out
// MIDI Control Change messages when you turn the
// potentiometers connected to the eight input pins of
// the multiplexer
CCPotentiometer volumePotentiometers[] = {
};
// Initialize the Control Surface
void setup() {
}
// Update the Control Surface
void loop() {
}

First Input: Getting Feedback from the MIDI Software

Unlike the MIDI Controller library, the Control Surface library does support MIDI input.
This example shows how to use the NoteValueLED class to turn on and off LEDs when MIDI Note On/Off messages are received.
The example shows the use of a shift register to drive the LEDs, but you can of course use any pins you want.

1. Include the library

Include the library so that you have access to all the classes and functions.

2. Instantiate a MIDI Interface

See First Output: Instantiate a MIDI Interface.

3. Add Extended Input/Output elements (optional)

See First Output: Add Extended Input/Output elements.

In this example, we'll use a 74HC595 8-bit serial in/parallel out shift register. This allows us to drive eight LEDs using just the SPI bus and a single digital pin. You can daisy chain as many shift registers as you want, without requiring any more pins.

Each of the eight outputs of the shift register can be connected to the anode of an LED. Connect the cathodes to ground through a current-limiting resistor.

Connect the clock input (SH_CP or SRCLK) of the shift register to the Arduino's SCK pin, the serial data input (DS or SER) of the shift register to the Arduino's MOSI pin, and the latch pin (ST_CP or RCLK) of the shift register to digital pin 10 of the Arduino. Connect the Output Enable pin (OE) of the shift register to ground, and the Master Reset (MR) pin of the shift register to Vcc to enable it.

10, // Latch pin (ST_CP)
MSBFIRST, // Bit order
};

The 8 between angle brackets (<>) is the number of bits of the shift register. If you daisy chain two 8-bit shift registers together, you would use 16 instead of 8, for example.
The bit order determines which pin of the shift register is the first pin in the program. MSBFIRST means "most significant bit first". You can also use LSBFIRST (least significant bit first).

4. Add MIDI Control Elements

Now, we can specify the objects that listen for MIDI input, and update the status of the LEDs accordingly.

I'll refer to the overview of MIDI Input Elements here.

Let's define a single LED on pin 13 that listens for MIDI Note events for a middle C on channel 1.
In the documentation, you'll find that the first argument for the NoteValueLED constructor is the number of the pin with the LED connected, and the second is the MIDI address.
The MIDI address is a structure that consists of an address number, the MIDI channel, and the cable number.
In this case, the address number is the note number, which is a number from 0 to 127. The MIDI channel is a channel from CHANNEL_1 until CHANNEL_16. We'll ignore the cable number for now, if you don't specifically set it, it'll just use the default cable.

For the MIDI note numbers, you can use the note constants and the note function in the MIDI_Notes namespace, or you can just use a number.

using namespace MIDI_Notes;
NoteValueLED noteLed = { 13, {note(C, 4), CHANNEL_1} }; // C4 = middle C

In our case, we don't want a single LED, we want eight. It's much easier to define them in an array.
Also note how we state that it should use the pins of the shift register we defined in the previous step. We omit the channel here, so it'll just use the default channel, CHANNEL_1.

Note: The first pin is pin(0), not pin(1).

NoteValueLED leds[] = {
{sreg.pin(0), note(C, 4)},
{sreg.pin(1), note(D, 4)},
{sreg.pin(2), note(E, 4)},
{sreg.pin(3), note(F, 4)},
{sreg.pin(4), note(G, 4)},
{sreg.pin(5), note(A, 4)},
{sreg.pin(6), note(B, 4)},
{sreg.pin(7), note(C, 5)},
};

5. Initialize the Control Surface

See First Output: Initialize the Control Surface.

void setup() {
}

6. Continuously Update the Control Surface

See First Output: Continuously Update the Control Surface.

void loop() {
}

The finished sketch

That's it!
Now you can just upload the sketch to your Arduino, open up your favorite audio software, redirect the MIDI output to the Arduino, and start playing!

// Include the library
// Instantiate a MIDI Interface to use
// Instantiate a shift register as output for the LEDs
10, // Latch pin (ST_CP)
MSBFIRST, // Byte order
};
using namespace MIDI_Notes;
// Create an array of LEDs that listen to MIDI Note messages, turning on and off
// the LEDs connected to the eight input pins of the shift register
NoteValueLED leds[] = {
{sreg.pin(0), note(C, 4)}, // LED pin, address (note number, channel, cable)
{sreg.pin(1), note(D, 4)}, //
{sreg.pin(2), note(E, 4)}, //
{sreg.pin(3), note(F, 4)}, //
{sreg.pin(4), note(G, 4)}, //
{sreg.pin(5), note(A, 4)}, //
{sreg.pin(6), note(B, 4)}, //
{sreg.pin(7), note(C, 5)}, //
};
// Initialize the Control Surface
void setup() {
}
// Update the Control Surface
void loop() {
}
MIDI_Notes::A
constexpr int8_t A
Definition: Notes.hpp:27
USBMIDI_Interface
A class for MIDI interfaces sending MIDI messages over a USB MIDI connection.
Definition: USBMIDI_Interface.hpp:43
PBPotentiometer
A class of MIDIOutputElements that read the analog input from a potentiometer or fader,...
Definition: PBPotentiometer.hpp:21
MIDI_Notes::G
constexpr int8_t G
Definition: Notes.hpp:25
MIDI_Notes::E
constexpr int8_t E
Definition: Notes.hpp:22
SPIShiftRegisterOut
A class for serial-in/parallel-out shift registers, like the 74HC595 that are connected to the SPI bu...
Definition: SPIShiftRegisterOut.hpp:23
CHANNEL_3
constexpr Channel CHANNEL_3
Definition: Channel.hpp:113
CHANNEL_6
constexpr Channel CHANNEL_6
Definition: Channel.hpp:116
CHANNEL_8
constexpr Channel CHANNEL_8
Definition: Channel.hpp:118
Control_Surface.h
The main header file that includes all Control-Surface header files.
CHANNEL_7
constexpr Channel CHANNEL_7
Definition: Channel.hpp:117
AnalogMultiplex
A class for reading multiplexed analog inputs.
Definition: AnalogMultiplex.hpp:25
Control_Surface
Control_Surface_ & Control_Surface
A predefined instance of the Control Surface to use in the Arduino sketches.
Definition: Control_Surface_Class.cpp:171
MIDI_Notes::note
constexpr int8_t note(int8_t note, int8_t numOctave)
Get the MIDI note in the given octave.
Definition: Notes.hpp:35
CHANNEL_5
constexpr Channel CHANNEL_5
Definition: Channel.hpp:115
ExtendedIOElement::pin
pin_t pin(pin_t pin) const
Get the extended IO pin number of a given physical pin of this extended IO element.
Definition: ExtendedIOElement.cpp:26
CHANNEL_4
constexpr Channel CHANNEL_4
Definition: Channel.hpp:114
CHANNEL_9
constexpr Channel CHANNEL_9
Definition: Channel.hpp:119
NoteValueLED
Definition: NoteCCRangeLEDs.hpp:44
CCPotentiometer
A class of MIDIOutputElements that read the analog input from a potentiometer or fader,...
Definition: CCPotentiometer.hpp:19
MIDI_Notes::D
constexpr int8_t D
Definition: Notes.hpp:20
Control_Surface_::begin
void begin()
Initialize the Control_Surface.
Definition: Control_Surface_Class.cpp:22
CHANNEL_2
constexpr Channel CHANNEL_2
Definition: Channel.hpp:112
MIDI_Notes::B
constexpr int8_t B
Definition: Notes.hpp:29
MIDI_Notes::C
constexpr int8_t C
Definition: Notes.hpp:18
MIDI_Notes
MIDI note names.
Definition: Notes.hpp:16
MIDI_CC::Channel_Volume
constexpr uint8_t Channel_Volume
Definition: Control_Change.hpp:23
CHANNEL_1
constexpr Channel CHANNEL_1
Definition: Channel.hpp:111
Control_Surface_::loop
void loop()
Update all MIDI elements, send MIDI events and read MIDI input.
Definition: Control_Surface_Class.cpp:45
MIDI_Notes::F
constexpr int8_t F
Definition: Notes.hpp:23