Midi usb raw input question and possible bug in usb_midi.h

Status
Not open for further replies.

djamu

Member
Hello all,
I needed a converter serial midi to usb, and was wondering how to pass thru raw data from usb to serial ( and vice versa ) without needing to use the type specific usb_midi.h using a teensy 3.2

> in effect without having to write callbacks for every midi type.
I was specifically looking for a raw function in usb_midi.h without needing to dissect the midi stream as the usb_midi_read function does.

Is it a requirement for usb to send data packed ? > in this case the complete midi command eg. noteon note velocity as a single data packet ?
or more precise can I ( as long as I don't merge multiple streams ) treat midi as a stateless protocol, and make the teensy send 1 byte packets to it's usb port ?
> every byte that enters it's UART get passed to the usb port and vice versa ( I know it's inefficient, but makes a very small program )

I'd really appreciate it if someone finds the time to tell me how to change this to a working example > using the proper usb-midi port instead of the COM that I'm using now.
and without using libraries.

sample code
Code:
//
// raw serial midi to usb / usb to midi for use with DAW
// test, not to be used for production > merging is flawed, see comment on line 32
//

#define ledPin 13
//#define HWSERIAL1 Serial1
//#define HWSERIAL2 Serial2
//#define HWSERIAL3 Serial3
IntervalTimer blink_led_timer;
unsigned long blinktimer = 500000; //0.5 sec

void setup() {
  pinMode(ledPin, OUTPUT);
  Serial.begin(115200);
  Serial1.begin(31250);
  //  Serial2.begin(31250);
  //  Serial3.begin(31250);
  //  Serial.println("MIDI Input Test");
  blink_led_timer.begin(blink_led, blinktimer);
  blink_led_timer.priority(254);
}

void loop() {
  int incomingByte;

  if (Serial1.available() > 0) {  // UART to USB
    incomingByte = Serial1.read();
    //    Serial.print(incomingByte, BYTE);
    Serial.write(incomingByte); // usb out
    Serial1.write(incomingByte); // midi thru > comment out to disable
    // mind that merging multiple inputs this way is incorrect, midi isn't necessarily stateless
    // series of notes may be send as "NoteOn Note Velocity, Note Velocity, Note Velocity etc ... " as long as command stays the same
    // simply merging inputs can break this.
    // 
  }

  if (Serial.available() > 0) {  // USB to UART
    incomingByte = Serial.read();
    //    Serial1.print(incomingByte, BYTE);
    Serial1.write(incomingByte); 
  }
}

void blink_led() {
  digitalWriteFast(ledPin, !(digitalReadFast(ledPin))); // blinkled
}


In the end I wrote it with callbacks. As I needed to merge streams.

In usb_midi.h I found a possible bug that prevents the sendRealTime function to pass on any other command but F0 ( no F1 - FF > no start / stop / clock / continue / AS / Sytemreset)
I'm not sure if I'm missing a flag so I won't post it as a bug.

fix:
-- uint32_t data = ( (type & 0xFF) | ((type << 8) & 0xFF00) );
++ uint32_t data = ( (0x0F) | ((type << 8) & 0xFF00) );

see > hardware\teensy\avr\cores\usb_midi\usb_api.cpp line 278


I have a profound knowledge of midi, don't go easy on me > Have been programming (8051) midi controllers in ASM for years, but very new to working with c and arduino's so forgive my ignorance.

thanks
djamu
 
Last edited:
I'd really appreciate it if someone finds the time to tell me how to change this to a working example > using the proper usb-midi port instead of the COM that I'm using now.
and without using libraries.

I'm no expert, but without writing your own code, I think the answer is that you can't just use USBserial to send MIDI from the teensy and expect the stream to be recognised as USBMidi... USBMidi is going to give you the standard driver porting etc to MIDI proper on your host, and without that you are 'nowhere' (i.e. in pure Serial land) ....

Is there a way to take the USBserial output on the host and bung it into your host MIDI ports??? I guess there is ... there are Windows serial to MIDI drivers out there, and maybe with a bit of thinking, such a driver can 'talk' to the teensy USBSerial stream and bridge it into your host MIDI ports .... but I don't know of anything off the shelf like that ....

*Other than* just using USBMIDI out of the teensy and using the built in USBMIDI host driver!! That is what it is there for.
 
Here's the USB MIDI specification. On all questions regarding how you can and can't do USB MIDI, this document is the authoritative answer.

www.usb.org/developers/docs/devclass_docs/midi10.pdf

You can also transmit MIDI over serial and use a PC-side program like Hairless MIDI to convert to MIDI interfaces on your computer. This is much less efficient, because the data gets queued by the USB serial driver, then processed by a user-level program with normal non-realtime PC operating system scheduling latency, and *then* it enters the PC system's audio/MIDI layer. With real USB MIDI, that entire conversion step with extra latency is eliminated. But if you really want to send serial MIDI format, you can indeed use Hairless MIDI.

Real USB MIDI requires messages packed into 32 bits, even if they'd be only 1 or 2 or 3 bytes over serial MIDI. From section 3.2.1 on page 13:

... MIDI Data is transferred over the USB in 32-bit USB-
MIDI Event Packets, with the first 4 bits used to designate the appropriate Embedded MIDI Jack.

A 32-bit USB-MIDI Event Packet is adopted to construct multiplexed MIDI streams (MUX MIDI) that can be
sent or received by each MIDI Endpoint. At the sending end, multiple individual MIDI streams are placed
into constant sized packets (with cable number) and are interleaved into a single MUX MIDI stream. At the
receiving end, the multiplexed stream is properly demultiplexed, the data is extracted from the 32-bit USB-
MIDI Event Packets, and each original MIDI stream is routed to the indicated virtual MIDI port. In this way,
one endpoint can have multiple Embedded MIDI Jacks logically assigned. This method makes economical
use of few endpoints but requires a mux/demux process on both ends of the pipe. For more information, see
Section 4, "USB-MIDI Event Packets"?.

Section 4 goes into much more detail. You can read the PDF if you're interested.

You might also look at "Note 2" on page 17. Apparently you can just pack the serial bytes into 32 bit messages and transmit them to the PC without any proper parsing. I have personally never tried this, and I'm a bit skeptical whether it's properly supported by Windows, Linux or Mac drivers. If you do try, I'd be curious to hear your experience?
 
Thanks Paul for the info, I'll try that.
Suspected the requirement for packing,

Any comment on the possible bug ?
 
actual usage,
no realtime messaged got passed -didn't test sysex-, until I traced it down to line 129 in usb_midi.h

fix is
-- uint32_t data = ( (type & 0xFF) | ((type << 8) & 0xFF00) );
++ uint32_t data = ( (0x0F) | ((type << 8) & 0xFF00) );

it's propably not a proper fix, as I didn't spend enough time to trace it further down, but with that it works

upon inspection, my assumption that
hardware\teensy\avr\cores\usb_midi\usb_api.cpp line 278
was responsible is not correct as that belongs to the read function, I need to find something similar. I'll take another look, for a more appropriate fix.
 
here's the code I'm using now, so you might test it ( and my fix )

Code:
/* MIDI serial to USB adapter - for use with Teensy or boards where UART is separate from USB
   As MIDI messages arrive, they are sent as USB midi messages, and vice versa
   Transmits everything but.

   TimeCodeQuarterFrame
   SongPosition
   SongSelect
   TuneRequest

   found bug:
   usb_midi.h line129

  --   uint32_t data = ( (type & 0xFF) | ((type << 8) & 0xFF00) );
  ++  uint32_t data = ( (0x0F) | ((type << 8) & 0xFF00) );

  
  disabled midi thru in
  MIDI.h line 33
  --  #define COMPILE_MIDI_THRU       1
  ++  #define COMPILE_MIDI_THRU       0



   Note that this does not work on a regular Arduino boards (no separate USB and UART)!

   Based on Francois Best midi library example:
   http://arduinomidilib.fortyseveneffects.com/
   This example code is released into the public domain.
   
   
*/

