Control Surface master
MIDI Control Surface library for Arduino


This example demonstrates how to use the AppleMIDI library to use Control Surface over the network.

This example requires the AppleMIDI and MIDI libraries.



The on-board LED will be used, as well as the push button on GPIO0. These should be present on most ESP32 boards, if this is not the case for your board, connect an LED (+ series resistor) and a push button to the relevant pins (in the "MIDI Elements" section below). For more details, see the NoteButton.ino and 1.Note-LED.ino examples.

WiFi Credentials

Open the tab WiFi-Credentials.example.h, enter your WiFi credentials, and rename the file to WiFi-Credentials.h.


Upload the code to the ESP32, and open the Serial monitor. You should see output like this

Connecting to Your WiFi Network ...
IP address:
mDNS responder started (ESP32.local)

Next, connect to the device using your DAW or other MIDI software. If the software supports mDNS (Apple Bonjour), you can use ESP32.local, otherwise, you'll have to use the IP address.

When the connection is successful, you'll see the following message in the Serial monitor:

Connected to session Your Session Name

When the button is pushed, a MIDI note on message for note C4 (middle C) is sent.
When the ESP32 receives a MIDI note message for that note, it turn on/off the LED accordingly.

RTP MIDI Bridge (Linux)

If you're on Linux, you can use the rtpmidi-bridge application in the example folder. You'll need to install Node.js and NPM.

First, install the necessary dependencies and build tools:

sudo apt install build-essential libasound2-dev libavahi-compat-libdnssd-dev

Then install the dependencies using NPM:

npm install

You might get a compilation error for the avahi_pub module. This is not an issue, it's an optional dependency of the rtpmidi module.

Finally, run the application:

node rtpmidi-bridge.js

The application will initiate a RTP MIDI connection with the ESP32, create virtual MIDI ports, and bridge the MIDI traffic between the RTP MIDI connection and the virtual MIDI ports.
You can then connect your DAW or other MIDI application to the virtual MIDI ports.

When the ESP32 is connected, you should see the following in the serial monitor and the rtpmidi-bridge output respectively:

Connected to session Node RTPMidi
2020-05-06T15:50:42.956Z info: Invitation accepted by ESP32
2020-05-06T15:50:42.962Z info: Data channel to ESP32 established


Connect the virtual MIDI ports or the AppleMIDI connection to a device or application that can send and receive MIDI notes.

Written by PieterP, 2020-05-06

#include <AppleMIDI.h>
#include <ESPmDNS.h>
#include <WiFi.h>
#include <WiFiUdp.h>
#include "WiFi-Credentials.h" // See instructions above
// ----------------------------- MIDI Interface ----------------------------- //
// First create the AppleMIDI instance
// │ │ │ └──── Local port number
// │ │ └──────────── Name
// │ └─────────────────── MIDI instance name
// └─────────────────────────── Network socket class
// Then wrap it in a Control Surface-compatible MIDI interface
FortySevenEffectsMIDI_Interface<decltype(MIDI) &> AppleMIDI_interface = MIDI;
// ------------------------------ MIDI Elements ----------------------------- //
// Add some MIDI elements for testing
NoteButton button {
0, // GPIO0 has a push button connected on most boards
NoteLED led {
LED_BUILTIN, // If your board has one, otherwise, specify a pin number here
// --------------------------- AppleMIDI callbacks -------------------------- //
void onAppleMidiConnected(const ssrc_t &ssrc, const char *name) {
Serial << F("Connected to session ") << name << endl;
void onAppleMidiDisconnected(const ssrc_t &ssrc) {
Serial << F("Disconnected") << endl;
void onAppleMidiException(const ssrc_t &ssrc, const Exception &e,
int32_t value);
// ---------------------------------- Setup --------------------------------- //
void setup() {
// Connect to the WiFi network
Serial << endl << F("Connecting to ") << ssid << ' ';
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED)
Serial.print("."), delay(250);
Serial << endl
<< F("Connected!") << endl
<< F("IP address: ") << WiFi.localIP() << endl;
// Set up mDNS responder:
if (!MDNS.begin(AppleMIDI.getName()))
FATAL_ERROR(F("Error setting up MDNS responder!"), 0x0032);
Serial << F("mDNS responder started (") << AppleMIDI.getName() << ".local)"
<< endl;
MDNS.addService("apple-midi", "udp", AppleMIDI.getPort());
// Set up some AppleMIDI callback handles
// Initialize Control Surface (also calls MIDI.begin())
// ---------------------------------- Loop ---------------------------------- //
void loop() {
// Update all MIDI elements and handle incoming MIDI
// ----------------------------- Error handling ----------------------------- //
void onAppleMidiException(const ssrc_t &ssrc, const Exception &e,
int32_t value) {
switch (e) {
case Exception::BufferFullException:
Serial << F("*** BufferFullException") << endl;
case Exception::ParseException:
Serial << F("*** ParseException") << endl;
case Exception::TooManyParticipantsException:
Serial << F("*** TooManyParticipantsException") << endl;
case Exception::UnexpectedInviteException:
Serial << F("*** UnexpectedInviteException") << endl;
case Exception::ParticipantNotFoundException:
Serial << F("*** ParticipantNotFoundException: ") << value << endl;
case Exception::ComputerNotInDirectory:
Serial << F("*** ComputerNotInDirectory: ") << value << endl;
case Exception::NotAcceptingAnyone:
Serial << F("*** NotAcceptingAnyone: ") << value << endl;
case Exception::ListenerTimeOutException:
Serial << F("*** ListenerTimeOutException") << endl;
case Exception::MaxAttemptsException:
Serial << F("*** MaxAttemptsException") << endl;
case Exception::NoResponseFromConnectionRequestException:
<< F("*** No response to the connection request. Check the address and "
"port, and any firewall or router settings. (time)")
<< endl;
case Exception::SendPacketsDropped:
Serial << F("*** SendPacketsDropped: ") << value << endl;
case Exception::ReceivedPacketsDropped:
Serial << F("*** ReceivedPacketsDropped: ") << value << endl;
case Exception::UnexpectedParseException:
Serial << F("*** UnexpectedParseException") << endl;
// case Exception::UdpBeginPacketFailed:
// Serial << F("*** UdpBeginPacketFailed: ") << value << endl;
// break;
Serial << F("*** Unknown exception ") << +e << ": " << value << endl;
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.
void begin()
Initialize the Control_Surface.
void loop()
Update all MIDI elements, send MIDI events and read MIDI input.
Class that wraps the FortySevenEffects MIDI library.
A class of MIDIOutputElements that read the input of a momentary push button or switch,...
Definition: NoteButton.hpp:19
Generic base class for classes that listen for MIDI Note, Control Change and Key Pressure events on a...
Definition: NoteCCKPLED.hpp:20
#define FATAL_ERROR(msg, errc)
Print the error message and error code, and stop the execution.
Definition: Error.hpp:60
Print & endl(Print &printer)
Definition: PrintStream.cpp:27
constexpr Note C
C (Do)
Definition: Notes.hpp:40