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> ¬es,
59 const ReducedFraction &tupletOnTime,
60 const ReducedFraction &tupletLen)
61 {
62 if (chordOnTime == tupletOnTime) {
63 for (const auto ¬e: 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 ¬e: 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 ¬e: 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 ¬es = 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 ¬e = 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 ¬e: 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 ¬e: 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 ¬e: 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 ¬e: 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 ¬e: 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 ¬e: 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 ¬e: 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 ¬e: 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