Go Down

Topic: Read/Receive Midi - Midi NoteOff not returned on playing chords (Read 6608 times) previous topic - next topic

spaceavenger

Hi Arduino Audio developers.

I am developing a polyphonic synthesiser controller as part of a larger project.  Whilst the code works there seem to be some serious performance (or data stream corruption) issues when receiving multiple notes or chords via Midi In.

Currently I have an Arduino Uno connected to a Midi IN circuit based on a 6n137 opto-isolator.  I am using the standard Midi library (version 4.2).  The Midi Rx from pin 6 of the 6n137 opto-isolator is connected to Digital Pin 2 of the Arduino, using a dedicated SoftwareSerial port for Midi.  This is to allow use of the hardware Serial USB port for debugging using Serial.println as the SerialMonitor cannot be set at the correct speed for Midi (31250 baud).  The Lcd display uses digital pins 4-7 and 8,9.   The Midi IN socket is connected to a polyphonic keyboard.  

The code is as follows:

Code: [Select]

#include <LiquidCrystal.h>
#include <MIDI.h>
#include <string.h>
#include <stdio.h>

#define DEBUG
#define NUM_VOICES 6

#if defined(ARDUINO_SAM_DUE) || defined(USBCON)
   // Print through USB and bench with Hardware serial
   MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI);
#else
   #include <SoftwareSerial.h>
   SoftwareSerial midiSerial(2, 3); // midi Serial Port
   MIDI_CREATE_INSTANCE(SoftwareSerial, midiSerial, MIDI);
#endif

LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

typedef struct Voice
{
 byte channel;
 byte note;
 byte velocity;
 boolean assigned;
};

static struct Voice v[NUM_VOICES];
static short voiceloop = 0;


void setup()
{
 MIDI.setHandleNoteOn(handleNoteOn);
 MIDI.setHandleNoteOff(handleNoteOff);
 MIDI.begin(MIDI_CHANNEL_OMNI);

 lcd.begin(16,2);
 lcd.noCursor();
 lcd.clear();
 lcd.print("Synthtest");
 
 memset(v,  0, sizeof(struct Voice) * NUM_VOICES);
 for (int i = 0; i < NUM_VOICES; i++)
 {
   v[i].assigned = false;
 }

#ifdef DEBUG
   Serial.begin(115200); // This is the Debug Serial Port (using Serial.println to view data)
   Serial.println("Synthesiser Debug Test");
#endif
} /* function setup */

void loop()
{
 MIDI.read();
} /* function loop */

void handleNoteOn(byte inChannel, byte inNote, byte inVelocity)
{
 if (inVelocity > 0)
 {
   assignNoteToVoice(inChannel, inNote, inVelocity);
#ifdef DEBUG  
   debugMidi(9, inChannel, inNote, inVelocity);
#endif
 }
 else
 {
   unassignNoteFromVoice(inChannel, inNote);
#ifdef DEBUG  
   debugMidi(8, inChannel, inNote, inVelocity);
#endif
 }

#ifdef DEBUG  
 debugVoice();
#endif
}

void handleNoteOff(byte inChannel, byte inNote, byte inVelocity)
{
 unassignNoteFromVoice(inChannel, inNote);
#ifdef DEBUG  
   debugMidi(8, inChannel, inNote, inVelocity);
#endif

#ifdef DEBUG  
 debugVoice();
#endif
}

#ifdef DEBUG
void debugMidi(byte inCmd, byte inChannel, byte inNote, byte inVelocity)
{
// dumps midi data to Serial.println
}
#endif

#ifdef DEBUG
void debugVoice()
{
// dump voice data to Serial.println
}
#endif

void assignNoteToVoice(byte inChannel, byte inNote, byte inVelocity)
{
 int index;
 boolean steal = true;
 
 for (int i=0; i < NUM_VOICES; i++)
 {
   index = (voiceloop + i) % NUM_VOICES;  
   if (! v[index].assigned)
   {
     steal = false;
     v[index].assigned = true;
     v[index].channel = inChannel;
     v[index].note = inNote;
     v[index].velocity = inVelocity;
     break;      
   }
 }

 if (steal)
 {
   index = (voiceloop) % NUM_VOICES;  
   v[index].assigned = true;
   v[index].channel = inChannel;
   v[index].note = inNote;
   v[index].velocity = inVelocity;
 }
 voiceloop = (voiceloop + 1) % NUM_VOICES;  
}

void unassignNoteFromVoice(byte inChannel, byte inNote)
{
 for (int i=0; i < NUM_VOICES; i++)
 {
   if ((v[i].assigned) && (v[i].channel == inChannel) && (v[i].note == inNote))
   {
        v[i].assigned = false;
   }
 }
}


The code works correctly for single notes played slowly.  For example, playing a single middle C, the NoteOn callback captures this note (ID 60, Midi C4) and this is placed in the first voice slot.  On receiving the NoteOff message the voice is unassigned from the note.  Subsequent notes are placed in sucessive voices.

Synthesiser Debug Test
note ON:  chn:01 nte:60 vel:84
[60] [  ] [  ] [  ] [  ] [  ]
note OFF: chn:01 nte:60 vel:00
[  ] [  ] [  ] [  ] [  ] [  ]

When playing chords the underlying logic works most of the time.   For example, playing a C-major triad from middle C is shown.  The debug dump below shows all 3 Midi NoteOn commands being assigned to a voice, and the corresponding NoteOff calls result in the voice being de-assigned.  This is correct, and the Note off messages from the callback routine correspond to a note which has already had a prior NoteOn message.

note ON:  chn:01 nte:67 vel:69
[  ] [67] [  ] [  ] [  ] [  ]
note ON:  chn:01 nte:64 vel:69
[  ] [67] [64] [  ] [  ] [  ]
note ON:  chn:01 nte:60 vel:81
[  ] [67] [64] [60] [  ] [  ]
note OFF: chn:01 nte:64 vel:00
[  ] [67] [  ] [60] [  ] [  ]
note OFF: chn:01 nte:67 vel:00
[  ] [  ] [  ] [60] [  ] [  ]
note OFF: chn:01 nte:60 vel:00
[  ] [  ] [  ] [  ] [  ] [  ]

However,  in many cases the NoteOff either fails to be recognised (i.e. data not sent to the callback routine) , or I receive a NoteOff command for a Note that has not been played.  In both cases, this leaves a stuck note assigned to the voice.  The below example is a 5-note E-minor chord, but the Midi callback has not received the NoteOff message for one of the notes (in this case the B of the E-minor chord):  

note ON:  chn:01 nte:52 vel:28
[52] [  ] [  ] [  ] [  ] [  ]
note ON:  chn:01 nte:55 vel:44
[52] [55] [  ] [  ] [  ] [  ]
note ON:  chn:01 nte:59 vel:44
[52] [55] [59] [  ] [  ] [  ]
note ON:  chn:01 nte:67 vel:84
[52] [55] [59] [67] [  ] [  ]
note ON:  chn:01 nte:64 vel:55
[52] [55] [59] [67] [64] [  ]
note OFF: chn:01 nte:55 vel:00
[52] [  ] [59] [67] [64] [  ]
note OFF: chn:01 nte:67 vel:00
[52] [  ] [59] [  ] [64] [  ]
note OFF: chn:01 nte:52 vel:00
[  ] [  ] [59] [  ] [64] [  ]
note OFF: chn:01 nte:64 vel:00
[  ] [  ] [59] [  ] [  ] [  ]

Here is a dump of a C-major triad - where one Note off is missing and a noteOn has been received for note 25 (which is outside the bounds of the keyboard being used to transmit the midi data.

note ON:  chn:01 nte:55 vel:67
[  ] [55] [  ] [  ] [  ] [  ]
note ON:  chn:01 nte:52 vel:69
[  ] [55] [52] [  ] [  ] [  ]
note ON:  chn:01 nte:48 vel:49
[  ] [55] [52] [48] [  ] [  ]
note OFF: chn:01 nte:52 vel:00
[  ] [55] [  ] [48] [  ] [  ]
note OFF: chn:01 nte:55 vel:00
[  ] [  ] [  ] [48] [  ] [  ]
note ON:  chn:01 nte:25 vel:38
[  ] [  ] [  ] [48] [25] [  ]

Another incorrect outcome is that a NoteOff is received for a note that has not been played.

Possibilities:

1 - Does Midi Receive work correctly on a SoftwareSerial port (in this case using pin 2 for Midi Rx)?  Do you require any extra commands to flush the Serial port stream?   As mentioned, receiving Midi via Pin 0 corrupts the data as the Serial Print also uses the same port, for example Pin 0 is specified when using the "default" Midi class initialisation macro:  MIDI_CREATE_DEFAULT INSTANCE, so this cannot be used.

2 - could this be a problem with the opto-isolator?  I believe that the 6N137 is faster than the 6N138.

3 - is this a problem with the speed of the Arduino Uno itself, i.e. it cannot keep up with the midi In stream when chords are played.   I would have thought that a Arduino Uno would be many times faster than the chips used to control polyphonic synthesisers back in the 1980s.  There is no other code within the loop function other than the MIDI.Read() method.

Please note that I have removed the Lcd library and Lcd display code from the project in an effort to isolate this -  this does not cure the issue.  

The most puzzling issue is that the NoteOn messages appear to work correctly, it is the NoteOff data that is missing or corrupted.   However this may be an illusion, as the callback would not be called if the valid Note On data had not been read from the Midi data stream.

Any help would be much appreciated.

Thanks in advance

SpaceAvenger.

Grumpy_Mike

I would not use software serial for MIDI, it just uses up too much of the software resources and I am not surprised at what you see.
As to the MIDI input, I hope there is more than just the opto isolator you describe, for a proper MIDI in circuit see:-
http://www.thebox.myzen.co.uk/Hardware/MIDI_Shield.html

spaceavenger

Hi Mike.

Many thanks for the info.

My Midi In circuit/schematic is the standard Midi In circuit containing a 220R resistor from pin 4 of Midi IN to pin 2 of the 6N137 with a  1N4148 diode between pins 2 and 3 on the Midi socket side.  A 270R resistor is connected from +5v to the 6N137 pin 6 and Arduino Rx .  This value 270R, AFAIK, is the correct value for the 6N137 chip.  There seems to be a bit of discussion whether you need another resistor of around 1-10k connected from pin 7 to GND.  Interestingly, your circuit is the first that I have seen that uses a PNP transistor for Midi OUT.   There are several examples with no buffering at all (not recommended), and a couple which incorrectly invert the Tx stream (incorrect for Arduino).   On the Midi out side my circuit uses a pair of 74HC14 inverters back to back - some examples use a combination of one inverter and one NPN transistor.  A test  Midi OUT project works fine from the Arduino, but as part of the investigation I removed the inverter chip and disconnected the Tx cable and this did not help with the Midi In problems as described.   I also have wired in a Midi Thru socket but this is not currently connected to the Rx.

More interesting is the fact that it could be that SoftwareSerial is not up to the job for Midi.  I will try changing over to using the Hardware Serial, although then I will not be able to see what it is doing as will not have Serial.println for debugging.  However I could try to get some debug info on the Lcd.    It is also possible that things may be better on the Due if the Serial interfaces are better.  However, on a performance basis, I really would expect the Uno to be fast enough to read and decode Midi IN data at 31250bps

Warm regards

SpaceAvenger
 

Grumpy_Mike

Quote
I really would expect the Uno to be fast enough to read and decode Midi IN data at 31250bps

But bit banging the RX is not the most efficient, it just sucks CUP power from the system.
A better platform is the Mega, while the processor is of the same power it has four hardware serial ports so you can have your debug and your MIDI I/O.

The HC series of logic is not the best for driving power, better is the LS.

Sorry I got a bit lost in your description of the input side, I only talk schematic.  :)

However I am sure it is the software, either the software serial or the MIDI library. I have not been too impressed with it myself.

deseipel

you might contact the rugged circuits guys and see if they have some MDI shields left.  They're designed to work around the Serial issue via jumpers & the use of D2 through D11. 

http://shieldlist.org/ruggedcircuits/midi

unsaturated

Did you ever figure this out?  I'm thinking you might have missed the Running Status encoded events.  It could be more likely that note-offs are batched with Running Status than Note-On's for the keyboard. I'm not sure what the default is for the MIDI lib.

Pat-The-Pirate

I hope someone is still watching this ancient post . . . I have had similar problems, using Casio digital piano AP-21 and a "normal" MIDI hardware connection to the RX pin in the Arduino.

I want to use the MIDI input so that I can play a monophonic synthesiser on the Arduino. The synthesiser bit works a treat, it just needs to be told what notes to play. (It creates a "traditional" 888000000 Hammond sound, for those of you who like such things).

The only sketch that I've found that worked at all (trying for a cheap & fast solution) is the one by Amamda Ghassaei in

http://www.instructables.com/id/Send-and-Receive-MIDI-with-Arduino Step 10

(polling using Serial.read)

- her interrupt-driven one looks better but I can't figure out of it is working, because of course I can't send serial data to my PC at 31250 bps.

I haven't found any other sketches that work at all (AFAIK) despite weeks of trawling the web. (At 1Mbps broadband, this is tedious!).

So I wondered if anyone has got a working solution. With over 1000 MIDI-Receive related threads in this Forum, there seems to be an awful lot of people having MIDI woes.

Maybe I can change my PC serial port to dream about receiving at 31250 bps . . . well within the normal maximum range.

- Jim McLay (grumpy Old Man)

sparx266

Hello grumpy old pirate

So you're going from the keyboard to the arduino and the sound should come from the Arduino?

Are you taking the output of the keyboard to the standard opto isolator then to the rx input? Use a 6N138 or 6N139 opto circuit if you can.

If so, connect an LED and resistor to the opto output and see if it flickers when you press a key and then release it. It should flicker both times.
If not, then make that work.

Can you post your code please?

Let us know the results.

S

Jim_McLay

#8
Feb 12, 2017, 03:47 pm Last Edit: Feb 12, 2017, 04:37 pm by Jim_McLay Reason: Add attachment
Hi Sparx,

I eventually figured out how to get the MIDI working, and have posted the working code in the Forum yesterday.  However, I can no longer find the post, as it seems to have been moved by a Moderator. Maybe it's being vetted. I'll attach it here if I can.

It turned out (surprise surprise!) that I didn't read the Casio MIDI Users Guide with 3-D glasses on. They don't mention MIDI Note On commands as such although they do talk about 9n H which turns out to be just that, i.e. 1001 nnnn, although I only ever get 144 decimal from it when I press a new note. The trouble was, all the Serial.write lines produced rubbish on screen because I set the sketch baud rate to 31250, even though I tried Serial.end and Serial.begin(9600) with the Serial.write statements in between.

I almost gave up at this stage, but the breakthrough came when I spotted someone else trying to get Serial.write to work when the baud rate is set to suit MIDI, i.e. 31250. My PC doesn't recognise this speed, and I couldn't persuade it otherwise, but this other person recommended using "PuTTY" and sure enough, I could now see exactly what the Casio was sending out.

A typical sequence would be, say, press Bottom "A" and then A#, B & C (all next to each other) without letting the first note go, and then letting the notes back up one by one. The casio puts out

