Control Surface main
MIDI Control Surface library for Arduino
Loading...
Searching...
No Matches
VU-Bridge-Dual-Display.ino

This is an example on how to use multiple displays to display the VU meters of many tracks, by using the Arduino as a Mackie Control Universal with extenders.

This is an example on how to use multiple displays to display the VU meters of many tracks, by using the Arduino as a Mackie Control Universal with extenders.

VU-Bridge-Dual-Display

Boards: 🛈
Teensy 3.x, Teensy 4.0

This example is currenty only supported on boards that support multiple virtual MIDI USB cables (e.g. Teensy).

Connections

This example drives two SSD1306 OLED displays over SPI

Connect the reset pins of the two displays together, connect a capacitor from reset to 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 displays, without it, they won't work.
Alternatively, you could use an IO pin from the Teensy to reset the displays, but this just "wastes" a pin.

Note
Don't forget that most OLED displays are 3.3V only, so connecting them to a 5V Arduino directly will destroy them!

Behavior

Select "MIDIx4" from the Tools > USB Type menu.
Map "Control Surface (1)" as a Mackie Control Universal unit in your DAW, and map "Control Surface (2)" as a Mackie Control Universal Extender (XT).
If you have to manually set the track offset of the extender, choose 8.

The first display should now display the level meters and mute/solo states of the first 8 tracks, and the second display should display the ones of tracks 9-16.

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 decay options of the MCU::VUDecay class, or try a different decay time.

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