#include <MIDI.h>
#define ledPin 13
byte beatcounter = 0;


void SerialNoteOn(byte channel, byte note, byte velocity) {
  usbMIDI.sendNoteOn(note, velocity, channel);
}
void usbNoteOn(byte channel, byte note, byte velocity) {
  MIDI.sendNoteOn(note, velocity, channel);
}

void SerialNoteOff(byte channel, byte note, byte velocity) {
  usbMIDI.sendNoteOff(note, velocity, channel);
}
void usbNoteOff(byte channel, byte note, byte velocity) {
  MIDI.sendNoteOff(note, velocity, channel);
}

void SerialControlChange(byte channel, byte number, byte value) {
  usbMIDI.sendControlChange(number, value, channel);
}
void usbControlChange(byte channel, byte number, byte value) {
  MIDI.sendControlChange(number, value, channel);
}

void SerialAfterTouchPoly(byte channel, byte number, byte value) {
  usbMIDI.sendPolyPressure(number, value, channel);
}
void usbAfterTouchPoly(byte channel, byte number, byte value) {
  MIDI.sendPolyPressure(number, value, channel);
}

void SerialProgramChange(byte channel, byte number) {
  usbMIDI.sendProgramChange(number, channel);
}
void usbProgramChange(byte channel, byte number) {
  MIDI.sendProgramChange(number, channel);
}

void SerialAfterTouch(byte channel, byte number) {
  usbMIDI.sendAfterTouch(number, channel);
}
void usbAfterTouch(byte channel, byte number) {
  MIDI.sendAfterTouch(number, channel);
}

void SerialPitchBend(byte channel, int number) {
  usbMIDI.sendPitchBend(number, channel);
}
void usbPitchBend(byte channel, int number) {
  MIDI.sendPitchBend(number, channel);
}

//void SerialTimeCodeQuarterFrame(byte channel, int number) {
//  usbMIDI.sendTimeCodeQuarterFrame(number,channel);
// }

void SerialClock() {
  usbMIDI.sendRealTime(248);
  beatcounter++;
if(beatcounter == 24) {
beatcounter = 0;
digitalWriteFast(ledPin, HIGH);
}

if(beatcounter == 2) {
digitalWriteFast(ledPin, LOW);
} 
}

void SerialStart() {
  usbMIDI.sendRealTime(250);
  beatcounter = 0;
digitalWriteFast(ledPin, HIGH);
} 

void SerialContinue() {
  usbMIDI.sendRealTime(251);
}
void SerialStop() {
  usbMIDI.sendRealTime(252);
}
void SerialActiveSensing() {
  usbMIDI.sendRealTime(254);
}
void SerialSystemReset() {
  usbMIDI.sendRealTime(255);
}
void usbRealTimeSystem(byte number) {
  Serial1.write(number);
}


void blink_led() {
  digitalWriteFast(ledPin, !(digitalReadFast(ledPin))); // blinkled
}



void setup() {
  Serial1.begin(31250);
  pinMode(ledPin, OUTPUT);
  digitalWriteFast(ledPin, HIGH);

  MIDI.begin(MIDI_CHANNEL_OMNI);

  MIDI.setHandleNoteOn(SerialNoteOn);
  usbMIDI.setHandleNoteOn(usbNoteOn);

  MIDI.setHandleNoteOff(SerialNoteOff);
  usbMIDI.setHandleNoteOff(usbNoteOff);

  MIDI.setHandleControlChange(SerialControlChange);
  usbMIDI.setHandleControlChange(usbControlChange);

  MIDI.setHandleAfterTouchPoly(SerialAfterTouchPoly);
  usbMIDI.setHandleVelocityChange(usbAfterTouchPoly);

  MIDI.setHandleProgramChange(SerialProgramChange);
  usbMIDI.setHandleProgramChange(usbProgramChange);

  MIDI.setHandleAfterTouchChannel(SerialAfterTouch);
  usbMIDI.setHandleAfterTouch(usbAfterTouch);

  MIDI.setHandlePitchBend(SerialPitchBend);
  usbMIDI.setHandlePitchChange(usbPitchBend);

  //  MIDI.setHandleTimeCodeQuarterFrame(SerialTimeCodeQuarterFrame);
  //  MIDI.setHandleSongPosition(SerialSongPosition);
  //  MIDI.setHandleSongSelect(SerialSongSelect);
  //  MIDI.setHandleTuneRequest(SerialTuneRequest);
  MIDI.setHandleClock(SerialClock);
  MIDI.setHandleStart(SerialStart);
  MIDI.setHandleContinue(SerialContinue);
  MIDI.setHandleStop(SerialStop);
  MIDI.setHandleActiveSensing(SerialActiveSensing);
  MIDI.setHandleSystemReset(SerialSystemReset);

  usbMIDI.setHandleRealTimeSystem(usbRealTimeSystem);

}

