/**
  * Within the given set of notes find the one that is the leftmost one and that is nearest to the
  * given pitch, but exclude notes that have the same pitch.
  *
  * @param pitch
  * @param notes
  * @return
  */
 private static Note nearestNote(int pitch, List<Note> notes) {
   Note result = null;
   int smallestPitchdiff = Integer.MAX_VALUE;
   long nearestPos = Long.MAX_VALUE;
   for (Note n : notes) {
     int thisPitchDiff = Math.abs(pitch - n.getPitch());
     long thisPos = n.getTickPos();
     if (thisPos < nearestPos) {
       result = n;
       nearestPos = thisPos;
       smallestPitchdiff = thisPitchDiff;
     } else {
       if (nearestPos == thisPos) {
         if (thisPitchDiff < smallestPitchdiff) {
           result = n;
           smallestPitchdiff = thisPitchDiff;
         }
       }
     }
   }
   if (smallestPitchdiff != 0) {
     return result;
   } else {
     return null;
   }
 }
 private static void handleTrack(
     Track outputTrack, Track inputTrack, long startTick, long endTick) {
   // handle all other events
   for (int eventI = 0; eventI < inputTrack.size(); eventI++) {
     MidiEvent midEvent = inputTrack.get(eventI);
     if (!Note.isNoteOnEvent(midEvent)) {
       if (!Note.isNoteOffEvent(midEvent)) {
         outputTrack.add(midEvent);
       }
     }
   }
   // handle all note events
   NoteTrack noteTrack = new NoteTrack(inputTrack);
   for (int i = 0; i < noteTrack.size(); i++) {
     Note thisNote = noteTrack.get(i);
     List<Note> followingNotes = findFollowingNotes(noteTrack, i);
     Note newNote = handleNote(thisNote, followingNotes, startTick, endTick);
     outputTrack.add(newNote.getNoteOnEvent());
     outputTrack.add(newNote.getNoteOffEvent());
   }
 }
 /**
  * Collect all the notes that follow the given note, within a range correspondin to the minimum
  * rest.
  *
  * @param noteTrack
  * @param index
  * @return
  */
 private static List<Note> findFollowingNotes(NoteTrack noteTrack, int index) {
   ArrayList<Note> result = new ArrayList<>();
   long posStart = noteTrack.get(index).getTickPos();
   long noteDur = noteTrack.get(index).getDuration();
   long posEnd = posStart + noteDur;
   long maxSearch = posEnd + minimumRest;
   for (int i = index + 1; i < noteTrack.size(); i++) {
     Note note = noteTrack.get(i);
     if (note.getTickPos() > maxSearch) {
       // the note is outside our search area, as the note are sorted in ascending order, we have
       // finished.
       break;
     }
     // all notes that follow the end of the given note are added to the list
     // remarque: we allow for an overlap of five ticks because of the impression of some Midi
     // editors.
     if (note.getTickPos() >= (posEnd - 5)) {
       result.add(note);
     }
   }
   return result;
 }
  /**
   * Within the set set of following notes find one that we can link with.
   *
   * @param note
   * @param follwoingNotes
   * @return
   */
  private static Note noteToLinkWith(Note note, List<Note> follwoingNotes) {
    if (follwoingNotes.isEmpty()) {
      return null;
    }

    Note candidate = nearestNote(note.getPitch(), follwoingNotes);
    if (candidate == null) {
      return null;
    }
    // avoid collision with a following note
    long newNoteEnd = note.getTickPos() + prolongatedDuration(note, candidate);
    for (Note fNote : follwoingNotes) {
      if (fNote.getPitch() == note.getPitch()) {
        if (fNote.getTickPos() <= newNoteEnd) {
          return null;
        }
      }
    }
    return candidate;
  }
  private static Note handleNote(
      Note note, List<Note> follwoingNotes, long startTick, long endTick) {
    if (note.getTickPos() < startTick) {
      return note;
    }
    if (note.getTickPos() + note.getDuration() > endTick) {
      return note;
    }

    Note noteToLinkWith = noteToLinkWith(note, follwoingNotes);
    if (noteToLinkWith != null) {
      long newDuration = prolongatedDuration(note, noteToLinkWith);

      Note newNote =
          new Note(
              note.getChannel(),
              note.getPitch(),
              note.getVelocity(),
              note.getTickPos(),
              newDuration);
      slurEndNotes.add(noteToLinkWith);
      slurEndNotes.remove(note);
      return newNote;
    }

    if (slurEndNotes.contains(note)) {
      Note newNote =
          new Note(
              note.getChannel(),
              note.getPitch(),
              (note.getVelocity() * 80) / 100,
              note.getTickPos(),
              (note.getDuration() * 80) / 100);
      slurEndNotes.remove(note);
      return newNote;
    }

    return note;
  }
 private static long prolongatedDuration(Note note, Note noteToLinkWith) {
   return (noteToLinkWith.getTickPos() - note.getTickPos()) + noteOverlap;
 }