Control Surface
Arduino library for creating MIDI controllers and other MIDI devices.
Control Surface is an Arduino library for building MIDI controllers and control surfaces. The project is written primarily in C++, distributed under the GNU General Public License v3.0 license, first published in 2017. It has gained significant community traction with 1,627 stars and 164 forks on GitHub. Key topics include: arduino, arduino-library, ble-midi, control-surface, esp32.
Control Surface is an Arduino library for building MIDI controllers and control
surfaces.
At its core, the library features a flexible MIDI abstraction layer
with support for serial 5-pin DIN MIDI, MIDI over USB, MIDI over BLE, etc.
These MIDI interfaces are compatible with a wide range of Arduino boards
(a full table can be found here)
and are useful in any Arduino MIDI project.
In addition to MIDI input/output, Control Surface also provides easy-to-use utilities
intended for building MIDI controllers, supporting controls that send MIDI messages
─ like potentiometers, push buttons, rotary encoders, etc. ─
and controls that react to incoming MIDI messages ─ LEDs, displays, and so on.
More advanced controls that combine MIDI input and output ─
such as motorized faders
─ are supported as well.
In projects with large numbers of inputs and outputs, Control Surface allows you
to seamlessly add multiplexers, shift registers and other port expanders, and
treat them as if they were ordinary GPIO pins.
Table of contents
<span class="mono">¶</span> Example usage
<span class="mono">¶</span> Getting started
<span class="mono">¶</span> Documentation
<span class="mono">¶</span> Feature overview
<span class="mono">¶</span> Supported boards
<span class="mono">¶</span> Change log and updating
Example usage
An extensive list of examples can be found in the documentation.
Below are a handful of simple examples that give an idea of how the Control
Surface library can be used.
Example 1: A complete sketch for a MIDI controller with a potentiometer that sends out MIDI
Control Change message can be written in just five lines of code:
cpp#include <Control_Surface.h> USBMIDI_Interface midi; CCPotentiometer pot { A0, MIDI_CC::General_Purpose_Controller_1 }; void setup() { Control_Surface.begin(); } void loop() { Control_Surface.loop(); }
Example 2: Larger MIDI controllers can be implemented very easily as well, with clean and
easy to modify code.
The following sketch is for 8 potentiometers (connected using an analog
multiplexer) that send out MIDI Control Change messages over USB. A detailed
walkthrough of this example can be found on the Getting Started page.
cpp#include <Control_Surface.h> // Include the library USBMIDI_Interface midi; // 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 CCPotentiometer objects that send out MIDI Control Change // messages when you turn the potentiometers connected to the 8 inputs of the mux. CCPotentiometer volumePotentiometers[] { { mux.pin(0), { MIDI_CC::Channel_Volume, Channel_1 } }, { mux.pin(1), { MIDI_CC::Channel_Volume, Channel_2 } }, { mux.pin(2), { MIDI_CC::Channel_Volume, Channel_3 } }, { mux.pin(3), { MIDI_CC::Channel_Volume, Channel_4 } }, { mux.pin(4), { MIDI_CC::Channel_Volume, Channel_5 } }, { mux.pin(5), { MIDI_CC::Channel_Volume, Channel_6 } }, { mux.pin(6), { MIDI_CC::Channel_Volume, Channel_7 } }, { mux.pin(7), { MIDI_CC::Channel_Volume, Channel_8 } }, }; void setup() { Control_Surface.begin(); // Initialize the Control Surface } void loop() { Control_Surface.loop(); // Update the Control Surface }
Example 3: Control Surface also supports many types of MIDI inputs.
For example, an LED that turns on when a MIDI Note On message for middle C is
received:
cpp#include <Control_Surface.h> USBMIDI_Interface midi; NoteLED led { LED_BUILTIN, MIDI_Notes::C[4] }; void setup() { Control_Surface.begin(); } void loop() { Control_Surface.loop(); }
Example 4: Control Surface's MIDI interfaces can also be used directly, for example, to
implement a MIDI-over-USB to MIDI-over-BLE adapter:
cpp#include <Control_Surface.h> // Instantiate MIDI over BLE and MIDI over USB interfaces BluetoothMIDI_Interface midi_ble; USBMIDI_Interface midi_usb; // Pipes allow routing between MIDI interfaces BidirectionalMIDI_Pipe pipes; void setup() { // Route the MIDI input from the USB interface to the BLE interface, // and the MIDI input from the BLE interface to the USB interface midi_usb | pipes | midi_ble; // Initialize the MIDI interfaces MIDI_Interface::beginAll(); } void loop() { // Continuously poll all interfaces and route the traffic between them MIDI_Interface::updateAll(); }
Getting started
See the Getting Started
page to get started using the library.
It'll also point you to the Installation Instructions.
The MIDI tutorial
might be useful if you want to use Control Surface as a regular MIDI library,
for sending and receiving MIDI messages.
Documentation
Detailed documentation for this library can be found here:
Documentation
Arduino examples can be found here:
Examples
The User Manual
is the best entry point to the documentation. To get a complete overview of all
features of the Control Surface library, have a look at the following section
and at the
Topics page.
You can find an answer to some frequently asked questions on the
FAQ page.
Feature overview
This library turns your Arduino-compatible board into a MIDI control surface.
Just connect some push buttons, potentiometers, LEDs ... and declare them in
your code.
The following sections give a brief overview of the features of the library.
MIDI Interfaces
- MIDI over USB
- Serial MIDI (e.g. 5-pin DIN MIDI)
- Debug MIDI (prints out the messages in a readable format, and allows you
to input text based messages, like a MIDI monitor) - MIDI over Bluetooth LE
- AppleMIDI over WiFi or Ethernet
<sub>→ MIDI Interfaces documentation</sub>
MIDI Control Output
- Push buttons and toggle switches
- Potentiometers, faders and other analog sensors
- Rotary encoders
- Scanning keyboard matrices
Digital inputs are debounced, and analog inputs are filtered using
digital filters and hysteresis. This results in high accuracy without noise,
without introducing latency.
These MIDI control outputs can be used to send MIDI notes, Control Change,
Pitch Bend, Program/Patch change, etc.
<sub>→ MIDI Output Elements documentation</sub>
MIDI Control Input
- LEDs (e.g. to indicate whether a track is muted/armed/soloed)
- LED rings (e.g. to indicate the position of a pan knob)
- LED strips (using the FastLED
library) - VU meters
- OLED displays
- 7-segment displays
A large portion of the Mackie Control Universal (MCU) protocol is
implemented.
<sub>→ MIDI Input Elements documentation</sub>
Motorized faders
- Motorized faders are supported through the tttapa/Control-Surface-Motor-Fader repository.
<sub>→ Control Surface Motor Fader documentation</sub>
Bank support
All controls can be arranged in banks: for example, if you have only 4
physical faders, you can make them bankable, so they can be used to control
the volume of many more different tracks. Changing banks can be done using push
buttons, rotary encoders, etc.
Apart from banks and bank selectors, you can also add transposers to change
the key of your notes, for example.
Extended input/output
In order to save some IO pins, the library natively supports multiplexers
(e.g. 74HC4051 or 74HC4067) to read many switches or potentiometers,
Shift Registers (e.g. 74HC595) to drive many LEDs, MAX7219 LED drivers,
etc.
<sub>→ Extended IO documentation</sub>
Audio
If you are using a Teensy 3.x or 4.x, you can use it as a
USB audio interface. Just add an I²S DAC (e.g. PCM5102) and 5 lines of code,
and you can start playing audio through your Teensy, by combining Control
Surface with the Teensy Audio library.
You can also add volume controls and VU meters for these audio connections.
<sub>→ Teensy Audio documentation</sub>
Modular and extensible
Thanks to the structure of the library, you can easily add your own MIDI or
display elements, using some minimal, high level code. All low level stuff is
completely reusable (e.g. all MIDI operations, debouncing switches,
filtering analog inputs, and so on).
Installation
Download the repository as a ZIP archive by going to the home page of the
repository and clicking
the green <kbd>Code</kbd> button in the top right, then choosing “Download ZIP”.
Alternatively, click the following direct download link:
https://github.com/tttapa/Control-Surface/archive/refs/heads/main.zip
Open the Arduino IDE, and go to the Sketch > Include Library > Add .ZIP
Library menu.
Then navigate to your downloads directory where you just downloaded the
library.
Select it, and click Ok.
Supported boards
For each commit, the continuous integration tests compile the examples for the
following boards:
- Arduino UNO
- Arduino Mega
- Arduino Leonardo
- Teensy 3.2
- Teensy 4.1
- Arduino Due
- Arduino Nano Every
- Arduino Nano 33 IoT
- Arduino Nano 33 BLE
- Arduino Nano Every
- Arduino UNO R4 Minima
- Arduino UNO R4 WiFi
- ESP8266
- ESP32
- ESP32-S3
- Raspberry Pi Pico
This covers a very large part of the Arduino platform, and similar boards will
also work. For example, the Arduino Nano, Mega, Micro, Pro Micro, Teensy 2.0,
Teensy LC, Teensy 3.x, Teensy 4.x are all known to work.
If you have a board that's not supported, please
open an issue
and let me know!
Note that MIDI over USB and MIDI over Bluetooth are not supported on all boards.
See the MIDI over USB
documentation page for a table with supported features per board.
Change log and updating
2.1
- (1e9f5f6)
Added support for version 3.3.0 of the arduino-esp32
core: The Bluedroid-based Bluetooth stacks for all boards except the original
ESP32 have been replaced by NimBLE-based stacks1. For these configurations,
Control Surface now automatically switches to theESP32NimBLEBackendfor
MIDI over BLE.
The h2zero/NimBLE-Arduino library does not need to be installed in this case
(it is necessary for older versions of arduino-esp32 and for the original
ESP32 only).
2.0
- (7bd5268)
Thepin_ttype is now a distinct type rather than an alias to an integer.
This improves type safety, becausepin_tis no longer implicitly
convertible to an integer (although integers are still convertible to
pin_t), and it is no longer possible to accidentally use incorrect
constructs such asmux.digitalRead(mux.pin(0)). A new type,pin_int_t,
was added to represent sizes and offsets of pins (e.g. “the sixth pin of this
multiplexer)”, and the argument types of the member functions of the
ExtendedIOElement class have been modified accordingly. If you were using a
class that inherited from ExtendedIOElement, you should update the signatures
of any overridden methods. - (b0f4d63)
ReplaceMIDI_Notes::X(n)byMIDI_Notes::X[n]to avoid issues with the ArduinoF(...)macro. - (7b0eee1)
Speed up compilation by removing many source files that were only used for testing. - (07b8729)
Improved Mbed OS USB MIDI implementation. - (d7a4e69)
Added support forBluetoothMIDI_Interfaceon the Raspberry Pi Pico W. - (196b5fc)
AddedArduinoBLEbackend forBluetoothMIDI_Interface. - (d4d7435)
Completely refactoredBluetoothMIDI_Interface, with support for the NimBLE and ArduinoBLE backends. - (9c4cdd4)
More upper case constants and enumerators have been deprecated. For example,
ControlChangeshould be used instead ofCONTROL_CHANGE. If you continue
using the old versions, you will get a compiler warning. These old versions
will be removed in a future version of Control Surface. - (aaf6eea)
The upper caseCHANNEL_xandCABLE_xconstants have been deprecated in
favor of the title case versionsChannel_xandCable_x. This was done to
avoid conflicts with macros defined by the ArduinoCore-renesas.
For the same reason, theCSnamespace has been renamed tocs. - (47b2d5e)
TheEncoderclass has been replaced byAHEncoder.
The code has been cleaned up and support was added for newer boards like the
Raspberry Pi Pico. - (c35f29c)
The SoftwareSerial MIDI interfaces are now in separate header files that have
to be included explicitly if you want to use them. The headers in question
areSoftwareSerialMIDI_Interface.hpp
andSoftwareSerialDebugMIDI_Interface.hpp.
This prevents redefinition errors of pin change interrupt vectors even if
SoftwareSerial is not used. - (bf8fb66)
The abbreviated MIDI send functions (such assendCC()) have been deprecated
in favor of the full names (e.g.sendControlChange()). See the
MIDI_Sender documentation
for a full overview. - (cf32e7e)
Control_Surface.setMIDIInputCallbacks()now takes four arguments instead of
three, because a System Common callback was added. - (b727931)
The MIDI note name for the note F has been changed fromFtoF_in order
to avoid conflicts with theF()macro and its functional equivalent
introduced here.
It is now recommended to useMIDI_Notes::C(4)instead ofnote(C, 4). - (a81bd19)
Classes that make use of the SPI interface now require you to pass theSPI
object as a constructor argument. This allows you to useSPI1orSPI2
(if available for your hardware). - (c6e35b9)
TheNoteBitmapDisplayclass has been renamed toBitmapDisplay. - (37b6901)
TheNoteValueLEDandCCValueLEDclasses (and similar) have been replaced
byNoteLEDandCCLEDrespectively.
The display elementsBitmapDisplay,VPotDisplay,VUDisplayetc. are
now generic in the type of value that they display. In most cases, you should
be able to update your sketch by adding<>after the type names, e.g.
BitmapDisplay<>,VPotDisplay<>, etc. - (1a21d13)
The line numbers ofLCDDisplayare now one-based:1is the first line and
2is the second line. This is more consistent with the track parameter and
the API of the rest of the library. (Before, the first line was0and the
second line was1.) - (40e3d7a)
Control Surface now comes with an Encoder library out of the box. You no
longer have to install or includeEncoder.hin your sketches.
1.x
- (8a3b1b3)
Before,Control_Surface.MIDI()was used to get the MIDI interface used by
Control Surface. This method was removed, because you can now connect
multiple interfaces to Control Surface, using the
MIDI Pipe routing system.
To send MIDI using Control Surface, you can now use
Control_Surface.sendCC(...)and the other similar methods directly. - (8a3b1b3)
For the same reason as the bullet above, theMultiMIDI_Interfacewas
removed, and has been replaced by the
MIDI Pipe routing system. - (bca6e11)
The color mapper forNoteRangeFastLEDand the like now takes a second
parameter that represents the index of the LED within the LED strip. - (3c01c7d)
The maximum supported ADC resolution is now used by default (e.g. 13 bits
on Teensy 3.x, 12 bits on ESP32).
This increases the accuracy of analog inputs and controls for the Control
Surface library, but could cause problems if your code uses other libraries
that expect the resolution to be 10 bits.
You can change the default resolution to 10 bits in
src/AH/Hardware/ADCConfig.hpp
if you have to. - (31edaa6)
The mapping function is now applied before applying hysteresis.
This means that the input and output values of the function should be
16 -ANALOG_FILTER_SHIFT_FACTORbits wide instead of 7. By default this is
14 bits. You can get the maximum value in a portable way by using the
FilteredAnalog<>::getMaxRawValue()function.
The signature of the mapping function is nowanalog_t f(analog_t raw),
where the return value and raw are both numbers in [0, 16383] by default.
Footnotes
-
More details about this migration can be found in https://github.com/espressif/arduino-esp32/discussions/10991. ↩
Contributors
Showing top 5 contributors by commit count.
