YuuichiAkagawa/Arduino-UHS2MIDI

Example showing how to use multiple USB MIDI devices, and be able to identify them individually

Closed this issue · 5 comments

Unsure if this is really the correct project to be asking this, but I'd love to see an example that shows how to identify and handle multiple USB devices.

Currently I'm doing this by having multiple UHS2MIDI_CREATE_INSTANCEs, eg

`USB Usb;
USBHub Hub1(&Usb);

UHS2MIDI_CREATE_INSTANCE(&Usb,0,Midi1);
UHS2MIDI_CREATE_INSTANCE(&Usb,0,Midi2);
UHS2MIDI_CREATE_INSTANCE(&Usb,0,Midi3);`

But it isn't clear to me how to have it so, say, the code knows that Midi1 is my Beatstep and Midi2 is my APCmini, so that I can treat them differently in the code.

I've tried to achieve this by modifying the underlying USB_Host_Shield_Library to pass the USBH_MIDI object through to the attached OnInit function, but then I can't figure out how to get the corresponding object (transport object?) that can I can call eg sendClock() on.

Maybe just a pointer to an explanation of how this whole 'stack' relates to each other may help?

Thank you for reading and any help :)

It's not elegant, but you can identify individual devices with the code below.
Change the USB VID and PID in the setupmidi() function to match your device.

#include <UHS2-MIDI.h>
#include <usbhub.h>
USB Usb;
USBHub  Hub1(&Usb);

#define NUMBER_OF_DEVICES 3
UHS2MIDI_CREATE_INSTANCE(&Usb, 0, Midi1);
UHS2MIDI_CREATE_INSTANCE(&Usb, 0, Midi2);
UHS2MIDI_CREATE_INSTANCE(&Usb, 0, Midi3);

MIDI_NAMESPACE::MidiInterface<UHS2MIDI_NAMESPACE::uhs2MidiTransport> *Midi[] {&Midi1, &Midi2, &Midi3};
//The instance name of uhs2MidiTransport is prefixed with __uhs2.
UHS2MIDI_NAMESPACE::uhs2MidiTransport *MidiTransports[] {&__uhs2Midi1, &__uhs2Midi2, &__uhs2Midi3};

//device index
uint8_t ixBeatStep = 0xff;
uint8_t ixMPD218   = 0xff;
uint8_t ixnanoKEY  = 0xff;

void setupmidi(uint8_t idx)
{
  uint16_t vid = MidiTransports[idx]->idVendor();
  uint16_t pid = MidiTransports[idx]->idProduct();
  char buf[16];
  sprintf(buf, "%d:%04X,%04X - ",  idx, vid, pid);
  Serial.print(buf);

  //is Arturia BeatStep?
  if ( vid == 0x1c75 && pid == 0x0206 ) {
    ixBeatStep = idx;
    Serial.println("BeatStep connected.");
    return;
  }

  //is AKAI MPD218?
  if ( vid == 0x09e8 && pid == 0x0034 ) {
    ixMPD218 = idx;
    Serial.println("MPD218 connected.");
    return;
  }

  //is KORG nanoKEY?
  if ( vid == 0x0944 && pid == 0x010D ) {
    ixnanoKEY = idx;
    Serial.println("nanoKEY connected.");
    return;
  }
}

void onInit1()
{
  setupmidi(0);
}

void onInit2()
{
  setupmidi(1);
}

void onInit3()
{
  setupmidi(2);
}

void handleNoteOn1(byte inChannel, byte inNumber, byte inVelocity)
{
  Serial.print("NoteOn(0)  ");
  Serial.print(inNumber);
  Serial.print("\tvelocity: ");
  Serial.println(inVelocity);
}

void handleNoteOff1(byte inChannel, byte inNumber, byte inVelocity)
{
  Serial.print("NoteOff(0) ");
  Serial.print(inNumber);
  Serial.print("\tvelocity: ");
  Serial.println(inVelocity);
}

void handleNoteOn2(byte inChannel, byte inNumber, byte inVelocity)
{
  Serial.print("NoteOn(1)  ");
  Serial.print(inNumber);
  Serial.print("\tvelocity: ");
  Serial.println(inVelocity);
}

void handleNoteOff2(byte inChannel, byte inNumber, byte inVelocity)
{
  Serial.print("NoteOff(1) ");
  Serial.print(inNumber);
  Serial.print("\tvelocity: ");
  Serial.println(inVelocity);
}

