Go Down

Topic: Using UNO R3 + USB Host Shield to relay MIDI via powered USB Hub? (Read 5068 times) previous topic - next topic

_nic

Hi everyone, I'm new here so I hope I'm getting this done right - please do let me know if this is in the wrong place and so on.

A bit of background, I'm trying to use Arduino to relay MIDI program change messages from a MIDI controller to some MIDI-controllable guitar effect pedals. Both sides are USB slave devices. To be exact, I am using:

- KMI Softstep: MIDI controller, sends MIDI PC and CC as a USB slave device
- Zoom MS60B: guitar effect pedal, can be controlled by MIDI PC as a USB slave device
- Source Audio HUB: guitar effect pedal, can be controlled by MIDI PC as a USB slave device and also forwards MIDI CC messages

What I want to do is this: when I press a button on my MIDI controller, it sends a MIDI PC by USB to the USB hub to the Arduino. The Arduino reads this messages, and sends it back out to everything connected to the USB hub. This means that the controller will also receive this message, but that doesn't matter. It seems to be the easiest way to get all the pedals to receive the same PC at once.

I have done this with my laptop and MidiOX, so in theory it should work. So I ordered an Arduino and a USB Host Shield and set off to give it a try. I'm using the UNO R3 and a Keyes USB Host Shield, which is an OEM but apparently has the same functions as the Arduino USB Host Shield. However, I seem to be hitting a rather steep learning curve here. Some googling and looking at other threads around here gives me some hints, but I can't seem to find exactly what I need, or at least I can't find enough information to help me understand what I need. I'm new to this, so I'm not sure if I'm searching for the correct keywords.

First question:

This is probably the most important thing I want to know - how do I monitor what my Arduino unit is doing? The only suggestion I've seen is to use Serial Monitor, but when I run that with my Arduino plugged into my laptop I don't see anything coming through the serial monitor window on my laptop. I don't think it's a connection problem since I can upload code, and my MIDI devices are able to run off USB power from the USB Host Shield. However, I don't know if I'm using serial monitor wrongly, or whether the MIDI messages are not coming in through the correct pin, or what. I'd like to be able to see if the MIDI messages are coming in.

Second question:

Does this outline seem feasible?
1) Have a loop which continuously reads MIDI input from the USB host shield. I believe MIDI PC is two bytes long, does this mean I have to set two variables to read it? I'm guessing something like I check if the first byte is for PC, and if so, it reads the second byte and saves it as the program change number.
2) Immediately send this same program change number out by the USB host shield.
I believe this should give me the result I need.

Third question:

What methods of reading and writing MIDI should I be looking at? There seems to be a few various ways from what I've seen so far. I'm getting a bit confused.

One thing first, since I'm using a USB Host Shield, I should NOT be using serial read or serial write right?

Other than that... for reading MIDI, I've seen the following options:

1) The Arduino MIDI library (https://github.com/FortySevenEffects/arduino_midi_library/releases/tag/4.2) seems to have direct commands to read and write MIDI, but the examples are all for notes rather than PC messages, and I'm not sure how to find out what exactly these commands do.

2) http://www.thebox.myzen.co.uk/Hardware/MIDI_Shield.html this one seems to be reading the midi byte directly, but can this be done with USB shield instead of MIDI shield?

3) http://forum.arduino.cc/index.php?PHPSESSID=t0btgidk01b02v50kcqiomfla2&topic=205078.0
This part here looks like what I need, but I don't understand it!
Code: [Select]
void MIDI_poll()
{
    byte outBuf[ 3 ];
    uint8_t size;

    if( (size=Midi1.RcvData(outBuf)) > 0 ){
      //MIDI Output
      Serial.write(outBuf, size);
    }
    if( (size=Midi2.RcvData(outBuf)) > 0 ){
      //MIDI Output
      Serial.write(outBuf, size);
    }   
}

Does outBuf[3] here indicate that the message is expected to have 3 bytes? So is this creating a variable that is 3 bytes long, saving incoming USB midi data to outBuf, then using serial.write to send out this data?

I would like to use something like this, but replace the Serial.write with some kind of USB output, for example, I was looking at the code from Lawrence Doss, the method used to send MIDI via a USB host shield is

Code: [Select]
    byte Message[2];                 // Construct the midi message (2 bytes)
    Message[0]=0xC0;                 // 0xC0 is for Program Change
    Message[1]=number;               // Number is the program/patch
    Midi.SendData(Message);          // Send the message