#include <Control_Surface.h> // Include the Control Surface library
// Include the display interface you'd like to use
// ----------------------------- 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 = 19; // Data/Command pin of the displays
constexpr int8_t OLED_reset = -1; // Use the external RC circuit for reset
constexpr int8_t OLED_CSA = 17; // Chip Select pin of the first display
constexpr int8_t OLED_CSB = 18; // Chip Select pin of the second display
constexpr uint32_t SPI_Frequency = SPI_MAX_SPEED;
// Instantiate the displays
Adafruit_SSD1306 ssd1306DisplayA = {
SCREEN_WIDTH, SCREEN_HEIGHT,
&SPI, OLED_DC, OLED_reset, OLED_CSA, SPI_Frequency
};
Adafruit_SSD1306 ssd1306DisplayB = {
SCREEN_WIDTH, SCREEN_HEIGHT,
&SPI, OLED_DC, OLED_reset, OLED_CSB, 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 {
if(!disp.begin())
FATAL_ERROR(F("SSD1306 allocation failed."), 0x1306);
SSD1306_DisplayInterface::begin(); // If you override the begin method,
// remember to call the super class
// method
}
void drawBackground() override {
disp.drawFastHLine(0, 52, 128, WHITE);
disp.drawRect(0, 0, 128, 64, WHITE);
}
} displayA = ssd1306DisplayA, displayB = ssd1306DisplayB;
// -------------------------- MIDI Input Elements --------------------------- //
// ========================================================================== //
NoteValue mute[8] = { // Main unit uses cable number 0x0
{{ MCU::MUTE_1, Channel_1, Cable_1 }}, // The mute status of the first track
};
NoteValue muteXT[8] = { // First extender uses cable number 0x1
{{ MCU::MUTE_1, Channel_1, Cable_2 }}, // The mute status of the first track
};
NoteValue solo[8] = {
{{ MCU::SOLO_1, Channel_1, Cable_1 }}, // The solo status of the first track
};
NoteValue soloXT[8] = {
{{ MCU::SOLO_1, Channel_1, Cable_2 }}, // The solo status of the first track on the extender
};
// const auto decay = MCU::VUDecay::Hold;
const auto decay = MCU::VUDecay::Default;
// VU meters
MCU::VU VUMeters[8] = {
{ 1, {Channel_1, Cable_1}, decay }, // The VU meter for the first track,
{ 2, {Channel_1, Cable_1}, decay }, // second track, etc.
{ 3, {Channel_1, Cable_1}, decay },
{ 4, {Channel_1, Cable_1}, decay },
{ 5, {Channel_1, Cable_1}, decay },
{ 6, {Channel_1, Cable_1}, decay },
{ 7, {Channel_1, Cable_1}, decay },
{ 8, {Channel_1, Cable_1}, decay },
};
MCU::VU VUMetersXT[8] = {
{ 1, {Channel_1, Cable_2}, decay }, // The VU meter for the first track on the extender,
{ 2, {Channel_1, Cable_2}, decay }, // second track, etc.
{ 3, {Channel_1, Cable_2}, decay },
{ 4, {Channel_1, Cable_2}, decay },
{ 5, {Channel_1, Cable_2}, decay },
{ 6, {Channel_1, Cable_2}, decay },
{ 7, {Channel_1, Cable_2}, decay },
{ 8, {Channel_1, Cable_2}, 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.
{ displayA, VUMeters[0], { 2 + 16 * 0, 50 }, 12, 3, 1, WHITE },
{ displayA, VUMeters[1], { 2 + 16 * 1, 50 }, 12, 3, 1, WHITE },
{ displayA, VUMeters[2], { 2 + 16 * 2, 50 }, 12, 3, 1, WHITE },
{ displayA, VUMeters[3], { 2 + 16 * 3, 50 }, 12, 3, 1, WHITE },
{ displayA, VUMeters[4], { 2 + 16 * 4, 50 }, 12, 3, 1, WHITE },
{ displayA, VUMeters[5], { 2 + 16 * 5, 50 }, 12, 3, 1, WHITE },
{ displayA, VUMeters[6], { 2 + 16 * 6, 50 }, 12, 3, 1, WHITE },
{ displayA, VUMeters[7], { 2 + 16 * 7, 50 }, 12, 3, 1, WHITE },
};
MCU::VUDisplay<> vuDispXT[8] = {
{ displayB, VUMetersXT[0], { 2 + 16 * 0, 50 }, 12, 3, 1, WHITE },
{ displayB, VUMetersXT[1], { 2 + 16 * 1, 50 }, 12, 3, 1, WHITE },
{ displayB, VUMetersXT[2], { 2 + 16 * 2, 50 }, 12, 3, 1, WHITE },
{ displayB, VUMetersXT[3], { 2 + 16 * 3, 50 }, 12, 3, 1, WHITE },
{ displayB, VUMetersXT[4], { 2 + 16 * 4, 50 }, 12, 3, 1, WHITE },
{ displayB, VUMetersXT[5], { 2 + 16 * 5, 50 }, 12, 3, 1, WHITE },
{ displayB, VUMetersXT[6], { 2 + 16 * 6, 50 }, 12, 3, 1, WHITE },
{ displayB, VUMetersXT[7], { 2 + 16 * 7, 50 }, 12, 3, 1, WHITE },
};
BitmapDisplay<> muteDisp[8] = {
// Draw the first mute indicator to the display, at position (4, 54),
// using bitmap icon mute_7 with a white foreground color.
{ displayA, mute[0], XBM::mute_7, { 4 + 16 * 0, 54 }, WHITE },
{ displayA, mute[1], XBM::mute_7, { 4 + 16 * 1, 54 }, WHITE },
{ displayA, mute[2], XBM::mute_7, { 4 + 16 * 2, 54 }, WHITE },
{ displayA, mute[3], XBM::mute_7, { 4 + 16 * 3, 54 }, WHITE },
{ displayA, mute[4], XBM::mute_7, { 4 + 16 * 4, 54 }, WHITE },
{ displayA, mute[5], XBM::mute_7, { 4 + 16 * 5, 54 }, WHITE },
{ displayA, mute[6], XBM::mute_7, { 4 + 16 * 6, 54 }, WHITE },
{ displayA, mute[7], XBM::mute_7, { 4 + 16 * 7, 54 }, WHITE },
};
BitmapDisplay<> muteDispXT[8] = {
{ displayB, muteXT[0], XBM::mute_7, { 4 + 16 * 0, 54 }, WHITE },
{ displayB, muteXT[1], XBM::mute_7, { 4 + 16 * 1, 54 }, WHITE },
{ displayB, muteXT[2], XBM::mute_7, { 4 + 16 * 2, 54 }, WHITE },
{ displayB, muteXT[3], XBM::mute_7, { 4 + 16 * 3, 54 }, WHITE },
{ displayB, muteXT[4], XBM::mute_7, { 4 + 16 * 4, 54 }, WHITE },
{ displayB, muteXT[5], XBM::mute_7, { 4 + 16 * 5, 54 }, WHITE },
{ displayB, muteXT[6], XBM::mute_7, { 4 + 16 * 6, 54 }, WHITE },
{ displayB, muteXT[7], XBM::mute_7, { 4 + 16 * 7, 54 }, WHITE },
};
BitmapDisplay<> soloDisp[8] = {
// Draw the first solo indicator to the display, at position (4, 54),
// using bitmap icon solo_7 with a white foreground color.
{ displayA, solo[0], XBM::solo_7, { 4 + 16 * 0, 54 }, WHITE },
{ displayA, solo[1], XBM::solo_7, { 4 + 16 * 1, 54 }, WHITE },
{ displayA, solo[2], XBM::solo_7, { 4 + 16 * 2, 54 }, WHITE },
{ displayA, solo[3], XBM::solo_7, { 4 + 16 * 3, 54 }, WHITE },
{ displayA, solo[4], XBM::solo_7, { 4 + 16 * 4, 54 }, WHITE },
{ displayA, solo[5], XBM::solo_7, { 4 + 16 * 5, 54 }, WHITE },
{ displayA, solo[6], XBM::solo_7, { 4 + 16 * 6, 54 }, WHITE },
{ displayA, solo[7], XBM::solo_7, { 4 + 16 * 7, 54 }, WHITE },
};
BitmapDisplay<> soloDispXT[8] = {
{ displayB, soloXT[0], XBM::solo_7, { 4 + 16 * 0, 54 }, WHITE },
{ displayB, soloXT[1], XBM::solo_7, { 4 + 16 * 1, 54 }, WHITE },
{ displayB, soloXT[2], XBM::solo_7, { 4 + 16 * 2, 54 }, WHITE },
{ displayB, soloXT[3], XBM::solo_7, { 4 + 16 * 3, 54 }, WHITE },
{ displayB, soloXT[4], XBM::solo_7, { 4 + 16 * 4, 54 }, WHITE },
{ displayB, soloXT[5], XBM::solo_7, { 4 + 16 * 5, 54 }, WHITE },
{ displayB, soloXT[6], XBM::solo_7, { 4 + 16 * 6, 54 }, WHITE },
{ displayB, soloXT[7], XBM::solo_7, { 4 + 16 * 7, 54 }, WHITE },
};
// --------------------------------- Setup ---------------------------------- //
// ========================================================================== //
void setup() {
Control_Surface.begin(); // Initialize everything
}
// ---------------------------------- Loop ---------------------------------- //
// ========================================================================== //
void loop() {
Control_Surface.loop(); // Refresh all elements
}
constexpr Cable Cable_2
Definition Cable.hpp:119
constexpr Cable Cable_1
Definition Cable.hpp:118
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 that displays a bitmap depending on the state of a MIDINote or any other object that has a ge...
void begin()
Initialize the Control_Surface.
void loop()
Update all MIDI elements, send MIDI events and read MIDI input.
virtual void begin()
Initialize the display.
Displays a MCU level meter.
Definition VUDisplay.hpp:16
A MIDI input element that represents a Mackie Control Universal VU meter.
Definition VU.hpp:190
Generic base class for classes that listen for MIDI Note, Control Change and Key Pressure events on a...
This class creates a mapping between the Adafruit_SSD1306 display driver and the general display inte...
void drawBackground() override=0
Draw a custom background.
A class for MIDI interfaces sending MIDI messages over a USB MIDI connection.
#define FATAL_ERROR(msg, errc)
Print the error message and error code, and stop the execution.
Definition Error.hpp:57
constexpr uint8_t MUTE_1
Mute 1 (In/Out)
Definition MCU.hpp:40
constexpr uint8_t SOLO_6
Solo 6 (In/Out)
Definition MCU.hpp:36
constexpr uint8_t MUTE_5
Mute 5 (In/Out)
Definition MCU.hpp:44
constexpr uint8_t MUTE_6
Mute 6 (In/Out)
Definition MCU.hpp:45
constexpr uint8_t SOLO_2
Solo 2 (In/Out)
Definition MCU.hpp:32
constexpr uint8_t SOLO_1
Solo 1 (In/Out)
Definition MCU.hpp:31
constexpr uint8_t SOLO_8
Solo 8 (In/Out)
Definition MCU.hpp:38
constexpr uint8_t MUTE_8
Mute 8 (In/Out)
Definition MCU.hpp:47
constexpr uint8_t SOLO_4
Solo 4 (In/Out)
Definition MCU.hpp:34
constexpr uint8_t SOLO_5
Solo 5 (In/Out)
Definition MCU.hpp:35
constexpr uint8_t MUTE_7
Mute 7 (In/Out)
Definition MCU.hpp:46
constexpr uint8_t MUTE_4
Mute 4 (In/Out)
Definition MCU.hpp:43
constexpr uint8_t SOLO_7
Solo 7 (In/Out)
Definition MCU.hpp:37
constexpr uint8_t SOLO_3
Solo 3 (In/Out)
Definition MCU.hpp:33
constexpr uint8_t MUTE_2
Mute 2 (In/Out)
Definition MCU.hpp:41
constexpr uint8_t MUTE_3
Mute 3 (In/Out)
Definition MCU.hpp:42
constexpr unsigned int Default
Decay one segment/block every 150 ms if no new values are received.
Definition VU.hpp:179
constexpr Note F
F (Fa)
Definition Notes.hpp:61
const XBitmap solo_7
XBitmap solo_7 (7px × 7px)
Definition XBitmaps.hpp:129
const XBitmap mute_7
XBitmap mute_7 (7px × 7px)
Definition XBitmaps.hpp:52