Pages: [1]   Go Down
Author Topic: play & sync a midi file to external clock  (Read 2416 times)
0 Members and 1 Guest are viewing this topic.
Offline Offline
Full Member
***
Karma: 0
Posts: 120
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hi guyz

I've searched quite a bit online and I think I have a basic understanding of what I want to do but I'll just throw my code out here to see if you have thoughts.

I want to play a midi file from the SD card (one track/channel, 1-4 bar phrase) and have it sync to an external clock (a Korg ES1).  When I press play (or send a play signal via MIDI), I want the code to also play a midi file and be in sync.  I'm using the MIDI library but also using the MIDIFILE library to play files from an SD card.  

I'm using the Handleclock MIDI callback and my question is, do I simply need to get the next midi event at each clock signal?  would that sync it?  Again, I'm a little new and I haven't connected the dots on some things....

The code was first designed to listen for certain MIDI notes and then translate them to specific commands.  That part works just fine.

* sketch_jan10a.ino (9.66 KB - downloaded 16 times.)
Logged

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

I've made some progress on this, but I need to do more testing.  There's a concept I'm having trouble with and that is:

-Is syncing to an external clock as simple as playing a midi event at each clock tick?

I'm also having trouble with some bytes and the MIDIFile library's function (to set the filename from the SD card).  The .MID file I want to play is going to have the same number as the current program (0-127).  The MIDI library uses bytes to work with program change messages.  But the MIDIFile library is looking for :
Code:
    // MIDI file name
    void      setFilename(const char* aname);
    const char* getFilename(void);


And I simply want to play the equivalent .MID file.  So if P=28, then it should play "28.MID".  I'll post some code


Code:
void Sync() {

  //char songname = "";
  char buffer [13];
  sprintf(buffer, "%d.MID", P);
  //  songname = const char(P) + ".mid";
  SMF.setFilename(buffer);  //DON'T NEED TO GET THE SONGNAME AT EACH CLOCK MESSAGE.
  SMF.load();
  //String songname;   //name of the current midi file
  int err;
  if (err != -1)
  {
    DEBUG("\nSMF load Error ");
    DEBUG(err);
    delay(WAIT_DELAY);
  }
  else
  {
    if (!SMF.isEOF())
    {
      SMF.getNextEvent();
    }
    else
    {
      SMF.restart();
      midiSilence();
    }
  }
}
« Last Edit: August 31, 2013, 10:04:01 am by deseipel » Logged

Sydney, Australia
Offline Offline
Edison Member
*
Karma: 33
Posts: 1284
Big things come in large packages
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

If you are trying to open the file 28.mid, then the code you gave looks like it should do it. Does it work? You could try printing out the file name using Serial.print (if the serial port is available) or using an LCD to display the data if you need confirmation.

Note however, that
Code:
  int err;
  if (err != -1)
is not likely to work as err has not been set by anything.

As I understand it, the MIDI beat clock is a clock signal that is broadcast via MIDI to ensure that several MIDI-enabled devices such as a synthesizer or music sequencer stay in synch, controlled from a central place. This is different from a MIDI time code, which is actually providing timing in frames/second (eg, for film based timing). This makes the MIDI beat clock tempo-dependent. Clock events are sent at a rate of 24 pulses per quarter note (ppqn), with no additional information, like bar number or time code, and so must be used in conjunction with a positional reference (such as timecode or a synchronised start) for complete sync.

The MIDIFILE library reads the time signature of the MIDI file and converts the ppqn signature (usually in track 0) into 'milliseconds between events'. It then essentially implements the technique of checking millis() against the previous time value to determine when events needs to be fired off. This keeps all the tracks in synch but the actual timing could be slightly off compared to external 'real' time. It should be possible to change the way the library handles time so that it is synchronised to an external clock rather than using its internal timing. From memory, most of that code sits in the MIDIFile object code.
« Last Edit: August 31, 2013, 06:00:31 pm by marco_c » Logged

Arduino libraries http://arduinocode.codeplex.com
Parola hardware & library http://parola.codeplex.com

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

I haven't had time today to test it.

Are you saying it should work except for the timing then? 
Logged

Sydney, Australia
Offline Offline
Edison Member
*
Karma: 33
Posts: 1284
Big things come in large packages
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

In principle it could work. I am guessing that if you just play when you get the event it will not work.

I would start by getting a copy of the MIDI standard and reading up on the messages you expect to process.  Then capture what is coming down the line and check that it is as you expect. The code is then easy.  The whole thing is straightforward but you need to get you head around it.
Logged

Arduino libraries http://arduinocode.codeplex.com
Parola hardware & library http://parola.codeplex.com

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

right away, nothing works.  And other code I had working before also doesn't work now.  I wonder if there's some conflict when trying to use both MIDI and MIDIFile libraries.  I can tell that setup is working, because I see those MIDI msgs come through (MIDIOX).  But when I send a MIDI message, I get nothing.  Everything is based on noteOn callbacks.  So Im' wondering if the MIDIfile library is conflicting with the MIDI library. 
Logged

Sydney, Australia
Offline Offline
Edison Member
*
Karma: 33
Posts: 1284
Big things come in large packages
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Leave the midifile library out and see if the NoteOn callback works.

I don't see any reason why the two libs can't work together as I originally was using the midi library as a way to send messages from midi file before I realized I could just send my own message through the serial port.

