Arduino Forum upgrade scheduled for Monday, October 20th, 11am-4pm (CEST). Sorry for the inconvenience!
Pages: [1]   Go Down
Author Topic: Read/Receive Midi - Midi NoteOff not returned on playing chords  (Read 1897 times)
0 Members and 1 Guest are viewing this topic.
Offline Offline
Newbie
*
Karma: 0
Posts: 2
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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

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

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
Logged

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

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
   
Logged

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

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

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

Offline Offline
Full Member
***
Karma: 0
Posts: 120
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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
Logged

Pages: [1]   Go Up
Arduino Forum upgrade scheduled for Monday, October 20th, 11am-4pm (CEST). Sorry for the inconvenience!
Jump to: