void collectChords(std::multimap<int, MTrack> &tracks)
      {
      for (auto &track: tracks) {
            auto &chords = track.second.chords;
            if (chords.empty())
                  continue;

            const ReducedFraction threshTime = minAllowedDuration() / 2;
            const ReducedFraction fudgeTime = threshTime / 4;
            const ReducedFraction threshExtTime = threshTime / 2;

            ReducedFraction currentChordStart(-1, 1);    // invalid
            ReducedFraction curThreshTime(-1, 1);
                        // if note onTime goes after max chord offTime
                        // then this is not a chord but arpeggio
            ReducedFraction maxOffTime(-1, 1);

                              // chords here should consist of a single note
                              // because notes are not united into chords yet
            Q_ASSERT_X(areSingleNoteChords(chords),
                       "MChord: collectChords", "Some chords have more than one note");

            for (auto it = chords.begin(); it != chords.end(); ) {
                  const auto &note = it->second.notes[0];

                              // short events with len < minAllowedDuration must be cleaned up
                  Q_ASSERT_X(note.offTime - it->first >= minAllowedDuration(),
                             "MChord: collectChords", "Note length is less than min allowed duration");

                  if (it->first <= currentChordStart + curThreshTime) {

                                    // this branch should not be executed when it == chords.begin()
                        Q_ASSERT_X(it != chords.begin(),
                                   "MChord: collectChords", "it == chords.begin()");

                        if (it->first < maxOffTime) {
                                          // add current note to the previous chord
                              auto prev = std::prev(it);
                              prev->second.notes.push_back(note);
                              if (it->first >= currentChordStart + curThreshTime - fudgeTime
                                          && curThreshTime == threshTime) {
                                    curThreshTime += threshExtTime;
                                    }
                              if (note.offTime > maxOffTime)
                                    maxOffTime = note.offTime;
                              it = chords.erase(it);
                              continue;
                              }
                        }
                  currentChordStart = it->first;
                  maxOffTime = note.offTime;
                  curThreshTime = threshTime;
                  ++it;
                  }

            Q_ASSERT_X(areOnTimeValuesDifferent(chords),
                       "MChord: collectChords",
                       "onTime values of chords are equal but should be different");
            }
      }
ReducedFraction maxNoteOffTime(const QList<MidiNote> &notes)
      {
      ReducedFraction maxOffTime(0, 1);
      for (const auto &note: notes) {
            if (note.offTime > maxOffTime)
                  maxOffTime = note.offTime;
            }
      return maxOffTime;
      }
ReducedFraction findSumLengthOfRests(
            const TupletInfo &tupletInfo,
            const ReducedFraction &startBarTick)
      {
      auto beg = tupletInfo.onTime;
      const auto tupletEndTime = tupletInfo.onTime + tupletInfo.len;
      const auto tupletNoteLen = tupletInfo.len / tupletInfo.tupletNumber;
      ReducedFraction sumLen = {0, 1};

      const auto &opers = midiImportOperations.data()->trackOpers;
      const int currentTrack = midiImportOperations.currentTrack();

      for (const auto &chord: tupletInfo.chords) {
            const auto staccatoIt = (opers.simplifyDurations.value(currentTrack))
                        ? tupletInfo.staccatoChords.find(chord.first)
                        : tupletInfo.staccatoChords.end();

            const MidiChord &midiChord = chord.second->second;
            const auto &chordOnTime = (chord.second->first < startBarTick)
                        ? startBarTick
                        : Quantize::findQuantizedTupletChordOnTime(*chord.second, tupletInfo.len,
                                  tupletLimits(tupletInfo.tupletNumber).ratio, startBarTick);
            if (beg < chordOnTime)
                  sumLen += (chordOnTime - beg);
            ReducedFraction maxOffTime(0, 1);
            for (int i = 0; i != midiChord.notes.size(); ++i) {
                  auto noteOffTime = midiChord.notes[i].offTime;
                  if (staccatoIt != tupletInfo.staccatoChords.end() && i == staccatoIt->second)
                        noteOffTime = chordOnTime + tupletNoteLen;
                  if (noteOffTime > maxOffTime)
                        maxOffTime = noteOffTime;
                  }
            beg = Quantize::findQuantizedTupletNoteOffTime(chord.first, maxOffTime, tupletInfo.len,
                                    tupletLimits(tupletInfo.tupletNumber).ratio, startBarTick).first;
            if (beg >= tupletEndTime)
                  break;
            }
      if (beg < tupletEndTime)
            sumLen += (tupletEndTime - beg);
      return sumLen;
      }
void collectChords(std::multimap<int, MTrack> &tracks)
      {
      for (auto &track: tracks) {
            auto &chords = track.second.chords;
            if (chords.empty())
                  continue;

            const auto &opers = preferences.midiImportOperations.data()->trackOpers;
            const auto minAllowedDur = minAllowedDuration();

            const auto threshTime = (opers.isHumanPerformance.value())
                                          ? minAllowedDur * 2 : minAllowedDur / 2;
            const auto fudgeTime = threshTime / 4;
            const auto threshExtTime = threshTime / 2;

            ReducedFraction currentChordStart(-1, 1);    // invalid
            ReducedFraction curThreshTime(-1, 1);
                        // if note onTime goes after max chord offTime
                        // then this is not a chord but arpeggio
            ReducedFraction maxOffTime(-1, 1);

                              // chords here should consist of a single note
                              // because notes are not united into chords yet
            Q_ASSERT_X(areSingleNoteChords(chords),
                       "MChord: collectChords", "Some chords have more than one note");

            for (auto it = chords.begin(); it != chords.end(); ) {
                  const auto &note = it->second.notes[0];

                              // short events with len < minAllowedDuration must be cleaned up
                  Q_ASSERT_X(note.offTime - it->first >= minAllowedDuration(),
                             "MChord: collectChords", "Note length is less than min allowed duration");

                  if (it->first < currentChordStart + curThreshTime) {

                                    // this branch should not be executed when it == chords.begin()
                        Q_ASSERT_X(it != chords.begin(),
                                   "MChord: collectChords", "it == chords.begin()");

                        if (it->first <= maxOffTime - minAllowedDur) {
                                          // add current note to the previous chord
                              auto prev = std::prev(it);

                              bool hasNoteWithThisPitch = false;
                              for (const auto &n: prev->second.notes) {
                                    if (n.pitch == note.pitch) {
                                          hasNoteWithThisPitch = true;
                                          break;
                                          }
                                    }
                              if (!hasNoteWithThisPitch) {
                                    prev->second.notes.push_back(note);
                                    if (note.offTime > maxOffTime)
                                          maxOffTime = note.offTime;
                                    }
                              if (it->first >= currentChordStart + curThreshTime - fudgeTime
                                          && curThreshTime == threshTime) {
                                    curThreshTime += threshExtTime;
                                    }
                              it = chords.erase(it);
                              continue;
                              }
                        }
                  currentChordStart = it->first;
                  maxOffTime = note.offTime;
                  curThreshTime = threshTime;
                  ++it;
                  }

            Q_ASSERT_X(areOnTimeValuesDifferent(chords),
                       "MChord: collectChords",
                       "onTime values of chords are equal but should be different");
            Q_ASSERT_X(areNotesLongEnough(chords),
                       "MChord::collectChords", "There are too short notes");
            }
      }