Parsing MIDI file and generating new MIDI file

Hey guys,

I’m working on a project where essentially what I need to do is parse a midi file (it is a midi recording of a two handed piano piece) and split it up into its voices. Fortunately the voices do not overlap much so I have hardcoded 4 sequences for each voice. I then iterate through the midi file and check each event to see if it is the next note in one the expected sequences and if so I copy it to a new midi sequence. After all that is done I add each sequence to a new midi file and output 4 midi files. However, I seem to be losing the time structure or perhaps the note-off events when I copy each voice over. I’m not sure exactly. The following is done in the process function:

    const MidiMessageSequence* eventList;
    eventList = inputFile.getTrack(0);
    inputFile.convertTimestampTicksToSeconds();
    
    extractVoices(eventList); //Separate voices into individual midi sequences

    outputFile1.setTicksPerQuarterNote(inputFile.getTimeFormat());
    outputFile2.setTicksPerQuarterNote(inputFile.getTimeFormat());
    outputFile3.setTicksPerQuarterNote(inputFile.getTimeFormat());
    outputFile4.setTicksPerQuarterNote(inputFile.getTimeFormat());

    outputFile1.addTrack(rightHandSequence);
    outputFile2.addTrack(leftHandTopSequence);
    outputFile3.addTrack(leftHandMiddleSequence);
    outputFile4.addTrack(leftHandBottomSequence);
    
    File file;
    file = File::createFileWithoutCheckingPath("right hand.mid");
    
    FileOutputStream stream1(file);
    stream1.flush();
    stream1.setPosition(0);
    outputFile1.writeTo(stream1);
    
    file = File::createFileWithoutCheckingPath("left hand top.mid");
    FileOutputStream stream2(file);
    stream2.flush();
    stream2.setPosition(0);
    outputFile2.writeTo(stream2);
    
    file = File::createFileWithoutCheckingPath("left hand middle.mid");
    FileOutputStream stream3(file);
    stream3.flush();
    stream3.setPosition(0);
    outputFile3.writeTo(stream3);
    
    file = File::createFileWithoutCheckingPath("left hand bottom.mid");
    FileOutputStream stream4(file);
    stream4.flush();
    stream4.setPosition(0);
    outputFile4.writeTo(stream4);

And here is the extract voices function:

void extractVoices(const MidiMessageSequence* midiSequence)
{
    MidiMessageSequence::MidiEventHolder* currentEvent;
    MidiMessage currentMessage;
    int noteNumber;
            
    int i = 0;
    int j = 0;
    int k = 0;
    int l = 0;
    for(int event = 0; event < midiSequence->getNumEvents(); event++)
    {
        currentEvent = midiSequence->getEventPointer(event);
        currentMessage = currentEvent->message;
        noteNumber = currentMessage.getNoteNumber();
        
        if(currentMessage.isNoteOn())
        {
            /*See if current note belongs to one of the predefined voice sequences*/
            if(noteNumber == galopSequenceRH[i])
            {
                rightHandSequence.addEvent(currentMessage, currentMessage.getTimeStamp());
                rightHandSequence.updateMatchedPairs();
                i++;
            }
            else if(noteNumber == galopSequenceLHTop[j])
            {
                leftHandTopSequence.addEvent(currentMessage, currentMessage.getTimeStamp());
                leftHandTopSequence.updateMatchedPairs();
                j++;
            }
            else if(noteNumber == galopSequenceLHMiddle[k])
            {
                leftHandMiddleSequence.addEvent(currentMessage, currentMessage.getTimeStamp());
                leftHandMiddleSequence.updateMatchedPairs();
                k++;
            }
            else if(noteNumber == galopSequenceLHBottom[l])
            {
                leftHandBottomSequence.addEvent(currentMessage, currentMessage.getTimeStamp());
                leftHandBottomSequence.updateMatchedPairs();
                l++;
            }
        }
    }
}
1 Like

Of course right after I post this I figure out that the time stamp conversion to seconds was screwing everything up. However, I think I am still losing the note-off events…

1 Like

And for some reason I cannot get the output to maintain the event timing/tempo. I need the output sequences to maintain the relative timing/tempo exactly.

Others couldn’t help me but maybe you can, I have a midiFile which has 5 diffrent tracks, my goal is to seaprete each track to indivudial midiFile while conserving the all the data. i tried somthing but it isnt working well:

MidiFile midiFile;
if (midiFile.readFrom(*fileStream))
{
   DBG("MIDI file successfully loaded!");

   // Get the time format
   short timeFormat = midiFile.getTimeFormat();

   // Store global meta events
   MidiMessageSequence globalMetaEvents;

   // Extract global meta events from the first track
   if (midiFile.getNumTracks() > 0)
   {
       const MidiMessageSequence* firstTrack = midiFile.getTrack(0);
       for (int i = 0; i < firstTrack->getNumEvents(); ++i)
       {
           const MidiMessage& msg = firstTrack->getEventPointer(i)->message;
           if (msg.isMetaEvent() && !msg.isTrackNameEvent() && !msg.isTextMetaEvent())
           {
               globalMetaEvents.addEvent(msg);
           }
       }
   }

   for (int i = 0; i < midiFile.getNumTracks(); i++)
   {
       MidiFile singleTrackFile;
       singleTrackFile.setTicksPerQuarterNote(timeFormat);

       // Add global meta events to the new file
       MidiMessageSequence newTrack = globalMetaEvents;

       // Add track events
       newTrack.addSequence(*midiFile.getTrack(i), 0, 0, midiFile.getLastTimestamp() + 1);

       singleTrackFile.addTrack(newTrack);

       // Get the name of the track (if available)
       String trackName = "Track_" + String(i + 1);
       for (int j = 0; j < newTrack.getNumEvents(); ++j)
       {
           const MidiMessage& msg = newTrack.getEventPointer(j)->message;
           if (msg.isTrackNameEvent())
           {
               trackName = msg.getTextFromTextMetaEvent();
               break;
           }
       }

       // Sanitize the track name for use as a filename
       trackName = File::createLegalFileName(trackName);

       File newFile = file.getSiblingFile(trackName + ".mid");
       if (auto outputStream = std::unique_ptr<FileOutputStream>(newFile.createOutputStream()))
       {
           singleTrackFile.writeTo(*outputStream);
           files.add(newFile);
           DBG("File created: " + newFile.getFullPathName());
       }
       else
       {
           DBG("Failed to create output stream for: " + newFile.getFullPathName());
       }
   }