void loop() {
  MIDI.read();
  usbMIDI.read();
}
 
Last edited:
Code:
@PaulStoffregen thanks for the links.

I looked at the single serial byte part of the protocol (in note 2 on page 17). It still needs the USB-MIDI Event packet header etc, so (from a brief skim), unless you have the "right" USB driver installed on your host, wouldn't you have problems? (i.e. USB serial only driver is probably not going to recognise USB-MIDI Event packets, parsed or non parsed??)

I see there is an offtheshelf product that will bridge into host MIDI from serial ... the hairless MIDI bridge!!. I remember seeing this ages ago, but well, why not just use the awesome USB midi implementation??

@djamu... you probably only need callbacks for the USB stuff.... here is some untested code for serial USB passthrough (I think I might have actually tested it in the dim dark past ...) it is limited to the simple types, but I have sysex code at home somewhere ... as for the realtime stuff, I haven't thought about that for a while ...

This is not using MIDI 4.2, but can be converted easy enough ... the only trick is a typecast..
Code:
#include <MIDI.h>
int chnl,d1,d2,dd;
kMIDIType type;
void setup() {

  Serial.begin(31250);

}

void loop() {
  if (MIDI.read() &&  MIDI.getType() < SystemExclusive) {
    type = MIDI.getType();
    d1 = MIDI.getData1();
    d2 = MIDI.getData2();
    dd = d1 + (d2 << 8);
    chnl = MIDI.getChannel();
    // and then send...
    switch(type){
      case NoteOn:
        usbMIDI.sendNoteOn(d1,d2,chnl);
      break;
      case NoteOff:
        usbMIDI.sendNoteOff(d1,d2,chnl);
      break;
      case AfterTouchPoly:
        usbMIDI.sendPolyPressure(d1,d2,chnl);
      break;
      case ControlChange:
        usbMIDI.sendControlChange(d1,d2,chnl);
      break;
      case ProgramChange:
        usbMIDI.sendProgramChange(dd,chnl);
      break;
      case AfterTouchChannel:
        usbMIDI.sendAfterTouch(dd,chnl);
      break;
      case PitchBend:

        usbMIDI.sendPitchBend(dd,chnl);
      break;
      case SystemExclusive:
        // handle sysex
      break;
      default:
        // F8 et seq.
      break;
    }
  }
  if (usbMIDI.read() &&  usbMIDI.getType() < SystemExclusive) {
    type = (kMIDIType) usbMIDI.getType();
    d1 = usbMIDI.getData1();
    d2 = usbMIDI.getData2();

    chnl = usbMIDI.getChannel();
    // and then send...
    MIDI.send(type,d1,d2,chnl);
  }
}
 
Cool,
already quite a bit shorter.
Just wrote my code in a hurry

Just need to add the realtime stuff,
I'm using this to hook up Pioneer DJ mixers ( multiple ) to an Avolite DMX controller for a big club > to get a proper sync of the lightshow to music, with some extra filters.
Currently I'm trying to add Paul's audio library to it too > for the FFT stuff, hence no offtheshelf stuff for me :)
 
MIDI to usbMIDI and reverse

...here is some untested code for serial USB passthrough (I think I might have actually tested it in the dim dark past ...) it is limited to the simple types, but I have sysex code at home somewhere ...
This code is starting to get around on here.
https://forum.pjrc.com/threads/31708-Passing-regular-MIDI-from-host-to-host
https://forum.pjrc.com/threads/3238...rface-1-in-2-Out-Thru-(split)-Midi-Controller
https://forum.pjrc.com/threads/34483-Passing-MIDI-thru-Teensy-2-0-to-and-from-pc


Perhaps it's time we get the sysEx issue sorted. It does need someone with the hardware setup to test.

It looks to me like the sysex part just needs to figure out the slight differences between MIDI. and usbMIDI. calling syntax.

I think usbMIDI.getSysExArray is a 'byte' but MIDI.getSysExArray is a 'const byte' definition and MIDI.sendSysEx looks like it has a flag for whether the array includes F7...F0 while usbMIDI.sendSysEx appears not to have this.

If you (or anyone) has this sorted already then I'd like to see working code.
 
...oh... and would the sans-sysEx code choke if sysEx was present or would the message flush from the .getType() calls and the next non-sysEx message would be sitting waiting on the next pass?
 
...oh... and would the sans-sysEx code choke if sysEx was present or would the message flush from the .getType() calls and the next non-sysEx message would be sitting waiting on the next pass?

I think you are right about flushing. I have done sysex code. I have the hardware. I have had other priorities (and I did test it a while ago from memory ....) Hopefully *someone* will test ... I'll try and make this a priority, but there are so many things to do with my spare time!!!

I actually went with callbacks from memory ...sysex is pretty easy, just pointers and arrays ...;)

Edit ... found an old post in which I reported I tested it!! thought so ... I'll reconfirm .... I'm working on a 2 x 2 interface split merge and thru.... your code 'overinspired' me
 
Last edited:
Ah ... When I read the notes from my old post, I realised that I hadn't tested USB host to teensy, then out the teensy serial .... Of course that doesn't work !!! because teensy USB uses its own numerical system for message type rather than 0x80, 0x90, 0xA0 etc which the serial MIDI library uses ...

Callbacks are looking a better option, although I have hacked the teensy USB code to use the HEX values, so .... Hopefully that will work ... On a quick look at the teensy USB implementation, I'm not sure that it is that well-equipped for 0xF8 messages for instance, although it does provide callbacks for those messages, not miditypes though.... will have to look a bit more closely.
 
Last edited:
1 x 1 midi / usb interface

Ok. So I haven't sorted out sysex and realtime yet, and I haven't really gone much past the public apis yet (type casting my way out of trouble ...), but here is working code for usb to serial / serial to usb. Seems to have minimal latency!! but there are speed improvements yet to be had ... It requires a minor tweak to usb_midi.c in /hardware/teensy/avr/cores/teensy3/ so I have attached a replacement file ... here is the .ino .. Note MIDI 4.2 is used (I need 4.2 for other functionality, but a 3.2 version is just a one type different!)

Code:
#include <MIDI.h> //MIDI 4.2

MIDI_CREATE_INSTANCE (HardwareSerial, Serial1, midiA);

void setup() {
  midiA.begin();
}

void loop() {
  if (usbMIDI.read()) {
    midiA.send((midi::MidiType) usbMIDI.getType(),usbMIDI.getData1(),usbMIDI.getData2(),usbMIDI.getChannel());
  }
  if (midiA.read()) {
    uint32_t TypeSerial1 =  (uint32_t) midiA.getType();
    usb_midi_write_packed(TypeSerial1 >> 4 | TypeSerial1 << 8 | ( ((  ((uint32_t) midiA.getChannel()) - 1) & 0x0F) << 8)
                          | ((   ((uint32_t)midiA.getData1()) & 0x7F) << 16) | ((   ((uint32_t)midiA.getData2()) & 0x7F) << 24)  );
  }
}
 

Attachments

  • usb_midi.c
    10.1 KB · Views: 156
How extensive are your library changes? Are you asking to merge it to the standard lib?
 
