1 #include "importmidi_tuplet.h"
2 #include "importmidi_tuplet_detect.h"
3 #include "importmidi_tuplet_filter.h"
4 #include "importmidi_tuplet_voice.h"
5 #include "importmidi_chord.h"
6 #include "importmidi_quant.h"
7 #include "importmidi_inner.h"
8 #include "importmidi_operations.h"
9 #include "libmscore/sig.h"
10 #include "mscore/preferences.h"
11 
12 #include <set>
13 
14 
15 namespace Ms {
16 namespace MidiTuplet {
17 
tupletsLimits()18 const std::map<int, TupletLimits>& tupletsLimits()
19       {
20       const static std::map<int, TupletLimits> values = {
21             {2, {{2, 3}, 1, 2, 2, 2}},
22             {3, {{3, 2}, 1, 3, 3, 2}},
23             {4, {{4, 3}, 3, 4, 3, 3}},
24             {5, {{5, 4}, 3, 4, 4, 4}},
25             {7, {{7, 8}, 4, 6, 5, 6}},
26             {9, {{9, 8}, 6, 7, 7, 8}}
27             };
28       return values;
29       }
30 
tupletLimits(int tupletNumber)31 const TupletLimits& tupletLimits(int tupletNumber)
32       {
33       auto it = tupletsLimits().find(tupletNumber);
34 
35       Q_ASSERT_X(it != tupletsLimits().end(), "MidiTuplet::tupletValue", "Unknown tuplet");
36 
37       return it->second;
38       }
39 
tupletFromId(int id,const std::vector<TupletInfo> & tuplets)40 const TupletInfo& tupletFromId(int id, const std::vector<TupletInfo> &tuplets)
41       {
42       auto it = std::find_if(tuplets.begin(), tuplets.end(),
43                              [=](const TupletInfo &t) { return t.id == id; });
44 
45       Q_ASSERT_X(it != tuplets.end(), "MidiTuplet::tupletFromId", "Tuplet not found from id");
46 
47       return *it;
48       }
49 
tupletFromId(int id,std::vector<TupletInfo> & tuplets)50 TupletInfo& tupletFromId(int id, std::vector<TupletInfo> &tuplets)
51       {
52       return const_cast<TupletInfo &>(
53                         tupletFromId(id, const_cast<const std::vector<TupletInfo> &>(tuplets)));
54       }
55 
hasNonTrivialChord(const ReducedFraction & chordOnTime,const QList<MidiNote> & notes,const ReducedFraction & tupletOnTime,const ReducedFraction & tupletLen)56 bool hasNonTrivialChord(
57             const ReducedFraction &chordOnTime,
58             const QList<MidiNote> &notes,
59             const ReducedFraction &tupletOnTime,
60             const ReducedFraction &tupletLen)
61       {
62       if (chordOnTime == tupletOnTime) {
63             for (const auto &note: notes) {
64                   if (note.offTime - chordOnTime < tupletLen)
65                         return true;
66                   }
67             }
68       else {
69             if (chordOnTime > tupletOnTime && chordOnTime < tupletOnTime + tupletLen)
70                   return true;
71             if (chordOnTime >= tupletOnTime + tupletLen)
72                   return false;
73 
74             Q_ASSERT_X(chordOnTime < tupletOnTime, "MidiTuplet::hasNonTrivialChord",
75                        "Chord on time was not compared correctly");
76 
77             for (const auto &note: notes) {
78                   if (note.offTime < tupletOnTime + tupletLen)
79                         return true;
80                   }
81             }
82       return false;
83       }
84 
isTupletUseless(int voice,const ReducedFraction & onTime,const ReducedFraction & len,const ReducedFraction & maxChordLength,const std::multimap<ReducedFraction,MidiChord> & chords)85 bool isTupletUseless(
86             int voice,
87             const ReducedFraction &onTime,
88             const ReducedFraction &len,
89             const ReducedFraction &maxChordLength,
90             const std::multimap<ReducedFraction, MidiChord> &chords)
91       {
92       bool haveIntersectionWithChord = false;
93       const auto foundChords = MChord::findChordsForTimeRange(voice, onTime, onTime + len,
94                                                               chords, maxChordLength);
95       for (const auto &chordIt: foundChords) {
96                         // ok, tuplet contains at least one chord
97                         // check now does it have notes with len < tuplet.len
98             if (hasNonTrivialChord(chordIt->first, chordIt->second.notes, onTime, len)) {
99                   haveIntersectionWithChord = true;
100                   break;
101                   }
102             }
103 
104       return !haveIntersectionWithChord;
105       }
106 
107 std::multimap<ReducedFraction, TupletData>::iterator
removeTuplet(const std::multimap<ReducedFraction,TupletData>::iterator & tupletIt,std::multimap<ReducedFraction,TupletData> & tuplets,const ReducedFraction & maxChordLength,std::multimap<ReducedFraction,MidiChord> & chords)108 removeTuplet(
109             const std::multimap<ReducedFraction, TupletData>::iterator &tupletIt,
110             std::multimap<ReducedFraction, TupletData> &tuplets,
111             const ReducedFraction &maxChordLength,
112             std::multimap<ReducedFraction, MidiChord> &chords)
113       {
114                   // remove references to this tuplet in chords and notes
115       auto chordIt = chords.lower_bound(tupletIt->second.onTime + tupletIt->second.len);
116       if (chordIt != chords.begin()) {
117             --chordIt;
118             while (chordIt->first + maxChordLength > tupletIt->first) {
119                   MidiChord &c = chordIt->second;
120                   if (c.isInTuplet && c.tuplet == tupletIt)
121                         c.isInTuplet = false;
122                   for (auto &note: c.notes) {
123                         if (note.isInTuplet && note.tuplet == tupletIt)
124                               note.isInTuplet = false;
125                         }
126                   if (chordIt == chords.begin())
127                         break;
128                   --chordIt;
129                   }
130             }
131 
132       return tuplets.erase(tupletIt);
133       }
134 
135 std::multimap<ReducedFraction, TupletData>::iterator
removeTupletIfEmpty(const std::multimap<ReducedFraction,TupletData>::iterator & tupletIt,std::multimap<ReducedFraction,TupletData> & tuplets,const ReducedFraction & maxChordLength,std::multimap<ReducedFraction,MidiChord> & chords)136 removeTupletIfEmpty(
137             const std::multimap<ReducedFraction, TupletData>::iterator &tupletIt,
138             std::multimap<ReducedFraction, TupletData> &tuplets,
139             const ReducedFraction &maxChordLength,
140             std::multimap<ReducedFraction, MidiChord> &chords)
141       {
142       auto resultIt = tupletIt;
143       const auto &tuplet = tupletIt->second;
144 
145       if (isTupletUseless(tuplet.voice, tuplet.onTime, tuplet.len, maxChordLength, chords))
146             resultIt = removeTuplet(tupletIt, tuplets, maxChordLength, chords);
147 
148       return resultIt;
149       }
150 
151 // tuplets with no chords are removed
152 // tuplets with single chord with chord.onTime = tuplet.onTime
153 //    and chord.len = tuplet.len are removed as well
154 
155 // better to call this function after quantization
156 
removeEmptyTuplets(MTrack & track)157 void removeEmptyTuplets(MTrack &track)
158       {
159       auto &tuplets = track.tuplets;
160       if (tuplets.empty())
161             return;
162       auto &chords = track.chords;
163       const ReducedFraction maxChordLength = MChord::findMaxChordLength(chords);
164 
165       for (auto tupletIt = tuplets.begin(); tupletIt != tuplets.end(); ) {
166             const auto it = removeTupletIfEmpty(tupletIt, tuplets, maxChordLength, chords);
167             if (it != tupletIt) {
168                   tupletIt = it;
169                   continue;
170                   }
171             ++tupletIt;
172             }
173       }
174 
averagePitch(const std::map<ReducedFraction,std::multimap<ReducedFraction,MidiChord>::iterator> & chords)175 int averagePitch(const std::map<ReducedFraction,
176                  std::multimap<ReducedFraction, MidiChord>::iterator> &chords)
177       {
178       if (chords.empty())
179             return -1;
180 
181       int sumPitch = 0;
182       int noteCounter = 0;
183       for (const auto &chord: chords) {
184             const auto &midiNotes = chord.second->second.notes;
185             for (const auto &midiNote: midiNotes) {
186                   sumPitch += midiNote.pitch;
187                   ++noteCounter;
188                   }
189             }
190 
191       return qRound(sumPitch * 1.0 / noteCounter);
192       }
193 
sortNotesByPitch(const std::multimap<ReducedFraction,MidiChord>::iterator & startBarChordIt,const std::multimap<ReducedFraction,MidiChord>::iterator & endBarChordIt)194 void sortNotesByPitch(const std::multimap<ReducedFraction, MidiChord>::iterator &startBarChordIt,
195                       const std::multimap<ReducedFraction, MidiChord>::iterator &endBarChordIt)
196       {
197       struct {
198             bool operator()(const MidiNote &n1, const MidiNote &n2)
199                   {
200                   return (n1.pitch > n2.pitch);
201                   }
202             } pitchComparator;
203 
204       for (auto it = startBarChordIt; it != endBarChordIt; ++it) {
205             auto &midiNotes = it->second.notes;
206             std::sort(midiNotes.begin(), midiNotes.end(), pitchComparator);
207             }
208       }
209 
sortTupletsByAveragePitch(std::vector<TupletInfo> & tuplets)210 void sortTupletsByAveragePitch(std::vector<TupletInfo> &tuplets)
211       {
212       struct {
213             bool operator()(const TupletInfo &t1, const TupletInfo &t2)
214                   {
215                   return (averagePitch(t1.chords) > averagePitch(t2.chords));
216                   }
217             } averagePitchComparator;
218       std::sort(tuplets.begin(), tuplets.end(), averagePitchComparator);
219       }
220 
221 std::pair<ReducedFraction, ReducedFraction>
tupletInterval(const TupletInfo & tuplet,const ReducedFraction & basicQuant)222 tupletInterval(const TupletInfo &tuplet,
223                const ReducedFraction &basicQuant)
224       {
225       ReducedFraction tupletEnd = tuplet.onTime + tuplet.len;
226 
227       for (const auto &chord: tuplet.chords) {
228             const auto offTime = Quantize::findMaxQuantizedOffTime(*chord.second, basicQuant);
229             if (offTime > tupletEnd)
230                   tupletEnd = offTime;
231             }
232 
233       Q_ASSERT_X(tupletEnd > tuplet.onTime, "MidiTuplet::tupletInterval", "off time <= on time");
234 
235       return std::make_pair(tuplet.onTime, tupletEnd);
236       }
237 
238 std::vector<std::pair<ReducedFraction, ReducedFraction> >
findTupletIntervals(const std::vector<TupletInfo> & tuplets,const ReducedFraction & basicQuant)239 findTupletIntervals(const std::vector<TupletInfo> &tuplets,
240                     const ReducedFraction &basicQuant)
241       {
242       std::vector<std::pair<ReducedFraction, ReducedFraction>> tupletIntervals;
243       for (const auto &tuplet: tuplets)
244             tupletIntervals.push_back(tupletInterval(tuplet, basicQuant));
245 
246       return tupletIntervals;
247       }
248 
249 // find tuplets over which duration lies
250 
251 std::vector<TupletData>
findTupletsInBarForDuration(int voice,const ReducedFraction & barStartTick,const ReducedFraction & durationOnTime,const ReducedFraction & durationLen,const std::multimap<ReducedFraction,TupletData> & tupletEvents)252 findTupletsInBarForDuration(
253             int voice,
254             const ReducedFraction &barStartTick,
255             const ReducedFraction &durationOnTime,
256             const ReducedFraction &durationLen,
257             const std::multimap<ReducedFraction, TupletData> &tupletEvents)
258       {
259       std::vector<TupletData> tupletsData;
260       if (tupletEvents.empty())
261             return tupletsData;
262       auto tupletIt = tupletEvents.lower_bound(barStartTick);
263 
264       while (tupletIt != tupletEvents.end()
265                 && tupletIt->first < durationOnTime + durationLen) {
266             if (tupletIt->second.voice == voice
267                         && durationOnTime < tupletIt->first + tupletIt->second.len) {
268                               // if tuplet and duration intersect each other
269                   auto tupletData = tupletIt->second;
270                               // convert tuplet onTime to local bar ticks
271                   tupletData.onTime -= barStartTick;
272                   tupletsData.push_back(tupletData);
273                   }
274             ++tupletIt;
275             }
276       return tupletsData;
277       }
278 
279 std::vector<std::multimap<ReducedFraction, TupletData>::const_iterator>
findTupletsForTimeRange(int voice,const ReducedFraction & onTime,const ReducedFraction & len,const std::multimap<ReducedFraction,TupletData> & tupletEvents,bool strictComparison)280 findTupletsForTimeRange(
281             int voice,
282             const ReducedFraction &onTime,
283             const ReducedFraction &len,
284             const std::multimap<ReducedFraction, TupletData> &tupletEvents,
285             bool strictComparison)
286       {
287       Q_ASSERT_X(len >= ReducedFraction(0, 1),
288                  "MidiTuplet::findTupletForTimeRange", "Negative length of the time range");
289 
290       std::vector<std::multimap<ReducedFraction, TupletData>::const_iterator> result;
291 
292       if (tupletEvents.empty())
293             return result;
294 
295       auto it = tupletEvents.upper_bound(onTime + len);
296       if (it == tupletEvents.begin())
297             return result;
298       --it;
299       while (true) {
300             const auto &tupletData = it->second;
301             if (tupletData.voice == voice) {
302                   const auto interval = std::make_pair(onTime, onTime + len);
303                   const auto tupletInterval = std::make_pair(
304                                     tupletData.onTime, tupletData.onTime + tupletData.len);
305                   if (haveIntersection(interval, tupletInterval, strictComparison)) {
306                         result.push_back(it);
307                         }
308                   }
309             if (it == tupletEvents.begin())
310                   break;
311             --it;
312             }
313       return result;
314       }
315 
316 std::multimap<ReducedFraction, TupletData>::const_iterator
findTupletContainingTime(int voice,const ReducedFraction & time,const std::multimap<ReducedFraction,TupletData> & tupletEvents,bool strictComparison)317 findTupletContainingTime(
318             int voice,
319             const ReducedFraction &time,
320             const std::multimap<ReducedFraction, TupletData> &tupletEvents,
321             bool strictComparison)
322       {
323       const auto tuplets = findTupletsForTimeRange(voice, time, ReducedFraction(0, 1),
324                                                    tupletEvents, strictComparison);
325       if (tuplets.empty())
326             return tupletEvents.end();
327 
328       Q_ASSERT_X(tuplets.size() == 1, "MidiTuplet::findTupletContainsTime",
329                  "More than one tuplet was found for time moment");
330 
331       return tuplets.front();
332       }
333 
334 std::set<std::pair<const ReducedFraction, MidiChord> *>
findTupletChords(const std::vector<TupletInfo> & tuplets)335 findTupletChords(const std::vector<TupletInfo> &tuplets)
336       {
337       std::set<std::pair<const ReducedFraction, MidiChord> *> tupletChords;
338       for (const auto &tupletInfo: tuplets) {
339             for (const auto &tupletChord: tupletInfo.chords) {
340                   auto tupletIt = tupletChord.second;
341                   tupletChords.insert(&*tupletIt);
342                   }
343             }
344       return tupletChords;
345       }
346 
347 std::list<std::multimap<ReducedFraction, MidiChord>::iterator>
findNonTupletChords(const std::vector<TupletInfo> & tuplets,const std::multimap<ReducedFraction,MidiChord>::iterator & startBarChordIt,const std::multimap<ReducedFraction,MidiChord>::iterator & endBarChordIt)348 findNonTupletChords(
349             const std::vector<TupletInfo> &tuplets,
350             const std::multimap<ReducedFraction, MidiChord>::iterator &startBarChordIt,
351             const std::multimap<ReducedFraction, MidiChord>::iterator &endBarChordIt)
352       {
353       const auto tupletChords = findTupletChords(tuplets);
354       std::list<std::multimap<ReducedFraction, MidiChord>::iterator> nonTuplets;
355       for (auto it = startBarChordIt; it != endBarChordIt; ++it) {
356             if (tupletChords.find(&*it) == tupletChords.end())
357                   nonTuplets.push_back(it);
358             }
359 
360       return nonTuplets;
361       }
362 
363 // split first tuplet chord, that belong to 2 tuplets, into 2 chords
364 
splitTupletChord(const std::vector<TupletInfo>::iterator & lastMatch,std::multimap<ReducedFraction,MidiChord> & chords)365 void splitTupletChord(const std::vector<TupletInfo>::iterator &lastMatch,
366                       std::multimap<ReducedFraction, MidiChord> &chords)
367       {
368       auto &chordEvent = lastMatch->chords.begin()->second;
369       MidiChord &prevChord = chordEvent->second;
370       const auto onTime = chordEvent->first;
371       MidiChord newChord = prevChord;
372                         // erase all notes except the first one
373       auto beg = newChord.notes.begin();
374       newChord.notes.erase(++beg, newChord.notes.end());
375                         // erase the first note
376       prevChord.notes.erase(prevChord.notes.begin());
377       chordEvent = chords.insert({onTime, newChord});
378 
379       Q_ASSERT_X(!prevChord.notes.isEmpty(),
380                  "MidiTuplet::splitTupletChord",
381                  "Tuplets were not filtered correctly: same notes in different tuplets");
382       }
383 
splitFirstTupletChords(std::vector<TupletInfo> & tuplets,std::multimap<ReducedFraction,MidiChord> & chords)384 void splitFirstTupletChords(std::vector<TupletInfo> &tuplets,
385                             std::multimap<ReducedFraction, MidiChord> &chords)
386       {
387       for (auto now = tuplets.begin(); now != tuplets.end(); ++now) {
388             auto lastMatch = tuplets.end();
389             const auto nowChordIt = now->chords.begin();
390             for (auto prev = tuplets.begin(); prev != now; ++prev) {
391                   auto prevChordIt = prev->chords.begin();
392                   if (now->firstChordIndex == 0
393                               && prev->firstChordIndex == 0
394                               && nowChordIt->second == prevChordIt->second) {
395                         lastMatch = prev;
396                         }
397                   }
398             if (lastMatch != tuplets.end())
399                   splitTupletChord(lastMatch, chords);
400             }
401       }
402 
403 // first tuplet notes with offTime quantization error,
404 // that is greater for tuplet quant rather than for regular quant,
405 // are removed from tuplet, except that was the last note
406 
407 // if noteLen <= tupletLen
408 //     remove notes with big offTime quant error;
409 //     and here is a tuning for clearer tuplet processing:
410 //         if tuplet has only one (first) chord -
411 //             we can remove all notes and erase tuplet;
412 //         if tuplet has multiple chords -
413 //             we should leave at least one note in the first chord
414 
minimizeOffTimeError(std::vector<TupletInfo> & tuplets,std::multimap<ReducedFraction,MidiChord> & chords,std::list<std::multimap<ReducedFraction,MidiChord>::iterator> & nonTuplets,const ReducedFraction & startBarTick,const ReducedFraction & basicQuant)415 void minimizeOffTimeError(
416             std::vector<TupletInfo> &tuplets,
417             std::multimap<ReducedFraction, MidiChord> &chords,
418             std::list<std::multimap<ReducedFraction, MidiChord>::iterator> &nonTuplets,
419             const ReducedFraction &startBarTick,
420             const ReducedFraction &basicQuant)
421       {
422       for (auto it = tuplets.begin(); it != tuplets.end(); ) {
423             TupletInfo &tupletInfo = *it;
424             const auto firstChord = tupletInfo.chords.begin();
425             if (firstChord == tupletInfo.chords.end() || tupletInfo.firstChordIndex != 0) {
426                   ++it;
427                   continue;
428                   }
429             auto onTime = firstChord->second->first;
430             if (onTime < startBarTick)
431                   onTime = startBarTick;
432             MidiChord &midiChord = firstChord->second->second;
433             auto &notes = midiChord.notes;
434 
435             std::vector<int> removedIndexes;
436             std::vector<int> leavedIndexes;
437             const auto tupletNoteLen = tupletInfo.len / tupletInfo.tupletNumber;
438 
439             for (int i = 0; i != notes.size(); ++i) {
440                   const auto &note = notes[i];
441                   if (note.offTime - onTime <= tupletInfo.len
442                               && note.offTime - onTime > tupletNoteLen) {
443                                     // if note is longer than tuplet note length
444                                     // then it's simpler to move it outside the tuplet
445                                     // if the error is not larger than error inside tuplet
446                         if ((tupletInfo.chords.size() == 1
447                                     && notes.size() > (int)removedIndexes.size())
448                                  || (tupletInfo.chords.size() > 1
449                                     && notes.size() > (int)removedIndexes.size() + 1))
450                               {
451                               const auto tupletError = Quantize::findOffTimeTupletQuantError(
452                                                 onTime, note.offTime, tupletInfo.len,
453                                                 tupletLimits(tupletInfo.tupletNumber).ratio, startBarTick);
454                               const auto regularError = Quantize::findOffTimeQuantError(
455                                                 *firstChord->second, note.offTime, basicQuant);
456 
457                               if (tupletError >= regularError) {
458                                     removedIndexes.push_back(i);
459                                     continue;
460                                     }
461                               }
462                         }
463                   leavedIndexes.push_back(i);
464                   }
465             if (!removedIndexes.empty()) {
466                   MidiChord newTupletChord = midiChord;
467                   newTupletChord.notes.clear();
468                   for (int i: leavedIndexes)
469                         newTupletChord.notes.push_back(notes[i]);
470 
471                   QList<MidiNote> newNotes;
472                   for (int i: removedIndexes)
473                         newNotes.push_back(notes[i]);
474                   notes = newNotes;
475                               // force add chord to this bar, even if it has barIndex of another bar
476                   nonTuplets.push_back(firstChord->second);
477                   if (!newTupletChord.notes.empty()) {
478                         firstChord->second = chords.insert({firstChord->second->first,
479                                                             newTupletChord});
480                         }
481                   else {
482                         tupletInfo.chords.erase(tupletInfo.chords.begin());
483                         if (tupletInfo.chords.empty()) {
484                               it = tuplets.erase(it);   // remove tuplet without chords
485                               continue;
486                               }
487                         }
488                   }
489             ++it;
490             }
491       }
492 
addChordsBetweenTupletNotes(std::vector<TupletInfo> & tuplets,std::list<std::multimap<ReducedFraction,MidiChord>::iterator> & nonTuplets,const std::multimap<ReducedFraction,MidiChord> & chords,const ReducedFraction & startBarTick,const ReducedFraction & basicQuant)493 void addChordsBetweenTupletNotes(
494             std::vector<TupletInfo> &tuplets,
495             std::list<std::multimap<ReducedFraction, MidiChord>::iterator> &nonTuplets,
496             const std::multimap<ReducedFraction, MidiChord> &chords,
497             const ReducedFraction &startBarTick,
498             const ReducedFraction &basicQuant)
499       {
500       for (TupletInfo &tuplet: tuplets) {
501             for (auto it = nonTuplets.begin(); it != nonTuplets.end(); ) {
502                   const auto &chordIt = *it;
503                   const auto &onTime = chordIt->first;
504                   const auto tupletInterv = std::make_pair(tuplet.onTime,
505                                                            tuplet.onTime + tuplet.len);
506                   const auto chordInterv = chordInterval(*chordIt, chords,
507                                                          basicQuant, startBarTick);
508                   if (onTime > tuplet.onTime && haveIntersection(tupletInterv, chordInterv)) {
509                         const auto tupletRatio = tupletLimits(tuplet.tupletNumber).ratio;
510 
511                         auto tupletError = Quantize::findOnTimeTupletQuantError(
512                                                   *chordIt, tuplet.len, tupletRatio, startBarTick);
513                         auto regularError = Quantize::findOnTimeQuantError(*chordIt, basicQuant);
514 
515                         const auto offTime = MChord::maxNoteOffTime(chordIt->second.notes);
516                         if (offTime < tuplet.onTime + tuplet.len) {
517                               tupletError += Quantize::findOffTimeTupletQuantError(
518                                              onTime, offTime, tuplet.len, tupletRatio, startBarTick);
519                               regularError += Quantize::findOffTimeQuantError(
520                                                 *chordIt, offTime, basicQuant);
521                               }
522                         if (tupletError < regularError) {
523                               tuplet.chords.insert({onTime, chordIt});
524                               it = nonTuplets.erase(it);
525                               continue;
526                               }
527                         }
528                   ++it;
529                   }
530             }
531       }
532 
533 
534 #ifdef QT_DEBUG
535 
doTupletsHaveCommonChords(const std::vector<TupletInfo> & tuplets)536 bool doTupletsHaveCommonChords(const std::vector<TupletInfo> &tuplets)
537       {
538       if (tuplets.empty())
539             return false;
540       std::set<std::pair<const ReducedFraction, MidiChord> *> chordsI;
541       for (const auto &tuplet: tuplets) {
542             for (const auto &chord: tuplet.chords) {
543                   if (chordsI.find(&*chord.second) != chordsI.end())
544                         return true;
545                   chordsI.insert(&*chord.second);
546                   }
547             }
548       return false;
549       }
550 
551 // check - do all tuplets have references from some chords
552 
checkTupletsForExistence(const std::multimap<ReducedFraction,MidiChord> & chords,const std::multimap<ReducedFraction,TupletData> & tupletEvents)553 bool checkTupletsForExistence(
554             const std::multimap<ReducedFraction, MidiChord> &chords,
555             const std::multimap<ReducedFraction, TupletData> &tupletEvents)
556       {
557                   // check - do all tuplets have references from some chords
558       std::map<const std::pair<const ReducedFraction, TupletData> *, bool> tupletMap;
559       for (const auto &tuplet: tupletEvents)
560             tupletMap.insert({&tuplet, false});
561 
562       for (const auto &chord: chords) {
563             const MidiChord &c = chord.second;
564             if (c.isInTuplet) {
565                   const auto it = tupletMap.find(&*c.tuplet);
566                   if (it == tupletMap.end()) {
567                         qDebug() << "Chords have references to non-existing tuplets";
568                         return false;
569                         }
570                   }
571             for (const auto &note: c.notes) {
572                   if (note.isInTuplet) {
573                         const auto it = tupletMap.find(&*note.tuplet);
574                         if (it == tupletMap.end()) {
575                               qDebug() << "Notes have references to non-existing tuplets";
576                               return false;
577                               }
578                         }
579                   }
580             }
581       return true;
582       }
583 
584 // check - do all tuplets have references from some chords
585 
checkForDanglingTuplets(const std::multimap<ReducedFraction,MidiChord> & chords,const std::multimap<ReducedFraction,TupletData> & tupletEvents)586 bool checkForDanglingTuplets(
587             const std::multimap<ReducedFraction, MidiChord> &chords,
588             const std::multimap<ReducedFraction, TupletData> &tupletEvents)
589       {
590       const ReducedFraction maxChordLength = MChord::findMaxChordLength(chords);
591 
592       for (auto tupletIt = tupletEvents.begin(); tupletIt != tupletEvents.end(); ++tupletIt) {
593             const auto &tuplet = tupletIt->second;
594             bool hasReference = false;
595             auto chordIt = chords.lower_bound(tuplet.onTime + tuplet.len);
596 
597             if (chordIt != chords.begin()) {
598                   --chordIt;
599                   while (chordIt->first + maxChordLength > tupletIt->first) {
600                         const MidiChord &c = chordIt->second;
601                         if (c.isInTuplet && c.tuplet == tupletIt) {
602                               hasReference = true;
603                               break;
604                               }
605                         for (const auto &note: c.notes) {
606                               if (note.isInTuplet && note.tuplet == tupletIt) {
607                                     hasReference = true;
608                                     break;
609                                     }
610                               }
611                         if (hasReference || chordIt == chords.begin())
612                               break;
613                         --chordIt;
614                         }
615                   }
616 
617             if (!hasReference) {
618                   qDebug() << "Not all tuplets have references in chords - "
619                               "there are dangling tuplets";
620                   return false;
621                   }
622             }
623       return true;
624       }
625 
626 // check tuplet events for uniqueness
627 
checkAreTupletsUnique(const std::multimap<ReducedFraction,TupletData> & tupletEvents)628 bool checkAreTupletsUnique(const std::multimap<ReducedFraction, TupletData> &tupletEvents)
629       {
630       std::set<std::pair<ReducedFraction, int>> referencedTuplets;      // <onTime, voice>
631       for (const auto &tuplet: tupletEvents) {
632             const auto &t = tuplet.second;
633             const auto result = referencedTuplets.insert({t.onTime, t.voice});
634 
635             if (!result.second) {
636                   qDebug() << "Not unique tuplets in tupletEvents";
637                   return false;
638                   }
639             }
640       return true;
641       }
642 
643 // check is referenced tuplet count equal to tuplet event count
644 
checkForEqualTupletCount(const std::multimap<ReducedFraction,MidiChord> & chords,const std::multimap<ReducedFraction,TupletData> & tupletEvents)645 bool checkForEqualTupletCount(
646             const std::multimap<ReducedFraction, MidiChord> &chords,
647             const std::multimap<ReducedFraction, TupletData> &tupletEvents)
648       {
649       std::set<std::pair<ReducedFraction, int>> referencedTuplets;      // <onTime, voice>
650       for (const auto &chord: chords) {
651             if (chord.second.isInTuplet) {
652                   const auto &tuplet = chord.second.tuplet->second;
653                   referencedTuplets.insert({tuplet.onTime, tuplet.voice});
654                   }
655             for (const auto &note: chord.second.notes) {
656                   if (note.isInTuplet) {
657                         const auto &tuplet = note.tuplet->second;
658                         referencedTuplets.insert({tuplet.onTime, tuplet.voice});
659                         }
660                   }
661             }
662 
663       if (referencedTuplets.size() < tupletEvents.size()) {
664             qDebug() << "Referenced tuplets count ("
665                      << referencedTuplets.size() <<
666                         ") < tuplet events count ("
667                      << tupletEvents.size() << ")";
668             }
669       if (referencedTuplets.size() > tupletEvents.size()) {
670             qDebug() << "Referenced tuplets count ("
671                      << referencedTuplets.size() <<
672                         ") > tuplet events count ("
673                      << tupletEvents.size() << ")";
674             }
675 
676       return tupletEvents.size() == referencedTuplets.size();
677       }
678 
areAllTupletsReferenced(const std::multimap<ReducedFraction,MidiChord> & chords,const std::multimap<ReducedFraction,TupletData> & tupletEvents)679 bool areAllTupletsReferenced(
680             const std::multimap<ReducedFraction, MidiChord> &chords,
681             const std::multimap<ReducedFraction, TupletData> &tupletEvents)
682       {
683       if (!checkAreTupletsUnique(tupletEvents))
684             return false;
685       if (!checkTupletsForExistence(chords, tupletEvents))
686             return false;
687       if (!checkForDanglingTuplets(chords, tupletEvents))
688             return false;
689       if (!checkForEqualTupletCount(chords, tupletEvents))
690             return false;
691       return true;
692       }
693 
694 // this check is not full but often if use invalid iterator for comparison
695 // it causes crash or something
696 
areTupletReferencesValid(const std::multimap<ReducedFraction,MidiChord> & chords)697 bool areTupletReferencesValid(const std::multimap<ReducedFraction, MidiChord> &chords)
698       {
699       for (const auto &chord: chords) {
700             const MidiChord &c = chord.second;
701             if (c.isInTuplet) {
702                   const auto &tuplet = c.tuplet->second;
703                   if (tuplet.onTime < ReducedFraction(0, 1)
704                               || tuplet.len < ReducedFraction(0, 1))
705                         return false;
706                   }
707             for (const auto &note: c.notes) {
708                   if (note.isInTuplet) {
709                         const auto &tuplet = note.tuplet->second;
710                         if (tuplet.onTime < ReducedFraction(0, 1)
711                                     || tuplet.len < ReducedFraction(0, 1))
712                               return false;
713                         }
714                   }
715             }
716       return true;
717       }
718 
areTupletNonTupletChordsDistinct(const std::vector<TupletInfo> & tuplets,const std::list<std::multimap<ReducedFraction,MidiChord>::iterator> & nonTuplets)719 bool areTupletNonTupletChordsDistinct(
720             const std::vector<TupletInfo> &tuplets,
721             const std::list<std::multimap<ReducedFraction, MidiChord>::iterator> &nonTuplets)
722       {
723       std::set<std::pair<const ReducedFraction, MidiChord> *> chords;
724       for (const TupletInfo &tuplet: tuplets) {
725             for (const auto &chord: tuplet.chords) {
726                   chords.insert(&*chord.second);
727                   }
728             }
729       for (const auto &nonTuplet: nonTuplets) {
730             const auto it = chords.find(&*nonTuplet);
731             if (it != chords.end())
732                   return false;
733             }
734       return true;
735       }
736 
isTupletRangeOk(const std::pair<const ReducedFraction,MidiChord> & chord,const std::multimap<ReducedFraction,MidiTuplet::TupletData> & tuplets)737 bool isTupletRangeOk(
738             const std::pair<const ReducedFraction, MidiChord> &chord,
739             const std::multimap<ReducedFraction, MidiTuplet::TupletData> &tuplets)
740       {
741       const MidiChord &c = chord.second;
742       const auto foundTuplets = findTupletsForTimeRange(
743                               c.voice, chord.first, ReducedFraction(0, 1), tuplets, false);
744       if (c.isInTuplet && foundTuplets.empty()) {
745             qDebug() << "Tuplet chord is actually outside tuplets, "
746                         "bar number (from 1):" << (c.barIndex + 1);
747             return false;
748             }
749       if (!c.isInTuplet && !foundTuplets.empty()) {
750                         // chord can touch the tuplet at the end and doesn't belong to it
751             for (const auto &t: foundTuplets) {
752                   if (chord.first != t->second.onTime + t->second.len) {
753                         qDebug() << "Non-tuplet chord is actually inside tuplet, "
754                                     "bar number (from 1):" << (c.barIndex + 1);
755                         return false;
756                         }
757                   }
758             }
759       for (const auto &note: c.notes) {
760             const auto foundTuplets1 = findTupletsForTimeRange(
761                               c.voice, note.offTime, ReducedFraction(0, 1), tuplets, false);
762             if (note.isInTuplet && foundTuplets1.empty()) {
763                   qDebug() << "Tuplet note off time is actually outside tuplets, "
764                               "bar number (from 1):" << (c.barIndex + 1);
765                   return false;
766                   }
767             if (!note.isInTuplet && !foundTuplets1.empty()) {
768                               // note off time can touch the tuplet
769                               // at the beg/end and doesn't belong to it
770                   for (const auto &t: foundTuplets1) {
771                         if (note.offTime != t->second.onTime
772                                     && note.offTime != t->second.onTime + t->second.len) {
773                               qDebug() << "Non-tuplet note off time is actually inside tuplet, "
774                                           "bar number (from 1):" << (c.barIndex + 1);
775                               return false;
776                               }
777                         }
778                   }
779             }
780       return true;
781       }
782 
783 // should be called after quantization
784 
areTupletRangesOk(const std::multimap<ReducedFraction,MidiChord> & chords,const std::multimap<ReducedFraction,TupletData> & tuplets)785 bool areTupletRangesOk(
786             const std::multimap<ReducedFraction, MidiChord> &chords,
787             const std::multimap<ReducedFraction, TupletData> &tuplets)
788       {
789       for (const auto &chord: chords) {
790             if (!isTupletRangeOk(chord, tuplets))
791                   return false;
792             }
793       return true;
794       }
795 
areAllTupletsDifferent(const std::multimap<ReducedFraction,TupletData> & tuplets)796 bool areAllTupletsDifferent(const std::multimap<ReducedFraction, TupletData> &tuplets)
797       {
798       std::set<std::pair<ReducedFraction, int> > tupletsSet;      // <on time, voice>
799       for (const auto &tuplet: tuplets)
800             {
801             const auto result = tupletsSet.insert({tuplet.first, tuplet.second.voice});
802             const bool isAlreadyInSet = !result.second;
803             if (isAlreadyInSet)
804                   return false;
805             }
806       return true;
807       }
808 
809 #endif
810 
811 
addTupletEvents(std::multimap<ReducedFraction,TupletData> & tupletEvents,const std::vector<TupletInfo> & tuplets,const std::list<TiedTuplet> & backTiedTuplets)812 void addTupletEvents(std::multimap<ReducedFraction, TupletData> &tupletEvents,
813                      const std::vector<TupletInfo> &tuplets,
814                      const std::list<TiedTuplet> &backTiedTuplets)
815       {
816       for (size_t i = 0; i != tuplets.size(); ++i) {
817             const auto &tupletInfo = tuplets[i];
818             TupletData tupletData = {
819                   tupletInfo.chords.begin()->second->second.voice,
820                   tupletInfo.onTime,
821                   tupletInfo.len,
822                   tupletInfo.tupletNumber,
823                   {}
824                   };
825 
826             const auto it = tupletEvents.insert({tupletData.onTime, tupletData});
827             for (auto &chord: tupletInfo.chords) {
828                   MidiChord &midiChord = chord.second->second;
829                   midiChord.tuplet = it;
830 
831                   Q_ASSERT_X(!midiChord.isInTuplet,
832                              "MidiTuplet::addTupletEvents",
833                              "Chord is already in tuplet but it shouldn't");
834 
835                   midiChord.isInTuplet = true;
836                   }
837 
838             for (const TiedTuplet &tiedTuplet: backTiedTuplets) {
839                   if (tiedTuplet.tupletId == tupletInfo.id) {
840                         MidiChord &midiChord = tiedTuplet.chord->second;
841 
842 #ifdef QT_DEBUG
843                         QString message = "Tied tuplet and tied chord have different voices, "
844                                           "tuplet voice = ";
845                         message += QString::number(tiedTuplet.voice) + ", chord voice = ";
846                         message += QString::number(midiChord.voice) + ", bar number (from 1) = ";
847                         message += QString::number(midiChord.barIndex + 1);
848 #endif
849                         Q_ASSERT_X(tiedTuplet.voice == midiChord.voice,
850                                    "MidiTuplet::addTupletEvents", message.toLatin1().data());
851 
852                         for (int j: tiedTuplet.tiedNoteIndexes) {
853                               midiChord.notes[j].tuplet = it;
854                               midiChord.notes[j].isInTuplet = true;
855                               }
856                         break;
857                         }
858                   }
859 
860             for (auto &chord: tupletInfo.chords) {
861                   MidiChord &midiChord = chord.second->second;
862                   for (auto &note: midiChord.notes) {
863                         if (note.offTime <= tupletInfo.onTime + tupletInfo.len) {
864 
865                               Q_ASSERT_X(!note.isInTuplet,
866                                          "MidiTuplet::addTupletEvents",
867                                          "Note is already in tuplet but it shouldn't");
868 
869                               note.tuplet = it;
870                               note.isInTuplet = true;
871                               }
872                         }
873                   }
874             }
875       }
876 
markStaccatoTupletNotes(std::vector<TupletInfo> & tuplets)877 void markStaccatoTupletNotes(std::vector<TupletInfo> &tuplets)
878       {
879       for (auto &tuplet: tuplets) {
880             for (const auto &staccato: tuplet.staccatoChords) {
881                   const auto it = tuplet.chords.find(staccato.first);
882                   if (it != tuplet.chords.end()) {
883                         MidiChord &midiChord = it->second->second;
884                         midiChord.notes[staccato.second].staccato = true;
885                         }
886                   }
887             tuplet.staccatoChords.clear();
888             }
889       }
890 
891 // if notes with staccato were in tuplets previously - remove staccato
892 
cleanStaccatoOfNonTuplets(std::list<std::multimap<ReducedFraction,MidiChord>::iterator> & nonTuplets)893 void cleanStaccatoOfNonTuplets(
894             std::list<std::multimap<ReducedFraction, MidiChord>::iterator> &nonTuplets)
895       {
896       for (auto &nonTuplet: nonTuplets) {
897             for (auto &note: nonTuplet->second.notes) {
898                   if (note.staccato)
899                         note.staccato = false;
900                   }
901             }
902       }
903 
904 // chord on time shouldn't go before previous chord on time or after next chord on time
905 // this function finds such on time value between previous and next chords
906 
findOnTimeBetweenChords(const std::pair<const ReducedFraction,MidiChord> & chord,const std::multimap<ReducedFraction,MidiChord> & chords,const ReducedFraction & basicQuant,const ReducedFraction & barStart)907 ReducedFraction findOnTimeBetweenChords(
908             const std::pair<const ReducedFraction, MidiChord> &chord,
909             const std::multimap<ReducedFraction, MidiChord> &chords,
910             const ReducedFraction &basicQuant,
911             const ReducedFraction &barStart)
912       {
913       ReducedFraction onTime(-1, 1);
914       auto quant = basicQuant;
915 
916       const auto range = chords.equal_range(chord.first);
917       auto chordIt = chords.end();
918 
919       for (auto it = range.first; it != range.second; ++it) {
920             if (it->second.voice == chord.second.voice) {
921                   chordIt = it;
922                   break;
923                   }
924             }
925 
926       Q_ASSERT_X(chordIt != chords.end(),
927                  "MidiTuplet::findOnTimeBetweenChords", "Chord iterator was not found");
928 
929       if (chordIt->first < barStart)
930             return barStart;
931 
932                   // chords with equal voices here can have equal on time values
933                   // so skip such chords
934       const int voice = chordIt->second.voice;
935       while (true) {
936             onTime = Quantize::findMinQuantizedOnTime(*chordIt, quant);
937             bool changed = false;
938 
939             if (chordIt != chords.begin()) {
940                   auto it = std::prev(chordIt);
941                   while (true) {
942                         if (it->first < chord.first && it->second.voice == voice) {
943                               const auto prevChordOnTime
944                                                 = Quantize::findMinQuantizedOnTime(*it, quant);
945                               if (onTime < prevChordOnTime) {
946 
947                                     Q_ASSERT_X(quant >= MChord::minAllowedDuration() * 2,
948                                                "MidiTuplet::findOnTimeBetweenChords",
949                                                "Too small quantization value");
950 
951                                     quant /= 2;
952                                     changed = true;
953                                     }
954                               break;
955                               }
956                         if (it == chords.begin() || it->first < chord.first - basicQuant * 2)
957                               break;
958                         --it;
959                         }
960                   }
961 
962             if (!changed) {
963                   for (auto it = std::next(chordIt);
964                               it != chords.end() && it->first < chord.first + basicQuant * 2; ++it) {
965                         if (it->first == chord.first || it->second.voice != voice)
966                               continue;
967                         const auto nextChordOnTime = Quantize::findMinQuantizedOnTime(*it, quant);
968                         if (onTime > nextChordOnTime) {
969 
970                               Q_ASSERT_X(quant >= MChord::minAllowedDuration() * 2,
971                                          "MidiTuplet::findOnTimeBetweenChords",
972                                          "Too small quantization value");
973 
974                               quant /= 2;
975                               changed = true;
976                               }
977                         break;
978                         }
979                   }
980 
981             if (!changed)
982                   break;
983             }
984 
985       Q_ASSERT_X(onTime != ReducedFraction(-1, 1), "MidiTuplet::findOnTimeBetweenChords",
986                  "On time for chord interval was not found");
987 
988       return onTime;
989       }
990 
findPrevBarStart(const ReducedFraction & barStart,const ReducedFraction & barLen)991 ReducedFraction findPrevBarStart(const ReducedFraction &barStart,
992                                  const ReducedFraction &barLen)
993       {
994       auto prevBarStart = barStart - barLen;
995       if (prevBarStart < ReducedFraction(0, 1))
996             prevBarStart = ReducedFraction(0, 1);
997       return prevBarStart;
998       }
999 
setBarIndexesOfNextBarChords(std::vector<TupletInfo> & tuplets,std::list<std::multimap<ReducedFraction,MidiChord>::iterator> & nonTuplets,int barIndex)1000 void setBarIndexesOfNextBarChords(
1001             std::vector<TupletInfo> &tuplets,
1002             std::list<std::multimap<ReducedFraction, MidiChord>::iterator> &nonTuplets,
1003             int barIndex)
1004       {
1005       for (auto &tuplet: tuplets) {
1006             for (auto &chord: tuplet.chords) {
1007                   if (chord.second->second.barIndex > barIndex)
1008                         chord.second->second.barIndex = barIndex;
1009                   }
1010             }
1011       for (auto &chord: nonTuplets) {
1012             if (chord->second.barIndex > barIndex)
1013                   chord->second.barIndex = barIndex;
1014             }
1015       }
1016 
1017 // indexes of each new bar should be -1 except possibly chords at the end of prev bar
1018 
findTuplets(const std::multimap<ReducedFraction,MidiChord>::iterator & startBarChordIt,const std::multimap<ReducedFraction,MidiChord>::iterator & endBarChordIt,std::multimap<ReducedFraction,MidiChord> & chords,const ReducedFraction & basicQuant,std::multimap<ReducedFraction,TupletData> & tupletEvents,const TimeSigMap * sigmap,int barIndex)1019 void findTuplets(
1020             const std::multimap<ReducedFraction, MidiChord>::iterator &startBarChordIt,
1021             const std::multimap<ReducedFraction, MidiChord>::iterator &endBarChordIt,
1022             std::multimap<ReducedFraction, MidiChord> &chords,
1023             const ReducedFraction &basicQuant,
1024             std::multimap<ReducedFraction, TupletData> &tupletEvents,
1025             const TimeSigMap *sigmap,
1026             int barIndex)
1027       {
1028       if (chords.empty() || startBarChordIt == endBarChordIt)
1029             return;
1030 
1031       const auto &opers = midiImportOperations.data()->trackOpers;
1032       const int currentTrack = midiImportOperations.currentTrack();
1033       if (!opers.searchTuplets.value(currentTrack))
1034             return;
1035 
1036       const auto startBarTick = ReducedFraction::fromTicks(
1037                                     sigmap->bar2tick(startBarChordIt->second.barIndex, 0));
1038       const auto endBarTick = ReducedFraction::fromTicks(
1039                                     sigmap->bar2tick(startBarChordIt->second.barIndex + 1, 0));
1040 
1041       const auto barFraction = ReducedFraction(sigmap->timesig(startBarTick.ticks()).timesig());
1042       std::vector<TupletInfo> tuplets = detectTuplets(startBarChordIt, endBarChordIt, startBarTick,
1043                                                       barFraction, chords, basicQuant, barIndex);
1044       if (tuplets.empty())
1045             return;
1046 
1047       filterTuplets(tuplets, basicQuant);
1048                   // later notes will be sorted and their indexes become invalid
1049                   // so assign staccato information to notes now
1050       if (opers.simplifyDurations.value(currentTrack))
1051             markStaccatoTupletNotes(tuplets);
1052 
1053       auto nonTuplets = findNonTupletChords(tuplets, startBarChordIt, endBarChordIt);
1054       addChordsBetweenTupletNotes(tuplets, nonTuplets, chords, startBarTick, basicQuant);
1055       sortNotesByPitch(startBarChordIt, endBarChordIt);
1056       sortTupletsByAveragePitch(tuplets);
1057 
1058       if (tupletVoiceLimit() > 1) {
1059             splitFirstTupletChords(tuplets, chords);
1060             minimizeOffTimeError(tuplets, chords, nonTuplets, startBarTick, basicQuant);
1061             }
1062 
1063       if (opers.simplifyDurations.value(currentTrack))
1064             cleanStaccatoOfNonTuplets(nonTuplets);
1065 
1066       Q_ASSERT_X(!doTupletsHaveCommonChords(tuplets),
1067                  "MIDI tuplets: findTuplets", "Tuplets have common chords but they shouldn't");
1068 
1069       const auto prevBarStart = findPrevBarStart(startBarTick, endBarTick - startBarTick);
1070       auto backTiedTuplets = findBackTiedTuplets(chords, tuplets, prevBarStart, startBarTick,
1071                                                  basicQuant, startBarChordIt->second.barIndex);
1072                   // backTiedTuplets can be changed here (incompatible are removed)
1073       assignVoices(tuplets, nonTuplets, backTiedTuplets, chords, basicQuant,
1074                    startBarTick, barIndex);
1075 
1076       Q_ASSERT_X(areTupletNonTupletChordsDistinct(tuplets, nonTuplets),
1077                  "MIDI tuplets: findTuplets", "Tuplets have common chords with non-tuplets");
1078 
1079       addTupletEvents(tupletEvents, tuplets, backTiedTuplets);
1080       setBarIndexesOfNextBarChords(tuplets, nonTuplets, barIndex);
1081       }
1082 
setAllTupletOffTimes(std::multimap<ReducedFraction,TupletData> & tupletEvents,std::multimap<ReducedFraction,MidiChord> & chords,const TimeSigMap * sigmap)1083 void setAllTupletOffTimes(
1084             std::multimap<ReducedFraction, TupletData> &tupletEvents,
1085             std::multimap<ReducedFraction, MidiChord> &chords,
1086             const TimeSigMap *sigmap)
1087       {
1088       for (auto &chordEvent: chords) {
1089             MidiChord &chord = chordEvent.second;
1090             for (MidiNote &note: chord.notes) {
1091                   if (note.isInTuplet)
1092                         continue;
1093                   const auto barEnd = ReducedFraction::fromTicks(
1094                                     sigmap->bar2tick(chord.barIndex + 1, 0));
1095                   if (note.offTime > barEnd) {
1096                         const auto it = findTupletContainingTime(
1097                                           chord.voice, note.offTime, tupletEvents, true);
1098                         if (it != tupletEvents.end()) {
1099                               note.isInTuplet = true;
1100                                           // hack to remove constness of iterator
1101                               note.tuplet = tupletEvents.erase(it, it);
1102                               }
1103                         }
1104                   }
1105             }
1106       }
1107 
findAllTuplets(std::multimap<ReducedFraction,TupletData> & tuplets,std::multimap<ReducedFraction,MidiChord> & chords,const TimeSigMap * sigmap,const ReducedFraction & basicQuant)1108 void findAllTuplets(
1109             std::multimap<ReducedFraction, TupletData> &tuplets,
1110             std::multimap<ReducedFraction, MidiChord> &chords,
1111             const TimeSigMap *sigmap,
1112             const ReducedFraction &basicQuant)
1113       {
1114       if (chords.empty())
1115             return;
1116 
1117       Q_ASSERT_X(MChord::areNotesLongEnough(chords),
1118                  "MidiTuplet::findAllTuplets", "There are too short notes");
1119       Q_ASSERT_X(MChord::areBarIndexesSet(chords),
1120                  "MidiTuplet::findAllTuplets", "Not all bar indexes were set");
1121       Q_ASSERT_X(MChord::areBarIndexesSuccessive(chords),
1122                  "MidiTuplet::findAllTuplets", "Bar indexes are not successive");
1123 
1124       {
1125       auto startBarIt = chords.begin();
1126       for (auto endBarIt = std::next(startBarIt); endBarIt != chords.end(); ++endBarIt) {
1127 
1128             Q_ASSERT_X(endBarIt->second.barIndex >= startBarIt->second.barIndex,
1129                        "MidiTuplet::findAllTuplets", "Bar indexes are not successive");
1130 
1131             const int currentBarIndex = startBarIt->second.barIndex;
1132             if (endBarIt->second.barIndex > currentBarIndex) {
1133                   const size_t oldTupletCount = tuplets.size();
1134                   findTuplets(startBarIt, endBarIt, chords, basicQuant,
1135                               tuplets, sigmap, currentBarIndex);
1136 
1137                   Q_ASSERT_X(tuplets.size() >= oldTupletCount, "MidiTuplet::findAllTuplets",
1138                              "Some old tuplets were deleted that is incorrect");
1139 
1140                   if (tuplets.size() > oldTupletCount) {                // new tuplets were found
1141                               // chords at the end of the current bar
1142                               // may have changed bar index - from next bar to the current bar
1143                               // because they were included in tuplets
1144 #ifdef QT_DEBUG
1145                         bool nextBarFound = false;
1146 #endif
1147                         const auto endBarTick = ReducedFraction::fromTicks(
1148                                                 sigmap->bar2tick(currentBarIndex + 1, 0));
1149                         for (auto it = endBarIt; it != chords.end() && it->first < endBarTick; ++it) {
1150                               if (it->second.barIndex == currentBarIndex) {
1151 
1152                                     Q_ASSERT_X(!nextBarFound, "MidiTuplet::findAllTuplets",
1153                                                "Bar indexes become not successive");
1154                                     Q_ASSERT_X(endBarIt != chords.end(), "MidiTuplet::findAllTuplets",
1155                                                "End bar iterator cannot be incremented");
1156 
1157                                     ++endBarIt;
1158                                     }
1159 #ifdef QT_DEBUG
1160                               if (it->second.barIndex > currentBarIndex)
1161                                     nextBarFound = true;
1162 #endif
1163                               }
1164                         }
1165 
1166                   startBarIt = endBarIt;
1167                   if (endBarIt == chords.end())
1168                         break;
1169                   }
1170             }
1171                   // handle the last bar containing chords
1172       findTuplets(startBarIt, chords.end(), chords, basicQuant, tuplets,
1173                   sigmap, startBarIt->second.barIndex);
1174       }
1175                   // check if there are not detected off times inside tuplets
1176       setAllTupletOffTimes(tuplets, chords, sigmap);
1177 
1178       Q_ASSERT_X(areAllTupletsReferenced(chords, tuplets),
1179                  "MidiTuplet::findAllTuplets",
1180                  "Not all tuplets are referenced in chords or notes");
1181       Q_ASSERT_X(MChord::areNotesLongEnough(chords),
1182                  "MidiTuplet::findAllTuplets", "There are too short notes");
1183       Q_ASSERT(areAllTupletsDifferent(tuplets));
1184       }
1185 
1186 } // namespace MidiTuplet
1187 } // namespace Ms
1188