Virtual Midi Cables - 8 ports OSX and PC

Status
Not open for further replies.

BuzzBang

Member
I've managed to get 8 virtual midi cables working over USB for Teensy 3.2/3.6 on OSX and Windows 10, don't have Linux but it should work (famous last words)...

This thread helped start the ball rolling : thread

Hopefully the way I've done it is fairly clean (the midi send/receive commands haven't changed :)) and useful to some people.

The files that need to be changed (OSX locations) are:

/Applications/Arduino.app/Contents/Java/hardware/teensy/avr/cores/teensy3/usb_desc.c
/Applications/Arduino.app/Contents/Java/hardware/teensy/avr/cores/teensy3/usb_midi.c
/Applications/Arduino.app/Contents/Java/hardware/teensy/avr/cores/teensy3/usb_midi.h

Installation:

1/ Make a copy of these files: usb_desc.c , usb_midi.c , usb_midi.h

On MacOs in finder goto Applications/Arduino right click show Package contents then

/Contents/Java/hardware/teensy/avr/cores/teensy3/

2/ replace them with the three files attached.

Usage:

Before sending set the virtual midi cable using usbMIDI.setMidiCable(cable). Note there is no bounds checking and cable needs to be between 0 and 7.

int cable = 7; // cable can be from 0 to 7
int note = 45;

usbMIDI.setMidiCable(cable);
usbMIDI.sendNoteOn(note,0x7F,1); // note , vel, channel

Receiving:

It up to you to check what virtual midi cable received the midi data and decide what to do with it...

Serial.print("Cable, cb=");
int cable = usbMIDI.getCable();
Serial.print(cable, DEC);

Example code

The example below sends 50 note on's and 50 note off's as fast as possible when a button is pressed.
It also writes to the serial terminal when a midi message (note on, off, pitch bend, control change etc) is received.


Code:
#include <Bounce.h>

Bounce button2 = Bounce(2, 55); 


void setup() {
  Serial.begin(115200);
  delay(300);
  usbMIDI.setHandleNoteOff (OnNoteOff);
  usbMIDI.setHandleNoteOn (OnNoteOn);
  usbMIDI.setHandleVelocityChange(OnVelocityChange);
  usbMIDI.setHandleControlChange(OnControlChange);
  usbMIDI.setHandleProgramChange(OnProgramChange);
  usbMIDI.setHandleAfterTouch(OnAfterTouch);
  usbMIDI.setHandlePitchChange(OnPitchChange);
   pinMode(2, INPUT_PULLUP);
}

void loop() { 
  usbMIDI.read(); // USB MIDI receive

  button2.update();
  int value = button2.read();

  if(value == LOW){
    delay(50);
    send_notes();
    delay(250);
  }
    
}



void send_notes(){
  
  int cable = 0;
  int i = 0;
 
  
     for (i=0;i<50;i++){
        for ( cable = 0; cable < 8; cable++){ 
            usbMIDI.setMidiCable(cable);
            usbMIDI.sendNoteOn(i,0x7F,1);  // note , vel, channel 
        }
       }

  
        for (i=0;i<50;i++){ 
          for ( cable = 0; cable < 8; cable++){ 
              usbMIDI.setMidiCable(cable);
              usbMIDI.sendNoteOff(i,0x00,1);  // note , vel, channel
        }
   }
  usbMIDI.send_now();
}



void OnNoteOn( byte channel, byte note, byte velocity) {
  
  Serial.print("Cable, cb=");
  int cable = usbMIDI.getCable();
  Serial.print(cable, DEC);
  Serial.print("Note On, ch=");
  Serial.print(channel, DEC);
  Serial.print(", note=");
  Serial.print(note, DEC);
  Serial.print(", velocity=");
  Serial.print(velocity, DEC);
  Serial.println();
}

void OnNoteOff(byte channel, byte note, byte velocity) {
  Serial.print("Cable, cb=");
  Serial.print(usbMIDI.getCable(), DEC);
  Serial.print("Note Off, ch=");
  Serial.print(channel, DEC);
  Serial.print(", note=");
  Serial.print(note, DEC);
  Serial.print(", velocity=");
  Serial.print(velocity, DEC);
  Serial.println();
}

void OnVelocityChange(byte channel, byte note, byte velocity) {


  Serial.print("Cable, cb=");
  Serial.print(usbMIDI.getCable(), DEC);
  Serial.print("Velocity Change, ch=");
  Serial.print(channel, DEC);
  Serial.print(", note=");
  Serial.print(note, DEC);
  Serial.print(", velocity=");
  Serial.print(velocity, DEC);
  Serial.println();
}

void OnControlChange(byte channel, byte control, byte value) {
  Serial.print("Cable, cb=");
  Serial.print(usbMIDI.getCable(), DEC);
  Serial.print("Control Change, ch=");
  Serial.print(channel, DEC);
  Serial.print(", control=");
  Serial.print(control, DEC);
  Serial.print(", value=");
  Serial.print(value, DEC);
  Serial.println();
}

void OnProgramChange(byte channel, byte program) {
  Serial.print("Cable, cb=");
  Serial.print(usbMIDI.getCable(), DEC);
  Serial.print("Program Change, ch=");
  Serial.print(channel, DEC);
  Serial.print(", program=");
  Serial.print(program, DEC);
  Serial.println();
}

void OnAfterTouch(byte channel, byte pressure) {
  
  Serial.print("Cable, cb=");
  Serial.print(usbMIDI.getCable(), DEC);
  Serial.print("After Touch, ch=");
  Serial.print(channel, DEC);
  Serial.print(", pressure=");
  Serial.print(pressure, DEC);
  Serial.println();
}

void OnPitchChange( byte channel, int pitch) {
  uint8_t data1;
  uint8_t data2;
  uint8_t type;
  uint8_t ch;
  data1 = usbMIDI.getData1();
  data2 = usbMIDI.getData1();
  type = usbMIDI.getType();
  ch = usbMIDI.getChannel();
  Serial.print("Cable, cb=");
  Serial.print(usbMIDI.getCable(), DEC);
  
  Serial.print("Data1 = ");
  Serial.print(data1, DEC);
  Serial.print("Data2 = ");
  Serial.print(data2, DEC);
  Serial.print("Type = ");
  Serial.print(type, DEC);
  Serial.print("Channel = ");
  Serial.print(ch, DEC);
  
 
  Serial.print("Pitch Change, ch=");
  Serial.print(channel, DEC);
  Serial.print(", pitch=");
  Serial.print(pitch, DEC);
  Serial.println();
}

The 50 notes sent across eight ports/virtual midi cables (400 in total) were received by the software (Reaper64 in this case) in 3ms - not too shabby.

Preliminary Testing

- tested Arduino 1.6.12 and Teensyduino 1.32(?) and
Arduino 1.8.0 and Teensyduino 1.34

- left running for a couple of hours pumping midi Note on/off's across 8 ports to Ableton Live no problems.

- works using USB profiles: Midi / Midi + Serial and Midi + Serial + Audio (audio not tested but shows up in device manager and in OSX and PC)

- works on T3.2, T3.6 (fairly cursory - mainly 3.2)

The need for more than one midi port/cable:

If you are trying to implement some kinds to midi automation e.g. HUI/Mackie etc then you need more than one port. One port limits you to 8 channels. Frequently you need to be able to control up to 32 channels.

---

Hope I haven't missed something...
 

Attachments

  • USB_Midi_8_Cables.zip
    16.9 KB · Views: 158
Last edited:
I've been looking for hours to find this and could only find the original thread that started this one, thank you for making this. I was wondering if it would be possible to make the number of virtual midi cables editable from inside the project code instead of having to edit the usb_desc.c everytime I switch projects?
 
Thank you very much for this!
It's exactly what I need for my DAW (Logic Pro X) plugin controller project. Following the MIDI_name example I set the device name to "ZeusDPC" (DAW Plugin Controller) and it shows up in the OS X Audio MIDI setup with 8 virtual ports:

TeensyUsbMidi.png

The system exclusive message communication with Logic Pro X is also working, the Teensy 3.6 emulates a Logic Control Master unit and a Logic control XT unit:

TeensyDualControl.png

Now that the basic communication is operational I can continue working on the hardware.

Kind regards,

Gerrit
 
It seems my enthusiasm was a bit premature as I'm running into problems with the handling of system exclusive messages (sysEx) on multiple ports. Using getChannel and a switch statement like this:

Code:
void handleSysEx(const byte* sysExData, uint16_t sysExSize, bool complete){
  uint8_t midiPort;
  boolean isMackieControlMessage = true;
  boolean isMackieControlXTMessage = true;

  midiPort = usbMIDI.getChannel();
  switch (midiPort) {
    // Logic Control Main on Port 0
    case 0:
      // compare the first five bytes of the message to the sysExHeader to check if it is a Mackie control message
      for (int i=0;i<5;i++) {
        if (sysExData[i]!=sysExHeader[i]) {
          isMackieControlMessage = false;
        }
      }
      // handle mackie control message
      if (isMackieControlMessage) {
        boolean processMessage = true;
        // handle different message types
        switch (sysExData[5]) {
         ............

    // Logic Control XT on Port 1
    case 1:
      // compare the first five bytes of the message to the sysExHeader to check if it is a Mackie control XT message
      for (int i=0;i<5;i++) {
        if (sysExData[i]!=sysExHeaderXT[i]) {
          isMackieControlXTMessage = false;
        }
       }
      // handle mackie control message
      if (isMackieControlXTMessage) {
        boolean processMessage = true;
        // handle different message types
      ...............
    break;
    // Synth control on Port 2
    case 2:
      // nothing yet
    break;
    default: 
      // ignore sysExData
    break;
  }
}

Results in Logic Pro not recognising one of the units. Also, only one unit will be automatically installed. Quitting and restarting Logic Pro results in the other unit being installed but the sysEx communication is only working on one unit.
If the incoming channel is ignored everything works fine:

Code:
void handleSysEx(const byte* sysExData, uint16_t sysExSize, bool complete){
  //uint8_t midiPort;
  boolean isMackieControlMessage = true;
  boolean isMackieControlXTMessage = true;
  for (int i=0;i<5;i++) {
    if (sysExData[i]!=sysExHeader[i]) isMackieControlMessage = false;
    if (sysExData[i]!=sysExHeaderXT[i]) isMackieControlXTMessage = false;
  }
  // handle mackie control message
  if (isMackieControlMessage) {
    boolean processMessage = true;
    // handle different message types
    switch (sysExData[5]) {
      // respond to device query
      case 0:
        usbMIDI.setMidiCable(0);
        usbMIDI.sendSysEx(18, sysExHostConnectionQuery);
        break;
    ...............
  }
  
  // handle mackie control XT message
  if (isMackieControlXTMessage) {
    boolean processMessage = true;
    // handle different message types
    switch (sysExData[5]) {
      // respond to device query
      case 0:
        usbMIDI.setMidiCable(1);
        usbMIDI.sendSysEx(18, sysExHostConnectionQueryXT);
        break;
       ..............
  }       
  
}

On startup Logic will recognise both units and install them both. Because I only emulate one Logic control XT unit this solves my problem but if one would like to use more extension units then this will be a serious issue. The DAW host software does not distinguish extension units (Logic Control XT or Mackie Control XT) based on a device id, each unit is supposed to be connected to a separate midi port.

It might also be a good idea to increase the maximum sysEx length to 255 bytes. Projects using this library will probably use a Teensy with enough RAM to handle this.

Kind regards,

Gerrit
 
I seem to remember also having a problem with SysEx messages when the max length isn’t set to 255 bytes or when the SysEx messages aren’t handled correctly with the complete boolean. But once I had the SysEx messages being handled correctly I could easily emulate all 16 ports as a Mackie device.

If you want more or less than the 8 ports that BuzzBang has made available it’s easy enough to edit his work and make if variable from 1-16 depending on a define. I just haven’t figured out a way to set it from the project, but best regards with your project.
 
I seem to remember also having a problem with SysEx messages when the max length isn’t set to 255 bytes or when the SysEx messages aren’t handled correctly with the complete boolean. But once I had the SysEx messages being handled correctly I could easily emulate all 16 ports as a Mackie device.

If you want more or less than the 8 ports that BuzzBang has made available it’s easy enough to edit his work and make if variable from 1-16 depending on a define. I just haven’t figured out a way to set it from the project, but best regards with your project.

How did you distinguish the virtual input ports in your code?

I've set the max length to 255, this is more than enough for all sysEx communication regarding Mackie control units so it is not necessary to handle chuncks of sysEx data. In another project I successfully handled large (16k-32k) sysEx program dumps in chuncks of 255 bytes, this will come in handy at a later stage.
The next stage is connecting the two displays and the 16 rotary encoders, when this is completed I can see if de sysEx communication is completely functional.

Kind regards,

Gerrit
 
How did you distinguish the virtual input ports in your code?

I've set the max length to 255, this is more than enough for all sysEx communication regarding Mackie control units so it is not necessary to handle chuncks of sysEx data. In another project I successfully handled large (16k-32k) sysEx program dumps in chuncks of 255 bytes, this will come in handy at a later stage.
The next stage is connecting the two displays and the 16 rotary encoders, when this is completed I can see if de sysEx communication is completely functional.

Kind regards,

Gerrit

The way I distinguished them is a basically had one Teensy that would decode the midi message and then send it over i2c to another Teensy for each bank of channels and I would just have the port number point to an array with each i2c address and vise versa when it read data from the i2c address it would point to its corresponding port. But it’s easy enough to make 3D arrays of all your buttons/leds and knobs and when ever you go to write to it just set the x of the array equal to the port number and y is the button/led or knob ID. This way you don’t have to make a case test for every port.
 
Sorry for the delay in replying. I constantly seem to be snowed under at my day job...

Glad to see that it of use to people :)

vjmusik could you post your code for variable number of ports (up to 16) using a define? That would be very useful, both for me and others. I did mean to update for 16 ports but got pulled off onto other things

SysEx was something I didn't really test. Gerrit, did the changing the max length of the buffer surtout your problems? Vjmusik can you confirm that you're tested sysEx with multiple ports successfully?

Cheers

BuzzBang
 
Sorry for the delay in replying. I constantly seem to be snowed under at my day job...

Glad to see that it of use to people :)

vjmusik could you post your code for variable number of ports (up to 16) using a define? That would be very useful, both for me and others. I did mean to update for 16 ports but got pulled off onto other things

SysEx was something I didn't really test. Gerrit, did the changing the max length of the buffer surtout your problems? Vjmusik can you confirm that you're tested sysEx with multiple ports successfully?

Cheers

BuzzBang

I will have to post the code once I get back, I’m about to leave on a trip for a couple days.

I can confirm that SysEx with multiple ports works from the initial testing I’ve done and no data was lost as far as I could tell with 16 ports and 8 channels of MCU and HUI. Though I never got around to fully emulating the Logic Control with the auto detection but I had all the LCD messages the encoder knob LEDs and the VU meter programmed for the test I did.
 
Sorry for the delay in replying. I constantly seem to be snowed under at my day job...

Glad to see that it of use to people :)

vjmusik could you post your code for variable number of ports (up to 16) using a define? That would be very useful, both for me and others. I did mean to update for 16 ports but got pulled off onto other things

SysEx was something I didn't really test. Gerrit, did the changing the max length of the buffer surtout your problems? Vjmusik can you confirm that you're tested sysEx with multiple ports successfully?

Cheers

BuzzBang

No need to apologise, I'm already glad you're willing to provide support for something you made available for free :)

No, changing the max length does not solve the issue with the ports. It could very well be that I just need to change the code handle the incoming data differently. As far as programming in C is concerned I'm a newbie and still learning.
The other midi events all seem to work fine.

My setup does fully emulate a Logic control base unit and XT unit including the auto detection by Logic Pro and initial sysEx handshake. On startup Logic Pro will send device queries over all available ports to detect new devices and to wake up the connected controllers. Somehow the second query is not handled correctly when I use a case statement to distinguish the ports.

I will have to post the code once I get back, I’m about to leave on a trip for a couple days.

I can confirm that SysEx with multiple ports works from the initial testing I’ve done and no data was lost as far as I could tell with 16 ports and 8 channels of MCU and HUI. Though I never got around to fully emulating the Logic Control with the auto detection but I had all the LCD messages the encoder knob LEDs and the VU meter programmed for the test I did.

128 channels, that's a really large controller setup! May I ask what the intended use is? Any pictures you might want to share?


Kind regards,

Gerrit
 
Vjmusik, that's really encouraging :)
No rush for me on posting the code, look forward to seeing it.

Gerrit, Vjmusik's experience sounds very positive, I'm sure that there's a way to make it work for you.
I don't have Logic, recently purchased Digital Performer and also have Ableton Live and Reason. Happy to look at any code you want to share, and see if it plays ball with the above.

Cheers

Rob
 
Here’s a screenshot of a short video I took before I had the VU meter code in place, but I don’t have video of that currently.

6CE682F3-932B-421D-8053-6850E0D79E41.jpeg

This is a part of a project of mine of replacing an Uptown 990 Automation System on a Trident Vector 432. As a side note I didn’t physically have 128 channels built but I saved all channels on the usb Teensy and then I had a potentiometer setup to scroll through all the channels saved on the Teensy but I had next to latency as far as I could tell with all 128 channels. I wrote the code as a proof of concept to see how much I could do on one Teensy. The results were promising since I only need about 56 channels of automation that I need to make.

The Uptown 990 system was so obsolete because the company went under and they never released an upgrade from the old DOS pc it had to use. So I hooked up a logic analyzer to the cable running to the pc and was able to reverse engineer the i2c data they were using to communicate with the controller board in the console. I used a Teensy LC to replace the old computer and make it work with MCU and HUI.
 
Vjmusik, that's really encouraging :)
No rush for me on posting the code, look forward to seeing it.

Gerrit, Vjmusik's experience sounds very positive, I'm sure that there's a way to make it work for you.
I don't have Logic, recently purchased Digital Performer and also have Ableton Live and Reason. Happy to look at any code you want to share, and see if it plays ball with the above.

Cheers

Rob

Here's a little test containing only the sysEx part:
Code:
// Logic Control system exclusive messages
// changed serialnumber of Logic control from 48 to 49
const byte sysExHeader[5]                       = {0xF0,0x00,0x00,0x66,0x10};
const byte sysExDeviceQuery[7]                  = {0xF0,0x00,0x00,0x66,0x10,0x00,0xF7};
const byte sysExHostConnectionQuery[18]         = {0xF0,0x00,0x00,0x66,0x10,0x01,0x49,0x41,0x31,0x33,0x35,0x36,0x38,0x64,0x7A,0x61,0x41,0xF7};
const byte sysExHostConnectionConfirmation[14]  = {0xF0,0x00,0x00,0x66,0x10,0x03,0x49,0x41,0x31,0x33,0x35,0x36,0x38,0xF7};
const byte sysExVersionReply[12]                = {0xF0,0x00,0x00,0x66,0x10,0x14,0x56,0x31,0x2E,0x30,0x32,0xF7};
// Logic Control XT system exclusive messages
const byte sysExHeaderXT[5]                       = {0xF0,0x00,0x00,0x66,0x11};
const byte sysExDeviceQueryXT[7]                  = {0xF0,0x00,0x00,0x66,0x11,0x00,0xF7};
const byte sysExHostConnectionQueryXT[18]         = {0xF0,0x00,0x00,0x66,0x11,0x01,0x49,0x41,0x31,0x33,0x35,0x36,0x38,0x64,0x7A,0x61,0x41,0xF7};
const byte sysExHostConnectionConfirmationXT[14]  = {0xF0,0x00,0x00,0x66,0x11,0x03,0x49,0x41,0x31,0x33,0x35,0x36,0x38,0xF7};
const byte sysExVersionReplyXT[12]                = {0xF0,0x00,0x00,0x66,0x11,0x14,0x56,0x31,0x2E,0x30,0x32,0xF7};

void handleSysEx(const byte* sysExData, uint16_t sysExSize, bool complete){
  uint8_t midiPort;
  boolean isMackieControlMessage = true;
  boolean isMackieControlXTMessage = true;

  midiPort = usbMIDI.getChannel();
  switch (midiPort) {
    // Logic Control Main on Port 0
    case 0:
      // compare the first five bytes of the message to the sysExHeader to check if it is a Mackie control message
      for (int i=0;i<5;i++) {
        if (sysExData[i]!=sysExHeader[i]) {
          isMackieControlMessage = false;
        }
      }
      // handle mackie control message
      if (isMackieControlMessage) {
        // handle different message types
        switch (sysExData[5]) {
          // respond to device query
          case 0:
            usbMIDI.setMidiCable(0);
            usbMIDI.sendSysEx(18, sysExHostConnectionQuery);
            break;
          // respond to host connection reply with connection confirmation
          case 2:
            usbMIDI.setMidiCable(0);
            usbMIDI.sendSysEx(14, sysExHostConnectionConfirmation);
            break;
          // respond to firmware version request
          case 19:
            usbMIDI.setMidiCable(0);
            usbMIDI.sendSysEx(12, sysExVersionReply);
            break;
          default: 
            // statements
          break;
        }
      }
      break;
    // Logic Control XT on Port 1
    case 1:
      // compare the first five bytes of the message to the sysExHeader to check if it is a Mackie control XT message
      for (int i=0;i<5;i++) {
        if (sysExData[i]!=sysExHeaderXT[i]) {
          isMackieControlXTMessage = false;
        }
       }
      // handle mackie control message
      if (isMackieControlXTMessage) {
        // handle different message types
        switch (sysExData[5]) {
          // respond to device query
          case 0:
            usbMIDI.setMidiCable(1);
            usbMIDI.sendSysEx(18, sysExHostConnectionQueryXT);
            break;
          // respond to host connection reply with connection confirmation
          case 2:
            usbMIDI.setMidiCable(1);
            usbMIDI.sendSysEx(14, sysExHostConnectionConfirmationXT);
            break;
          // respond to firmware version request
          case 19:
            usbMIDI.setMidiCable(1);
            usbMIDI.sendSysEx(12, sysExVersionReplyXT);
            break;
          default: 
            // statements
          break;
        }
      }       
    break;
    // Synth control on Port 2
    case 2:
      // nothing yet
    break;
    default: 
      // ignore sysExData
    break;
  }
}



void setup() {
  // register sysEx event handler function
  usbMIDI.setHandleSysEx(handleSysEx);


}

void loop() {
  // read usbMIDI, let the handlers do their job
  usbMIDI.read(); 

}

The code emulates a Logic Control Basic unit on port 0 and a XT unit on port 1 and will respond to device queries from a DAW. I don't know if your DAWs can work with a Logic control unit, it could be that it has to be a Mackie control. I have a Logic Control hardware unit at hand which I can switch to Mackie control mode. I'll have to take a look at the sysEx communication between the unit and the DAW when in this mode.

Here's the complete code for the project I'm working on:View attachment Zeus-DPC.ino
Just to be clear on this, identifying the units based on the sysEx header in stead of port number solves the problem and allows me to continue but the question remains why the identification by port (as in the example above) does not work.

...This is a part of a project of mine of replacing an Uptown 990 Automation System on a Trident Vector 432.....

Wow, that's a serious console. Very interesting project!


Kind regards,

Gerrit
 
Here's a little test containing only the sysEx part:
Code:
// Logic Control system exclusive messages
// changed serialnumber of Logic control from 48 to 49
const byte sysExHeader[5]                       = {0xF0,0x00,0x00,0x66,0x10};
const byte sysExDeviceQuery[7]                  = {0xF0,0x00,0x00,0x66,0x10,0x00,0xF7};
const byte sysExHostConnectionQuery[18]         = {0xF0,0x00,0x00,0x66,0x10,0x01,0x49,0x41,0x31,0x33,0x35,0x36,0x38,0x64,0x7A,0x61,0x41,0xF7};
const byte sysExHostConnectionConfirmation[14]  = {0xF0,0x00,0x00,0x66,0x10,0x03,0x49,0x41,0x31,0x33,0x35,0x36,0x38,0xF7};
const byte sysExVersionReply[12]                = {0xF0,0x00,0x00,0x66,0x10,0x14,0x56,0x31,0x2E,0x30,0x32,0xF7};
// Logic Control XT system exclusive messages
const byte sysExHeaderXT[5]                       = {0xF0,0x00,0x00,0x66,0x11};
const byte sysExDeviceQueryXT[7]                  = {0xF0,0x00,0x00,0x66,0x11,0x00,0xF7};
const byte sysExHostConnectionQueryXT[18]         = {0xF0,0x00,0x00,0x66,0x11,0x01,0x49,0x41,0x31,0x33,0x35,0x36,0x38,0x64,0x7A,0x61,0x41,0xF7};
const byte sysExHostConnectionConfirmationXT[14]  = {0xF0,0x00,0x00,0x66,0x11,0x03,0x49,0x41,0x31,0x33,0x35,0x36,0x38,0xF7};
const byte sysExVersionReplyXT[12]                = {0xF0,0x00,0x00,0x66,0x11,0x14,0x56,0x31,0x2E,0x30,0x32,0xF7};

void handleSysEx(const byte* sysExData, uint16_t sysExSize, bool complete){
  uint8_t midiPort;
  boolean isMackieControlMessage = true;
  boolean isMackieControlXTMessage = true;

  midiPort = usbMIDI.getChannel();
  switch (midiPort) {
    // Logic Control Main on Port 0
    case 0:
      // compare the first five bytes of the message to the sysExHeader to check if it is a Mackie control message
      for (int i=0;i<5;i++) {
        if (sysExData[i]!=sysExHeader[i]) {
          isMackieControlMessage = false;
        }
      }
      // handle mackie control message
      if (isMackieControlMessage) {
        // handle different message types
        switch (sysExData[5]) {
          // respond to device query
          case 0:
            usbMIDI.setMidiCable(0);
            usbMIDI.sendSysEx(18, sysExHostConnectionQuery);
            break;
          // respond to host connection reply with connection confirmation
          case 2:
            usbMIDI.setMidiCable(0);
            usbMIDI.sendSysEx(14, sysExHostConnectionConfirmation);
            break;
          // respond to firmware version request
          case 19:
            usbMIDI.setMidiCable(0);
            usbMIDI.sendSysEx(12, sysExVersionReply);
            break;
          default: 
            // statements
          break;
        }
      }
      break;
    // Logic Control XT on Port 1
    case 1:
      // compare the first five bytes of the message to the sysExHeader to check if it is a Mackie control XT message
      for (int i=0;i<5;i++) {
        if (sysExData[i]!=sysExHeaderXT[i]) {
          isMackieControlXTMessage = false;
        }
       }
      // handle mackie control message
      if (isMackieControlXTMessage) {
        // handle different message types
        switch (sysExData[5]) {
          // respond to device query
          case 0:
            usbMIDI.setMidiCable(1);
            usbMIDI.sendSysEx(18, sysExHostConnectionQueryXT);
            break;
          // respond to host connection reply with connection confirmation
          case 2:
            usbMIDI.setMidiCable(1);
            usbMIDI.sendSysEx(14, sysExHostConnectionConfirmationXT);
            break;
          // respond to firmware version request
          case 19:
            usbMIDI.setMidiCable(1);
            usbMIDI.sendSysEx(12, sysExVersionReplyXT);
            break;
          default: 
            // statements
          break;
        }
      }       
    break;
    // Synth control on Port 2
    case 2:
      // nothing yet
    break;
    default: 
      // ignore sysExData
    break;
  }
}



void setup() {
  // register sysEx event handler function
  usbMIDI.setHandleSysEx(handleSysEx);


}

void loop() {
  // read usbMIDI, let the handlers do their job
  usbMIDI.read(); 

}

The code emulates a Logic Control Basic unit on port 0 and a XT unit on port 1 and will respond to device queries from a DAW. I don't know if your DAWs can work with a Logic control unit, it could be that it has to be a Mackie control. I have a Logic Control hardware unit at hand which I can switch to Mackie control mode. I'll have to take a look at the sysEx communication between the unit and the DAW when in this mode.

Here's the complete code for the project I'm working on:View attachment 12507
Just to be clear on this, identifying the units based on the sysEx header in stead of port number solves the problem and allows me to continue but the question remains why the identification by port (as in the example above) does not work.



Wow, that's a serious console. Very interesting project!


Kind regards,

Gerrit



The problem with the code posted above is you used usbMIDI.getChannel() by accident instead of usbMIDI.getCable() to get the port number, if you fix that then it should function correctly.
 
The problem with the code posted above is you used usbMIDI.getChannel() by accident instead of usbMIDI.getCable() to get the port number, if you fix that then it should function correctly.

Oops, thank you for noticing this rookie mistake! This is the kind of thing you tend to overlook by yourself.

Kind regards,

Gerrit
 
I forgot I had the files on my iCloud so here they are.

View attachment USB_MIDI_Variable_Cable.zip

To change the number of midi cables you change “#define MIDI_CABLE 8” anywhere from 1-16 in the usb_names.h.
If you’re on a Mac you won’t see the change in the Audio Midi Setup until you unplug the device and delete it from there then reconnect it. Windows is a little more complicated and probably the easiest way is to just change the serial number the same way you can change the device name just so it registers as a new device.

As far as emulating the Logic Control I did do it in Logic but I was using the MCU in Logic Control mode protocol which is the same exact commands just the SysEx device ID is for the MCU and not the Logic Control, however on that same note the MCU protocol and the Logic Control protocol are for the most part functionally identical there’s mostly just less SysEx commands and no handshake based on the documents I’ve read. So from the limited testing I did with Logic I programmed it for the MCU in Logic Control mode and the same code worked for what I tested in Ableton.

When Mackie designed the Logic Control protocol they used separate commands from HUI so you can have your project detect both simultaneously and not have to switch modes like the real MCU and Logic Control has to.
 
I corrected my silly mistake in the code and now it's all working perfectly!

The corrected file: View attachment Zeus-DPC.ino

Emulating a Logic Control uit was the obvious choice as I have a hardware unit and can analyse the communication and the manual contains the complete sysEx documentation. I have used the device in MCU (Mackie Control Unit) mode together with Cubase and noticed that the behaviour with respect to plugin editing is quite different. As my goal is to build a controller with the focus on plugin editing in a DAW I'll stick to Logic Control mode for now.

Kind regards,

Gerrit
 
I corrected my silly mistake in the code and now it's all working perfectly!

The corrected file: View attachment 12510

Emulating a Logic Control uit was the obvious choice as I have a hardware unit and can analyse the communication and the manual contains the complete sysEx documentation. I have used the device in MCU (Mackie Control Unit) mode together with Cubase and noticed that the behaviour with respect to plugin editing is quite different. As my goal is to build a controller with the focus on plugin editing in a DAW I'll stick to Logic Control mode for now.

Kind regards,

Gerrit

Yeah that’s true the way the DAW will respond to MCU will be different as opposed to a Logic Control, but based on looking at the command set they are the same, the only other difference I found is that the encoder knob only has one mode in MCU whereas Logic Control has 4 modes. But yeah I understand what you mean about being about to see the exact commands your physical controller sends, I also worked on emulating a Pro Control that I have and it was pretty simple once you get the handshake part of the Ethernet message down because it mainly uses midi messages to communicate, including SysEx messages. I was also able to figure out how to reverse engineer most of the Diginet devices that Digidesign/Avid made by just changing the device ID in the Ethernet message. From what I found only the Pro Control uses standard midi messages but the rest of them are in a similar format that it’s easy enough to distinguish it.
 
I'm planning to merge this 8X MIDI cable feature, and a number of other updates to bring Teensy's USB MIDI support up to date and in sync with the MIDI 4.3 library.
 
I forgot I had the files on my iCloud so here they are.

View attachment 12508

To change the number of midi cables you change “#define MIDI_CABLE 8” anywhere from 1-16 in the usb_names.h.
If you’re on a Mac you won’t see the change in the Audio Midi Setup until you unplug the device and delete it from there then reconnect it. Windows is a little more complicated and probably the easiest way is to just change the serial number the same way you can change the device name just so it registers as a new device.

As far as emulating the Logic Control I did do it in Logic but I was using the MCU in Logic Control mode protocol which is the same exact commands just the SysEx device ID is for the MCU and not the Logic Control, however on that same note the MCU protocol and the Logic Control protocol are for the most part functionally identical there’s mostly just less SysEx commands and no handshake based on the documents I’ve read. So from the limited testing I did with Logic I programmed it for the MCU in Logic Control mode and the same code worked for what I tested in Ableton.

When Mackie designed the Logic Control protocol they used separate commands from HUI so you can have your project detect both simultaneously and not have to switch modes like the real MCU and Logic Control has to.

Thanks for posting your update to implement all 16 midi ports vjmuzik, great job (getting those numbers right drove me slightly mad hence I stopped when I had the eight ports I needed; straight forward(ish), but very easy to make a mistake), and apologies for not getting back to you re your earlier post.

@Gerrit, thanks for uploading your code. If work pans out as expected I should be back in 'midi mode' in about 3-4 weeks...

@Paul, If you can roll this into the next release that would be fantastic, vjmusik's update for 16 ports is the one to go for. I was being slightly lazy by only doing the 8 ports, but to be fair work pulled me off onto something else with higher priority.
 
@Paul, If you can roll this into the next release that would be fantastic

I've already committed the new code on github.

https://github.com/PaulStoffregen/cores/commit/c9d0122505068854183225a46272da6cc9bcfee7

It's slightly different, but the same general idea. I added an extra optional parameter to the send functions for the cable number, rather than setMidiCable(). I made this decision in anticipation of a future event-driven (via EventResponder) version of usbMIDI and USBHost_t36's MIDI.

I'm also updating the usbMIDI functions to closely match the Arduino MIDI library version 4.3.1 (latest version).

My plan is to publish 1.41-beta3 within a few days, with MIDIx8 in the Tools > USB Type menu, so everyone can easily use it. Could really use some help testing...
 
Status
Not open for further replies.
Back
Top