MIDI-IN questions

So, as part of my project with the Commodore 64 SID chip, I want to use MIDI input from a keyboard to drive the chip. I’m trying to figure out how to get MIDI in to play well with the Arduino. I used this Instructable to get the basic circuit down and connect it to the Arduino Uno. Since I am using Serial Rx to receive MIDI, I also have an LCD circuit wired in to monitor and debug. (From what I understand, I can’t use the Serial monitor to debug in this case. Correct me if I’m misunderstanding.)

Here’s the code I have for basic monitoring and debugging of MIDI messages:

#include <Wire.h>
#include <LiquidCrystal_SR.h>

byte incomingByte;
void setup(){
  Serial.begin(31250);
  lcd.begin(16,2);               // initialize the lcd
  lcd.clear();
  lcd.print("hello");
}

void loop(){
 checkMIDI();
}

void checkMIDI(){
    if (Serial.available()) {
      lcd.clear();
      incomingByte=Serial.read();
      if (incomingByte == 144) {
        lcd.print(incomingByte);
        lcd.print("Note=");
        lcd.print(Serial.read());
        lcd.setCursor(0,1);
        lcd.print("Velocity=");
        lcd.print(Serial.read());
      }
      else {
        lcd.print("Command Byte =");
        lcd.print(incomingByte);
        lcd.setCursor(0,1);
        lcd.print("Next Byte =");
        lcd.print(Serial.read());
      }
    }
}

Anyhow, it appears to me that my circuit is wired correctly, and that the serial port is receiving data. When I hit middle C, and trigger a NoteOn command (incomingByte ==144) and hold it, my LCD displays: “144Note = 60, Velocity = (whatever my Velocity value is)”. When I release the note, my LCD displays: “144Note = 60, Velocity = 0.” (The “144” is the NoteOn Command and I have it reprinted a bit redundantly for feedback.)

However, I noticed that when I just hit a note quickly and release it, the 144 Note On Command seems to be dropped, and the LCD displays “Command Byte =” (the MIDI value of the note I hit) and “Next Byte = 0” (the Note Off Command.)

What is going on here? To clarify, if I hit a note hold and release, it seems I get this sequence:

144, 60, 64 (NoteOn, Middle C, 64 velocity)
144, 60 ,0 (NoteOn, Middle C, 0 velocity aka NoteOff)

But if I hit it staccato, it appears that my Arduino is picking up:

144, 60, 64
60, 0

(I just rewrote the code a little bit to fit all the data on the LCD screen without clearing it, and, indeed, if I hit the note quickly, the bytes reported are 144, 60 ,64, then 60, 0, with what appears to be a 144 being swallowed there.)

Am I missing something in understanding the MIDI specification? Why is the second 144 commandByte being dropped?

 if (Serial.available()) {
     lcd.clear();
     incomingByte=Serial.read();
     if (incomingByte == 144) {
       lcd.print(incomingByte);
       lcd.print("Note=");
       lcd.print(Serial.read());
       lcd.setCursor(0,1);
       lcd.print("Velocity=");
       lcd.print(Serial.read());
     }

http://www.gammon.com.au/forum/?id=12153#trap1

OK, I'm trying to follow, as that is something I was wondering about--reading too much or too little, but I'm not understanding my error.

I'm expecting 144, 60, 128; 144, 60, 0 for any note, slow or quick. I am getting those values for a sustained note. I'm losing the second 144 for the quick (staccato) note. I'm not sure how to use the above to solve my problem. Usually, if I'm reading too much from Serial, I get obvious junk values of 255. I'm not getting that here. So what am I missing?

Ok, so I simplified it to:

void checkMIDI(){
    if (Serial.available()) {
      //lcd.clear();
      incomingByte=Serial.read();
      lcd.print(incomingByte);
      lcd.print(",");
}

Here's what I get when I hit a middle C staccato:

144,60,110,60,0

What I thought I should expect is a:

144,60,100,144,60,0

OK, well you should fix the reading anyway.* However there is something else happening.

  • Which you had by the time I posted this. :slight_smile:

Commands have the high-order bit set (eg. 144). If you get another sequence where you expect a command, but with the high-order bit not set, then you assume the last command is repeated.

Something like this, conceptually:

void loop() 
{
  byte c = getNext ();

  if (((c & 0x80) == 0) && (lastCommand & 0x80))
    {
    lookAhead = c;
    c = lastCommand; 
    }

This saves the byte we really got in lookAhead, and then pretends we got lastCommand. So I made a getNext routine to handle that:

// get next byte from serial (blocks)
int getNext ()
  {

  if (lookAhead != noLookAhead)
    {
    int c = lookAhead;
    // finished with look ahead
    lookAhead = noLookAhead;
    return c;
    }
    
  while (midi.available () == 0)
    {}
  return midi.read ();
  }

So this then returns the lookAhead byte the first time around (not reading anything more) and then discards that so the next time plus one, you get the next byte from the MIDI.

My whole sketch is in the attachment.

It works, I have a demo here:

That shows it handling quite fast sequences.

MIDI_decoder.ino (13.6 KB)

[quote author=Nick Gammon link=topic=192162.msg1420128#msg1420128 date=1381211003] Commands have the high-order bit set (eg. 144). If you get another sequence where you expect a command, but with the high-order bit not set, then you assume the last command is repeated. [/quote]

This is exactly the conclusion I was starting to come to, but wasn't sure if that was correct, or if something was getting swallowed in the pipeline. (For some reason, none of the MIDI tutorials I came across mentioned the "if a command byte is not sent [leftmost bit is not 1], assume the last command." That would have made things obvious to me. ) I think I should be able to figure it out from here.

Thanks! Pete

The jargon for this in MIDI is "Running Status" http://www.blitter.com/~russtopia/MIDI/~jglatt/tech/midispec/run.htm

Not every MIDI device does this, but it's encouraged by the MIDI spec to save some bytes :)

n/m I think I may have figured it out. For some reason, inserting a brief delay(1) before my Serial reads sorts things out.

void checkMIDI(){
    if (Serial.available()) {
      incomingByte=Serial.read();
      if (incomingByte & 0x80) { //check MSB. MIDI commands have this set
        commandByte=incomingByte;
        noteByte=Serial.read();
        velocityByte=Serial.read();
      }
      else {
        delay(1);
        noteByte = incomingByte;
        velocityByte = Serial.read();
      }
    
   
   lcd.clear();
   lcd.setCursor(0,0);
   lcd.print(commandByte);
   lcd.print(",");
   lcd.print(noteByte);
   lcd.print(",");
   lcd.print(velocityByte); 
  }

See that else block? If I comment out the delay, a byte gets swallowed, and what is the velocityByte gets reported as the noteByte, and then velocityByte gets junk because it's reading too far ahead in the serial port (yes, I know this is not how I'm supposed to do it, but I'm just being quick & dirty at the moment--I know other command/status bytes don't necessarily take two bytes of data, so will screw everything if, say, I hit the program up key on my keyboard.)

When I leave the delay(1) in, everything works great. All my values display correctly. I don't know what possessed me to try to put a delay in there, but it works. I just can't figure out WHY it works.

Any ideas?

Oh, by the way, thanks for the link to "Running Status." In the articles that I've read about MIDI, I didn't find mention of that, and that's exactly the documentation I was looking for. I was wondering if this was something all MIDI devices do, or whether it depends on how they're programmed. Looks like the latter.

ETA: Further experimentation shows that the delay needs to be at least 325 Microseconds for the Serial.read()s to behave as I expect them to.

You check if at least one byte is ready to be read and then you proceed to read three bytes. Spot anything wrong with that?

OK, I think I see what you're saying. I did read Gammon's article but thought I could get around it if I knew there were supposed to be 2 or 3 bytes to read. But I suppose what you're saying is that you can't even assume that, because the second byte might still be en-route, so if you do successive reads like that, if it's faster than the transmission rate, you'll get garbage. So, conceptually, what is happening when I put in that delay is that it allows enough time for those two bytes to come through and sit in the buffer, ready to be read. Is that correct? I like making these kinds of mistakes, because I learn/remember them better than if I had it right all along. So, is that conceptually what is happening and why this is screwing up?

ETA: No, it looks like I didn't read the whole article last night. I clicked through to the post saying that it's wrong unless you test specifically for X number of bytes, but I didn't click on the link on that page to another thread further explaining the issues.

I also suppose that I could leave this delay in, and it will work, but that it's a "hackish" way of doing things and sloppy programming, right?

I was just about to ask whether it would make sense to put in an empty block like while (!Serial.available()) { }, but I think that's what Gammon is doing in his code. In other words, since I know a noteOn command is going to be followed by two bytes, one for note, one for velocity, would it be acceptable to just add the "while (!Serial.available()) {}" before every Serial.read() in my code? It seems to work okay, but is there something I'm not considering?

In my code I specifically made the function to get a single byte “block”. Thus the loop waiting for available before each read.

I don’t generally recommend that, as while it is blocking you can’t do other stuff (like test switches, flash LEDs etc.). In the case of that particular sketch I didn’t care (there were no switches or LEDs).

Adding in the delay is not recommended, that will not necessarily work.

When I leave the delay(1) in, everything works great. All my values display correctly. I don’t know what possessed me to try to put a delay in there, but it works. I just can’t figure out WHY it works.

It works in this case because after the command byte the other two bytes are likely to arrive soon after, and the delay gives them time to arrive. At 31250 baud each byte will take 1/3125 seconds to arrive (320 µS) so one mS delay is enough time for 3 bytes to arrive.

More reliable, if you don’t mind it blocking (and the delay blocks after all) is to check the byte(s) actually arrive. eg.

    if (Serial.available()) {
      incomingByte=Serial.read();
      if (incomingByte & 0x80) { //check MSB. MIDI commands have this set
        commandByte=incomingByte;
        while (Serial.available() < 2) 
          { }  // wait for two more bytes
        noteByte=Serial.read();
        velocityByte=Serial.read();
      }
      else {
        noteByte = incomingByte;
        while (Serial.available() < 1) 
          { }  // wait for one more byte
        velocityByte = Serial.read();
      }

Thanks for your help and patience. I just made a function/method called waitForByte() that is just an empty while block and that just waits for a byte to become available on the serial port, but after looking at your code, I might have it accept an argument so I can wait for two bytes where appropriate instead of making two calls to waitForByte. For what I'm doing right now, I don't foresee that little no operation loop to be problematic as I don't think I need the program to be doing anything else in the meantime.

Thanks again. With your help, I was able to get the MIDI stuff down and interface it with the SID chip. Right now, I only have control of keyboard notes and the sliders set to control some elements of the envelope (attack, decay, sustain, release), but I've got it up and running!

Video here.

This content is currently unavailable The page you requested cannot be displayed right now. It may be temporarily unavailable, the link you clicked on may have expired, or you may not have permission to view this page.

Sadly I cancelled my Facebook account. :(

OK, I'll upload it to Youtube:

Here you go

Pretty cool. :)

Thanks. I'm stoked that it worked, and the community here has been very helpful in seeing my project along. I've never really done this kind of stuff before. Now to incorporate all three notes of polyphony into the next iteration of the project (although I have a couple extra SID chips now, so I can make it 9 notes of polyphony), and then the more complicated stuff like vibrato, filtering (including sweeps) and all that kind of stuff.

Yes, it is what MIDI defines as "Running Status". It helps when transmittning chords.

You only read 1byte of the serial. If it has the top bit set, it is a status change and you store it as the current status and read 2 more bytes.

If it has the top bit cleared, it is an event with the same status you already have stored and you read just one more byte.

Tricky but very common.

it is an event with the same status you already have stored and you read just one more byte.

Make that two more bytes, note number and velocity.

janost: Yes, it is what MIDI defines as "Running Status". It helps when transmittning chords.

You only read 1byte of the serial. If it has the top bit set, it is a status change and you store it as the current status and read 2 more bytes.

If it has the top bit cleared, it is an event with the same status you already have stored and you read just one more byte.

Tricky but very common.

There's actually a couple more caveats I discovered if you want to be true to MIDI spec -- I haven't implemented any of these in the code yet but if the upper four bits are set in a MIDI message, running status behaves differently. System common category messages (0xF0-F7) cancel running status. But that shouldn't be a problem, as a status message should follow. However, real time category messages (0xF8-0xFF) don't affect running status, so should not update your status value.

Anyhow, there's a good run-down here about it, which djbouche posted earlier.

I don't think it's anything I should run into with the keyboard project, but I'll probably implement it in code just to be safe.

(And, of course, if the top bit is set, this doesn't necessarily mean two bytes of data are coming along. Different MIDI status changes have different parameters, from 0 - 2.)