MIDI timing


#1

I just started learning JUCE today and it’s been really excellent so far.

I’m trying to write a conversion tool that converts Notes to CCs. I’m starting out simple by reading in a MIDI file and trying to write out the same MIDI file. If I can do that correctly, I think I can figure out the conversion. I’m able to read in the file and write a new file, but the timing of the written file isn’t correct. How can I get the timing of the written file to match the tempo, measures, etc of the original? The output ideally should be exactly the same as the input.

Is there a way to determine setTicksPerQuarterNote based on the MIDI file being read in? Do I need to write meta data for the tempo, beats, etc? Is there any example or any info on this?

Thanks!

This is my main code for reading/writing:


void MainComponent::readFromFile(File const& file)
{
    MidiFile midiFile; 
    FileInputStream inputStream(file);
    midiFile.readFrom(inputStream);

    const int tickPQN = 960; //this isn't correct? How do I determine this?    
    writeMidi.setTicksPerQuarterNote(tickPQN);

    for(int trackIndex = 0; trackIndex < midiFile.getNumTracks(); trackIndex++)
    {
        const MidiMessageSequence * seqRead = midiFile.getTrack(trackIndex);
         
        for(int eventIndex = 0; eventIndex < seqRead->getNumEvents(); eventIndex++)
        {
			MidiMessageSequence::MidiEventHolder* event = seqRead->getEventPointer(eventIndex);
                      
			MidiMessageSequence::MidiEventHolder* offEvent = event->noteOffObject;

			writeSeq.addEvent(MidiMessage::noteOn(1, event->message.getNoteNumber(), event->message.getVelocity()), event->message.getTimeStamp());
        }	
    }

	writeMidi.addTrack(writeSeq);
    
    File writeFile("new_filename.mid"));
    FileOutputStream outputStream(writeFile);
    writeMidi.writeTo(outputStream);
}

#2

Hi.

The MIDI file can have 2 types of format: bars/beats (ticks) or SMPTE absolute times.

You should use MidiFile::getTimeFormat() :

“If the value returned is positive, it indicates the number of midi ticks per quarter-note - see setTicksPerQuarterNote().
It it’s negative, the upper byte indicates the frames-per-second (but negative), and the lower byte is the number of ticks per frame - see setSmpteTimeFormat().”


#3

If you load a MIDI file that is in SMPTE format and write the file with the ticks format, the timings will be botched. :shock:

That aside, if the file is already in ticks format, you should be able to change the resolution without a hitch at any given point as it should not modify the timestamps (unless they are on really specific tick values).

You should think through the difference between notes and CC messages since they are not equivalent or directly interchangeable, by any means. (Not to say that you can’t reuse the same char value (ie: 0 - 127) of course!) Plus, I suggest you do this instead of using event holders (for readability and it gives more powerful useage of the data):

This way you can easily check if it’s a note on (or off) message or not, grab its timestamp if it is, and do whatever it is you need to do. (See: http://www.rawmaterialsoftware.com/juce/api/classMidiMessage.html)

Only if you’re creating a MIDI file from scratch (a file with no data - like you’re doing), then you would need to add the MIDI file data. From off the top-of-my-head, in a MIDI Format 1, you have to add the MIDI Format 1 header and add a track begin and track end message (and of course put all notes and CC messages and stuff between!). [I might be missing data there though!] (See: http://www.omega-art.com/midi/mfiles.html)

Always good to grab a hex editor to analyze the MIDI file information and check how it works; each message has a corresponding byte value! :wink: (See: http://www.midi.org/techspecs/midimessages.php)

You could just reuse all the information from the file you loaded, and modify the tracks’ messages (ie: clear them all?). (juce::MidiMessageSequence) All the meta-data like tempo, time-signatures and such are held in a MIDI file’s track 0.

Cheers. 8)