Logged

Arduino libraries http://arduinocode.codeplex.com
Parola hardware & library http://parola.codeplex.com

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

yeah, my older code before I added the midifile code works fine.

If I have a MIDI noteOn callback, would it interfere with the MIDIfile handler, or does the MIDIfile handler only 'do stuff' for MIDI coming from the file on the sd card?
Logged

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

my theory of operation is that when a note comes into the MIDI IN, the code will send a start command to the sequencer, which will begin playing the sequence of MIDI.  But I think I need a way to begin playing the MIDI file on the SD card as well.  And be in sync with the sequencer....



Here's some of my code that I'm scrutinizing:



Code:
void HandleClock(){
Sync();
}

void Sync() {


  int err;
  if (err != -1)
  {
    DEBUG("\nSMF load Error ");
    DEBUG(err);
    delay(WAIT_DELAY);
  }
  else
  {
    if (!SMF.isEOF())
    {
      SMF.getNextEvent();
    }
    else
    {
      SMF.restart();
      midiSilence();
    }
  }
}


//this handles changing the program number,up or down and also start/stop toggle.

void HandleNoteOn (byte channel, byte note, byte velocity){
  //if note X is sent, send program change control to go up
  if (note == 58 and velocity>0){
    if (S == 0){
      S==1;
      MIDI.sendRealTime(Start);
      //Sync();
     
        //char songname = "";
  char buffer [13];
  sprintf(buffer, "%d.MID", P);
  //  songname = const char(P) + ".mid";
  SMF.setFilename(buffer);  //DON'T NEED TO GET THE SONGNAME AT EACH CLOCK MESSAGE.
  SMF.load();
  //String songname;   //name of the current midi file
     
    }
    else
    {
      S==0;
      MIDI.sendRealTime(Stop);
    }

  }
Logged

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

While I'm not sure exactly what the issue is with the code that previously worked, I think there's a more fundamental issue:  with my current setup, there's no way my arduino can receive the clock from my sequencer AND also read the incoming notes to trigger program changes (to flow to the sequencer). 

the flow of data, currently is   

(electronic drum)----->(Alesis IO [trigger to MIDI interface]) ------->  (Arduino MIDI IN/OUT, SD card)  --------->(KORG ES1 Sequencer).


But the KORG has MIDI IN/OUT/THRU, so I'll have to think about if that opens up another setup possibility.
Logged

Sydney, Australia
Offline Offline
Edison Member
*
Karma: 33
Posts: 1284
Big things come in large packages
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
If I have a MIDI noteOn callback, would it interfere with the MIDIfile handler, or does the MIDIfile handler only 'do stuff' for MIDI coming from the file on the sd card?

There is no logical reason it should (but that does not mean that it does not).

The MIDI library reads a serial port and then calls a callback to tell you a certain type of message has been received. The MIDIFile library reads the SD card and calls the callback for the user to deal with the MIDI event (which could be a message). If you want to you could use the MIDI library to send the message - that is what I used to do before I simplified the example code to reduce the dependency on yet another library (MIDI). The MIDIFile library boils down to reading a file on the SD card, interpreting the bytes (especially wrt timing) and then calling the callback to make the user handle the MIDI event however they want.

Code:
  int err;
  if (err != -1)
  {

I still don't see what this does. Variable err has an undefined value when you are checking for -1 as you have done nothing with it.

I don't understand what you are trying to do with the code you posted. You cannot just assume that the MIDI file will be synched wih the other MIDI stream because there will be timing and latency issues with the comms, as well as slight differences in the clocking between Arduino and synthesiser. So the MIDI streams' synch will progressively diverge (small changes accumulated over a longer time) unless they are resynched on a periodic basis. That's the whole point of the time beat messages - to minimise or eliminate these differences - by sending a beat 96 times per note (a lot, IMHO, but who am I to argue!).
Logged

Arduino libraries http://arduinocode.codeplex.com
Parola hardware & library http://parola.codeplex.com

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

unless I add another MIDI IN port, the whole thing is "shot".  The arduino is never going to get the external clock messages from my sequencer without an additional MIDI IN port. 


my idea was that with each clock event that is sent by the external MIDI device, that the HandleClock call back would 'play' the next MIDI event.  I guess that's a faulty assumption.  That's what I was trying to do with the HandleClock callback. 

and the whole business with the err variable was actually pulled from your MIDIfile Play example, but modified by me.  I'm not sure if you ever define err in your code either, but it's no matter.  I appreciate the feedback though. 
Logged

Sydney, Australia
Offline Offline
Edison Member
*
Karma: 33
Posts: 1284
Big things come in large packages
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Ok, your call. I still think it is possible but I don't have any of the equipment to try it.

And, just to make sure I was not doing something silly in the example code, I checked
Code:
  err = SMF.load();
  if (err != -1)
  {
Logged

Arduino libraries http://arduinocode.codeplex.com
Parola hardware & library http://parola.codeplex.com

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

ok, I see what you're saying now about the 'err' variable.  Originally, I had it in another callback and that's where I had the rest of your code; where it's defined.  My mistake. 

I'm sure it is possible, but again I don't have a physical way to get the clock signal from the sequencer and notes from another device at the same time.



Logged

Pages: [1]   Go Up
Jump to: