Go Down

Topic: MIDI note-off logic for player piano - new technique? (Read 2184 times) previous topic - next topic


Mar 29, 2011, 09:23 pm Last Edit: Mar 30, 2011, 04:12 am by chrisclementdotcom Reason: 1
I ran into some difficulty getting notes to stop and learned a lot in the last few days. Using my Yamaha PSR-292 as a test source, I not only found that it never sends note-off codes but also never sends note-on-zero-velocity (nozv) codes either. These nozv codes are used in "status streaming" to reduce redundant status codes in the MIDI stream and stop notes already started with non-zero velocity. Also my kbd was sending a steady stream of high (>F7) codes that did nothing for me. Having seen a commercial decoder operate properly, I was convinced that there had to be another way for the Yamaha to stop notes. I ruled out all codes other than the note-on 144 (and other channels) and the high hex (>F7) by lighting the LED if any were seen. The only possible way left was to toggle notes on and off with pairs of note-on codes. This could get upside down but not likely if other flaws are carefully avoided. I implemented it in code, keeping other techniques as well, and matched the performance of the commercial decoder perfectly!

Code follows:
Code: [Select]

// MIDIplayerpiano v1.0 code by Chris Clement 3/29/2011 - chris(at)chrisclement(dot)com
// Drawn from Midi In Basic  0.2 // kuki 8.2007 but greatly modified.
// As MIDI evolved, quicker methods were developed to turn notes off. With solenoids,
// it is essential not to cook them so reliable, redundant note-off methods are needed.
// Testing with a PSR-292, the original did not perform as well as a commercial decoder.
// In addition to note-off code which my kbd doesn't seem to use, I added "status streaming" logic.
// But, even the note-on-velocity-zero codes were not forthcoming so I made a subsequent note-on
// stop a prior note-on if the note was on. This is sort of a toggle button action that seems to do
// what I want. It is possible but unlikely to get "upside down". Make sure source doesn't
// send a note-on-velocity-zero code first. Power cycle the Atmel chips to clear.
// Additional insurance against stuck notes is present as "all notes off" code, power reset, and
// a crude periodic notesoff() call. I will use 8 Atmel chips for the piano each starting
// at A0 thru A7 (cval = 9, 21, 33, 45, 57, 69, 81, 93).
// This code may be used noncommercially with credit.

byte incomingByte=0;
byte note=0;
byte velocity=0;
int cval=60;   // select the octave starting note (originally C4).
int action=2;  // state machine 0=note off ; 1=note on ; 2= nada ; 3=all notes off
int hilo;int cnt1=0;int cnt2=0;
int digitalWrit2[16];//array to model status of pins

void setup() {
//       c  c# d  d# e  f  f# g g# a  a# b      
// 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15

 pinMode(2,OUTPUT);  pinMode(3,OUTPUT);  pinMode(4,OUTPUT);  pinMode(5,OUTPUT);
 pinMode(6,OUTPUT);  pinMode(7,OUTPUT);  pinMode(8,OUTPUT);  pinMode(9,OUTPUT);
 pinMode(10,OUTPUT);  pinMode(11,OUTPUT);  pinMode(12,OUTPUT);  pinMode(13,OUTPUT);

void loop () {
L10: // Although I abhor "goto", this is not proper COBOL.
 if (Serial.available() > 0) {
   incomingByte = Serial.read();
   cnt1++;if (cnt1 > 100){cnt1 = 0;cnt2++;}//not to worry about INT sizes and types
   if (cnt2 > 1){cnt2 = 0;notesoff();}

   //digitalWrite(13, HIGH);digitalWrite(13, LOW);
   // If we have a status byte, decide how to handle exclusively.
   if  (incomingByte  > 247){goto L10;}//PSR-292 puts out a steady stream of > F7 codes
   if  (incomingByte  > 127){action=2;note=0;velocity=0;}
   //Listening on all channels
   if ((incomingByte  > 143)&&(incomingByte  < 160)){action=1;incomingByte = Serial.read();}//note on
   if ((incomingByte  > 127)&&(incomingByte  < 144)){action=0;incomingByte = Serial.read();}//note off
   if ((incomingByte  > 175)&&(incomingByte  < 192)){action=3;incomingByte = Serial.read();}//all notes off
   if ((incomingByte  > 127)&&(action==2)){goto L10;}
   //handle 1st databyte (and second if note-on)
   if ((action==1)&&(note==0)){note=incomingByte;}
   if ((action==1)&&(note!=0)){incomingByte = Serial.read();velocity=incomingByte;
        if (digitalWrit2[note-cval+2]==HIGH){hilo=LOW;}
        if(note>=cval && note<cval+12){
             digitalWrite(note-cval+2, hilo);
             digitalWrit2[note-cval+2]= hilo;
   if ((action==0)&&(note==0)){note=incomingByte;}
   if ((action==0)&&(note!=0)){incomingByte = Serial.read();velocity=incomingByte;
        hilo=LOW ;//if(velocity<10){hilo=LOW;}
        if(note>=cval && note<cval+12){
             digitalWrite(note-cval+2, hilo);
             digitalWrit2[note-cval+2]= hilo;

   if ((action==3)&&(incomingByte==123)){notesoff();

void blinker(){
 digitalWrite(13, HIGH);
 digitalWrite(13, LOW);

void notesoff(){
 digitalWrite(2, LOW);  digitalWrite(3, LOW);  digitalWrite(4, LOW);  digitalWrite(5, LOW);
 digitalWrite(6, LOW);  digitalWrite(7, LOW);  digitalWrite(8, LOW);  digitalWrite(9, LOW);
 digitalWrite(10, LOW);  digitalWrite(11, LOW);  digitalWrite(12, LOW);  digitalWrite(13, LOW);
 for (int i=0; i <= 15; i++){digitalWrit2[i]=LOW;}//not to worry about ends of array




Please modify your post, select the code, and press the # button

Thank you.
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

Go Up