What USB and Port settings for Hardware MIDI on LC?

Status
Not open for further replies.

mmryspace

Active member
Using tutorial provided here (https://www.pjrc.com/teensy/td_libs_MIDI.html), I'm unable to receive hardware MIDI input from a hardware sequencer. Any guidance is greatly appreciated!

I am a new coder and new to Teensy. Was successful with a MIDI monitor sketch using UNO and a different sketch and basic hardware MIDI I/O circuit but due to UNO limitations, the sketch stopped working at higher BPMs, which is why I moved to Teensy.

Trying to determine what I am missing here? Do I need additional usbMIDI commands included in the sketch for the serial output?

I am also unclear what USB and Port settings for Hardware MIDI on my Teensy-LC?

I've followed the schematic here: https://www.pjrc.com/teensy/td_libs_MIDI_sch_t3.png
td_libs_MIDI_sch_t3.png

Using this sketch:
Code:
/* MIDI Input Test - for use with Teensy or boards where Serial is separate from MIDI
 * As MIDI messages arrive, they are printed to the Arduino Serial Monitor.
 *
 * Where MIDI is on "Serial", eg Arduino Duemilanove or Arduino Uno, this does not work!
 *
 * This example code is released into the public domain.
 */
 
#include <MIDI.h>

MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI);

void setup() {
  MIDI.begin(MIDI_CHANNEL_OMNI);
  Serial.begin(57600);
  Serial.println("MIDI Input Test");
}

unsigned long t=0;

void loop() {
  int type, note, velocity, channel, d1, d2;
  if (MIDI.read()) {                    // Is there a MIDI message incoming ?
    byte type = MIDI.getType();
    switch (type) {
      case midi::NoteOn:
        note = MIDI.getData1();
        velocity = MIDI.getData2();
        channel = MIDI.getChannel();
        if (velocity > 0) {
          Serial.println(String("Note On:  ch=") + channel + ", note=" + note + ", velocity=" + velocity);
        } else {
          Serial.println(String("Note Off: ch=") + channel + ", note=" + note);
        }
        break;
      case midi::NoteOff:
        note = MIDI.getData1();
        velocity = MIDI.getData2();
        channel = MIDI.getChannel();
        Serial.println(String("Note Off: ch=") + channel + ", note=" + note + ", velocity=" + velocity);
        break;
      default:
        d1 = MIDI.getData1();
        d2 = MIDI.getData2();
        Serial.println(String("Message, type=") + type + ", data = " + d1 + " " + d2);
    }
    t = millis();
  }
  if (millis() - t > 10000) {
    t += 10000;
    Serial.println("(inactivity)");
  }
}

Here's the photos of my MIDI I/O circuit built based on the PJRC schematic (pins are correctly connected for serial 1 & power but camera angle is not):
IMG_5401.jpg
IMG_5404.jpg
IMG_5405.jpg
IMG_5406.jpg

MIDI out from sequencer to Board Hardware Din input and Teensy-LC USB plugged into computer USB and is recognized:
Screen Shot 2020-10-03 at 12.17.18 PM.png
 
Hi forum admins - is there any way you can move my post to the audio forums? Perhaps I could receive some guidance there? Apologies if I posted in the wrong place...
 
I've looked that breadboard over several times and keep thinking I see the problem but then it's just me...

The Tx line isn't clear (at the resistor) but you say that's working and I can't see anything with the Rx side.

I'm not an EE and not great at spotting breadboard error but since no one else was answering i thought maybe it was worth saying the problem doesn't look obvious to me.

Reversed polarity at the MIDI socket is a common problem but it looks to me you have that correct.

I didn't read the resistor codes, are you sure about them? (Maybe meter test them to make sure they're not way off but none of the values are crucial so it would have to be a significant difference to cause the circuit to fail -- but I don't trust my code reading so I often meter test resistors to see i have the order of magnitude correct.)



Sorry I can't help more than that.
 
Oh... are you getting the setup message on the serial monitor?

FYI - You don't want to select MIDI for USB type as you are just sending serial to the computer for this sketch.
 
Oops, sorry I missed this thread. If you want the Teensy to work as a Midi - usbMidi interface this might be a useful starting point:-
Code:
/* Create a "class compliant " USB to 1 MIDI IN and 1 MIDI OUT interface.

   MIDI receive (6N138 optocoupler) input circuit and series resistor
   outputs need to be connected to Serial1

   You must select MIDI from the "Tools > USB Type" menu

   This example code is in the public domain.
*/

