Control Surface  1.1.1
MIDI Control Surface library for Arduino
VU-Meter-Bridge.ino

VU-Meter-Bridge

This is an example on how to use an OLED display to display the VU meters and mute/solo states of the eight first tracks, by using the Arduino as a Mackie Control Universal. This is an example modified to work with the ESP32 only, using MIDI over Bluetooth Low Energy.

Boards:
ESP32

Connections

This example drives two SSD1306 OLED displays over SPI

Add a capacitor between the reset pin of the display and ground, and a resistor from reset to 3.3V. The values are not critical, 0.1µF and 10kΩ work fine.
You do need some way to reset the display, without it, it won't work.
Alternatively, you could use an IO pin from the ESP32 to reset the display, but this just "wastes" a pin.

Behavior

The first display should now display the level meters and mute/solo states of the first 8 tracks.

Mapping

Map "Control Surface" as a Mackie Control Universal unit in your DAW.

Note
There seem to be some differences in the way some applications handle VU meters: some expect the hardware to decay automatically, some don't.
If you notice that the meters behave strangely, try both MCU::VUDecay::Hold and MCU::VUDecay::Default, or try a different decay time.

Demo

Written by PieterP, 2019-08-09
https://github.com/tttapa/Control-Surface

#include <Control_Surface.h> // Include the Control Surface library
// Include the display interface you'd like to use
// Include the BLE MIDI interface
// ----------------------------- MIDI Interface ----------------------------- //
// ========================================================================== //
// Instantiate a MIDI interface to use for the Control Surface.
// ----------------------------- Display setup ------------------------------ //
// ========================================================================== //
constexpr uint8_t SCREEN_WIDTH = 128;
constexpr uint8_t SCREEN_HEIGHT = 64;
constexpr int8_t OLED_DC = 15; // Data/Command pin of the display
constexpr int8_t OLED_reset = -1; // Use the external RC circuit for reset
constexpr int8_t OLED_CS = 2; // Chip Select pin of the display
constexpr uint32_t SPI_Frequency = SPI_MAX_SPEED;
// Instantiate the displays
Adafruit_SSD1306 ssd1306Display = {SCREEN_WIDTH, SCREEN_HEIGHT, &SPI,
OLED_DC, OLED_reset, OLED_CS,
SPI_Frequency};
// --------------------------- Display interface ---------------------------- //
// ========================================================================== //
// Define and instantiate a display interface
class MySSD1306_DisplayInterface : public SSD1306_DisplayInterface {
public:
MySSD1306_DisplayInterface(Adafruit_SSD1306 &display)
void begin() override {
// Initialize the Adafruit_SSD1306 display
if (!disp.begin())
FATAL_ERROR(F("SSD1306 allocation failed."), 0x1306);
// If you override the begin method,remember to call the superclass method
}
void drawBackground() override {
disp.drawFastHLine(0, 52, 128, WHITE);
disp.drawRect(0, 0, 128, 64, WHITE);
}
} display = ssd1306Display;
// -------------------------- MIDI Input Elements --------------------------- //
// ========================================================================== //
NoteValue mute[8] = {
{MCU::MUTE_1}, // The mute status of the first track
};
NoteValue solo[8] = {
{MCU::SOLO_1}, // The solo status of the first track
};
constexpr unsigned int decay = MCU::VUDecay::Hold;
// Try this option if your DAW doesn't decay the VU meters automatically
// constexpr unsigned int decay = 60; // milliseconds to decay one block
// VU meters
MCU::VU VUMeters[8] = {
{1, decay}, // The VU meter for the first track, decay time as specified above
{2, decay}, {3, decay}, {4, decay}, {5, decay},
{6, decay}, {7, decay}, {8, decay},
};
// ---------------------------- Display Elements ---------------------------- //
// ========================================================================== //
MCU::VUDisplay vuDisp[8] = {
// Draw the first VU meter to the display, at position (2, 50),
// (12) pixels wide, blocks of (3) pixels high, a spacing between
// blocks of (1) pixel, and draw in white.
{display, VUMeters[0], {2 + 16 * 0, 50}, 12, 3, 1, WHITE},
{display, VUMeters[1], {2 + 16 * 1, 50}, 12, 3, 1, WHITE},
{display, VUMeters[2], {2 + 16 * 2, 50}, 12, 3, 1, WHITE},
{display, VUMeters[3], {2 + 16 * 3, 50}, 12, 3, 1, WHITE},
{display, VUMeters[4], {2 + 16 * 4, 50}, 12, 3, 1, WHITE},
{display, VUMeters[5], {2 + 16 * 5, 50}, 12, 3, 1, WHITE},
{display, VUMeters[6], {2 + 16 * 6, 50}, 12, 3, 1, WHITE},
{display, VUMeters[7], {2 + 16 * 7, 50}, 12, 3, 1, WHITE},
};
NoteBitmapDisplay muteDisp[8] = {
// Draw the first mute indicator to the display, at position (4, 54),
// using bitmap icon mute_7 with a white foreground color.
{display, mute[0], XBM::mute_7, {4 + 16 * 0, 54}, WHITE},
{display, mute[1], XBM::mute_7, {4 + 16 * 1, 54}, WHITE},
{display, mute[2], XBM::mute_7, {4 + 16 * 2, 54}, WHITE},
{display, mute[3], XBM::mute_7, {4 + 16 * 3, 54}, WHITE},
{display, mute[4], XBM::mute_7, {4 + 16 * 4, 54}, WHITE},
{display, mute[5], XBM::mute_7, {4 + 16 * 5, 54}, WHITE},
{display, mute[6], XBM::mute_7, {4 + 16 * 6, 54}, WHITE},
{display, mute[7], XBM::mute_7, {4 + 16 * 7, 54}, WHITE},
};
NoteBitmapDisplay soloDisp[8] = {
// Draw the first solo indicator to the display, at position (4, 54),
// using bitmap icon solo_7 with a white foreground color.
{display, solo[0], XBM::solo_7, {4 + 16 * 0, 54}, WHITE},
{display, solo[1], XBM::solo_7, {4 + 16 * 1, 54}, WHITE},
{display, solo[2], XBM::solo_7, {4 + 16 * 2, 54}, WHITE},
{display, solo[3], XBM::solo_7, {4 + 16 * 3, 54}, WHITE},
{display, solo[4], XBM::solo_7, {4 + 16 * 4, 54}, WHITE},
{display, solo[5], XBM::solo_7, {4 + 16 * 5, 54}, WHITE},
{display, solo[6], XBM::solo_7, {4 + 16 * 6, 54}, WHITE},
{display, solo[7], XBM::solo_7, {4 + 16 * 7, 54}, WHITE},
};
// --------------------------------- Setup ---------------------------------- //
// ========================================================================== //
void setup() {
Control_Surface.begin(); // Initialize Control Surface
}
// ---------------------------------- Loop ---------------------------------- //
// ========================================================================== //
void loop() {
Control_Surface.loop(); // Refresh all elements
}
NoteBitmapDisplay
A class that displays a bitmap depending on the state of a MIDINote.
Definition: NoteBitmapDisplay.hpp:10
SSD1306_DisplayInterface::drawBackground
void drawBackground() override=0
Draw a custom background.
BluetoothMIDI_Interface.hpp
BluetoothMIDI_Interface
Bluetooth Low Energy MIDI Interface for the ESP32.
Definition: BluetoothMIDI_Interface.hpp:15
NoteValue
MIDI Input Element that listens to a single note and saves its velocity value.
Definition: NoteCCRange.hpp:205
MCU::SOLO_6
constexpr uint8_t SOLO_6
Definition: MCU.hpp:32
AH::SPI_MAX_SPEED
constexpr static Frequency SPI_MAX_SPEED
Definition: AH/Settings/Settings.hpp:85
MCU::MUTE_5
constexpr uint8_t MUTE_5
Definition: MCU.hpp:40
MCU::MUTE_4
constexpr uint8_t MUTE_4
Definition: MCU.hpp:39
MCU::SOLO_4
constexpr uint8_t SOLO_4
Definition: MCU.hpp:30
MCU::MUTE_7
constexpr uint8_t MUTE_7
Definition: MCU.hpp:42
MCU::MUTE_8
constexpr uint8_t MUTE_8
Definition: MCU.hpp:43
FATAL_ERROR
#define FATAL_ERROR(msg, errc)
Print the error message and error code, and stop the execution.
Definition: Error.hpp:60
MCU::MUTE_1
constexpr uint8_t MUTE_1
Definition: MCU.hpp:36
MCU::SOLO_5
constexpr uint8_t SOLO_5
Definition: MCU.hpp:31
MCU::VU
A class for MIDI input elements that represent Mackie Control Universal VU meters.
Definition: VU.hpp:222
Control_Surface.h
The main header file that includes all Control-Surface header files.
Control_Surface_::loop
void loop()
Update all MIDI elements, send MIDI events and read MIDI input.
Definition: Control_Surface_Class.cpp:48
MCU::SOLO_8
constexpr uint8_t SOLO_8
Definition: MCU.hpp:34
MCU::MUTE_3
constexpr uint8_t MUTE_3
Definition: MCU.hpp:38
SSD1306_DisplayInterface
This class creates a mapping between the Adafruit_SSD1306 display driver and the general display inte...
Definition: DisplayInterfaceSSD1306.hpp:13
Control_Surface
Control_Surface_ & Control_Surface
A predefined instance of the Control Surface to use in the Arduino sketches.
Definition: Control_Surface_Class.cpp:176
MCU::SOLO_2
constexpr uint8_t SOLO_2
Definition: MCU.hpp:28
MCU::SOLO_3
constexpr uint8_t SOLO_3
Definition: MCU.hpp:29
MCU::SOLO_7
constexpr uint8_t SOLO_7
Definition: MCU.hpp:33
XBM::mute_7
const XBitmap mute_7
XBitmap mute_7 (7px × 7px)
Definition: XBitmaps.hpp:53
MCU::MUTE_6
constexpr uint8_t MUTE_6
Definition: MCU.hpp:41
MIDI_Notes::F
constexpr int8_t F
Definition: Notes.hpp:23
DisplayInterfaceSSD1306.hpp
MCU::VUDisplay
Definition: VUDisplay.hpp:10
DisplayInterface::begin
virtual void begin()
Initialize the display.
Definition: DisplayInterface.cpp:7
XBM::solo_7
const XBitmap solo_7
XBitmap solo_7 (7px × 7px)
Definition: XBitmaps.hpp:130
MCU::VUDecay::Hold
constexpr unsigned int Hold
Don't decay automatically, hold the latest value until a new one is received.
Definition: VU.hpp:41
Control_Surface_::begin
void begin()
Initialize the Control_Surface.
Definition: Control_Surface_Class.cpp:25
MCU::MUTE_2
constexpr uint8_t MUTE_2
Definition: MCU.hpp:37
MCU::SOLO_1
constexpr uint8_t SOLO_1
Definition: MCU.hpp:27