Go Down

Topic: MIDI-IN questions (Read 3416 times) previous topic - next topic

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:

Code: [Select]

#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?



Nick Gammon

Code: [Select]

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
Please post technical questions on the forum, not by personal message. Thanks!

More info:
http://www.gammon.com.au/electronics

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:

Code: [Select]

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


Nick Gammon

#4
Oct 08, 2013, 07:43 am Last Edit: Nov 26, 2013, 01:54 am by Nick Gammon Reason: 1
OK, well you should fix the reading anyway.* However there is something else happening.

* Which you had by the time I posted this. :)

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:

Code: [Select]

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:

Code: [Select]

// 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:

http://vimeo.com/80328016

That shows it handling quite fast sequences.
Please post technical questions on the forum, not by personal message. Thanks!

More info:
http://www.gammon.com.au/electronics

#5
Oct 08, 2013, 07:45 am Last Edit: Oct 08, 2013, 08:00 am by pulykamell Reason: 1

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.


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

djbouche

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 :)

#7
Oct 09, 2013, 02:50 pm Last Edit: Oct 09, 2013, 05:53 pm by pulykamell Reason: 1
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.

Code: [Select]

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.

Grumpy_Mike

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?

#9
Oct 09, 2013, 07:10 pm Last Edit: Oct 09, 2013, 07:45 pm by pulykamell Reason: 1
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?

Nick Gammon

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.

Quote

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.

Code: [Select]

   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();
     }

Please post technical questions on the forum, not by personal message. Thanks!

More info:
http://www.gammon.com.au/electronics

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.

Nick Gammon

Quote

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. :(
Please post technical questions on the forum, not by personal message. Thanks!

More info:
http://www.gammon.com.au/electronics

OK, I'll upload it to Youtube:

Here you go

Go Up