    #include "importmidi_quant.h"
    #include "libmscore/sig.h"
    #include "importmidi_fraction.h"
    #include "libmscore/mscore.h"
    #include "mscore/preferences.h"
    #include "importmidi_chord.h"
    #include "importmidi_meter.h"
    #include "importmidi_tuplet.h"
    #include "importmidi_inner.h"
    #include "importmidi_beat.h"
    #include "importmidi_voice.h"
    #include "importmidi_tempo.h"
    #include "importmidi_operations.h"

    #include <set>
    #include <deque>

    namespace Ms {

    extern Preferences preferences;

    namespace Quantize {

    ReducedFraction quantValueToFraction(MidiOperations::QuantValue quantValue)
          {
          const auto division = ReducedFraction::fromTicks(MScore::division);
          ReducedFraction fraction;

          switch (quantValue) {
                case MidiOperations::QuantValue::Q_4:
                      fraction = division;
                      break;
                case MidiOperations::QuantValue::Q_8:
                      fraction = division / 2;
                      break;
                case MidiOperations::QuantValue::Q_16:
                      fraction = division / 4;
                      break;
                case MidiOperations::QuantValue::Q_32:
                      fraction = division / 8;
                      break;
                case MidiOperations::QuantValue::Q_64:
                      fraction = division / 16;
                      break;
                case MidiOperations::QuantValue::Q_128:
                      fraction = division / 32;
                      break;
                default:
                      Q_ASSERT_X(false, "Quantize::quantValueToFraction", "Unknown quant value");
                      break;
                }

          return fraction;
          }

    MidiOperations::QuantValue fractionToQuantValue(const ReducedFraction &fraction)
          {
          const auto division = ReducedFraction::fromTicks(MScore::division);
           MidiOperations::QuantValue quantValue = MidiOperations::QuantValue::Q_4;

          if (fraction == division)
                quantValue = MidiOperations::QuantValue::Q_4;
          else if (fraction == division / 2)
                quantValue = MidiOperations::QuantValue::Q_8;
          else if (fraction == division / 4)
                quantValue = MidiOperations::QuantValue::Q_16;
          else if (fraction == division / 8)
                quantValue = MidiOperations::QuantValue::Q_32;
          else if (fraction == division / 16)
                quantValue = MidiOperations::QuantValue::Q_64;
          else if (fraction == division / 32)
                quantValue = MidiOperations::QuantValue::Q_128;
          else {
                qDebug("unknown quant fraction %d/%d  division %d/%d", fraction.numerator(), fraction.denominator(),
                   division.numerator(), division.denominator());
                Q_ASSERT_X(false, "Quantize::fractionToQuantValue", "Unknown quant fraction");
                }

          return quantValue;
          }

    MidiOperations::QuantValue defaultQuantValueFromPreferences()
          {
          const auto fraction = ReducedFraction::fromTicks(preferences.shortestNote);
          return fractionToQuantValue(fraction);
          }

    ReducedFraction shortestQuantizedNoteInRange(
                const std::multimap<ReducedFraction, MidiChord>::const_iterator &beg,
                const std::multimap<ReducedFraction, MidiChord>::const_iterator &end)
          {
          const auto division = ReducedFraction::fromTicks(MScore::division);
          auto minDuration = division;
          for (auto it = beg; it != end; ++it) {
                for (const auto &note: it->second.notes) {
                      if (note.offTime - it->first < minDuration)
                            minDuration = note.offTime - it->first;
                      }
                }
          const auto minAllowedDuration = MChord::minAllowedDuration();
          auto shortest = division;
          for ( ; shortest > minAllowedDuration; shortest /= 2) {
                if (shortest <= minDuration)
                      break;
                }
          return shortest;
          }

    ReducedFraction reduceQuantIfDottedNote(const ReducedFraction &noteLen,
                                            const ReducedFraction &raster)
          {
          auto newRaster = raster;
          const auto div = noteLen / raster;
          const double ratio = div.toDouble();
          if (ratio > 1.45 && ratio < 1.55)     // 1.5: dotted note that is larger than quantization value
                newRaster /= 2;                 // reduce quantization error for dotted notes
          return newRaster;
          }

    ReducedFraction quantizeValue(const ReducedFraction &value,
                                  const ReducedFraction &quant)
          {
          const auto valueReduced = value.reduced();
          const auto rasterReduced = quant.reduced();
          int valNum = valueReduced.numerator() * rasterReduced.denominator();
          const int rastNum = rasterReduced.numerator() * valueReduced.denominator();
          const int commonDen = valueReduced.denominator() * rasterReduced.denominator();
          valNum = ((valNum + rastNum / 2) / rastNum) * rastNum;
          return ReducedFraction(valNum, commonDen).reduced();
          }

    ReducedFraction quantForLen(const ReducedFraction &noteLen,
                                const ReducedFraction &basicQuant)
          {
          auto quant = basicQuant;
          while (quant > noteLen && quant >= MChord::minAllowedDuration() * 2)
                quant /= 2;

          if (quant >= MChord::minAllowedDuration() * 2)
                quant = reduceQuantIfDottedNote(noteLen, quant);

          return quant;
          }

    ReducedFraction quantForTuplet(const ReducedFraction &tupletLen,
                                   const ReducedFraction &tupletRatio)
          {
          const auto quant = tupletLen / tupletRatio.numerator();

          Q_ASSERT_X(quant >= MChord::minAllowedDuration(),
                     "Quantize::quantForTuplet", "Too small quant value");

          if (quant >= MChord::minAllowedDuration() * 2)
                return quant / 2;

          return quant;
          }

    ReducedFraction findMinQuant(
                const std::pair<const ReducedFraction, MidiChord> &chord,
                const ReducedFraction &basicQuant)
          {
          ReducedFraction minQuant(-1, 1);
          for (const auto &note: chord.second.notes) {
                const auto quant = quantForLen(note.offTime - chord.first, basicQuant);
                if (minQuant == ReducedFraction(-1, 1) || quant < minQuant)
                      minQuant = quant;
                }
          return minQuant;
          }

    ReducedFraction findQuantizedTupletChordOnTime(
                const std::pair<const ReducedFraction, MidiChord> &chord,
                const ReducedFraction &tupletLen,
                const ReducedFraction &tupletRatio,
                const ReducedFraction &rangeStart)
          {
          if (chord.first <= rangeStart)
                return rangeStart;
          const auto quant = quantForTuplet(tupletLen, tupletRatio);
          return rangeStart + quantizeValue(chord.first - rangeStart, quant);
          }

    ReducedFraction findQuantizedChordOnTime(
                const std::pair<const ReducedFraction, MidiChord> &chord,
                const ReducedFraction &basicQuant)
          {
          const ReducedFraction quant = findMinQuant(chord, basicQuant);
          return quantizeValue(chord.first, quant);
          }

    std::pair<ReducedFraction, ReducedFraction>
    findQuantizedTupletNoteOffTime(
                const ReducedFraction &onTime,
                const ReducedFraction &offTime,
                const ReducedFraction &tupletLen,
                const ReducedFraction &tupletRatio,
                const ReducedFraction &rangeStart)
          {
          if (offTime <= rangeStart)
                return {rangeStart, tupletLen};

          ReducedFraction qOffTime;
          auto quant = quantForTuplet(tupletLen, tupletRatio);

          while (true) {
                qOffTime = rangeStart + quantizeValue(offTime - rangeStart, quant);
                if (qOffTime <= onTime) {
                      if (quant >= MChord::minAllowedDuration() * 2) {
                            quant /= 2;
                            continue;
                            }
                      qOffTime = onTime + quant;
                      }
                break;
                }
          return {qOffTime, quant};
          }

    std::pair<ReducedFraction, ReducedFraction>
    findQuantizedNoteOffTime(
                const std::pair<const ReducedFraction, MidiChord> &chord,
                const ReducedFraction &offTime,
                const ReducedFraction &basicQuant)
          {
          ReducedFraction qOffTime;
          auto quant = quantForLen(offTime - chord.first, basicQuant);

          while (true) {
                qOffTime = quantizeValue(offTime, quant);
                if (qOffTime <= chord.first) {
                      if (quant >= MChord::minAllowedDuration() * 2) {
                            quant /= 2;
                            continue;
                            }
                      qOffTime = chord.first + quant;
                      }
                break;
                }
          return {qOffTime, quant};
          }

    ReducedFraction findMinQuantizedOnTime(
                const std::pair<const ReducedFraction, MidiChord> &chord,
                const ReducedFraction &basicQuant)
          {
          ReducedFraction minOnTime(-1, 1);
          for (const auto &note: chord.second.notes) {
                const auto quant = quantForLen(note.offTime - chord.first, basicQuant);
                const auto onTime = quantizeValue(chord.first, quant);
                if (minOnTime == ReducedFraction(-1, 1) || onTime < minOnTime)
                      minOnTime = onTime;
                }
          return minOnTime;
          }

    ReducedFraction findMaxQuantizedTupletOffTime(
                const std::pair<const ReducedFraction, MidiChord> &chord,
                const ReducedFraction &tupletLen,
                const ReducedFraction &tupletRatio,
                const ReducedFraction &rangeStart)
          {
          ReducedFraction maxOffTime(0, 1);
          for (const auto &note: chord.second.notes) {
                if (note.offTime <= rangeStart)
                      continue;
                const auto offTime = findQuantizedTupletNoteOffTime(
                                  chord.first, note.offTime, tupletLen, tupletRatio, rangeStart).first;
                if (offTime > maxOffTime)
                      maxOffTime = offTime;
                }
          return maxOffTime;
          }

    ReducedFraction findMaxQuantizedOffTime(
                const std::pair<const ReducedFraction, MidiChord> &chord,
                const ReducedFraction &basicQuant)
          {
          ReducedFraction maxOffTime(0, 1);
          for (const auto &note: chord.second.notes) {
                const auto offTime = findQuantizedNoteOffTime(chord, note.offTime, basicQuant).first;
                if (offTime > maxOffTime)
                      maxOffTime = offTime;
                }
          return maxOffTime;
          }

    ReducedFraction findOnTimeTupletQuantError(
                const std::pair<const ReducedFraction, MidiChord> &chord,
                const ReducedFraction &tupletLen,
                const ReducedFraction &tupletRatio,
                const ReducedFraction &rangeStart)
          {
          const auto qOnTime = findQuantizedTupletChordOnTime(chord, tupletLen,
                                                              tupletRatio, rangeStart);
          return (chord.first - qOnTime).absValue();
          }

    ReducedFraction findOnTimeQuantError(
                const std::pair<const ReducedFraction, MidiChord> &chord,
                const ReducedFraction &basicQuant)
          {
          const auto qOnTime = findQuantizedChordOnTime(chord, basicQuant);
          return (chord.first - qOnTime).absValue();
          }

    ReducedFraction findOffTimeTupletQuantError(
                const ReducedFraction &onTime,
                const ReducedFraction &offTime,
                const ReducedFraction &tupletLen,
                const ReducedFraction &tupletRatio,
                const ReducedFraction &rangeStart)
          {
          const auto qOffTime = findQuantizedTupletNoteOffTime(onTime, offTime, tupletLen,
                                                               tupletRatio, rangeStart).first;
          return (offTime - qOffTime).absValue();
          }

    ReducedFraction findOffTimeQuantError(
                const std::pair<const ReducedFraction, MidiChord> &chord,
                const ReducedFraction &offTime,
                const ReducedFraction &basicQuant)
          {
          const auto qOffTime = findQuantizedNoteOffTime(chord, offTime, basicQuant).first;
          return (offTime - qOffTime).absValue();
          }

    ReducedFraction findQuantForRange(
                const std::multimap<ReducedFraction, MidiChord>::const_iterator &beg,
                const std::multimap<ReducedFraction, MidiChord>::const_iterator &end,
                const ReducedFraction &basicQuant)
          {
          const auto shortestLen = shortestQuantizedNoteInRange(beg, end);
          return quantForLen(shortestLen, basicQuant);
          }

    //--------------------------------------------------------------------------------------------

    bool isHumanPerformance(
                const std::multimap<ReducedFraction, MidiChord> &chords,
                const TimeSigMap *sigmap)
          {
          if (chords.empty())
                return false;

          const auto basicQuant = ReducedFraction::fromTicks(MScore::division) / 4;    // 1/16
          int matches = 0;
          int count = 0;

          std::set<ReducedFraction> usedOnTimes;

          for (const auto &chord: chords) {
                const auto quant = qMin(basicQuant,
                                        quantForLen(MChord::maxNoteLen(chord), basicQuant));
                const auto onTime = quantizeValue(chord.first, quant);
                int barIndex, beat, tick;
                sigmap->tickValues(onTime.ticks(), &barIndex, &beat, &tick);

                const auto barStart = ReducedFraction::fromTicks(sigmap->bar2tick(barIndex, 0));
                const auto barFraction = ReducedFraction(sigmap->timesig(barStart.ticks()).timesig());
                const auto beatLen = Meter::beatLength(barFraction);

                if (((onTime - barStart) / beatLen).reduced().denominator() == 1
                            && usedOnTimes.find(onTime) == usedOnTimes.end()) {
                      usedOnTimes.insert(onTime);
                      ++count;
                      const auto diff = (onTime - chord.first).absValue();
                      if (diff < MChord::minAllowedDuration())
                            ++matches;
                      }
                }

          const double TOL = 0.6;
          const double matched = matches * 1.0 / count;

          return matched < TOL;
          }

    std::multimap<int, MTrack>
    getTrackWithAllChords(const std::multimap<int, MTrack> &tracks)
          {
          std::multimap<int, MTrack> singleTrack{{0, MTrack()}};
          auto &allChords = singleTrack.begin()->second.chords;
          for (const auto &track: tracks) {
                const MTrack &t = track.second;
                for (const auto &chord: t.chords) {
                      allChords.insert(chord);
                      }
                }
          return singleTrack;
          }

    void setIfHumanPerformance(
                const std::multimap<int, MTrack> &tracks,
                TimeSigMap *sigmap)
          {
          auto allChordsTrack = getTrackWithAllChords(tracks);
          MChord::collectChords(allChordsTrack, {2, 1}, {1, 2});
          const MTrack &track = allChordsTrack.begin()->second;
          const auto &allChords = track.chords;
          if (allChords.empty())
                return;
          const bool isHuman = isHumanPerformance(allChords, sigmap);
          auto &opers = preferences.midiImportOperations.data()->trackOpers;
          if (opers.isHumanPerformance.canRedefineDefaultLater())
                opers.isHumanPerformance.setDefaultValue(isHuman);

          if (isHuman) {
                if (opers.quantValue.canRedefineDefaultLater())
                      opers.quantValue.setDefaultValue(MidiOperations::QuantValue::Q_8);
                if (opers.maxVoiceCount.canRedefineDefaultLater())
                      opers.maxVoiceCount.setDefaultValue(MidiOperations::VoiceCount::V_2);
                const double ticksPerSec = MidiTempo::findBasicTempo(tracks, true) * MScore::division;
                MidiBeat::findBeatLocations(allChords, sigmap, ticksPerSec);      // and set time sig
                }
          }

    //--------------------------------------------------------------------------------------------

    // remove small intersection with the next chord

    void preserveLegato(
                ReducedFraction &offTime,
                bool isOffTimeInTuplet,
                const std::multimap<ReducedFraction, MidiChord>::iterator &chordIt,
                const std::multimap<ReducedFraction, MidiChord> &chords,
                const ReducedFraction &basicQuant)
          {
          auto it = std::next(chordIt);
          while (it != chords.end() && it->second.voice != chordIt->second.voice)
                ++it;
          if (it != chords.end()) {
                const auto ioi = it->first - chordIt->first;
                const auto cross = offTime - it->first;

                if (it->second.isInTuplet && isOffTimeInTuplet) {
                                  // while we don't split tuplet into voices we don't want to have
                                  // note intersections smaller than tuplet note length
                      const auto &tuplet = it->second.tuplet->second;
                      const auto tupletNoteLen = tuplet.len / tuplet.tupletNumber;
                      if (cross > ReducedFraction(0, 1) && cross < tupletNoteLen)
                            offTime = it->first;
                      }
                else if (!it->second.isInTuplet && !isOffTimeInTuplet) {
                      if (cross > ReducedFraction(0, 1) && cross < ioi / 2 && cross < basicQuant / 2)
                            offTime = it->first;
                      }
                }
          }

    std::pair<ReducedFraction, ReducedFraction>
    quantizeOffTimeForTuplet(
                const ReducedFraction &noteOffTime,
                const std::multimap<ReducedFraction, MidiChord>::iterator &chordIt,
                const std::multimap<ReducedFraction, MidiChord> &chords,
                const ReducedFraction &basicQuant,
                const MidiTuplet::TupletData &tuplet)
          {

          Q_ASSERT_X(chordIt->first < noteOffTime,
                     "Quantize::quantizeOffTimeForTuplet", "Negative or zero note length");

          const auto tupletRatio = MidiTuplet::tupletLimits(tuplet.tupletNumber).ratio;
          const auto result = findQuantizedTupletNoteOffTime(
                                 chordIt->first, noteOffTime, tuplet.len, tupletRatio, tuplet.onTime);
          auto offTime = result.first;
          auto quant = result.second;

          preserveLegato(offTime, true, chordIt, chords, basicQuant);

                      // verify that offTime is still inside tuplet
          if (offTime < tuplet.onTime) {
                offTime = tuplet.onTime;
                quant = tuplet.len;
                }
          else if (offTime > tuplet.onTime + tuplet.len) {
                offTime = tuplet.onTime + tuplet.len;
                quant = tuplet.len;
                }

          return {offTime, quant};
          }

    std::pair<ReducedFraction, ReducedFraction>
    quantizeOffTimeForNonTuplet(
                const ReducedFraction &noteOffTime,
                const std::multimap<ReducedFraction, MidiChord>::iterator &chordIt,
                const std::multimap<ReducedFraction, MidiChord> &chords,
                const ReducedFraction &basicQuant)
          {

          Q_ASSERT_X(chordIt->first < noteOffTime,
                     "Quantize::quantizeOffTimeForNonTuplet", "Negative or zero note length");

          const MidiChord &chord = chordIt->second;
          const auto result = findQuantizedNoteOffTime(*chordIt, noteOffTime, basicQuant);
          auto offTime = result.first;
          auto quant = result.second;
          preserveLegato(offTime, false, chordIt, chords, basicQuant);

                      // verify that offTime is still outside tuplets
          if (chord.isInTuplet) {
                const auto &tuplet = chord.tuplet->second;
                if (offTime < tuplet.onTime + tuplet.len) {
                      offTime = tuplet.onTime + tuplet.len;
                      quant = tuplet.len;
                      return {offTime, quant};
                      }
                }
          auto next = std::next(chordIt);
          while (true) {
                if (next == chords.end())
                      break;
                if (!next->second.isInTuplet || next->second.voice != chord.voice) {
                      ++next;
                      continue;
                      }
                const auto &tuplet = next->second.tuplet->second;
                if (next->second.tuplet == chord.tuplet
                            || tuplet.onTime + tuplet.len <= offTime) {
                      ++next;
                      continue;
                      }

                if (offTime > tuplet.onTime) {
                      offTime = tuplet.onTime;
                      quant = tuplet.len;
                      }
                break;
                }

          return {offTime, quant};
          }

    struct QuantPos
          {
          ReducedFraction time;
          int metricalLevel;
          double penalty;
          int prevPos;
          };

    struct QuantData
          {
          std::multimap<ReducedFraction, MidiChord>::const_iterator chord;
          ReducedFraction quant;
          ReducedFraction quantForLen;
          ReducedFraction chordRangeStart;
          ReducedFraction chordRangeEnd;
                      // if inter on time interval with previous chord
                      // is less than min allowed duration
                      // then chord can be merged with previous chord
          bool canMergeWithPrev = false;
          int metricalLevelForLen;
          std::vector<QuantPos> positions;
          };

    #ifdef QT_DEBUG

    bool areAllVoicesSame(
                const std::deque<std::multimap<ReducedFraction, MidiChord>::const_iterator> &chords)
          {
          auto it = chords.begin();
          const int voice = (*it)->second.voice;
          for (++it; it != chords.end(); ++it) {
                if ((*it)->second.voice != voice)
                      return false;
                }
          return true;
          }

    bool areChordsDifferent(
                const std::deque<std::multimap<ReducedFraction, MidiChord>::const_iterator> &chords)
          {
          std::set<const std::pair<const ReducedFraction, MidiChord> *> chordSet;

          for (const auto &chord: chords) {
                const auto it = chordSet.find(&*chord);
                if (it == chordSet.end())
                      chordSet.insert(&*chord);
                else
                      return false;
                }

          return true;
          }

    bool notLessThanPrev(
                const std::vector<QuantData>::iterator &it,
                const std::vector<QuantData> &data)
          {
          if (it != data.begin()) {
                const auto prev = std::prev(it);
                if (prev->chordRangeStart > it->chordRangeStart)
                      return false;
                }
          return true;
          }

    bool isTupletRangeCorrect(
                const MidiTuplet::TupletData &tuplet,
                const ReducedFraction &rangeStart,
                const ReducedFraction &rangeEnd)
          {
          return (rangeStart == tuplet.onTime && rangeEnd == tuplet.onTime + tuplet.len);
          }

    void checkOffTime(
                const MidiNote &note,
                const std::multimap<ReducedFraction, MidiChord>::iterator &chordIt,
                const std::multimap<ReducedFraction, MidiChord> &chords)
          {

          Q_ASSERT_X(note.offTime - chordIt->first >= MChord::minAllowedDuration(),
                     "Quantize::checkOffTime", "Too small note length");

          if (note.isInTuplet) {
                const auto &tuplet = note.tuplet->second;

                Q_ASSERT_X(note.offTime >= tuplet.onTime
                           && note.offTime <= tuplet.onTime + tuplet.len,
                           "Quantize::checkOffTime",
                           "Note off time is outside tuplet but should be inside");
                }
          else {
                const auto &chord = chordIt->second;

                auto next = std::next(chordIt);
                while (true) {
                      if (next == chords.end())
                            break;
                      if (!next->second.isInTuplet || next->second.voice != chord.voice) {
                            ++next;
                            continue;
                            }
                      const auto &tuplet = next->second.tuplet->second;
                      if ((chord.isInTuplet && next->second.tuplet == chord.tuplet)
                                  || tuplet.onTime + tuplet.len <= note.offTime) {
                            ++next;
                            continue;
                            }

                      Q_ASSERT_X(note.offTime <= tuplet.onTime,
                                 "Quantize::checkOffTime",
                                 "Note off time is inside next tuplet but it shouldn't");
                      break;
                      }

                auto prev = chordIt;
                while (true) {
                      if (prev->second.barIndex != chord.barIndex)
                            break;
                      if (prev->second.voice != chord.voice
                                  || !prev->second.isInTuplet
                                  || (chord.isInTuplet && prev->second.tuplet == chord.tuplet)) {
                            if (prev != chords.begin()) {
                                  --prev;
                                  continue;
                                  }
                            }
                      else {
                            const auto &tuplet = prev->second.tuplet->second;

                            Q_ASSERT_X(note.offTime >= tuplet.onTime + tuplet.len,
                                       "Quantize::checkOffTime",
                                       "Note off time is inside prev tuplet but it shouldn't");
                            }
                      break;
                      }
                }
          }

    bool areOnTimeValuesDifferent(const std::multimap<ReducedFraction, MidiChord> &chords)
          {
          std::set<std::tuple<ReducedFraction, int, int>> onTimeVoicesBars;
          for (const auto &chord: chords) {
                const auto tuple = std::make_tuple(chord.first,
                                                   chord.second.voice,
                                                   chord.second.barIndex);
                if (onTimeVoicesBars.find(tuple) == onTimeVoicesBars.end())
                      onTimeVoicesBars.insert(tuple);
                else
                      return false;
                }
          return true;
          }

    bool areTupletChordsConsistent(const std::multimap<ReducedFraction, MidiChord> &chords)
          {
          std::multimap<ReducedFraction, MidiTuplet::TupletData>::iterator prevTuplet;
          bool prevTupletSet = false;
          bool isInTuplet = false;
          const int limit = MidiVoice::voiceLimit();

          for (int voice = 0; voice != limit; ++voice) {
                for (const auto &chord: chords) {
                      const MidiChord &c = chord.second;
                      if (c.voice != voice)
                            continue;
                      if (c.isInTuplet) {
                            if (!isInTuplet && prevTupletSet && c.tuplet == prevTuplet) {
                                  qDebug() << "Inconsistent tuplets, bar (from 1):"
                                           << (c.barIndex + 1);
                                  return false;     // there is a non-tuplet chord inside tuplet
                                  }
                            isInTuplet = true;
                            prevTuplet = c.tuplet;
                            prevTupletSet = true;
                            }
                      else {
                            isInTuplet = false;
                            }
                      }
                }
          return true;
          }

    bool areTupletChordsConsistent(
                const std::deque<std::multimap<ReducedFraction, MidiChord>::const_iterator> &chords)
          {
          auto it = chords.begin();
          const bool isInTuplet = (*it)->second.isInTuplet;
          for (std::next(it); it != chords.end(); ++it) {
                if (isInTuplet && (!(*it)->second.isInTuplet
                                   || (*it)->second.tuplet != (*chords.begin())->second.tuplet)) {
                      return false;
                      }
                if (!isInTuplet && (*it)->second.isInTuplet)
                      return false;
                }
          return true;
          }

    bool areChordsSortedByOnTime(
                const std::deque<std::multimap<ReducedFraction, MidiChord>::const_iterator> &chords)
          {
          for (size_t i = 0; i != chords.size() - 1; ++i) {
                if (chords[i]->first >= chords[i + 1]->first)
                      return false;
                }
          return true;
          }

    #endif

    ReducedFraction quantizeToLarge(
                const ReducedFraction &time,
                const ReducedFraction &quant)
          {
          const auto ratio = time / quant;
          auto quantized = quant * (ratio.numerator() / ratio.denominator());
          if (quantized < time)
                quantized += quant;
          return quantized;
          }

    ReducedFraction quantizeToSmall(
                const ReducedFraction &time,
                const ReducedFraction &quant)
          {
          const auto ratio = time / quant;
          auto quantized = quant * (ratio.numerator() / ratio.denominator());
          if (quantized >= time)
                quantized -= quant;
          return quantized;
          }

    void findMetricalLevels(
                std::vector<QuantData> &data,
                const std::deque<std::multimap<ReducedFraction, MidiChord>::const_iterator> &chords,
                const ReducedFraction &tupletQuant,
                const ReducedFraction &barStart,
                const ReducedFraction &barFraction)
          {
          const auto divsInfo = (tupletQuant != ReducedFraction(-1, 1))
                      ? Meter::divisionInfo(barFraction, {(*chords.begin())->second.tuplet->second})
                      : Meter::divisionInfo(barFraction, {});

          for (QuantData &d: data) {
                for (auto t = d.chordRangeStart; t <= d.chordRangeEnd; t += d.quant) {
                      QuantPos p;
                      p.time = t;
                      p.metricalLevel = Meter::levelOfTick(t - barStart, divsInfo);
                      d.positions.push_back(p);
                      }

                int minLevel = std::numeric_limits<int>::max();
                while (true) {
                      for (auto t = d.chordRangeStart; t <= d.chordRangeEnd; t += d.quant) {
                            if (((t - barStart) / d.quantForLen).reduced().denominator() != 1)
                                  continue;
                            int level = Meter::levelOfTick(t - barStart, divsInfo);
                            if (level < minLevel)
                                  minLevel = level;
                            }
                      if (minLevel == std::numeric_limits<int>::max()) {
                            d.quantForLen /= 2;

                            Q_ASSERT_X(d.quantForLen >= MChord::minAllowedDuration(),
                                       "Quantize::findMetricalLevels",
                                       "quantForLen < min allowed duration");

                            continue;
                            }
                      break;
                      }
                d.metricalLevelForLen = minLevel;
                }
          }

    void findChordRangeEnds(
                std::vector<QuantData> &data,
                const ReducedFraction &rangeStart,
                const ReducedFraction &rangeEnd,
                const ReducedFraction &barStart,
                const ReducedFraction &beatLen)
          {
    #ifdef NDEBUG
          (void)rangeStart;
    #endif
          for (auto it = data.rbegin(); it != data.rend(); ++it) {
                QuantData &d = *it;
                d.chordRangeEnd = barStart + quantizeToSmall(rangeEnd - barStart, d.quant);

                Q_ASSERT_X(d.chord->first + beatLen >= rangeStart,
                           "Quantize::findChordRangeEnds", "chord on time + beatLen < rangeStart");

                if (d.chord->first + beatLen < rangeEnd) {
                      d.chordRangeEnd = barStart + quantizeToSmall(
                                              d.chord->first + beatLen - barStart, d.quant);
                      }
                if (it != data.rbegin()) {
                      const auto prev = std::prev(it);    // next in terms of time
                      if (prev->chordRangeEnd < d.chordRangeEnd) {
                            d.chordRangeEnd = barStart + quantizeToSmall(
                                                    prev->chordRangeEnd - barStart, d.quant);
                            }
                      if (!prev->canMergeWithPrev && d.chordRangeEnd == prev->chordRangeEnd)
                            d.chordRangeEnd -= d.quant;
                      }
                if (d.chordRangeEnd < d.chordRangeStart)
                      d.chordRangeEnd = d.chordRangeStart;

                Q_ASSERT_X(d.chordRangeEnd < rangeEnd,
                           "Quantize::findChordRangeEnds", "chordRangeEnd > rangeEnd");
                Q_ASSERT_X(d.chordRangeStart <= d.chordRangeEnd,
                           "Quantize::findChordRangeEnds", "chordRangeStart is greater than chordRangeEnd");
                Q_ASSERT_X(((d.chordRangeEnd - barStart) / d.quant).reduced().denominator() == 1,
                           "Quantize::findChordRangeEnds",
                           "chordRangeEnd - barStart is not dividable by quant");
                Q_ASSERT_X(((d.chordRangeEnd - d.chordRangeStart) / d.quant).reduced().denominator() == 1,
                           "Quantize::findChordRangeEnds",
                           "chordRangeEnd - chordRangeStart is not dividable by quant");
                }
          }

    void findChordRangeStarts(
                std::vector<QuantData> &data,
                const ReducedFraction &rangeStart,
                const ReducedFraction &rangeEnd,
                const ReducedFraction &barStart,
                const ReducedFraction &beatLen)
          {
          for (auto it = data.begin(); it != data.end(); ++it) {
                QuantData &d = *it;
                while (true) {
                      d.chordRangeStart = barStart + quantizeToLarge(rangeStart - barStart, d.quant);

                      Q_ASSERT_X(d.chord->first - beatLen <= rangeEnd,
                                 "Quantize::findChordRangeStarts", "chord on time - beatLen > rangeEnd");

                      if (d.chord->first - beatLen > rangeStart) {
                            d.chordRangeStart = barStart + quantizeToLarge(
                                                    d.chord->first - beatLen - barStart, d.quant);
                            }

                      if (it != data.begin()) {
                            const auto prev = std::prev(it);
                            if (prev->chordRangeStart > d.chordRangeStart) {
                                  d.chordRangeStart = barStart + quantizeToLarge(
                                                          prev->chordRangeStart - barStart, d.quant);
                                  }
                            if (!d.canMergeWithPrev && d.chordRangeStart == prev->chordRangeStart)
                                  d.chordRangeStart += d.quant;
                            }

                      if (d.chordRangeStart >= rangeEnd) {
                            if (d.quant >= MChord::minAllowedDuration() * 2)
                                  d.quant /= 2;
                            else
                                  d.canMergeWithPrev = true;
                            continue;
                            }
                      break;
                      }

                Q_ASSERT_X(notLessThanPrev(it, data),
                           "Quantize::findChordRangeStarts",
                           "chordRangeStart is less than previous chordRangeStart");
                Q_ASSERT_X(d.chordRangeStart >= rangeStart,
                           "Quantize::findChordRangeStarts", "chordRangeStart < rangeStart");
                Q_ASSERT_X(d.chordRangeStart < rangeEnd,
                           "Quantize::findChordRangeStarts", "chordRangeStart >= rangeEnd");
                Q_ASSERT_X(((d.chordRangeStart - barStart) / d.quant).reduced().denominator() == 1,
                           "Quantize::findChordRangeStarts",
                           "chordRangeStart - barStart is not dividable by quant");
                }
          }

    void findQuants(
                std::vector<QuantData> &data,
                const std::deque<std::multimap<ReducedFraction, MidiChord>::const_iterator> &chords,
                const ReducedFraction &rangeStart,
                const ReducedFraction &rangeEnd,
                const ReducedFraction &basicQuant,
                const ReducedFraction &tupletQuant,
                const ReducedFraction &barFraction)
          {
          for (auto it = chords.begin(); it != chords.end(); ++it) {
                const auto chordIt = *it;
                QuantData d;
                d.chord = chordIt;
                auto len = MChord::minNoteLen(*chordIt);
                if (rangeEnd - rangeStart < len)
                      len = rangeEnd - rangeStart;
                if (it != chords.begin()) {
                      const auto prevChordIt = *std::prev(it);
                      if (chordIt->first - prevChordIt->first < len)
                            len = chordIt->first - prevChordIt->first;
                      if (len < MChord::minAllowedDuration())
                            d.canMergeWithPrev = true;
                      }
                if (tupletQuant != ReducedFraction(-1, 1)) {
                      const MidiTuplet::TupletData &tuplet = (*chords.begin())->second.tuplet->second;
                      d.quant = tupletQuant;
                      d.quantForLen = tuplet.len / tuplet.tupletNumber;
                      }
                else {
                      d.quant = quantForLen(len, basicQuant);
                      auto maxQuant = basicQuant;
                      while (maxQuant < barFraction)
                            maxQuant *= 2;
                      d.quantForLen = quantForLen(
                               qMin(MChord::minNoteLen(*chordIt), rangeEnd - rangeStart), maxQuant);
                      }

                Q_ASSERT_X(d.quant <= rangeEnd - rangeStart,
                           "Quantize::findQuants", "Quant value is larger than range interval");

                data.push_back(d);
                }
          }

    ReducedFraction findTupletQuant(
                const std::deque<std::multimap<ReducedFraction, MidiChord>::const_iterator> &chords)
          {
          ReducedFraction tupletQuant(-1, 1);
          if ((*chords.begin())->second.isInTuplet) {
                const MidiTuplet::TupletData &tuplet = (*chords.begin())->second.tuplet->second;
                const auto tupletRatio = MidiTuplet::tupletLimits(tuplet.tupletNumber).ratio;
                tupletQuant = quantForTuplet(tuplet.len, tupletRatio);
                }

          return tupletQuant;
          }

    std::vector<QuantData> findQuantData(
                const std::deque<std::multimap<ReducedFraction, MidiChord>::const_iterator> &chords,
                const ReducedFraction &rangeStart,
                const ReducedFraction &rangeEnd,
                const ReducedFraction &basicQuant,
                const ReducedFraction &barStart,
                const ReducedFraction &barFraction)
          {

          Q_ASSERT_X(!chords.empty(), "Quantize::findQuantData", "Empty chords");

          std::vector<QuantData> data;
          const auto tupletQuant = findTupletQuant(chords);
          const auto beatLen = Meter::beatLength(barFraction);

          findQuants(data, chords, rangeStart, rangeEnd, basicQuant, tupletQuant, barFraction);
          findChordRangeStarts(data, rangeStart, rangeEnd, barStart, beatLen);
          findChordRangeEnds(data, rangeStart, rangeEnd, barStart, beatLen);
          findMetricalLevels(data, chords, tupletQuant, barStart, barFraction);

          return data;
          }

    struct QuantInfo {
          ReducedFraction onTime;
          double penalty = 0.0;
          std::multimap<ReducedFraction, MidiChord>::const_iterator chord;
          };

    int findLastChordPosition(const std::vector<QuantData> &quantData)
          {
          int posIndex = -1;
          double minPenalty = std::numeric_limits<double>::max();
          const auto &lastPositions = quantData[quantData.size() - 1].positions;
          for (int i = 0; i != (int)lastPositions.size(); ++i) {
                if (lastPositions[i].penalty < minPenalty) {
                      minPenalty = lastPositions[i].penalty;
                      posIndex = i;
                      }
                }

          Q_ASSERT_X(posIndex != -1,
                     "Quantize::findLastChordPosition", "Last index was not found");

          return posIndex;
          }

    void applyDynamicProgramming(std::vector<QuantData> &quantData)
          {
          const auto &opers = preferences.midiImportOperations.data()->trackOpers;
          const bool isHuman = opers.isHumanPerformance.value();
          const double MERGE_PENALTY_COEFF = 5.0;

          for (int chordIndex = 0; chordIndex != (int)quantData.size(); ++chordIndex) {
                QuantData &d = quantData[chordIndex];
                for (int pos = 0; pos != (int)d.positions.size(); ++pos) {
                      QuantPos &p = d.positions[pos];

                      const auto timePenalty = (d.chord->first - p.time).absValue().toDouble();
                      const double levelDiff = qAbs(d.metricalLevelForLen - p.metricalLevel);

                      if (isHuman) {
                            if (p.metricalLevel <= d.metricalLevelForLen)
                                  p.penalty = timePenalty + levelDiff * d.quantForLen.toDouble();
                            else
                                  p.penalty = timePenalty / (1 + levelDiff);
                            }
                      else {
                            if (p.metricalLevel <= d.metricalLevelForLen)
                                  p.penalty = timePenalty * (1 + levelDiff);
                            else
                                  p.penalty = timePenalty;
                            }

                      if (chordIndex == 0)
                            continue;

                      const QuantData &dPrev = quantData[chordIndex - 1];
                      double minPenalty = std::numeric_limits<double>::max();
                      int minPos = -1;

                      for (int posPrev = 0; posPrev != (int)dPrev.positions.size(); ++posPrev) {
                            const QuantPos &pPrev = dPrev.positions[posPrev];
                            if (pPrev.time > p.time)
                                  continue;

                            double penalty = pPrev.penalty;
                            if (pPrev.time == p.time) {
                                  if (!d.canMergeWithPrev)
                                        continue;
                                  penalty += d.quant.toDouble() * MERGE_PENALTY_COEFF;
                                  }

                            if (penalty < minPenalty) {
                                  minPenalty = penalty;
                                  minPos = posPrev;
                                  }
                            }

                      Q_ASSERT_X(minPos != -1,
                                 "Quantize::applyDynamicProgramming", "Min pos was not found");

                      p.penalty += minPenalty;
                      p.prevPos = minPos;
                      }
                }
          }

    void quantizeOnTimesInRange(
                const std::deque<std::multimap<ReducedFraction, MidiChord>::const_iterator> &chords,
                std::map<const std::pair<const ReducedFraction, MidiChord> *, QuantInfo> &foundOnTimes,
                const ReducedFraction &rangeStart,
                const ReducedFraction &rangeEnd,
                const ReducedFraction &basicQuant,
                const ReducedFraction &barStart,
                const ReducedFraction &barFraction)
          {
          Q_ASSERT_X(!chords.empty(), "Quantize::quantizeOnTimesInRange", "Empty chords");
          Q_ASSERT_X(areAllVoicesSame(chords),
                     "Quantize::quantizeOnTimesInRange", "Chord voices are not the same");
          Q_ASSERT_X(areChordsDifferent(chords),
                     "Quantize::quantizeOnTimesInRange", "There are chord duplicates");
          Q_ASSERT_X(areTupletChordsConsistent(chords), "Quantize::quantizeOnTimesInRange",
                     "Tuplet and non-tuplet chords mismatch");
          Q_ASSERT_X(areChordsSortedByOnTime(chords),
                     "Quantize::quantizeOnTimesInRange", "Chords are not sorted by on time values");

          Q_ASSERT_X(rangeStart != ReducedFraction(-1, 1)
                      && rangeEnd != ReducedFraction(-1, 1)
                      && rangeStart < rangeEnd,
                     "Quantize::quantizeOnTimesInRange",
                     "Range start and/or range end are incorrect");

          Q_ASSERT_X((chords.front()->second.isInTuplet) ? isTupletRangeCorrect(
                      chords.front()->second.tuplet->second, rangeStart, rangeEnd) : true,
                     "Quantize::quantizeOnTimesInRange", "Tuplet range is incorrect");

          std::vector<QuantData> quantData = findQuantData(chords, rangeStart, rangeEnd,
                                                           basicQuant, barStart, barFraction);
          applyDynamicProgramming(quantData);

                      // backward dynamic programming step - collect optimal chord positions
          int posIndex = findLastChordPosition(quantData);

          Q_ASSERT_X(quantData.size() == chords.size(),
                     "Quantize::quantizeOnTimesInRange",
                     "Sizes of quant data and chords are not equal");

          for (int chordIndex = quantData.size() - 1; ; --chordIndex) {
                const QuantPos &p = quantData[chordIndex].positions[posIndex];
                const auto onTime = p.time;
                const auto chordIt = chords[chordIndex];

                const auto found = foundOnTimes.find(&*chordIt);
                if (found == foundOnTimes.end()) {
                      QuantInfo info;
                      info.chord = chordIt;
                      info.onTime = onTime;
                      info.penalty = p.penalty;
                      foundOnTimes.insert({&*chordIt, info});
                      }
                else if (p.penalty < found->second.penalty) {
                      found->second.onTime = onTime;
                      found->second.penalty = p.penalty;
                      }

                if (chordIndex == 0)
                      break;
                posIndex = p.prevPos;
                }
          }

    // input chords - sorted by onTime value

    void applyTupletStaccato(std::multimap<ReducedFraction, MidiChord> &chords)
          {
          for (auto chordIt = chords.begin(); chordIt != chords.end(); ++chordIt) {
                for (MidiNote &note: chordIt->second.notes) {
                      if (note.isInTuplet && note.staccato) {
                            const MidiTuplet::TupletData &tuplet = note.tuplet->second;
                                  // decrease tuplet error by enlarging staccato notes:
                                  // make note.len = tuplet note length
                            const auto tupletNoteLen = tuplet.len / tuplet.tupletNumber;
                            note.offTime = chordIt->first + tupletNoteLen;
                            }
                      }
                }
          }

    void quantizeOffTimes(
                std::multimap<ReducedFraction, MidiChord> &quantizedChords,
                const ReducedFraction &basicQuant)
          {
          for (auto chordIt = quantizedChords.begin(); chordIt != quantizedChords.end(); ++chordIt) {
                MidiChord &chord = chordIt->second;
                            // quantize off times
                for (auto &note: chord.notes) {
                                  // check if note is not in tuplet anymore
                      if (note.isInTuplet) {
                            if (chordIt->first >= note.tuplet->first + note.tuplet->second.len)
                                  note.isInTuplet = false;
                            }

                      const auto result = (note.isInTuplet)
                                  ? quantizeOffTimeForTuplet(note.offTime, chordIt, quantizedChords,
                                                             basicQuant, note.tuplet->second)
                                  : quantizeOffTimeForNonTuplet(note.offTime, chordIt,
                                                                quantizedChords, basicQuant);
                      note.offTime = result.first;
                      note.offTimeQuant = result.second;
    #ifdef QT_DEBUG
                      checkOffTime(note, chordIt, quantizedChords);
    #endif
                      }
                }
          }

    void addChordsFromPrevRange(
                std::deque<std::multimap<ReducedFraction, MidiChord>::const_iterator> &chordsToQuant,
                const std::multimap<ReducedFraction, MidiChord> &chords,
                bool currentlyInTuplet,
                int currentBarIndex,
                int voice,
                const ReducedFraction &barStart,
                const ReducedFraction &rangeStart,
                const ReducedFraction &basicQuant)
          {
          if (currentlyInTuplet)
                return;

          auto it = chordsToQuant.front();
          if (it == chords.begin())
                return;
          --it;

          if (rangeStart == barStart) {       // new bar
                while (it->second.barIndex >= currentBarIndex - 1) {
                      if (it->second.voice == voice && it->second.barIndex == currentBarIndex) {

                            Q_ASSERT_X(!it->second.isInTuplet, "Quantize::addChordsFromPrevRange",
                                       "Tuplet chord from previous bar belongs to the current bar");

                            chordsToQuant.push_front(it);
                            }
                      if (it == chords.begin())
                            break;
                      --it;
                      }
                }
          else {
                const auto tol = basicQuant / 2;
                while (it->first > rangeStart - tol) {
                      if (it->second.voice == voice && !it->second.isInTuplet)
                            chordsToQuant.push_front(it);
                      if (it == chords.begin())
                            break;
                      --it;
                      }
                }
          }

    void quantizeOnTimes(
                const std::multimap<ReducedFraction, MidiChord> &chords,
                std::map<const std::pair<const ReducedFraction, MidiChord> *, QuantInfo> &foundOnTimes,
                const ReducedFraction &basicQuant,
                const TimeSigMap *sigmap)
          {
          int maxVoice = 0;
          for (int voice = 0; voice <= maxVoice; ++voice) {
                int currentBarIndex = -1;
                ReducedFraction rangeStart(-1, 1);
                ReducedFraction rangeEnd(-1, 1);
                ReducedFraction barFraction(-1, 1);
                ReducedFraction barStart(-1, 1);
                bool currentlyInTuplet = false;
                std::deque<std::multimap<ReducedFraction, MidiChord>::const_iterator> chordsToQuant;

                for (auto chordIt = chords.begin(); chordIt != chords.end(); ++chordIt) {

                      Q_ASSERT_X(MChord::minNoteLen(*chordIt) >= MChord::minAllowedDuration(),
                                 "Quantize::quantizeOnTimes",
                                 "Note length is less than min allowed duration");

                      if (chordIt->second.voice > maxVoice)
                            maxVoice = chordIt->second.voice;
                      if (chordIt->second.voice != voice)
                            continue;

                      if (chordsToQuant.empty()) {
                            rangeStart = rangeEnd;
                            currentlyInTuplet = chordIt->second.isInTuplet;
                            if (currentBarIndex != chordIt->second.barIndex) {
                                  currentBarIndex = chordIt->second.barIndex;
                                  barStart = ReducedFraction::fromTicks(
                                                    sigmap->bar2tick(currentBarIndex, 0));
                                  barFraction = ReducedFraction(sigmap->timesig(barStart.ticks()).timesig());
                                  if (!currentlyInTuplet)
                                        rangeStart = barStart;
                                  }
                            if (currentlyInTuplet) {
                                  const auto &tuplet = chordIt->second.tuplet->second;
                                  rangeStart = tuplet.onTime;
                                  rangeEnd = tuplet.onTime + tuplet.len;
                                  }
                            }

                      chordsToQuant.push_back(chordIt);

                      auto nextChord = std::next(chordIt);
                      while (nextChord != chords.end() && nextChord->second.voice != voice)
                            ++nextChord;
                      if (nextChord == chords.end()
                                  || nextChord->second.barIndex != currentBarIndex
                                  || nextChord->second.isInTuplet != currentlyInTuplet
                                  || (nextChord->second.isInTuplet && currentlyInTuplet
                                      && nextChord->second.tuplet != chordIt->second.tuplet)) {

                            if (!currentlyInTuplet) {
                                  if (nextChord != chords.end()) {
                                        if (nextChord->second.barIndex != currentBarIndex) {
                                              rangeEnd = ReducedFraction::fromTicks(
                                                           sigmap->bar2tick(currentBarIndex + 1, 0));
                                              }
                                        else if (nextChord->second.isInTuplet) {
                                              rangeEnd = nextChord->second.tuplet->second.onTime;
                                              }
                                        }
                                  else {
                                        rangeEnd = ReducedFraction::fromTicks(
                                                          sigmap->bar2tick(currentBarIndex + 1, 0));
                                        }
                                  }

                            addChordsFromPrevRange(chordsToQuant, chords,
                                                   currentlyInTuplet, currentBarIndex, voice,
                                                   barStart, rangeStart, basicQuant);

                            quantizeOnTimesInRange(chordsToQuant, foundOnTimes, rangeStart, rangeEnd,
                                                   basicQuant, barStart, barFraction);
                            chordsToQuant.clear();
                            }
                      }
                }
          }

    // if on time after quantization become >= note off time
    // then shift note off time preserving old note length

    void moveOffTimes(
                const ReducedFraction &oldOnTime,
                const ReducedFraction &newOnTime,
                QList<MidiNote> &notes)
          {
          for (auto &note: notes) {
                if (newOnTime >= note.offTime) {

                      Q_ASSERT_X(newOnTime > oldOnTime,
                                 "Quantize::moveOffTimes", "Invalid note on time or off time");
                      Q_ASSERT_X(note.offTime > oldOnTime,
                                 "Quantize::moveOffTimes", "Invalid old note length");

                      note.offTime += newOnTime - oldOnTime;
                      }
                }
          }

    std::multimap<ReducedFraction, MidiChord>
    findQuantizedChords(
                const std::map<const std::pair<const ReducedFraction, MidiChord> *, QuantInfo> &foundOnTimes)
          {
          std::multimap<ReducedFraction, MidiChord> quantizedChords;
          for (const auto &f: foundOnTimes) {
                const QuantInfo &i = f.second;
                const MidiChord &chord = i.chord->second;

                auto found = quantizedChords.end();
                const auto range = quantizedChords.equal_range(i.onTime);
                for (auto it = range.first; it != range.second; ++it) {
                      if (it->second.voice == chord.voice) {
                            found = it;
                            break;
                            }
                      }
                if (found != quantizedChords.end()) {
                      MidiChord &fc = found->second;
                      fc.notes.append(chord.notes);     // merge chords with equal on times
                      moveOffTimes(i.chord->first, i.onTime, fc.notes);
                      if (chord.isInTuplet) {
                            if (!fc.isInTuplet) {
                                  fc.isInTuplet = true;
                                  fc.tuplet = chord.tuplet;
                                  }
    #ifdef QT_DEBUG
                            else {
                                  Q_ASSERT_X(fc.tuplet == chord.tuplet,
                                             "Quantize::findQuantizedChords",
                                             "Tuplets of merged chords are different");
                                  }
    #endif
                            }
                      }
                else {
                      MidiChord midiChord(chord);  // copy chord
                      moveOffTimes(i.chord->first, i.onTime, midiChord.notes);
                      quantizedChords.insert({i.onTime, midiChord});
                      }
                }

          return quantizedChords;
          }

    void removeUselessTupletReferences(std::multimap<ReducedFraction, MidiChord> &chords)
          {
          for (auto &chord: chords) {
                if (chord.second.isInTuplet) {
                      const auto &tuplet = chord.second.tuplet->second;
                      if (chord.first >= tuplet.onTime + tuplet.len)
                            chord.second.isInTuplet = false;
                      }
                for (auto &note: chord.second.notes) {
                      if (note.isInTuplet) {
                            const auto &tuplet = note.tuplet->second;
                            if (note.offTime <= tuplet.onTime)
                                  note.isInTuplet = false;
                            }
                      }
                }
          }

    void quantizeChords(
                std::multimap<ReducedFraction, MidiChord> &chords,
                const TimeSigMap *sigmap,
                const ReducedFraction &basicQuant)
          {

          Q_ASSERT_X(MidiTuplet::areTupletReferencesValid(chords), "Quantize::quantizeChords",
                     "Some tuplet references are invalid");
          Q_ASSERT_X(areOnTimeValuesDifferent(chords), "Quantize::quantizeChords",
                     "Chords of the same voices have equal on time values");
          Q_ASSERT_X(areTupletChordsConsistent(chords), "Quantize::quantizeChords",
                     "There are non-tuplet chords between tuplet chords");
          Q_ASSERT_X(MChord::areNotesLongEnough(chords), "Quantize::quantizeChords",
                     "There are too short notes");
          Q_ASSERT_X(MChord::areBarIndexesSuccessive(chords), "Quantize::quantizeChords",
                     "Bar indexes are not successive");

          applyTupletStaccato(chords);     // apply staccato for tuplet off times
          std::map<const std::pair<const ReducedFraction, MidiChord> *, QuantInfo> foundOnTimes;
          quantizeOnTimes(chords, foundOnTimes, basicQuant, sigmap);
          auto quantizedChords = findQuantizedChords(foundOnTimes);

          Q_ASSERT_X(MidiTuplet::areTupletReferencesValid(quantizedChords), "Quantize::quantizeChords",
                     "Some tuplet references are invalid");

          quantizeOffTimes(quantizedChords, basicQuant);
          std::swap(chords, quantizedChords);

          removeUselessTupletReferences(chords);
          }

    } // namespace Quantize
    } // namespace Ms

↑ V730 Not all members of a class are initialized inside the compiler generated constructor. Consider inspecting: metricalLevelForLen.