void ClipMultiTrack( const MIDIMultiTrack &src, MIDIMultiTrack &dst, double max_time_sec )
{
    dst.ClearAndResize( src.GetNumTracks() );
    dst.SetClksPerBeat( src.GetClksPerBeat() );

    double max_event_time = 1000.*max_time_sec; // msec
    double event_time = 0.; // msec

    MIDISequencer seq( &src );
    seq.GoToTimeMs( 0.f );
    if ( !seq.GetNextEventTimeMs ( &event_time ) )
        return; // empty src multitrack

    MIDITimedBigMessage ev;
    int ev_track;
    while ( seq.GetNextEvent( &ev_track, &ev ) )
    {
        // ignore NoOp, BeatMarker and other Service messages
        if ( ev.IsServiceMsg() || ev.IsNoOp() )
            continue;

        dst.GetTrack(ev_track)->PutEvent(ev);

        if ( event_time >= max_event_time )
            break; // end of max_time_sec

        if ( !seq.GetNextEventTimeMs( &event_time ) )
            break; // end of src multitrack
    }
}
void CollapseMultiTrack( const MIDIMultiTrack &src, MIDIMultiTrack &dst )
{
    dst.ClearAndResize( 1 );
    dst.SetClksPerBeat( src.GetClksPerBeat() );

    MIDISequencer seq( &src );
    seq.GoToZero();

    MIDITimedBigMessage ev;
    int ev_track;
    while ( seq.GetNextEvent( &ev_track, &ev ) )
    {
        // ignore all src EndOfTrack messages!!
        if ( ev.IsDataEnd() )
            continue;

        // ignore NoOp, BeatMarker and other Service messages
        if ( ev.IsServiceMsg() || ev.IsNoOp() )
            continue;

        dst.GetTrack(0)->PutEvent(ev);
    }

    // set (single!) dst EndOfTrack message
    MIDITimedBigMessage end(ev); // copy time of last src event
    end.SetDataEnd();
    dst.GetTrack(0)->PutEvent(end);
}
void CopyWithoutChannel( const MIDIMultiTrack &src, MIDIMultiTrack &dst, int ignore_channel )
{
    dst.ClearAndResize( src.GetNumTracks() );
    dst.SetClksPerBeat( src.GetClksPerBeat() );

    MIDIClockTime ev_time = 0;
    MIDISequencer seq( &src );
    seq.GoToTime( 0 );
    if ( !seq.GetNextEventTime ( &ev_time ) )
        return; // empty src multitrack

    MIDITimedBigMessage ev;
    int ev_track;

    while ( seq.GetNextEvent( &ev_track, &ev ) )
    {
        if ( ev.IsServiceMsg() || ev.IsNoOp() )
            continue;

        if ( ev.IsChannelEvent() && ev.GetChannel() == ignore_channel )
            continue;

        dst.GetTrack(ev_track)->PutEvent(ev);
    }
}
bool AddEndingPause( MIDIMultiTrack &tracks, int track_num, MIDIClockTime pause_ticks )
{
    MIDIClockTime t = tracks.GetTrack( track_num )->GetLastEventTime();
    MIDITimedBigMessage msg;
    msg.SetTime( t + pause_ticks );
    // add lowest "note on" in channel 0 with velocity 0 (i.e. "note off")
    msg.SetNoteOn( 0, 0, 0 );
    return tracks.GetTrack( track_num )->PutEvent( msg );
}
int main( int argc, char **argv )
{
    if ( argc > 1 )
    {
        const char *infile_name = argv[1];

        MIDIFileReadStreamFile rs( infile_name );
        MIDIMultiTrack tracks;
        MIDIFileReadMultiTrack track_loader( &tracks );
        MIDIFileRead reader( &rs, &track_loader );

        // set amount of tracks equal to midifile
        tracks.ClearAndResize( reader.ReadNumTracks() );

//      MIDISequencerGUIEventNotifierText notifier( stdout );
//      MIDISequencer seq( &tracks, &notifier );
        MIDISequencer seq ( &tracks );

        // load the midifile into the multitrack object
        if ( !reader.Parse() )
        {
            cerr << "\nError parse file " << infile_name << endl;
            return -1;
        }

        if ( argc > 2 )
        {
            cout << endl;
            int mode = atoi ( argv[2] );
            if ( mode == 0 )
            {
                DumpMIDIMultiTrack( &tracks );
            }
            else // mode = 1
            {
                PlayDumpSequencer( &seq );
            }
        }

//      cout << MultiTrackAsText( tracks ); // new util fun

        double dt = seq.GetMisicDurationInSeconds();

        cout << "\nMisic duration = " << dt << endl;
    }
    else
    {
        cerr << "\nusage:\n    jdkmidi_test_sequencer FILE.mid [0 for DumpMIDIMultiTrack]\n";
        cerr <<           "                                    [1 for PlayDumpSequencer]\n";
        return -1;
    }

    return 0;
}
void CompressStartPause( const MIDIMultiTrack &src, MIDIMultiTrack &dst, int ignore_channel )
{
    dst.ClearAndResize( src.GetNumTracks() );
    dst.SetClksPerBeat( src.GetClksPerBeat() );

    MIDIClockTime ev_time = 0;
    MIDISequencer seq( &src );
    seq.GoToTime( 0 );
    if ( !seq.GetNextEventTime ( &ev_time ) )
        return; // empty src multitrack

    MIDITimedBigMessage ev;
    int ev_track;
    bool compress = true;
    MIDIClockTime old_ev_time = 0, delta_ev_time = 0, ev_time0 = 0;

    while ( seq.GetNextEvent( &ev_track, &ev ) )
    {
        if ( ev.IsServiceMsg() || ev.IsNoOp() )
            continue;

        if ( ev.IsChannelEvent() && ev.GetChannel() == ignore_channel )
            continue;

        ev_time = ev.GetTime();
        if ( compress )
        {
            // compress time intervals between adjacent messages to 1 tick
            if (ev_time > old_ev_time)
                ++delta_ev_time;

            old_ev_time = ev_time;

            ev.SetTime( delta_ev_time );

            if ( ev.ImplicitIsNoteOn() )
            {
                compress = false;
                ev_time0 = ev_time - delta_ev_time;
            }
        }
        else
        {
            ev.SetTime( ev_time - ev_time0 );
        }

        dst.GetTrack(ev_track)->PutEvent(ev);
    }
}
bool ReadMidiFile(const char *file, MIDIMultiTrack &dst)
{
    MIDIFileReadStreamFile rs( file );
    MIDIFileReadMultiTrack track_loader( &dst );
    MIDIFileRead reader( &rs, &track_loader );
    // set amount of dst tracks equal to midifile
    dst.ClearAndResize( reader.ReadNumTracks() );
    // load the midifile into the multitrack object
    return reader.Parse();
}
bool WriteMidiFile(const MIDIMultiTrack &src, const char *file, bool use_running_status)
{
    MIDIFileWriteStreamFileName out_stream( file );
    if ( !out_stream.IsValid() )
        return false;

    MIDIFileWriteMultiTrack writer( &src, &out_stream );

    // write midifile with or without running status usage
    writer.UseRunningStatus( use_running_status );

    int tracks_number = src.GetNumTracksWithEvents();
    return writer.Write( tracks_number );
}
// delete all text events from multitrack object
bool DeleteAllTracksText( MIDIMultiTrack &tracks )
{
    bool text_deleted = false;
    int num_tracks = tracks.GetNumTracksWithEvents();

    for ( int nt = 0; nt < num_tracks; ++nt )
    {
        MIDITrack &trk = *tracks.GetTrack( nt );
        int num_events = trk.GetNumEvents();

        for ( int ne = 0; ne < num_events; ++ne )
        {
            MIDITimedBigMessage *msg = trk.GetEvent( ne );
            // convert any text midi event to NoOp event
            if ( msg->IsTextEvent() )
            {
                trk.MakeEventNoOp( ne );
                text_deleted = true;
            }
        }
    }

    return text_deleted;
}
void LastEventsProlongation( MIDIMultiTrack &tracks, int track_num, MIDIClockTime add_ticks )
{
    MIDITrack *track = tracks.GetTrack( track_num );
    int index = track->GetNumEvents() - 1;
    if ( add_ticks == 0 || index < 0 )
        return;

    MIDITimedBigMessage *msg = track->GetEvent( index );
    MIDIClockTime tmax = msg->GetTime();

    while ( msg->GetTime() == tmax )
    {
        msg->SetTime( tmax + add_ticks );
        if ( --index < 0 )
            break;
        msg = track->GetEvent( index );
    }
}
void SoloMelodyConverter( const MIDIMultiTrack &src, MIDIMultiTrack &dst, int ignore_channel )
{
    // this simple code works better for src MultiTrack with 1 track,
    // if not, we can make before the call of CollapseMultiTrack()

    dst.ClearAndResize( src.GetNumTracks() );
    dst.SetClksPerBeat( src.GetClksPerBeat() );

    MIDIClockTime ev_time = 0;
    MIDISequencer seq( &src );
    seq.GoToTime( 0 );
    if ( !seq.GetNextEventTime ( &ev_time ) )
        return; // empty src multitrack

    MIDITimedBigMessage ev;
    int ev_track;

    int solo_note = -1; // highest midi note number in current time, valid values 0...127
    bool solo_note_on = false;
    MIDITimedBigMessage solo_note_on_ev; // last solo note on event
    solo_note_on_ev.SetNoOp();

    while ( seq.GetNextEvent( &ev_track, &ev ) )
    {
        if ( ev.IsServiceMsg() || ev.IsNoOp() )
            continue;

        if ( ev.IsChannelEvent() )
        {
            if ( ev.GetChannel() == ignore_channel )
                continue;

//          if ( ev.IsAllNotesOff() ) ... ; // for future work...

            if ( ev.IsNote() )
            {
                int new_note = ev.GetNote();

                // skip all note events if new note lower than solo note
                if ( new_note < solo_note )
                    continue;
                // else ( new_note >= solo_note )

                if ( ev.ImplicitIsNoteOn() ) // new note on event
                {
                    if ( solo_note_on ) // new note on after previous solo note on
                    {
                        // make noteoff message for previous solo note
                        solo_note_on_ev.SetTime( ev.GetTime() );
                        solo_note_on_ev.SetVelocity( 0 ); // note off
                        dst.GetTrack(ev_track)->PutEvent( solo_note_on_ev );

                        // make new solo note
                        solo_note_on_ev = ev;
                        solo_note = new_note;
                    }
                    else // ( solo_note_on == false ) - new note on after previous silence
                    {
                        // make new solo note
                        solo_note_on = true;
                        solo_note_on_ev = ev;
                        solo_note = new_note;
                    }
                }
                else // new note off event ( new_note >= solo_note )
                {
                    if ( solo_note_on ) // new note off after previous solo note on
                    {
                        if ( new_note == solo_note ) // solo note off event
                        {
                            // test channels of the events
                            if ( ev.GetChannel() == solo_note_on_ev.GetChannel() )
                            {
                                solo_note_on = false;
                                solo_note = -1; // erase solo_note
                            }
                            else
                                continue; // skip other note off stuff
                        }
                        else // ( new_note > solo_note ) any other note off event
                            continue; // skip other note off stuff
                    }
                    else // ( solo_note_on == false ) - new note off after previous silence
                        continue; // skip other note off stuff
                }
            }
        }
        dst.GetTrack(ev_track)->PutEvent(ev);
    }
}
void CollapseAndExpandMultiTrack( const MIDIMultiTrack &src, MIDIMultiTrack &dst )
{
    CollapseMultiTrack(src, dst);
    dst.AssignEventsToTracks(0);
}
int main ( int argc, char **argv )
{
    int retcode = -1;

    if ( argc <= 2 )
    {
        args_err();
        return retcode;
    }

    const char *infile = argv[1];
    const char *outfile = argv[2];

    int mode = 0;
    if ( argc > 3 )
        mode = abs ( atol( argv[3] ) );

    MIDIMultiTrack tracks, tracks2;

    if ( !ReadMidiFile( infile, tracks ) )
    {
        cerr << "\nError reading file " << infile << endl;
        return retcode;
    }

    if ( mode%2 == 1 ) // need to reduce outfile size
    {
        // delete all text events from all tracks
        if ( DeleteAllTracksText( tracks ) )
        {
          cout << "\nAll midi text events deleted." << endl;
        }

        if ( tracks.GetNumTracksWithEvents() == 1 )
        {
            // remake multitrack object and optimize new tracks content:
            // move all channal events to tracks 1-16, and all other types of events to track 0
            // and reduce midifile size because of increase number of events with running status
            tracks.AssignEventsToTracks( 0 );
            cout << "\nAll midi channal events moved to tracks 1-16." << endl;
        }
    }

    MIDIMultiTrack *outtracks = &tracks;
    if ( mode >= 2 ) // need to delete start pause
    {
        CompressStartPause( tracks, tracks2 );
        outtracks = &tracks2;
        cout << "\nStart pause deleted (decreased)." << endl;
    }

    if ( WriteMidiFile( *outtracks, outfile) )
    {
        int num_tracks = outtracks->GetNumTracksWithEvents();
        cout << "\nAll OK. Number of tracks with events " << num_tracks << endl;
        retcode = 0;
    }
    else
    {
        cerr << "\nError writing file " << outfile << endl;
    }

    return retcode;
}