In that case, could I just replace the contents of Midi.SendData() with outbuf instead of Message?

I would try that, but the problem is that when I try to compile this code, it tells me that "MIDI does not name a type". I'm not sure why. On the MIDI library page, it warns that it removes MIDI as an object, but I'm not sure what exactly this means. I'm confused because with the MIDI library, the text of "MIDI" turns orange like "byte" or "int", so I'd have thought it was recognized as a type...

Yeah I think that's all I need to ask for now. This is my first time messing with Arduino or C, I've done some basics of other languages so it's not too bad, but whenever I look around discussions I get lost in the jargon. Also, I haven't seen anyone try to receive and send MIDI via USB both ways, it's always either a converter, or using switches to send MIDI via either USB or 5-pin.

Thanks in advance for any help!

Grumpy_Mike

Not too sure I can be helpful but what you are trying to do is very advanced.

I wrote the www.thebox.myzen.co.uk site and have done quite a bit with MIDI.
However, if I gather correctly you have a USB host shield feeding into a hub feeding to two devices.
Hubs do not just work by themselves they need software to drive and route them and I am not sure the USB host shield has the drivers to do that.

The trick of any project is to break it down into as smaller steps as you can.

I would start by just trying to communicate with one device using the shield, from what I gather that is not too easy. If you get that going then try and put a hub in between them and get that going. Do the same for the other device.

Remember that a USB client can not initiate a transfer of packets it has to come from the host polling the client to see if it has anything to give it.

Quote
I try to compile this code, it tells me that "MIDI does not name a type"
That normally means you have not installed the library in the right place.

Quote
the text of "MIDI" turns orange like "byte" or "int", so I'd have thought it was recognized as a type...
No that just means the name appears in a keywords.txt file in some library somewhere.

_nic

Thank you for the response! Looks like this is more difficult than I originally expected. I had assumed that the hub could run on its own without drivers since I used it with both computers and iPad with CCK to send midi. I guess the iPad must have had sufficient drivers to run the hub too.

Quote
I would start by just trying to communicate with one device using the shield, from what I gather that is not too easy. If you get that going then try and put a hub in between them and get that going. Do the same for the other device.
This seems like a very good plan. I think that's what I'll be doing for today.

If the hub doesn't work, I guess the next step would be to use the host shield just to receive MIDI from my controller, and manually wire up some MIDI jacks to serve as outputs. Or maybe just give up on using this controller, and instead wire up some switches.

Quote
That normally means you have not installed the library in the right place.
Thanks, I'll check up on that.

Quote
No that just means the name appears in a keywords.txt file in some library somewhere.
Just looked up the keywords.txt file in the MIDI library version 4.2 folder and yeah it's there. I'll see what's up with this. Thanks!

_nic

Okay, so some updates:

1) I decided to just go with Lawrence Doss's code and use switches to send MIDI. This works perfectly fine with one device.

2) After that, I added a USB Hub. If I just have one device, I could control it no problem with the inclusion of the USBHub Hub1(&Usb) instance. Even if both devices are connected to the USB Hub, and I have created two midi instances (i.e. USBH_MIDI  Midi1(&Usb); and USBH_MIDI  Midi2(&Usb);), as long as I only try to send to one device, there's no problem.

3) But when try to send to both devices, I have a very strange error.

Lawrence's code uses a function titled "SendMIDI" which sends a particular MIDI number to the USB device. It does so using the following code:
Code: [Select]
byte Message[2];                 // Construct the midi message (2 bytes)
    Message[0]=0xC0;                 // 0xC0 is for Program Change
    Message[1]=number;               // Number is the program/patch
    Midi.SendData(Message);          // Send the message


I made a small modification,

Code: [Select]
byte Message[2];                 // Construct the midi message (2 bytes)
    Message[0]=0xC0;                 // 0xC0 is for Program Change
    Message[1]=number;               // Number is the program/patch
    Midi1.SendData(Message);          // Send the message
    Midi2.SendData(Message);          // Send the message


and put in two instances at the top of the code for Midi1 and Midi2. So in theory, every time the SendMIDI function is called, it sends the same midi message to both Midi ports. At the moment I have two pedals attached to it as seen in this video - https://www.youtube.com/watch?v=TkQKiFhTPkg
On the left is a Source Audio pedal and on the right is the Zoom MS50G. It seems that the Source Audio is connected to Midi1, and the Zoom to Midi2.

So the problem is that when I press a button, it sends to the Zoom MS50G (on the right in the video link below) first, and then when I press the button again, then only does it send the previous command to the Source Audio (on the left). Basically, if I try to send the same command to Midi1 and Midi2, it sends to Midi2 first, and then only sends that command to Midi1 the next time I press the button.

Two examples:

1) If I keep pressing the same button, it's supposed to send 01, 00, 01, 00, and so on simultaneously to both devices. But when I do that, the first time I press, it sets the Zoom to 01, then the second time I press, it sets the Zoom to 00 and the Source Audio to 01; the third time the Zoom goes to 01 and the Source Audio to 00, and so on.

2) If I press the various buttons one by one, it's supposed to send 01, 02, 03 and so on to both devices simultaneously. However, what instead happens is that it first sets the Zoom to 01, then it sets the Zoom to 02 and the Source Audio to 01, then the Zoom to 03 and the Source Audio to 02, and so on.

I cannot understand why this is happening.

Also, if I swap the two devices, i.e. so that the Source Audio is now connected to Midi2, and the Zoom to Midi1, then the Zoom 'lags' instead of the Source Audio.

Based on this, and the fact that if I only try to send to one of the devices at a time, there's no problem, I don't think the problem is with the devices, the problem is more likely to be somewhere in the code.

Does anyone know what I might be doing wrong?

The full code is too long to attach here, but I'll copy and paste the important parts here. The bulk of the credit should go to Lawrence Doss (lawrenceadoss@yahoo.com & http://lawrencedoss.blogspot.in/):
Code: [Select]
#include <Usb.h>
#include <usbhub.h>
#include <usbh_midi.h>
#include <SoftPWM.h>

USB Usb;
USBHub Hub1(&Usb);
USBH_MIDI  Midi1(&Usb);
USBH_MIDI  Midi2(&Usb);

//Input pin declaration
int inPin1 = 2; // patch selection switch 1
int inPin2 = 3; // patch selection switch 2
int inPin3 = 4; // patch selection switch 3
int inPin4 = 5; // patch selection switch 4
int inPin5 = 6; // bank selection switch

// Output pin declaration
int outPin1 = 14; //indicator for patch 1/5
int outPin2 = 15; //indicator for patch 2/6
int outPin3 = 16; //indicator for patch 3/7
int outPin4 = 17; //indicator for patch 4/8
int outPinErr = 18; // error indicator

byte PatchA1 = 0x01;
byte PatchA2 = 0x05;
byte PatchB1 = 0x02;
byte PatchB2 = 0x06;
byte PatchC1 = 0x03;
byte PatchC2 = 0x07;
byte PatchD1 = 0x04;
byte PatchD2 = 0x08;
byte PatchOff = 0x00;

//Current state of output pins
int state1 = LOW;
int state2 = LOW;
int state3 = LOW;
int state4 = LOW;

//Current reading from the input pins
int reading1;
int reading2;
int reading3;
int reading4;
int reading5;

// Previous reading from the input pins
int previous1 = LOW;
int previous2 = LOW;
int previous3 = LOW;
int previous4 = LOW;

// DEBOUNCE SEQUENCE ---
// the follow variables are long's because the time, measured in miliseconds,
// will quickly become a bigger number than can be stored in an int.
long time = 0;         // the last time the output pin was toggled
long debounce = 200;   // the debounce time, increase if the output flickers


void setup()
{
  pinMode(inPin1, INPUT);
  pinMode(inPin2, INPUT);
  pinMode(inPin3, INPUT);
  pinMode(inPin4, INPUT);
  pinMode(inPin5, INPUT);
  pinMode(outPin1, OUTPUT);
  pinMode(outPin2, OUTPUT);
  pinMode(outPin3, OUTPUT);
  pinMode(outPin4, OUTPUT);
  pinMode(outPinErr, OUTPUT);

  pinMode( 7, OUTPUT);  // For the sheild
  digitalWrite( 7, HIGH); // for the sheild

  if (Usb.Init() == -1)
  {
    digitalWrite(outPinErr, HIGH);
    while(1);               // Halt
  }
  delay(200);
  Preset = 1;
}

void loop()
{
  reading1 = digitalRead(inPin1);
  reading2 = digitalRead(inPin2);
  reading3 = digitalRead(inPin3);
  reading4 = digitalRead(inPin4);
  reading5 = digitalRead(inPin5);

  // if the input just went from LOW and HIGH and we've waited long enough
  // to ignore any noise on the circuit, toggle the output pin and remember
  // the time
  {
    if (reading1 == HIGH && previous1 == LOW && millis() - time > debounce) {
      if (state1 == HIGH)
      {
        // Send program change for BYPASS / no effect
        SendMIDI(PatchOff);
        state1 = LOW;
      }
      else
      {
        if (reading5 == HIGH)
        {
          // Send program change for EFFECT - 1
          SendMIDI(PatchA1);
          state1 = HIGH;
          state2 = LOW;
          state3 = LOW;
          state4 = LOW;
        }
        else
        {
          // Send program change for EFFECT - 5
          SendMIDI(PatchA2);
          state1 = HIGH;
          state2 = LOW;
          state3 = LOW;
          state4 = LOW;
        }
      }
      time = millis();
    }
    digitalWrite(outPin1, state1);
    digitalWrite(outPin2, state2);
    digitalWrite(outPin3, state3);
    digitalWrite(outPin4, state4);
    previous1 = reading1;
  }
// I've omitted the code for the other three buttons, but they are identical to the first button

void SendMIDI(byte number)
{
  Usb.Task();
  if( Usb.getUsbTaskState() == USB_STATE_RUNNING )
  {
    byte Message[2];                 // Construct the midi message (2 bytes)
    Message[0]=0xC0;                 // 0xC0 is for Program Change
    Message[1]=number;               // Number is the program/patch
    Midi1.SendData(Message);          // Send the message
    Midi2.SendData(Message);          // Send the message
    delay(10);
  }
  else
  {
    digitalWrite(outPinErr, HIGH);
    delay (500);
    digitalWrite(outPinErr, LOW);
  }
}

Grumpy_Mike

In the realm of speculation here but your modifications use the same array to send a message to two devices. I would try using two seprate arrays one for each midi instance.

_nic

In the realm of speculation here but your modifications use the same array to send a message to two devices. I would try using two seprate arrays one for each midi instance.
I tried that too, duplicated it like

Code: [Select]
void SendMIDI1(byte number)
{
  Usb.Task();
  if( Usb.getUsbTaskState() == USB_STATE_RUNNING )
  {
    byte Message[2];                 // Construct the midi message (2 bytes)
    Message[0]=0xC0;                 // 0xC0 is for Program Change
    Message[1]=number;               // Number is the program/patch
    Midi1.SendData(Message);          // Send the message
    delay(10);
  }
  else
  {
    digitalWrite(outPinErr, HIGH);
    delay (500);
    digitalWrite(outPinErr, LOW);
  }
}
void SendMIDI2(byte number)
{
  Usb.Task();
  if( Usb.getUsbTaskState() == USB_STATE_RUNNING )
  {
    byte Message[2];                 // Construct the midi message (2 bytes)
    Message[0]=0xC0;                 // 0xC0 is for Program Change
    Message[1]=number;               // Number is the program/patch
    Midi2.SendData(Message);          // Send the message
    delay(10);
  }
  else
  {
    digitalWrite(outPinErr, HIGH);
    delay (500);
    digitalWrite(outPinErr, LOW);
  }
}


and called for both, e.g.
Code: [Select]

        SendMIDI1(PatchOff);
        SendMIDI2(PatchOff);


but the result was exactly the same so I switched back to reduce the number of lines...

Is that what you mean?

By the way, in the USBH_MIDI library example for multiple outputs on the same USB hub, the creator of the library used two separate Do{} to send to each output. I was thinking of trying that, but I wasn't sure what to use under While. That code did this:
Code: [Select]
{
    byte outBuf[ 3 ];
    uint8_t size;

    do {
      if( (size=Midi1.RecvData(outBuf)) > 0 ){
        //MIDI Output
        _MIDI_SERIAL_PORT.write(outBuf, size);
      }
    }while(size>0);
    do {
      if( (size=Midi2.RecvData(outBuf)) > 0 ){
        //MIDI Output
        _MIDI_SERIAL_PORT.write(outBuf, size);
      }
    }while(size>0);
}

which I take to be the following process:
1) Create a 3-byte-long variable called outBuf
2) Receive USB midi data from the first port on the powered USB hub
3) Send the bytes one by one through a midi shield until there's no more bytes left
4) Repeat steps 2 and 3 on input from the second port on the powered USB hub

However, in my case I couldn't think of anything to use for "while".

Grumpy_Mike

Quote
Is that what you mean?
No.

Code: [Select]

byte Message1[2],Message2[2];                 // Construct the midi message (2 bytes)
    Message1[0]=0xC0;                 // 0xC0 is for Program Change
    Message1[1]=number;               // Number is the program/patch
    Message2[0]=0xC0;                 // 0xC0 is for Program Change
    Message2[1]=number;               // Number is the program/patch
 
    Midi1.SendData(Message1);          // Send the message
    Midi2.SendData(Message2);          // Send the message


That do ... while construct looks very convoluted and not something I would ever use.

_nic

No.

Code: [Select]

byte Message1[2],Message2[2];                 // Construct the midi message (2 bytes)
    Message1[0]=0xC0;                 // 0xC0 is for Program Change
    Message1[1]=number;               // Number is the program/patch
    Message2[0]=0xC0;                 // 0xC0 is for Program Change
    Message2[1]=number;               // Number is the program/patch
 
    Midi1.SendData(Message1);          // Send the message
    Midi2.SendData(Message2);          // Send the message


That do ... while construct looks very convoluted and not something I would ever use.
I still get the same result, that one lags behind the other.

Also, I thought of a possible workaround, since it sends the other command on the next button push, perhaps if I set it to send the same command twice it might work if what it's doing is (for whatever reason) sending the lagged command on the next SendMIDI. Nope, now it just completely doesn't send to Midi1. So I suspect whatever the problem is, it's tied to the button push.

Grumpy_Mike

Quote
that one lags behind the other.
Maybe something needs flushing. How about sending a third "useless" message like a program change on a channel you are not using. Something like this:-

Code: [Select]

    Midi1.SendData(Message1);          // Send the message 1
    Midi2.SendData(Message2);          // Send the message 2
    Message1[0]=0xCF;                 // 0xCF is for Program Change on a MIDI channel you are not using
    Message1[1]=number;               // Number is the program/patch
    Midi1.SendData(Message1);


Quote
So I suspect whatever the problem is, it's tied to the button push.
No that is unlikely, I suspect it is the MIDI handlers.

_nic

Maybe something needs flushing. How about sending a third "useless" message like a program change on a channel you are not using. Something like this:-

Code: [Select]

    Midi1.SendData(Message1);          // Send the message 1
    Midi2.SendData(Message2);          // Send the message 2
    Message1[0]=0xCF;                 // 0xCF is for Program Change on a MIDI channel you are not using
    Message1[1]=number;               // Number is the program/patch
    Midi1.SendData(Message1);

Wonderful! That worked, thank you!

Previously I tried sending the whole thing again twice, I guess the key to making it work is to re-define the variable.

I've a new problem now - I suspect I was using a loophole on the Source Audio pedals. Today I couldn't get it to work with the MIDI controller, and after some tests I've found it will only accept MIDI USB after connecting to laptop. I think after that it's set to accept MIDI USB, but when you restart the pedal it doesn't accept MIDI USB by default. Oh well, that's a non-Arduino problem, I'm glad the Arduino side is all fixed now.

_nic

Fixed the problem with the Source Audio pedals, here's the MIDI controller in action now:

http://youtu.be/CMnZ0MOg7No

I accidentally messed up the LEDs on the right while closing the enclosure, but I'm too tired to go fix those now (and anyway the patch number will appear on the Zoom unit, so I'll use that as my actual indicator until I get down to fixing the LEDs I guess).

I also made some changes to the code, instead of going back to a default "off" state if you press the footswitch twice, I'm just setting the footswitches to send. That's more manageable for me.

Grumpy_Mike

Well done. I am glad to help, I did think there wasn't going to be a solution but great work from you to find one.

Toothay

Hello everyone, I am new to the subject and require some help with code. Maybe someone can set me on the track: I have a midi footcontroller that sends program changes and can send control changes. Here is what I would like to achieve. I have a Zoom MS 60B effectpedal with a USB input…I want to read serial incoming midi messages to the arduino and forward them to the zoom pedal via an USB shield. I have seen Lawrence Doss build a controller with switches attached to the arduino and then forward this message to the usb shield and out to the effectpedal…instead of switches I already have a midi message with a program change coming from my external controller. How do I get the arduino to read incoming midi and "relay" that to the usb shield where the message then reches the effect pedal? Is there a way to translate incoming midi to the usb shield? Would I need the regular midi library and the usb midi library in the arduino sketch? Thanks, for all help and some examples to start with
Best Regards

Go Up