How extensive are your library changes? Are you asking to merge it to the standard lib?

Tiny changes ... just changing the return of usbMIDI.getType() from 0 - 6 (for non-sysex) to 0x80, 0x90 etc as per the MIDI spec (and how it is done in the serial MIDI library ....) The problem with requesting a pull for the changes is that while they give compatibility with serial MIDI they will break people's programs that rely on the usbMIDI numbering of types .... I suppose I could submit and see what PaulStoffregen says.

Meanwhile here is a picture of my 2 x 2 midi merge / split interface, and the code (not for sysex and realtime etc) ... I haven't turned off "through" in the serial midi library, and I actually don't have enough bits of midi gear and cables to test all 3 ports running simultaneously!! :eek: I think I will have to buy some extra cables to do loops.... (the code needs the library tweak to work)

sysex etc should be easy but not right now ...

Code:
#include <MIDI.h> //MIDI 4.2

MIDI_CREATE_INSTANCE (HardwareSerial, Serial1, midiA);
MIDI_CREATE_INSTANCE (HardwareSerial, Serial2, midiB);

void setup() {
  midiA.begin();
  midiB.begin();
}

void loop() {
  if (usbMIDI.read()) {
    midi::MidiType TypeUSB = (midi::MidiType) usbMIDI.getType();
    byte dUSB1 = usbMIDI.getData1();
    byte  dUSB2 = usbMIDI.getData2();
    byte ChanUSB = usbMIDI.getChannel();
    midiA.send(TypeUSB, dUSB1, dUSB2, ChanUSB);
    midiB.send(TypeUSB, dUSB1, dUSB2, ChanUSB);
  }
  if (midiA.read()) {
    midi::MidiType TypeSerial1 = midiA.getType();
    uint32_t dmidiA1 = midiA.getData1();
    uint32_t  dmidiA2 = midiA.getData2();
    uint32_t ChanA = midiA.getChannel();
    midiB.send(TypeSerial1, dmidiA1, dmidiA2, ChanA);
    usb_midi_write_packed((uint32_t) TypeSerial1 >> 4 | (uint32_t) TypeSerial1 << 8 | (((ChanA - 1) & 0x0F) << 8)
                          | ((dmidiA1 & 0x7F) << 16) | ((dmidiA2 & 0x7F) << 24));
  }
  if (midiB.read()) {
    midi::MidiType TypeSerial2 = midiB.getType();
    uint32_t dmidiB1 = midiB.getData1();
    uint32_t  dmidiB2 = midiB.getData2();
    uint32_t ChanB = midiB.getChannel();
    midiA.send(TypeSerial2, dmidiB1, dmidiB2, ChanB);
    usb_midi_write_packed((uint32_t) TypeSerial2 >> 4 | (uint32_t) TypeSerial2 << 8 | (((ChanB - 1) & 0x0F) << 8)
                          | ((dmidiB1 & 0x7F) << 16) | ((dmidiB2 & 0x7F) << 24)); 
  }
}

WP_20160607_001.jpg

I've been goofing off playing amazing grace on my laptop keyboard, out over midiox via usb to teensy, and then over serial to my serial only midi rack synth. mucho fun.

edit: haven't worried about running status at this stage ... USB midi doesn't use running status anyway, but I'm not sure how it plays out on the serial split merge side of things ... maybe it just works? Seems to so far, but I haven't done a proper merge test yet (not enough gear)
 
Last edited:
So if I remap usbMIDI.getType () to the midi spec in my code (with a comment on how to fix once the lib gets updated to match MIDI 4.2) before sending the msg back out it could work with existing lib?
 
@oddson ...Yes

You could do a switch like this ... (pardon the pseudo code)

Code:
switch (usbMIDI.getType ()) {
  case 0:
       midi::MidiType SerialType a = 0x80;
       break;
  case 1:
       midi::MidiType SerialType a = 0x90;
       break;
  etc
}
midi.send (a, etc, etc, etc);

But you can see why I didn't do that, and a fix in the library is better ...
 
Status
Not open for further replies.
Back
Top