So I've set up the code that is extremely thorough at capturing the midi data (where Rhythm/timing is critical):
while(Serial.available() == 0);
commandByte = Serial.read();//read first byte
// If this is not a command byte, give up
if(commandByte & 0x80 == 0)return;
// Mask the high nibble of the byte (top 4 bits)
// (the low order nibble has the channel number)
byte cmd = commandByte & 0xF0;
//byte channel_check = commandByte & 0x0F;
// If the command byte is not Note On or Note Off, ignore it.
if((cmd != 0x80) && (cmd != 0x90))return;
// channel checking 1 and 2
// if (channel_check != 0x00){return;}
//if (channel_check != 0x01){return;}
// It is note on or note off, so we can read
// note and velocity bytes
while(Serial.available() == 0);
noteByte = Serial.read();
while(Serial.available() == 0);
velocityByte = Serial.read();
This code is stone solid, not a single note gets missed, everything is accounted for.
The problem is, after debugging, all those while loops are slowing down the rest of the code to a snails pace... For example I had a debounce timer for physical buttons in place that would set to 50 when the button is pressed and would not allow the button to run a function unless that timer went back to 0. The timer runs so slow now. I've disabled all the while loops for debugging and they are the ones that seem to be the culprit. Problem is that I need those while loops because otherwise the incoming midi messages can get missed / lost. And I'm also outputting midi data too so if the 328p detects a midi in note ON message and thus outputs it to the synthesizer to play but it also misses the note OFF message then the synth will now get stuck on that note.
So this project is effectively a midi message looper, you play a rhythm and the looper repeats it over and over again. So I also need to have reliable time keeping and executing other code all the while the code above reliable reads the incoming messages.\
Am I maybe asking too much of the little 328p for this project?
I've been playing around with if and other loop to see if I can get the same reliable data reading but it all resulted in lost or missed midi serial messages which is just a big breaking point.
No, just don't do it with a while loop. Serial data comes in pretty slowly. Let the processor do other things while it is waiting for the next character. Just make sure none of the individual steps that it has to do block execution for long periods of time.
Let the loop function handle the looping and build a state machine to figure out what actions need to happen. Those while loops will all turn into similar if statements. It's not as simple as just replacing the word while with if, but if you look at the logic and flow you should be able to tell the program to pull one character at a time instead of being trapped while there is data available.
Especially this one:
while(Serial.available() == 0);
That sticks the code while there is nothing to do. Let it run the rest of the code. Don't just sit and twiddle your thumbs because this one individual process is waiting. Run the rest of your code.
Think of code not like a story that tells from beginning to end what happens, and more like a checklist that runs all the way through thousands of times a second and decides each time what one small step needs to be taken.
I'm having a hard time visualizing this method of data collection in code.
The midi data comes in packets of 3 bytes. Command, Note and Velocity. So the while loop makes sure that the data is properly sorted and broken down into separate variables to be used later on.
How would you go about reading this data with your method? All my attempts have resulted in less than satisfactory results with data being lost or missed. Only this while loop set up does it 100% reliably.
I could try to use the serialEvent function which gets called every time there's data incoming, and then check if there's at least 3 pieces of data before collecting it all but that results in slower read times and yet still unreliable readings with some notes off messages missing.
void serialEvent(){
if (Serial.available() >2 ){
commandByte = Serial.read();//read first byte
noteByte = Serial.read();//read next byte
velocityByte = Serial.read();//read final byte
cmd = commandByte & 0xF0;
// if(commandByte & 0x80 == 0)return;
//if((cmd != 0x80) && (cmd != 0x90))return;
}
return;
}
void sendMidi(byte msg, byte note, byte vel){
Serial.write(msg); // note on
Serial.write(note); // note
Serial.write(vel); // velocity
//Serial.flush(); // was testing to see if those stuck midi messages weren't getting sent out in time
}
void loop() {
if (cmd == 0x90){
digitalWrite(9, HIGH);
sendMidi(0x90, noteByte, velocityByte);
}
if (cmd == 0x80){
digitalWrite(9, LOW);
sendMidi(0x80, noteByte, 0x00);
}
}
TobiasRipper:
I could try to use the serialEvent function which gets called every time there's data incoming,
No, serialEvent gets called each time loop ends if Serial.available() returns non-zero. So it would be the same as polling serial in your loop.
The thing you need is a Finite State Machine. Avoid the wikipedia article on that, it's confusing. Stick to stuff from programmers.
You've got a 64 byte receive buffer with Serial. As long as your other code isn't blocking for long periods of time, you're not going to miss 3 bytes coming in.
I can't really give you specifics on how that will work with YOUR particular code. Let's see if you can guess why. It's a pretty common sense reason.
In the last example you're using rb = Serial.read();
does it store all the data stored in the buffer right away? And then you sort it out?
You seem to be using < and > as start and end markers but you are sticking to only serial monitor mesages where you can define your own characters to be interpreted as start and end markers.
In my case messages 0x90 and 0x80 would signify the start of a message packet so there should be 2 more bytes expected to follow.
So this? I moved the serial reading back to it's own function.
void checkMIDI(){
while (Serial.available() > 0 ){
commandByte = Serial.read();//read first byte
cmd = commandByte & 0xF0;
if (cmd != 0x80 && cmd != 0x90) {return;}
noteByte = Serial.read();//read next byte
velocityByte = Serial.read();//read final byte
} //else {return;}
}
Okay so I think I might have now figured out the logic behind your method of reading messages. Since the serial data actually comes in at a slower rate you have implemented a counter of a sort, which counts up whenever the correct data comes in. So if you're expecting a package of 3 bytes then you wait for the first byte to come in, check if it contains the value usually expect this data packet to contain, and then simply wait until more data comes in as you kick counter up with every piece incoming. Am I getting this right?