void handleNoteOn3(byte inChannel, byte inNumber, byte inVelocity)
{
  Serial.print("NoteOn(2)  ");
  Serial.print(inNumber);
  Serial.print("\tvelocity: ");
  Serial.println(inVelocity);
}

void handleNoteOff3(byte inChannel, byte inNumber, byte inVelocity)
{
  Serial.print("NoteOff(2) ");
  Serial.print(inNumber);
  Serial.print("\tvelocity: ");
  Serial.println(inVelocity);
}

void setup() {
  Serial.begin(115200);
  __uhs2Midi1.attachOnInit(onInit1);
  Midi1.begin(MIDI_CHANNEL_OMNI);

  __uhs2Midi2.attachOnInit(onInit2);
  Midi2.begin(MIDI_CHANNEL_OMNI);

  __uhs2Midi3.attachOnInit(onInit3);
  Midi3.begin(MIDI_CHANNEL_OMNI);

  Midi1.setHandleNoteOn(handleNoteOn1);
  Midi1.setHandleNoteOff(handleNoteOff1);

  Midi2.setHandleNoteOn(handleNoteOn2);
  Midi2.setHandleNoteOff(handleNoteOff2);

  Midi3.setHandleNoteOn(handleNoteOn3);
  Midi3.setHandleNoteOff(handleNoteOff3);

  if (Usb.Init() == -1) {
    while (1); //halt
  }//if (Usb.Init() == -1...
  delay( 200 );

}

void loop() {
  Usb.Task();

  //BeatStep
  if ( ixBeatStep != 0xff) {
    do {
      Midi[ixBeatStep]->read();
    } while ( MidiTransports[ixBeatStep]->available() > 0);
  }

  //MPD218
  if ( ixMPD218 != 0xff) {
    do {
      Midi[ixMPD218]->read();
    } while ( MidiTransports[ixMPD218]->available() > 0);
  }

  //nanoKEY
  if ( ixnanoKEY != 0xff) {
    do {
      Midi[ixnanoKEY]->read();
    } while ( MidiTransports[ixnanoKEY]->available() > 0);
  }
}

Thank you very much! That's certainly more elegant than my attempt so far!

I have some questions about how this works and how it could be improved, but I'll have a play with this myself first, to figure out exactly how it works. Thanks again :)

Thanks so much @YuuichiAkagawa ! This has really got me started on a much more elegant prototype of my project.

I've come up with an example to make it a bit easier to tell the devices apart and write code that targets only particular devices. Rough sketch below, or a more fleshed-out app at https://github.com/doctea/usb_midi_clocker

If I tidy this example up to remove the unnecessary bits, and make a pull request would you be interested in accepting it?

I can also imagine that some it would be possible to make some wrapper functions to make it even easier to deal with separate devices with different capabilities.


#include <UHS2-MIDI.h>
#include <usbhub.h>
USB Usb;
USBHub  Hub1(&Usb);

#define PPQN  24  // midi pulses per quarter note (1 beat)

#define NUMBER_OF_DEVICES 3
UHS2MIDI_CREATE_INSTANCE(&Usb, 0, Midi1);
UHS2MIDI_CREATE_INSTANCE(&Usb, 0, Midi2);
UHS2MIDI_CREATE_INSTANCE(&Usb, 0, Midi3);

// The Midi[] array holds the MidiInterface objects (that you can call sendNoteOn(), sendClock(), setHandleNoteOn() etc on)
MIDI_NAMESPACE::MidiInterface<UHS2MIDI_NAMESPACE::uhs2MidiTransport> *Midi[] {&Midi1, &Midi2, &Midi3};
//The instance name of uhs2MidiTransport is prefixed with __uhs2.
UHS2MIDI_NAMESPACE::uhs2MidiTransport *MidiTransports[] {&__uhs2Midi1, &__uhs2Midi2, &__uhs2Midi3};

MIDI_NAMESPACE::MidiInterface<UHS2MIDI_NAMESPACE::uhs2MidiTransport> *midi_beatstep;
MIDI_NAMESPACE::MidiInterface<UHS2MIDI_NAMESPACE::uhs2MidiTransport> *midi_apcmini;
MIDI_NAMESPACE::MidiInterface<UHS2MIDI_NAMESPACE::uhs2MidiTransport> *midi_bamble;

//device index
uint8_t ixBeatStep = 0xff;
uint8_t ixAPCmini  = 0xff;
uint8_t ixBamble   = 0xff;

// for tracking clock
unsigned long last_tick;
unsigned long ticks = 0;

float bpm_current = 60.0f;
double ms_per_tick = 1000.0f * (60.0f / (double)(bpm_current * (double)PPQN));

void setupmidi(uint8_t idx)
{
  uint16_t vid = MidiTransports[idx]->idVendor();
  uint16_t pid = MidiTransports[idx]->idProduct();
  char buf[16];
  sprintf(buf, "%d:%04X,%04X - ",  idx, vid, pid);
  Serial.print(buf);

  if ( vid == 0x1c75 && pid == 0x0206 ) {         //is Arturia BeatStep?
    ixBeatStep = idx;
    Serial.println("BeatStep connected.");
    Midi[idx]->setHandleNoteOn(handleNoteOn1);
    Midi[idx]->setHandleNoteOff(handleNoteOff1);
    midi_beatstep = Midi[idx];
    return;
  } else if ( vid == 0x09e8 && pid == 0x0028 ) {   //is AKAI APCmini?
    ixAPCmini = idx;
    Serial.println("AKAI APCmini connected.");
    Midi[idx]->setHandleNoteOn(handleNoteOn2);
    Midi[idx]->setHandleNoteOff(handleNoteOff2);
    midi_apcmini = Midi[idx];
    return;
  } else if ( vid == 0x2886 && pid == 0x800B ) {            //is BAMBLE?
    ixBamble = idx;
    Serial.println("BAMBLEWEENY connected.");
    Midi[idx]->setHandleNoteOn(handleNoteOn3);
    Midi[idx]->setHandleNoteOff(handleNoteOff3);
    midi_bamble = Midi[idx];
    return;
  }
}

void onInit1()
{
  setupmidi(0);
}

void onInit2()
{
  setupmidi(1);
}

void onInit3()
{
  setupmidi(2);
}

void handleNoteOn1(byte inChannel, byte inNumber, byte inVelocity)
{
  Serial.print("BeatStep\tNoteOn(");
  Serial.print(ixBeatStep);
  Serial.print(")\t");
  Serial.print(inNumber);
  Serial.print("\tvelocity:\t");
  Serial.println(inVelocity);
}

void handleNoteOff1(byte inChannel, byte inNumber, byte inVelocity)
{
  Serial.print("BeatStep\tNoteOff(");
  Serial.print(ixBeatStep);
  Serial.print(")\t");
  Serial.print(inNumber);
  Serial.print("\tvelocity:\t");
  Serial.println(inVelocity);
}

void handleNoteOn2(byte inChannel, byte inNumber, byte inVelocity)
{
  ticks = 0;  // reset counter
  Serial.print("Received a note from APCmini -- sending stop+start to devices [ ");
  if (midi_beatstep) {
    Serial.print("beatstep ");
    midi_beatstep->sendStop();
    midi_beatstep->sendStart();
  }
  if (midi_bamble) {
    Serial.print("bamble ");
    midi_bamble->sendStop();
    midi_bamble->sendStart();
  }
  Serial.println("]");
  
  Serial.print("APCmini \tNoteOn(");
  Serial.print(ixAPCmini);
  Serial.print(")\t");
  Serial.print(inNumber);
  Serial.print("\tvelocity:\t");
  Serial.println(inVelocity);
}

void handleNoteOff2(byte inChannel, byte inNumber, byte inVelocity)
{
  Serial.print("APCmini \tNoteOff(");
  Serial.print(ixAPCmini);
  Serial.print(")\t");
  Serial.print(inNumber);
  Serial.print("\tvelocity:\t");
  Serial.println(inVelocity);
}

void handleNoteOn3(byte inChannel, byte inNumber, byte inVelocity)
{
  Serial.print("Bamble\tNoteOn(");
  Serial.print(ixBamble);
  Serial.print(")\t");
  Serial.print(inNumber);
  Serial.print("\tvelocity: ");
  Serial.println(inVelocity);
}

void handleNoteOff3(byte inChannel, byte inNumber, byte inVelocity)
{
  Serial.print("Bamble\tNoteOff(");
  Serial.print(ixBamble);
  Serial.print(")\t");
  Serial.print(inNumber);
  Serial.print("\tvelocity:\t");
  Serial.println(inVelocity);
}

void setup() {
  Serial.begin(115200);
  __uhs2Midi1.attachOnInit(onInit1);
  Midi1.turnThruOff();
  Midi1.begin(MIDI_CHANNEL_OMNI);

  __uhs2Midi2.attachOnInit(onInit2);
  Midi2.turnThruOff();
  Midi2.begin(MIDI_CHANNEL_OMNI);

  __uhs2Midi3.attachOnInit(onInit3);
  Midi3.turnThruOff();
  Midi3.begin(MIDI_CHANNEL_OMNI);

  pinMode(4, OUTPUT); // for flashing LED (or sending clock pulse!) every 4 beats
  pinMode(5, OUTPUT); // for flashing LED on every beat
  
  /*(Midi1.setHandleNoteOn(handleNoteOn1);
  Midi1.setHandleNoteOff(handleNoteOff1);

  Midi2.setHandleNoteOn(handleNoteOn2);
  Midi2.setHandleNoteOff(handleNoteOff2);

  Midi3.setHandleNoteOn(handleNoteOn3);
  Midi3.setHandleNoteOff(handleNoteOff3);*/

  if (Usb.Init() == -1) {
    while (1); //halt
  }//if (Usb.Init() == -1...
  delay( 200 );

}

void loop() {
  Usb.Task();

  //BeatStep
  if ( ixBeatStep != 0xff) {
    do {
      Midi[ixBeatStep]->read();
    } while ( MidiTransports[ixBeatStep]->available() > 0);
  }

  //MPD218
  if ( ixAPCmini != 0xff) {
    do {
      Midi[ixAPCmini]->read();
    } while ( MidiTransports[ixAPCmini]->available() > 0);
  }

  //nanoKEY
  if ( ixBamble != 0xff) {
    do {
      Midi[ixBamble]->read();
    } while ( MidiTransports[ixBamble]->available() > 0);
  }

  // send clock to BeatStep and Bamble only
  if ((millis() - last_tick) >= ms_per_tick) {
    last_tick = millis();
    ticks++;
    if (midi_beatstep) midi_beatstep->sendClock();  // could also use Midi[ixBeatStep].sendClock()
    if (midi_bamble) midi_bamble->sendClock();

    // blink LED (or clock output) connected to pin 5 on each quarter note
    if (ticks == 0 || ticks%PPQN==0) 
      digitalWrite(5, HIGH);
    else if (ticks == 2 || ticks%PPQN==2) 
      digitalWrite(5, LOW);

    if (ticks == 0 || ticks%(PPQN*4)==0) 
      digitalWrite(4, HIGH);
    else if (ticks == 2 || ticks%(PPQN*4)==2)
      digitalWrite(4, LOW);
  }
}

Pull requests are always welcome.

It's a good idea to give it a name.

MIDI_NAMESPACE::MidiInterface<UHS2MIDI_NAMESPACE::uhs2MidiTransport> *midi_beatstep;
MIDI_NAMESPACE::MidiInterface<UHS2MIDI_NAMESPACE::uhs2MidiTransport> *midi_apcmini;
MIDI_NAMESPACE::MidiInterface<UHS2MIDI_NAMESPACE::uhs2MidiTransport> *midi_bamble;

I also refactored it.
You can get the transport from MidiInterface. Therefore, you can delete midiTransports[].

////The instance name of uhs2MidiTransport is prefixed with __uhs2.
//UHS2MIDI_NAMESPACE::uhs2MidiTransport *MidiTransports[] {&__uhs2Midi1, &__uhs2Midi2, &__uhs2Midi3};

void setupmidi(uint8_t idx)
{
//  uint16_t vid = MidiTransports[idx]->idVendor();
//  uint16_t pid = MidiTransports[idx]->idProduct();
  uint16_t vid = Midi[idx]->getTransport()->idVendor();
  uint16_t pid = Midi[idx]->getTransport()->idProduct();
//  __uhs2Midi1.attachOnInit(onInit1);
  Midi1.getTransport()->attachOnInit(onInit1);
  Midi1.begin(MIDI_CHANNEL_OMNI);
//    } while ( MidiTransports[ixBeatStep]->available() > 0);
    } while ( Midi[ixBeatStep]->getTransport()->available() > 0);

By giving it a name, the index variable is no longer needed.

Closing this issue due to lack of feedback. Feel free to reopen the issue again if needed.