#include <MIDI.h>

// Create the Serial MIDI ports
MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI1);

// A variable to know how long the LED has been turned on
elapsedMillis ledOnMillis;


void setup() {
  Serial.begin(115200);
  pinMode(13, OUTPUT); // LED pin
  digitalWrite(13, LOW);
  MIDI1.begin(MIDI_CHANNEL_OMNI);
 
}


void loop() {
  bool activity = false;

  if (MIDI1.read()) {
    // get a MIDI IN1 (Serial) message
    byte type = MIDI1.getType();
    byte channel = MIDI1.getChannel();
    byte data1 = MIDI1.getData1();
    byte data2 = MIDI1.getData2();

    // forward the message to USB MIDI virtual cable #0
    if (type != midi::SystemExclusive) {
      // Normal messages, simply give the data to the usbMIDI.send()
      usbMIDI.send(type, data1, data2, channel, 0);
    } else {
      // SysEx messages are special.  The message length is given in data1 & data2
      unsigned int SysExLength = data1 + data2 * 256;
      usbMIDI.sendSysEx(SysExLength, MIDI1.getSysExArray(), true, 0);
    }
    activity = true;
  }

 

  if (usbMIDI.read()) {
    // get the USB MIDI message, defined by these 5 numbers (except SysEX)
    byte type = usbMIDI.getType();
    byte channel = usbMIDI.getChannel();
    byte data1 = usbMIDI.getData1();
    byte data2 = usbMIDI.getData2();
    byte cable = usbMIDI.getCable();

    // forward this message to 1 of the 3 Serial MIDI OUT ports
    if (type != usbMIDI.SystemExclusive) {
      // Normal messages, first we must convert usbMIDI's type (an ordinary
      // byte) to the MIDI library's special MidiType.
      midi::MidiType mtype = (midi::MidiType)type;

      // Then simply give the data to the MIDI library send()
      switch (cable) {
        case 0:
          MIDI1.send(mtype, data1, data2, channel);
          break;
      
      }

    } else {
      // SysEx messages are special.  The message length is given in data1 & data2
      unsigned int SysExLength = data1 + data2 * 256;
      switch (cable) {
        case 0:
          MIDI1.sendSysEx(SysExLength, usbMIDI.getSysExArray(), true);
          break;
    
      }
    }
    activity = true;
  }

  // blink the LED when any activity has happened
  if (activity) {
    digitalWriteFast(13, HIGH); // LED on
    ledOnMillis = 0;
  }
  if (ledOnMillis > 15) {
    digitalWriteFast(13, LOW);  // LED off
  }

}

Hope this helps.
 
I've looked that breadboard over several times and keep thinking I see the problem but then it's just me...

The Tx line isn't clear (at the resistor) but you say that's working and I can't see anything with the Rx side.

I'm not an EE and not great at spotting breadboard error but since no one else was answering i thought maybe it was worth saying the problem doesn't look obvious to me.

Reversed polarity at the MIDI socket is a common problem but it looks to me you have that correct.

I didn't read the resistor codes, are you sure about them? (Maybe meter test them to make sure they're not way off but none of the values are crucial so it would have to be a significant difference to cause the circuit to fail -- but I don't trust my code reading so I often meter test resistors to see i have the order of magnitude correct.)



Sorry I can't help more than that.


Thanks so much for looking over the breadboard. I took everything down and double checked the connections (reverse polarity, resistor values, wire continuity) so I think I am all set there now. The extra sets of eyes are always a help!

Oh... are you getting the setup message on the serial monitor?

FYI - You don't want to select MIDI for USB type as you are just sending serial to the computer for this sketch.

Thanks for confirming the USB type as "serial". To clarify: I want to be able to monitor everything (incoming external MIDI input) over hardware serial. Initially want to see it print out over serial monitor.

Oops, sorry I missed this thread. If you want the Teensy to work as a Midi - usbMidi interface this might be a useful starting point:-
Code:
/* Create a "class compliant " USB to 1 MIDI IN and 1 MIDI OUT interface.

   MIDI receive (6N138 optocoupler) input circuit and series resistor
   outputs need to be connected to Serial1

   You must select MIDI from the "Tools > USB Type" menu

   This example code is in the public domain.
*/

#include <MIDI.h>

// Create the Serial MIDI ports
MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI1);

// A variable to know how long the LED has been turned on
elapsedMillis ledOnMillis;


void setup() {
  Serial.begin(115200);
  pinMode(13, OUTPUT); // LED pin
  digitalWrite(13, LOW);
  MIDI1.begin(MIDI_CHANNEL_OMNI);
 
}


void loop() {
  bool activity = false;

  if (MIDI1.read()) {
    // get a MIDI IN1 (Serial) message
    byte type = MIDI1.getType();
    byte channel = MIDI1.getChannel();
    byte data1 = MIDI1.getData1();
    byte data2 = MIDI1.getData2();

    // forward the message to USB MIDI virtual cable #0
    if (type != midi::SystemExclusive) {
      // Normal messages, simply give the data to the usbMIDI.send()
      usbMIDI.send(type, data1, data2, channel, 0);
    } else {
      // SysEx messages are special.  The message length is given in data1 & data2
      unsigned int SysExLength = data1 + data2 * 256;
      usbMIDI.sendSysEx(SysExLength, MIDI1.getSysExArray(), true, 0);
    }
    activity = true;
  }

 

  if (usbMIDI.read()) {
    // get the USB MIDI message, defined by these 5 numbers (except SysEX)
    byte type = usbMIDI.getType();
    byte channel = usbMIDI.getChannel();
    byte data1 = usbMIDI.getData1();
    byte data2 = usbMIDI.getData2();
    byte cable = usbMIDI.getCable();

    // forward this message to 1 of the 3 Serial MIDI OUT ports
    if (type != usbMIDI.SystemExclusive) {
      // Normal messages, first we must convert usbMIDI's type (an ordinary
      // byte) to the MIDI library's special MidiType.
      midi::MidiType mtype = (midi::MidiType)type;

      // Then simply give the data to the MIDI library send()
      switch (cable) {
        case 0:
          MIDI1.send(mtype, data1, data2, channel);
          break;
      
      }

    } else {
      // SysEx messages are special.  The message length is given in data1 & data2
      unsigned int SysExLength = data1 + data2 * 256;
      switch (cable) {
        case 0:
          MIDI1.sendSysEx(SysExLength, usbMIDI.getSysExArray(), true);
          break;
    
      }
    }
    activity = true;
  }

  // blink the LED when any activity has happened
  if (activity) {
    digitalWriteFast(13, HIGH); // LED on
    ledOnMillis = 0;
  }
  if (ledOnMillis > 15) {
    digitalWriteFast(13, LOW);  // LED off
  }

}

Hope this helps.

Thanks so much for taking the time to share this code for the teensy work as a Midi - usbMidi interface and no worries on the late reply!

However, my goal is to have a hardware MIDI I/O interface that acts as a MIDI data monitor. The MIDI Out of my breadboard connects to a USB MIDI interface so I can connect to a software sound source in a DAW.

This is a good example of what I am after: https://github.com/sparkfun/MIDI_Shield/blob/V_1.5/Firmware/MIDI-sniffer/MIDI-sniffer.ino
However, it utilizes software serial.

When I built a hardware MIDI I/O interface and used an UNO with this sketch, it worked - to a point - but then at higher BPMs from the incoming MIDI input, it would stop working: while the serial monitor was reading all the MIDI data, the output would get "stuck". Notes sent from the MIDI input sequencer would get stuck "on", and after I stopped the sequencer, I'd hear a distinct echo/ feedback noise coming from the receiving sound source and have to reset it.

This is why I am trying to set things up on a Teensy LC to have access to three hardware serial ports independent of the USB line. This is also where I am getting confused about what USB and port settings I need for a Hardware MIDI I/O interface that also monitors MIDI data. I imagine it would be possible to "port" over the referenced sketch and omit software serial altogether?
 
Definitely don't use SoftwareSerial. It barely even works for just 1 instance, and when it does it hogs up the CPU which tends to make everything else perform quite badly.

From everything you've described, it sounds like you want 3 MIDI library instances using the 3 hardware serial ports, and then just use ordinary Serial (which is native USB serial on all Teensy boards) to talk to your PC running software like the Arduino Serial Monitor. If you just want to "see" the MIDI messages as readable text on your PC (but not as actual USB MIDI communication), that way would make the most sense. In Arduino, make sure Tools > USB Type is set to "Serial".


I imagine it would be possible to "port" over the referenced sketch and omit software serial altogether?

Yes. That Sparkfun code looks like it ought to work if you just delete the SoftwareSerial and change the MIDI_CREATE_INSTANCE to this:

Code:
MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI);

On Teensy LC, the SoftwareSerial library also "knows" how to use real serial if you use the pin numbers of a real hardware serial port. So you might be able to get that Sparkfun program running on Teensy LC if you just change the pin numbers, like this:

Code:
SoftwareSerial SoftSerial(0, 1);
 
Definitely don't use SoftwareSerial. It barely even works for just 1 instance, and when it does it hogs up the CPU which tends to make everything else perform quite badly.

From everything you've described, it sounds like you want 3 MIDI library instances using the 3 hardware serial ports, and then just use ordinary Serial (which is native USB serial on all Teensy boards) to talk to your PC running software like the Arduino Serial Monitor. If you just want to "see" the MIDI messages as readable text on your PC (but not as actual USB MIDI communication), that way would make the most sense. In Arduino, make sure Tools > USB Type is set to "Serial".




Yes. That Sparkfun code looks like it ought to work if you just delete the SoftwareSerial and change the MIDI_CREATE_INSTANCE to this:

Code:
MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI);

On Teensy LC, the SoftwareSerial library also "knows" how to use real serial if you use the pin numbers of a real hardware serial port. So you might be able to get that Sparkfun program running on Teensy LC if you just change the pin numbers, like this:

Code:
SoftwareSerial SoftSerial(0, 1);

Ok, fantastic, Paul! This is super helpful and thanks for all the clarification on the USB and port settings and pointers on how to utilize the sparkfun sketch I referenced with the Teensy LC. I will give it a try with and without the SoftwareSerial library to see how it performs.
 
If you expand this to all 3 hardware serial ports, and if all 3 could be expected to have sustained running at nearly max MIDI bandwidth, using Teensy LC you might need to be mindful of how much formatting and other work you're doing to convert the raw MIDI messages into human readable messages. As long as you keep the message formatting simple, I'm pretty sure Teensy LC can keep up and the native USB should be plenty fast enough.

But if you do more complex message formatting on the Teensy, you might need to step up to Teensy 3.2 or 4.0. Teensy LC is significantly faster than Arduino Uno, but nowhere nearly as powerful as Teensy 3.2 or 4.0. Many benchmark tests have shown Teensy LC can keep up with full USB bandwidth only when the code is quite simple. The faster models are able to do so much more without impacting ability to keep up with full USB bandwidth.

Keep the message printing simple if using Teensy LC and all 3 ports, and if you expect all 3 to have sustained periods of heavy MIDI bandwidth usage.
 
If you expand this to all 3 hardware serial ports, and if all 3 could be expected to have sustained running at nearly max MIDI bandwidth, using Teensy LC you might need to be mindful of how much formatting and other work you're doing to convert the raw MIDI messages into human readable messages. As long as you keep the message formatting simple, I'm pretty sure Teensy LC can keep up and the native USB should be plenty fast enough.

But if you do more complex message formatting on the Teensy, you might need to step up to Teensy 3.2 or 4.0. Teensy LC is significantly faster than Arduino Uno, but nowhere nearly as powerful as Teensy 3.2 or 4.0. Many benchmark tests have shown Teensy LC can keep up with full USB bandwidth only when the code is quite simple. The faster models are able to do so much more without impacting ability to keep up with full USB bandwidth.

Keep the message printing simple if using Teensy LC and all 3 ports, and if you expect all 3 to have sustained periods of heavy MIDI bandwidth usage.

That is great to know that all 3 hardware serials could potentially be monitored with the caveats you mention on the LC. For now, I plan to use one hardware serial port with minimal message formatting so I should be good to go.
 
I wanted to report back that the sparkfun MIDI sniffer sketch works flawlessly using both methods that Paul outlined above. Thanks to everyone who lent a helping hand!

Now on to converting those MIDI clock ticks to BPM values and note #s to note name =)
 
...so what was wrong with your initial attempts?

Glad to see your having success now.


Now on to converting those MIDI clock ticks to BPM values and note #s to note name =)
Hints:
Modulo division by 12 on MIDI notes gives you an zero-based array index for note names from C to B

e.g.
noteName = names[note%12]

You can even have one array for sharp names and one for flats and then de-reference the name based on whether the key sig. has flats or not.

I have problems with BPM conversions every time... but the crucial thing the inversion from a duration to a rate:

mSec/beat = x
Sec/beat = 1/1000*x
Min/beat = 1/60*1/1000*x
BPM = 60000 / (ms/Beat)

I.e. - Invert the duration and scale by the time factor.
 
...so what was wrong with your initial attempts?

Glad to see your having success now.

I am not entirely sure but I re-did the breadboard several times. All the components (resistors, diode, opto) were all ok but I think it was something to have to do with ground. I forgot that the DIN connectors have two front metal pins that were also on the ground line. Perhaps that was the cause. I took that out of the re-factoring and the circuit worked.

Code:
Hints:
Modulo division by 12 on MIDI notes gives you an zero-based array index for note names from C to B

e.g.
noteName = names[note%12]

You can even have one array for sharp names and one for flats and then de-reference the name based on whether the key sig. has flats or not.

I have problems with BPM conversions every time... but the crucial thing the inversion from a duration to a rate:

mSec/beat = x
Sec/beat = 1/1000*x
Min/beat = 1/60*1/1000*x
BPM = 60000 / (ms/Beat)

I.e. - Invert the duration and scale by the time factor.

Thank you so much for these hints - in researching the note names I've found some examples that are in line with your hint but I am not entirely sure how to incorporate them into my sketch just yet. In the MIDI sniffer sketch I am using, there are two switch cases for NoteOff and NoteOn that return the Note #:

Code:
case midi::NoteOff :
          {
            Serial.print("NoteOff, chan: ");
            Serial.print(MIDI.getChannel());
            Serial.print(" Note#: ");
            Serial.print(MIDI.getData1());
            Serial.print(" Vel#: ");
            Serial.println(MIDI.getData2());
          }
          break;
        case midi::NoteOn :
          {
            uint8_t vel;

            Serial.print("NoteOn, chan: ");
            Serial.print(MIDI.getChannel());
            Serial.print(" Note#: ");
            Serial.print(MIDI.getData1());
            Serial.print(" Vel#: ");
            vel = MIDI.getData2();
            Serial.print(vel);
            if (vel == 0)
            {
              Serial.print(" *Implied off*");
            }
            Serial.println();
          }
          break;

If I understand correctly:

Code:
MIDI.getData1();
MIDI.getData2();

are what return the 2 data bytes of the received MIDI message? So I need to use the MIDI note# data pulled from MIDI.getData1(?) and take it a step further. This seems similar to your method:

Code:
// https://forum.pjrc.com/threads/43924-convert-midi-note-to-note-name

String noteName[] = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"};
int MidiNoteName;
int noteOctave;

void setup() {
Serial.begin(9600);
}

void loop() {
  Serial.println("Start");
  delay(100);
  int i;
  for (i=0; i<=127; i++) {
    noteOctave = i/12;
    MidiNoteName = i%12;
    Serial.println("Note: "+String(i)+" "+String(noteName[MidiNoteName])+" "+String(noteName[MidiNoteName])+String(noteOctave));
    delay(50);
  }
  delay(200);
}

With regards to the BPM conversions, the MIDI sniffer sketch I am using as a guide uses the MsTimer2 library: https://github.com/sparkfun/MIDI_Shield/blob/V_1.5/Firmware/MIDI-sniffer/MIDI-sniffer.ino

To calculate the BPM as you describe, would I need to use MIDI.setHandleClock(handleClock) in addition to MsTimer library or would that be redundant? If I understand correctly these switch cases are displaying the clock byte values:

Code:
case midi::Clock :
          {
            clock_ticks++;

            Serial.print("Clock ");
            Serial.println(clock_ticks);
          }
          break;
        case midi::Start :
          {
            clock_ticks = 0;
            Serial.println("Starting");
          }
          break;
        case midi::Stop :
          {
            old_clock_ticks = clock_ticks;
            Serial.println("Stopping");
          }
          break;
 
Status
Not open for further replies.
Back
Top