144 21 (vv) 22 (nn) 23 (aa) 24 (jj) 21 00 22 00 23 00 24 00

where the (pp) numbers are velocity bytes. These I didn't need, because being an organ, velocity is irrelevant.

so because it's monophonic, my code had to be written (I think) to remember how many keys are pressed down at every instant, so that it didn't keep stopping the note all the time.

(A single note press would create 144 21 (xx) 21 00).

Anyway, once I figured out what the Arduino needed to look for, it all fell into place. I could see from ny scope that the MIDI signals were coming in Ok and fed straight into the RX pin.

I hope the attachment works Ok for you; I found so many sketches that didn't do what they were supposed to.

I tried attaching the actual sound but the Forum doesn't allow the MP3 as is. Will try and find a way around it.

Jim_McLay

Sorry, yes, MIDI in to the Arduino, and audio out from an external DAC MCP4921 mounted on a "shield".

Grumpy_Mike

Quote
Forum doesn't allow the MP3 as is. Will try and find a way around it.
Zip up the file first then it will allow you to post it.

sparx266

What you're describing reminds me of this project: Vox/Farfissa organ
Have a look there to see if that helps.
Now you know that the hardware is working as it should, you could use a MIDI library, the one I use is here, but as you're only interested in Note on and off messages, it may be a bit over the top.

Slightly off topic, but there is a truly awesome DIY Hammond B3 project called PropB3 which you might be interested in.
It works very well.

I will, hopefully this week, give your code a go.

Regards

S

Jim_McLay

"Forum doesn't allow the MP3 as is. Will try and find a way around it "/ "Zip up the file first then it will allow you to post it."

Thanks G. Mike, that's handy to know.

However, having listened to the PropB3 organ mentioned above, I'm not sure I'll bother! Mine is embarrassingly poor in comparison!

Sparx, you may need to build the hardware I used if you're going to try out the code. I have attached the circuit diagram (I think!).

I built a working prototype and now laying it out for a shield PCB; if you want one, I can add an extra one in to the order when I get the bugs out.

The Vox/Farfisa link was interesting because they used a Midivox PCB.  I tried very hard to get Narbotic to tell me a bit more; the guy did actually email the Eagle files for the board, but it had an Arduino pin-out that wasn't compatible with the Arduino SPI library (?). Took me ages to figure out why the SPI didn't work. I can't honestly see how to use it as-is, unless one was writing one's own SPI code.

The PropB3 demo is just staggering!

May need to buy the next level of C++ programming text books.

I notice in the Combo organ webpage:

http://sandsoftwaresound.net/source/arduino-project-source/combo-organ/midivox-organ-comboorgan-ino/

that he uses pin 9 for SS, which is where my hardware fell down. The Arduino UNO schematic clearly shows SS on pin 10 so I'm simply mystified, *sigh* , and not for the first time!

Grumpy_Mike

Quote
that he uses pin 9 for SS,
You can use any pin for the SS, your code has to set it specifically.
Quote
The Arduino UNO schematic clearly shows SS on pin 10
Yes but it is not fixed.

Jim_McLay

I must have cocked up my coding but feel righteous indignation, in that SPI didn't work (for me) until I changed only the hardware, nothing else as far as I can remember.  I physically disconnected pin 9 and shifted the SS signal to pin 10.

 I wonder why Arduino specifically describe those pins as SS, MOSI, MISO and SCK if one can use any pin.

The bit of SPI code I used was:

 " SPDR = highByte(sample) | 0x70;
  while (!(SPSR & (1 << SPIF)));

  SPDR = lowByte(sample);
  while (!(SPSR & (1 << SPIF)));"

but I didn't see any hardware advice in relation to SPI, and the circuit I was using was copied from a shield PCB called "Midivox", which was purported to be a working project.

Look, GM, I'll be interested to know, so's I don't fall into the same trap again.

(I hasten to add that I picked up this code without understanding it in depth,  and was testing it in a very newbie fashion).

Go Up