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.
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 :
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
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.
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.
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.
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.
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?
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:
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);
}
}
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).
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.
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!).
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.
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.