Pages: [1] 2   Go Down
Author Topic: MIDI-IN questions  (Read 3020 times)
0 Members and 1 Guest are viewing this topic.
Offline Offline
Newbie
*
Karma: 0
Posts: 25
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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


Logged

Global Moderator
Offline Offline
Brattain Member
*****
Karma: 498
Posts: 19065
Lua rocks!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Code:
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
Logged


Offline Offline
Newbie
*
Karma: 0
Posts: 25
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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

Offline Offline
Newbie
*
Karma: 0
Posts: 25
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Ok, so I simplified it to:

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

Logged

Global Moderator
Offline Offline
Brattain Member
*****
Karma: 498
Posts: 19065
Lua rocks!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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

* Which you had by the time I posted this. smiley

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:
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:
// 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.55 KB - downloaded 16 times.)
« Last Edit: November 25, 2013, 07:54:24 pm by Nick Gammon » Logged


Offline Offline
Newbie
*
Karma: 0
Posts: 25
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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
« Last Edit: October 08, 2013, 01:00:21 am by pulykamell » Logged

Offline Offline
Newbie
*
Karma: 0
Posts: 1
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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 smiley
Logged

Offline Offline
Newbie
*
Karma: 0
Posts: 25
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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:
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.
« Last Edit: October 09, 2013, 10:53:10 am by pulykamell » Logged

Manchester (England England)
Offline Offline
Brattain Member
*****
Karma: 634
Posts: 34541
Solder is electric glue
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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

Offline Offline
Newbie
*
Karma: 0
Posts: 25
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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?
« Last Edit: October 09, 2013, 12:45:04 pm by pulykamell » Logged

Global Moderator
Offline Offline
Brattain Member
*****
Karma: 498
Posts: 19065
Lua rocks!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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


Offline Offline
Newbie
*
Karma: 0
Posts: 25
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.

Logged

Offline Offline
Newbie
*
Karma: 0
Posts: 25
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.
Logged

Global Moderator
Offline Offline
Brattain Member
*****
Karma: 498
Posts: 19065
Lua rocks!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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. smiley-sad
Logged


Offline Offline
Newbie
*
Karma: 0
Posts: 25
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

OK, I'll upload it to Youtube:

Logged

Pages: [1] 2   Go Up
Jump to: