Question on instantiating Multiple USB & Serial Interfaces

Hi all,

Is it possible for a MIDI controller to have multiple USB and serial interfaces with some single and others with bidirectional communication? Here’s what I’m trying to set up:

  1. Controller keyboard midi out via 5 Pin DIN to Arduino in (RX)
  2. Arduino midi in & out via USB to stand alone PC with soft synth
  3. Arduino midi in & out via 5 Pin DIN to Emagic AMT8 hardware midi interface hooked up to my Mac Pro running Logic Audio.

I assume I need to define the proper RX & TX pins for the 5 pin DIN connectors?

Would this code work:


#include <Control_Surface.h>
 
// Instantiate a MIDI over USB interface
USBMIDI_Interface midi_usb;
// Instantiate 2 MIDI over Serial interface using `Serial1` & ‘Serial2’
HardwareSerialMIDI_Interface midi_ser1 = Serial1;
HardwareSerialMIDI_Interface midi_ser2 = Serial2;
// Instantiate the three pipes to connect the interfaces to Control_Surface
MIDI_Pipe pipe_tx_u, pipe_rx_u, pipe_tx_s1, pipe_rx_s1, pipe_rx_s2
 
// Instantiate a number of controls
// Buttons, Pots, LEDs, etc…
 
void setup() {
    // Manually route MIDI output from Control_Surface to the USB MIDI interface
    Control_Surface >> pipe_tx_u >> midi_usb1;
    // Manually route MIDI output from the USB MIDI interface to Control_Surface
    Control_Surface << pipe_rx_u << midi_usb1;
    // Manually route MIDI output from Control_Surface to the Serial MIDI interface
    Control_Surface >> pipe_tx_s >> midi_ser;
    // Manually route MIDI output from the Serial MIDI interface to Control_Surface
    Control_Surface << pipe_rx_s << midi_ser;
    // Manually route MIDI output from the USB MIDI interface to Control_Surface
    Control_Surface << pipe_rx_u << midi_usb2;

    // Initialize everything
    Control_Surface.begin();
}
 
void loop() {
    Control_Surface.loop();
}

Any direction would be great.

What type of Arduino is this intended to run on?

Right now I have a Mega 2560R3 and I use the midiklik to make is USB compliant. I’m thinkiyof getting a Teensy 4.1 also.

As long as you have enough memory and serial ports, you can keep adding more serial MIDI interfaces. You can only have one USB interface (which is a serial interface on the Arduino Mega).

In your code, you'll have to make sure the names of your interfaces are consistent of course. You could also use a “pipe factory” instead of naming each pipe individually.
If you haven't already, have a look at the “Routing MIDI messages” section of Control Surface: MIDI Tutorial.

ok got it. I assume I'd have to assign the proper RX & TX pins to the interfaces though correct?
Like this?

VOID setup() {
    Serial1.setRX(19); // Mega to receive Midi from controller Keyboard output on RX1 pin 19
    Serial2.setRX(17); // Mega to receive Midi from AMT8 output on RX2 pin 17
    Serial2.setTX(16); // Mega to send Midi to AMT8 input on TX2 pin 16

The AVR Serial object doesn't have a setRX() method.

ok so if I'm using multiple RX and TX pins, how do I assign them to the different interface?

You cannot assign them, there are fixed by the hardware (at least on the Mega2560).

ok so there's really no need to assign them.
Thanks.

Can someone tell me how to use a 300 degree rotation pot for MIDI where it looks like from 0 to 127 is only 240degree? I bought this pot:

Bourns PTV111-3415A-B103

Thank you

You can use Control Surface: CCPotentiometer-Map.ino.

Ok, so if I have an array of pots I just use the array name.map and I should be ok? What about other pots on a multiplexer?

No, you can't apply methods to an array. You have to access the array elements. For example, if you only want the first potentiometer:

CCPotentiometer potentiometers[] {
  ...
};

void setup() {
  potentiometers[0].map(...);
  ...
}

If you want to map all potentiometers in the array, use a for loop:

void setup() {
  for (auto &potentiometer : potentiometers)
    potentiometer.map(...);
  ...
}

Ok thank you, I will try that.

At 0, the pot marker is at 7 o'clock, at maximum it's at 1 o'clock. That's only 180 deg. Am I missing something or I'm not sure I have the proper pot. I just plugged in my Oxygen USB controller and the pots on it behave correctly. From 0 to 127 on the display and the soft synth is from 7 o'clock to 5 o'clock. I seems I'd have to map to a greater bit depth. I'm at a loos right now, any ideas?

just to be clear, the numbers in the mapping are 0 and 16,383. If I increase the minimum and lower the maximum, then I go from o to 127 in say 1/4 turn. That's why i'm thinking the maximum number should be higher than 16,383 so a full rotation shows o to 127.

For the MIDI interfaces I'm half way there. My connections are:

Teensy is plugged into my laptop via USB where my synth is, a pot is connected to the Teensy, a MIDI controller keyboard is plugged into the Teensy via 5 pin DIN. Here's the behaviour:

  • All the controls on the synth in my laptop show up in the MIDI monitor
  • The pot controls the appropriate knob on the synth but doesn't show up in the monitor
  • All the controls on the the MIDI controller keyboard show up in the monitor but doesn't control the synth.

I want the keyboard controller to be able to control the synth as well as the pot (and buttons) all via the Teensy. What's missing in my code? Am I missing an 3rd interface maybe?

#include <Control_Surface.h>
 
// Create two MIDI interfaces
USBMIDI_Interface usbmidi;
HardwareSerialMIDI_Interface serialmidi {Serial1, MIDI_BAUD};
 
// Create a MIDI pipe factory to connect the MIDI interfaces to Control Surface
BidirectionalMIDI_PipeFactory<2> pipes;
 
// Custom MIDI callback that prints incoming messages.
struct MyMIDI_Callbacks : FineGrainedMIDI_Callbacks<MyMIDI_Callbacks> {
  // Note how this ^ name is identical to the argument used here ^
 
  void onNoteOff(Channel channel, uint8_t note, uint8_t velocity, Cable cable) {
    Serial << "Note Off: " << channel << ", note " << note << ", velocity "
           << velocity << ", " << cable << endl;
  }
  void onNoteOn(Channel channel, uint8_t note, uint8_t velocity, Cable cable) {
    Serial << "Note On: " << channel << ", note " << note << ", velocity "
           << velocity << ", " << cable << endl;
  }
  void onKeyPressure(Channel channel, uint8_t note, uint8_t pressure,
                     Cable cable) {
    Serial << "Key Pressure: " << channel << ", note " << note << ", pressure "
           << pressure << ", " << cable << endl;
  }
  void onControlChange(Channel channel, uint8_t controller, uint8_t value,
                       Cable cable) {
    Serial << "Control Change: " << channel << ", controller " << controller
           << ", value " << value << ", " << cable << endl;
  }
  void onProgramChange(Channel channel, uint8_t program, Cable cable) {
    Serial << "Program Change: " << channel << ", program " << program << ", "
           << cable << endl;
  }
  void onChannelPressure(Channel channel, uint8_t pressure, Cable cable) {
    Serial << "Channel Pressure: " << channel << ", pressure " << pressure
           << ", " << cable << endl;
  }
  void onPitchBend(Channel channel, uint16_t bend, Cable cable) {
    Serial << "Pitch Bend: " << channel << ", bend " << bend << ", " << cable
           << endl;
  }
   
} callback;

// Add some MIDI elements to show that the MIDI interfaces actually work
CCPotentiometer pot {A17, MIDI_CC::General_Purpose_Controller_1};
NoteLED led {LED_BUILTIN, 0x3C};


void setup() {
  // Manually connect the MIDI interfaces to Control Surface
  Control_Surface | pipes | usbmidi;
  Control_Surface | pipes | serialmidi;
  // Initialize Control Surface _after_ connecting the interfaces

   Serial.begin(115200);        // For printing the messages
  usbmidi.begin();                // Initialize the MIDI interface
  serialmidi.begin();                // Initialize the MIDI interface
  usbmidi.setCallbacks(callback); // Attach the custom callback
  serialmidi.setCallbacks(callback); // Attach the custom callback
  Control_Surface.begin();
}
 
void loop() {
  Control_Surface.loop();
  usbmidi.update();
  serialmidi.update();
}

You'll have to make another connection, Control_Surface doesn't pass on MIDI messages to interfaces it is connected to.

...
MIDI_Pipe passthrough;
void setup() {
  Serial.begin(115200);        // For printing the messages
  // Manually connect the MIDI interfaces to Control Surface
  Control_Surface | pipes | usbmidi;
  Control_Surface | pipes | serialmidi;
  serialmidi >> passthrough >> usbmidi;
  usbmidi.setCallbacks(callback); // Attach the custom callback
  serialmidi.setCallbacks(callback); // Attach the custom callback
  // Initialize Control Surface _after_ connecting the interfaces
  Control_Surface.begin();
}

Note that there is no need to call usbmidi/serialmidi.update/begin(), this is handled automatically by Control_Surface.begin/loop().

This is normal, you only have callbacks on the MIDI inputs of the two interfaces, the outgoing messages sent by Control_Surface itself won't get printed. If you do want to print them, just add a USBDebugMIDI_Output:

...
USBDebugMIDI_Output dbgmidi {115200};
MIDI_Pipe dbgpipe;
void setup() {
  // Manually connect the MIDI interfaces to Control Surface
  Control_Surface | pipes | usbmidi;
  Control_Surface | pipes | serialmidi;
  serialmidi >> passthrough >> usbmidi;
  Control_Surface >> dbgpipe >> dbgmidi;
  usbmidi.setCallbacks(callback); // Attach the custom callback
  serialmidi.setCallbacks(callback); // Attach the custom callback
  // Initialize Control Surface _after_ connecting the interfaces
  Control_Surface.begin();
}

If all you do in your custom callbacks is printing the messages, you can remove them as well and just route everything to the debug output which will print it for you. If you want to differentiate between the different sources, use different debug outputs with different prefixes:

USBMIDI_Interface usbmidi;
HardwareSerialMIDI_Interface serialmidi {Serial1, MIDI_BAUD};
USBDebugMIDI_Output dbgmidis[] {
  {115200, "USB:"},
  {115200, "Ser:"},
  {115200, "Out:"},
};
BidirectionalMIDI_PipeFactory<2> pipes;
MIDI_PipeFactory<3> dbgpipes;
MIDI_Pipe passthrough;
void setup() {
  // Manually connect the MIDI interfaces to Control Surface
  Control_Surface |     pipes     |  usbmidi;
  Control_Surface |     pipes     |  serialmidi;
  serialmidi      >> passthrough  >> usbmidi;
  usbmidi         >>   dbgpipes   >> dbgmidis[0];
  serialmidi      >>   dbgpipes   >> dbgmidis[1];
  Control_Surface >>   dbgpipes   >> dbgmidis[2];
  // Initialize Control Surface _after_ connecting the interfaces
  Control_Surface.begin();
}

I'm not entirely sure what you mean here. It's probably easiest to just draw a graph of the expected behavior, with the actual raw measurement on the horizontal axis and the desired digial knob position on the vertical axis. Then it's just a matter of implementing this piecewise linear/constant function on the Arduino.

Wow thank you! that works beautifully. And for the pots, I think I need to find one that has a smaller rotation angle, maybe 280 deg or lower. I'll have to try a few.

So now that my keyboard controls the synth, what if I want to add another set of MIDI 5 pin In & Out so that I can communicate with my hardware AMT8 MIDI interface that's plugged to my Mac via USB? I assume I have to create a new interface:

// Create three MIDI interfaces
USBMIDI_Interface usbmidi;
HardwareSerialMIDI_Interface serialmidi {Serial1, MIDI_BAUD};
HardwareSerialMIDI_Interface serialmidi2 {Serial2, MIDI_BAUD}; // new one

Then I add a 3rd pipe:

BidirectionalMIDI_PipeFactory<3> pipes;

Then I connect it:

Control_Surface | pipes | serialmidi2;

Then that's where things are not working. I'm thinking I need to passthrough from usbmidi to serialmidi2 ?

usbmidi  >> passthrough >> serialmidi2;

but when I do and upload the code, the LED on the Teensy flashes on and off and when I launch my synth, it jams right away at boot up. Then I had another line for callbacks as this new port (in & out) would carry the info to and back from Logic Audio so the Teensy would have to recognize MIDI data coming from Logic.

I'm thinking that the usbmidi interface carries all the info so why not send all of it to Logic and back for MIDI recording?

What am I not getting?