1 #include "importmidi_chord.h"
2 #include "importmidi_inner.h"
3 #include "importmidi_chord.h"
4 #include "importmidi_clef.h"
5 #include "importmidi_operations.h"
6 #include "importmidi_quant.h"
7 #include "libmscore/mscore.h"
8 #include "libmscore/sig.h"
9 #include "mscore/preferences.h"
10 
11 #include <set>
12 
13 
14 namespace Ms {
15 namespace MChord {
16 
isGrandStaffProgram(int program)17 bool isGrandStaffProgram(int program)
18       {
19       const static std::set<int> grandStaffPrograms = {
20                   // Piano
21               0, 1, 2, 3, 4, 5, 6, 7
22                   // Chromatic Percussion
23             , 8, 10, 11, 12, 13, 15
24                   // Organ
25             , 16, 17, 18, 19, 20, 21, 23
26                   // Strings
27             , 46
28                   // Ensemble
29             , 50, 51, 54
30                   // Brass
31             , 62, 63
32                   // Synth Lead
33             , 80, 81, 82, 83, 84, 85, 86, 87
34                   // Synth Pad
35             , 88, 89, 90, 91, 92, 93, 94, 95
36                   // Synth Effects
37             , 96, 97, 98, 99, 100, 101, 102, 103
38             };
39 
40       return grandStaffPrograms.find(program) != grandStaffPrograms.end();
41       }
42 
43 std::multimap<ReducedFraction, MidiChord>::iterator
findFirstChordInRange(std::multimap<ReducedFraction,MidiChord> & chords,const ReducedFraction & startRangeTick,const ReducedFraction & endRangeTick)44 findFirstChordInRange(std::multimap<ReducedFraction, MidiChord> &chords,
45                       const ReducedFraction &startRangeTick,
46                       const ReducedFraction &endRangeTick)
47       {
48       auto iter = chords.lower_bound(startRangeTick);
49       if (iter != chords.end() && iter->first >= endRangeTick)
50             iter = chords.end();
51       return iter;
52       }
53 
54 std::multimap<ReducedFraction, MidiChord>::const_iterator
findFirstChordInRange(const std::multimap<ReducedFraction,MidiChord> & chords,const ReducedFraction & startRangeTick,const ReducedFraction & endRangeTick)55 findFirstChordInRange(const std::multimap<ReducedFraction, MidiChord> &chords,
56                       const ReducedFraction &startRangeTick,
57                       const ReducedFraction &endRangeTick)
58       {
59       auto iter = chords.lower_bound(startRangeTick);
60       if (iter != chords.end() && iter->first >= endRangeTick)
61             iter = chords.end();
62       return iter;
63       }
64 
minAllowedDuration()65 const ReducedFraction& minAllowedDuration()
66       {
67       const static auto minDuration = ReducedFraction::fromTicks(MScore::division) / 32;
68       return minDuration;
69       }
70 
minNoteOffTime(const QList<MidiNote> & notes)71 ReducedFraction minNoteOffTime(const QList<MidiNote> &notes)
72       {
73       if (notes.isEmpty())
74             return {0, 1};
75       auto it = notes.begin();
76       ReducedFraction minOffTime = it->offTime;
77       for (++it; it != notes.end(); ++it) {
78             if (it->offTime < minOffTime)
79                   minOffTime = it->offTime;
80             }
81       return minOffTime;
82       }
83 
maxNoteOffTime(const QList<MidiNote> & notes)84 ReducedFraction maxNoteOffTime(const QList<MidiNote> &notes)
85       {
86       ReducedFraction maxOffTime(0, 1);
87       for (const auto &note: notes) {
88             if (note.offTime > maxOffTime)
89                   maxOffTime = note.offTime;
90             }
91       return maxOffTime;
92       }
93 
minNoteLen(const std::pair<const ReducedFraction,MidiChord> & chord)94 ReducedFraction minNoteLen(const std::pair<const ReducedFraction, MidiChord> &chord)
95       {
96       const auto minOffTime = minNoteOffTime(chord.second.notes);
97       return minOffTime - chord.first;
98       }
99 
maxNoteLen(const std::pair<const ReducedFraction,MidiChord> & chord)100 ReducedFraction maxNoteLen(const std::pair<const ReducedFraction, MidiChord> &chord)
101       {
102       const auto maxOffTime = maxNoteOffTime(chord.second.notes);
103       return maxOffTime - chord.first;
104       }
105 
removeOverlappingNotes(QList<MidiNote> & notes)106 void removeOverlappingNotes(QList<MidiNote> &notes)
107       {
108       std::list<MidiNote> tempNotes;
109       for (const auto &note: notes)
110             tempNotes.push_back(note);
111 
112       for (auto noteIt1 = tempNotes.begin(); noteIt1 != tempNotes.end(); ++noteIt1) {
113             for (auto noteIt2 = std::next(noteIt1); noteIt2 != tempNotes.end(); ) {
114                   if (noteIt2->pitch == noteIt1->pitch) {
115                                     // overlapping notes found
116                         if (noteIt2->offTime > noteIt1->offTime)      // set max len before erase
117                               noteIt1->offTime = noteIt2->offTime;
118                         noteIt2 = tempNotes.erase(noteIt2);
119                         qDebug("Midi import: removeOverlappingNotes: note was removed");
120                         continue;
121                         }
122                   ++noteIt2;
123                   }
124             }
125       notes.clear();
126       for (const auto &note: tempNotes)
127             notes.append(note);
128       }
129 
130 // remove overlapping notes with the same pitch
131 
removeOverlappingNotes(std::multimap<int,MTrack> & tracks)132 void removeOverlappingNotes(std::multimap<int, MTrack> &tracks)
133       {
134       for (auto &track: tracks) {
135             auto &chords = track.second.chords;
136             if (chords.empty())
137                   continue;
138 
139             Q_ASSERT_X(MidiTuplet::areTupletRangesOk(chords, track.second.tuplets),
140                        "MChord::removeOverlappingNotes", "Tuplet chord/note is outside tuplet "
141                         "or non-tuplet chord/note is inside tuplet before overlaps remove");
142 
143             for (auto i1 = chords.begin(); i1 != chords.end(); ) {
144                   const auto &onTime1 = i1->first;
145                   auto &chord1 = i1->second;
146                   removeOverlappingNotes(chord1.notes);
147 
148                   for (auto note1It = chord1.notes.begin(); note1It != chord1.notes.end(); ) {
149                         auto &note1 = *note1It;
150 
151                         for (auto i2 = std::next(i1); i2 != chords.end(); ++i2) {
152                               const auto &onTime2 = i2->first;
153                               if (onTime2 >= note1.offTime)
154                                     break;
155                               auto &chord2 = i2->second;
156                               if (chord1.voice != chord2.voice)
157                                     continue;
158                               for (auto &note2: chord2.notes) {
159                                     if (note2.pitch != note1.pitch)
160                                           continue;
161                                                 // overlapping notes found
162                                     note1.offTime = onTime2;
163                                     if (!note1.isInTuplet && chord2.isInTuplet) {
164                                           if (note1.offTime > chord2.tuplet->second.onTime) {
165                                                 note1.isInTuplet = true;
166                                                 note1.tuplet = chord2.tuplet;
167                                                 }
168                                           }
169                                     else if (note1.isInTuplet && !chord2.isInTuplet) {
170                                           note1.isInTuplet = false;
171                                           }
172 
173                                     i2 = std::prev(chords.end());
174                                     break;
175                                     }
176                               }
177                         if (note1.offTime - onTime1 < MChord::minAllowedDuration()) {
178                               note1It = chord1.notes.erase(note1It);
179                               qDebug("Midi import: removeOverlappingNotes: note was removed");
180                               continue;
181                               }
182                         ++note1It;
183                         }
184                   if (chord1.notes.isEmpty()) {
185                         i1 = chords.erase(i1);
186                         continue;
187                         }
188                   ++i1;
189                   }
190 
191             MidiTuplet::removeEmptyTuplets(track.second);
192 
193             Q_ASSERT_X(MidiTuplet::areTupletRangesOk(chords, track.second.tuplets),
194                        "MChord::removeOverlappingNotes", "Tuplet chord/note is outside tuplet "
195                         "or non-tuplet chord/note is inside tuplet after overlaps remove");
196             }
197       }
198 
199 
200 #ifdef QT_DEBUG
201 
202 // check for equal on time values with the same voice that is invalid
areOnTimeValuesDifferent(const std::multimap<ReducedFraction,MidiChord> & chords)203 bool areOnTimeValuesDifferent(const std::multimap<ReducedFraction, MidiChord> &chords)
204       {
205       std::map<ReducedFraction, int> onTimeVoices;
206       for (const auto &chordEvent: chords) {
207             const auto it = onTimeVoices.find(chordEvent.first);
208             if (it == onTimeVoices.end())
209                   onTimeVoices.insert({chordEvent.first, chordEvent.second.voice});
210             else if (chordEvent.second.voice == it->second)
211                   return false;
212             }
213       return true;
214       }
215 
areNotesLongEnough(const std::multimap<ReducedFraction,MidiChord> & chords)216 bool areNotesLongEnough(const std::multimap<ReducedFraction, MidiChord> &chords)
217       {
218       for (const auto &chord: chords) {
219             if (minNoteLen(chord) < minAllowedDuration())
220                   return false;
221             }
222       return true;
223       }
224 
areBarIndexesSuccessive(const std::multimap<ReducedFraction,MidiChord> & chords)225 bool areBarIndexesSuccessive(const std::multimap<ReducedFraction, MidiChord> &chords)
226       {
227       int barIndex = 0;
228       for (const auto &chord: chords) {
229             const MidiChord &c = chord.second;
230             if (c.barIndex < 0)
231                   return false;
232             if (c.barIndex < barIndex)
233                   return false;
234             barIndex = c.barIndex;
235             }
236       return true;
237       }
238 
isLastTickValid(const ReducedFraction & lastTick,const std::multimap<ReducedFraction,MidiChord> & chords)239 bool isLastTickValid(const ReducedFraction &lastTick,
240                      const std::multimap<ReducedFraction, MidiChord> &chords)
241       {
242       for (const auto &chord: chords) {
243             if (maxNoteOffTime(chord.second.notes) > lastTick)
244                   return false;
245             }
246       return true;
247       }
248 
isLastTickValid(const ReducedFraction & lastTick,const std::multimap<int,MTrack> & tracks)249 bool isLastTickValid(const ReducedFraction &lastTick,
250                      const std::multimap<int, MTrack> &tracks)
251       {
252       for (const auto &track: tracks) {
253             if (!(isLastTickValid(lastTick, track.second.chords)))
254                   return false;
255             }
256       return true;
257       }
258 
areBarIndexesSet(const std::multimap<ReducedFraction,MidiChord> & chords)259 bool areBarIndexesSet(const std::multimap<ReducedFraction, MidiChord> &chords)
260       {
261       for (const auto &chord: chords) {
262             if (chord.second.barIndex == -1)
263                   return false;
264             }
265       return true;
266       }
267 
268 #endif
269 
setToNegative(ReducedFraction & v1,ReducedFraction & v2,ReducedFraction & v3)270 void setToNegative(ReducedFraction &v1, ReducedFraction &v2, ReducedFraction &v3)
271       {
272       v1 = ReducedFraction(-1, 1);
273       v2 = ReducedFraction(-1, 1);
274       v3 = ReducedFraction(-1, 1);
275       }
276 
hasNotesWithEqualPitch(const MidiChord & chord1,const MidiChord & chord2)277 bool hasNotesWithEqualPitch(const MidiChord &chord1, const MidiChord &chord2)
278       {
279       std::set<int> notes1;
280       for (const auto &note: chord1.notes)
281             notes1.insert(note.pitch);
282       for (const auto &note: chord2.notes) {
283             if (notes1.find(note.pitch) != notes1.end())
284                   return true;
285             }
286       return false;
287       }
288 
collectChords(std::multimap<int,MTrack> & tracks,const ReducedFraction & humanTolCoeff,const ReducedFraction & nonHumanTolCoeff)289 void collectChords(
290             std::multimap<int, MTrack> &tracks,
291             const ReducedFraction &humanTolCoeff,
292             const ReducedFraction &nonHumanTolCoeff)
293       {
294       for (auto &track: tracks)
295             collectChords(track.second, humanTolCoeff, nonHumanTolCoeff);
296       }
297 
298 // based on quickthresh algorithm
299 //
300 // http://www.cycling74.com/docs/max5/refpages/max-ref/quickthresh.html
301 // (link date 9 July 2013)
302 //
303 // here are default values for audio, in milliseconds
304 // for midi there will be another values, in ticks
305 
306 // all notes received in the left inlet within this time period are collected into a chord
307 // threshTime = 40 ms
308 
309 // if there are any incoming values within this amount of time
310 // at the end of the base thresh time,
311 // the threshold is extended to allow more notes to be added to the chord
312 // fudgeTime = 10 ms
313 
314 // this is an extension value of the base thresh time, which is used if notes arrive
315 // in the object's inlet in the "fudge" time zone
316 // threshExtTime = 20 ms
317 
318 //     chord                             |<--fudge time-->|
319 // ------x-------------------------------|----------------|---------------------|------
320 //       |<-----------------thresh time------------------>|<--thresh ext time-->|
321 //
collectChords(MTrack & track,const ReducedFraction & humanTolCoeff,const ReducedFraction & nonHumanTolCoeff)322 void collectChords(
323             MTrack &track,
324             const ReducedFraction &humanTolCoeff,
325             const ReducedFraction &nonHumanTolCoeff)
326       {
327       auto &chords = track.chords;
328       if (chords.empty())
329             return;
330 
331       Q_ASSERT_X(areNotesLongEnough(chords),
332                  "MChord::collectChords", "There are too short notes");
333 
334       const auto &opers = midiImportOperations.data()->trackOpers;
335       const auto minAllowedDur = minAllowedDuration();
336 
337       const auto threshTime = (opers.isHumanPerformance.value())
338                                     ? minAllowedDur * humanTolCoeff
339                                     : minAllowedDur * nonHumanTolCoeff;
340       const auto fudgeTime = threshTime / 4;
341       const auto threshExtTime = threshTime / 2;
342 
343       ReducedFraction currentChordStart;
344       ReducedFraction curThreshTime;
345                   // if note onTime goes after max chord offTime
346                   // then this is not a chord but arpeggio
347       ReducedFraction maxOffTime;
348 
349       setToNegative(currentChordStart, curThreshTime, maxOffTime); // invalidate
350 
351       for (auto it = chords.begin(); it != chords.end(); ) {
352             if (it->second.isInTuplet) {
353                   setToNegative(currentChordStart, curThreshTime, maxOffTime);
354                   ++it;
355                   continue;
356                   }
357 
358             const auto maxNoteOffTime = MChord::maxNoteOffTime(it->second.notes);
359             if (it->first < currentChordStart + curThreshTime) {
360 
361                   // this branch should not be executed when it == chords.begin()
362                   Q_ASSERT_X(it != chords.begin(),
363                              "MChord: collectChords", "it == chords.begin()");
364 
365                   if (it->first <= maxOffTime - minAllowedDur) {
366                                     // add current note to the previous chord
367                         auto chordAddTo = std::prev(it);
368                         if (it->second.voice != chordAddTo->second.voice) {
369                               setToNegative(currentChordStart, curThreshTime, maxOffTime);
370                               ++it;
371                               continue;
372                               }
373 
374                         if (!hasNotesWithEqualPitch(chordAddTo->second, it->second)) {
375                               for (const auto &note: qAsConst(it->second.notes))
376                                     chordAddTo->second.notes.push_back(note);
377                               if (maxNoteOffTime > maxOffTime)
378                                     maxOffTime = maxNoteOffTime;
379                               }
380                         if (it->first >= currentChordStart + curThreshTime - fudgeTime
381                                     && curThreshTime == threshTime) {
382                               curThreshTime += threshExtTime;
383                               }
384 
385                         it = chords.erase(it);
386                         continue;
387                         }
388                   }
389 
390             currentChordStart = it->first;
391             maxOffTime = maxNoteOffTime;
392             curThreshTime = threshTime;
393             ++it;
394             }
395 
396       Q_ASSERT_X(areOnTimeValuesDifferent(chords),
397                  "MChord: collectChords",
398                  "onTime values of chords are equal but should be different");
399       }
400 
sortNotesByPitch(std::multimap<ReducedFraction,MidiChord> & chords)401 void sortNotesByPitch(std::multimap<ReducedFraction, MidiChord> &chords)
402       {
403       struct {
404             bool operator()(const MidiNote &note1, const MidiNote &note2)
405                   {
406                   return note1.pitch < note2.pitch;
407                   }
408             } pitchSort;
409 
410       for (auto &chordEvent: chords) {
411                         // in each chord sort notes by pitches
412             auto &notes = chordEvent.second.notes;
413             std::sort(notes.begin(), notes.end(), pitchSort);
414             }
415       }
416 
sortNotesByLength(std::multimap<ReducedFraction,MidiChord> & chords)417 void sortNotesByLength(std::multimap<ReducedFraction, MidiChord> &chords)
418       {
419       struct {
420             bool operator()(const MidiNote &note1, const MidiNote &note2)
421                   {
422                   return note1.offTime < note2.offTime;
423                   }
424             } lenSort;
425 
426       for (auto &chordEvent: chords) {
427                         // in each chord sort notes by lengths
428             auto &notes = chordEvent.second.notes;
429             std::sort(notes.begin(), notes.end(), lenSort);
430             }
431       }
432 
433 // find notes of each chord that have different durations
434 // and separate them into different chords
435 // so all notes inside every chord will have equal lengths
436 
splitUnequalChords(std::multimap<int,MTrack> & tracks)437 void splitUnequalChords(std::multimap<int, MTrack> &tracks)
438       {
439       for (auto &track: tracks) {
440             std::vector<std::pair<ReducedFraction, MidiChord>> newChordEvents;
441             auto &chords = track.second.chords;
442             if (chords.empty())
443                   continue;
444             sortNotesByLength(chords);
445             for (auto &chordEvent: chords) {
446                   auto &chord = chordEvent.second;
447                   auto &notes = chord.notes;
448                   ReducedFraction offTime;
449                   for (auto it = notes.begin(); it != notes.end(); ) {
450                         if (it == notes.begin())
451                               offTime = it->offTime;
452                         else {
453                               ReducedFraction newOffTime = it->offTime;
454                               if (newOffTime != offTime) {
455                                     MidiChord newChord(chord);
456                                     newChord.notes.clear();
457                                     for (int j = it - notes.begin(); j > 0; --j)
458                                           newChord.notes.push_back(notes[j - 1]);
459                                     newChordEvents.push_back({chordEvent.first, newChord});
460                                     it = notes.erase(notes.begin(), it);
461                                     continue;
462                                     }
463                               }
464                         ++it;
465                         }
466                   }
467             for (const auto &event: newChordEvents)
468                   chords.insert(event);
469             }
470       }
471 
findMinDuration(const ReducedFraction & onTime,const QList<MidiChord> & midiChords,const ReducedFraction & length)472 ReducedFraction findMinDuration(const ReducedFraction &onTime,
473                                 const QList<MidiChord> &midiChords,
474                                 const ReducedFraction &length)
475       {
476       ReducedFraction len = length;
477       for (const auto &chord: midiChords) {
478             for (const auto &note: chord.notes) {
479                   if ((note.offTime - onTime < len)
480                               && (note.offTime - onTime != ReducedFraction(0, 1)))
481                         len = note.offTime - onTime;
482                   }
483             }
484       return len;
485       }
486 
mergeChordsWithEqualOnTimeAndVoice(std::multimap<int,MTrack> & tracks)487 void mergeChordsWithEqualOnTimeAndVoice(std::multimap<int, MTrack> &tracks)
488       {
489       for (auto &track: tracks) {
490             auto &chords = track.second.chords;
491             if (chords.empty())
492                   continue;
493                         // the key is pair<onTime, voice>
494             std::map<std::pair<ReducedFraction, int>,
495                      std::multimap<ReducedFraction, MidiChord>::iterator> onTimes;
496 
497             for (auto it = chords.begin(); it != chords.end(); ) {
498                   const auto &onTime = it->first;
499                   const int voice = it->second.voice;
500                   auto fit = onTimes.find({onTime, voice});
501                   if (fit == onTimes.end()) {
502                         onTimes.insert({{onTime, voice}, it});
503                         }
504                   else {
505                         auto &oldNotes = fit->second->second.notes;
506                         auto &newNotes = it->second.notes;
507                         oldNotes.append(newNotes);
508                         it = chords.erase(it);
509                         continue;
510                         }
511                   ++it;
512                   }
513             }
514       }
515 
chordAveragePitch(const QList<MidiNote> & notes,int beg,int end)516 int chordAveragePitch(const QList<MidiNote> &notes, int beg, int end)
517       {
518       Q_ASSERT_X(!notes.isEmpty(), "MChord::chordAveragePitch", "Empty notes");
519       Q_ASSERT_X(end > 0 && beg >= 0 && end > beg,
520                  "MChord::chordAveragePitch", "Invalid note indexes");
521 
522       int sum = 0;
523       for (int i = beg; i != end; ++i)
524             sum += notes[i].pitch;
525       return qRound(sum * 1.0 / (end - beg));
526       }
527 
chordAveragePitch(const QList<MidiNote> & notes)528 int chordAveragePitch(const QList<MidiNote> &notes)
529       {
530       Q_ASSERT_X(!notes.isEmpty(), "MChord::chordAveragePitch", "Empty notes");
531 
532       return chordAveragePitch(notes, 0, notes.size());
533       }
534 
535 // it's an optimization function: we can don't check chords
536 // with (on time + max chord len) < given time moment
537 // because chord cannot be longer than found max length
538 
findMaxChordLength(const std::multimap<ReducedFraction,MidiChord> & chords)539 ReducedFraction findMaxChordLength(const std::multimap<ReducedFraction, MidiChord> &chords)
540       {
541       ReducedFraction maxChordLength;
542 
543       for (const auto &chord: chords) {
544             const auto offTime = maxNoteOffTime(chord.second.notes);
545             if (offTime - chord.first > maxChordLength)
546                   maxChordLength = offTime - chord.first;
547             }
548       return maxChordLength;
549       }
550 
551 std::vector<std::multimap<ReducedFraction, MidiChord>::const_iterator>
findChordsForTimeRange(int voice,const ReducedFraction & onTime,const ReducedFraction & offTime,const std::multimap<ReducedFraction,MidiChord> & chords,const ReducedFraction & maxChordLength)552 findChordsForTimeRange(
553             int voice,
554             const ReducedFraction &onTime,
555             const ReducedFraction &offTime,
556             const std::multimap<ReducedFraction, MidiChord> &chords,
557             const ReducedFraction &maxChordLength)
558       {
559       std::vector<std::multimap<ReducedFraction, MidiChord>::const_iterator> result;
560 
561       if (chords.empty())
562             return result;
563 
564       auto it = chords.lower_bound(offTime);
565       if (it == chords.begin())
566             return result;
567       --it;
568 
569       while (it->first + maxChordLength > onTime) {
570             const MidiChord &chord = it->second;
571             if (chord.voice == voice) {
572                   const auto chordInterval = std::make_pair(it->first, maxNoteOffTime(chord.notes));
573                   const auto durationInterval = std::make_pair(onTime, offTime);
574 
575                   if (MidiTuplet::haveIntersection(chordInterval, durationInterval))
576                         result.push_back(it);
577                   }
578             if (it == chords.begin())
579                   break;
580             --it;
581             }
582 
583       return result;
584       }
585 
setBarIndexes(std::multimap<ReducedFraction,MidiChord> & chords,const ReducedFraction & basicQuant,const ReducedFraction & lastTick,const TimeSigMap * sigmap)586 void setBarIndexes(
587             std::multimap<ReducedFraction, MidiChord> &chords,
588             const ReducedFraction &basicQuant,
589             const ReducedFraction &lastTick,
590             const TimeSigMap *sigmap)
591       {
592       if (chords.empty())
593             return;
594       auto it = chords.begin();
595       for (int barIndex = 0;; ++barIndex) {       // iterate over all measures by indexes
596             const auto endBarTick = ReducedFraction::fromTicks(sigmap->bar2tick(barIndex + 1, 0));
597             if (endBarTick <= it->first)
598                   continue;
599             for (; it != chords.end(); ++it) {
600                   const auto onTime = Quantize::findQuantizedChordOnTime(*it, basicQuant);
601 #ifdef QT_DEBUG
602                   const auto barStart = ReducedFraction::fromTicks(sigmap->bar2tick(barIndex, 0));
603                   Q_ASSERT_X(!(it->first >= barStart && onTime < barStart),
604                              "MChord::setBarIndexes", "quantized on time cannot be in previous bar");
605 #endif
606                   if (onTime < endBarTick) {
607                         it->second.barIndex = barIndex;
608                         continue;
609                         }
610                   break;
611                   }
612             if (it == chords.end() || endBarTick > lastTick)
613                   break;
614             }
615 
616       Q_ASSERT_X(areBarIndexesSet(chords),
617                  "MChord::setBarIndexes", "Not all bar indexes were set");
618       Q_ASSERT_X(areBarIndexesSuccessive(chords),
619                  "MChord::setBarIndexes", "Bar indexes are not successive");
620       }
621 
622 } // namespace MChord
623 } // namespace Ms
624