@@ -383,7 +383,7 @@ class ExportMusicXml
383383    void  symbol (Symbol const * const  sym, staff_idx_t  staff);
384384    void  systemText (StaffTextBase const * const  text, staff_idx_t  staff);
385385    void  tempoText (TempoText const * const  text, staff_idx_t  staff);
386-     void  harmony (Harmony const * const , FretDiagram const * const  fd, int  offset = 0 );
386+     void  harmony (Harmony const * const , FretDiagram const * const  fd, size_t  offset = 0 );
387387    Score* score () const  { return  m_score; }
388388    double  getTenthsFromInches (double ) const ;
389389    double  getTenthsFromDots (double ) const ;
@@ -6263,28 +6263,6 @@ static void measureStyle(XmlWriter& xml, Attributes& attr, const Measure* const
62636263    }
62646264}
62656265
6266- // ---------------------------------------------------------
6267- //   findFretDiagram
6268- // ---------------------------------------------------------
6269- 
6270- static  const  FretDiagram* findFretDiagram (track_idx_t  strack, track_idx_t  etrack, track_idx_t  track, Segment* seg)
6271- {
6272-     if  (seg->segmentType () == SegmentType::ChordRest) {
6273-         for  (const  EngravingItem* e : seg->annotations ()) {
6274-             track_idx_t  wtrack = muse::nidx;       //  track to write annotation
6275- 
6276-             if  (strack <= e->track () && e->track () < etrack) {
6277-                 wtrack = findTrackForAnnotations (e->track (), seg);
6278-             }
6279- 
6280-             if  (track == wtrack && e->type () == ElementType::FRET_DIAGRAM) {
6281-                 return  static_cast <const  FretDiagram*>(e);
6282-             }
6283-         }
6284-     }
6285-     return  0 ;
6286- }
6287- 
62886266// ---------------------------------------------------------
62896267//   commonAnnotations
62906268// ---------------------------------------------------------
@@ -6332,47 +6310,104 @@ static bool commonAnnotations(ExportMusicXml* exp, const EngravingItem* e, staff
63326310//   annotations
63336311// ---------------------------------------------------------
63346312
6335- /* 
6336-  * Write annotations that are attached to chords or rests 
6337-  */  
6338- 
6339- //  In MuseScore, EngravingItem::FRET_DIAGRAM and EngravingItem::HARMONY are separate annotations,
6340- //  in MusicXML they are combined in the harmony element. This means they have to be matched.
6341- //  TODO: replace/repair current algorithm (which can only handle one FRET_DIAGRAM and one HARMONY)
6313+ //  Only handle common annotations, others are handled elsewhere
63426314
63436315static  void  annotations (ExportMusicXml* exp, track_idx_t  strack, track_idx_t  etrack, track_idx_t  track, staff_idx_t  sstaff, Segment* seg)
63446316{
6345-     if  (seg->segmentType () == SegmentType::ChordRest) {
6346-         const  FretDiagram* fd = findFretDiagram (strack, etrack, track, seg);
6347-         //  if (fd) LOGD("annotations seg %p found fretboard diagram %p", seg, fd);
6317+     for  (const  EngravingItem* e : seg->annotations ()) {
6318+         track_idx_t  wtrack = muse::nidx;       //  track to write annotation
63486319
6349-         for  (const  EngravingItem* e : seg->annotations ()) {
6350-             track_idx_t  wtrack = muse::nidx;       //  track to write annotation
6320+         if  (strack <= e->track () && e->track () < etrack) {
6321+             wtrack = findTrackForAnnotations (e->track (), seg);
6322+         }
63516323
6352-             if  (strack <= e->track () && e->track () < etrack) {
6353-                 wtrack = findTrackForAnnotations (e->track (), seg);
6324+         if  (track == wtrack) {
6325+             if  (commonAnnotations (exp, e, sstaff)) {
6326+                 //  already handled
63546327            }
6328+         }
6329+     }
6330+ }
63556331
6356-             if  (track == wtrack) {
6357-                 if  (commonAnnotations (exp, e, sstaff)) {
6358-                     //  already handled
6359-                 } else  if  (e->isHarmony ()) {
6360-                     //  LOGD("annotations seg %p found harmony %p", seg, e);
6361-                     exp->harmony (toHarmony (e), fd);
6362-                     fd = nullptr ;           //  make sure to write only once ...
6363-                 } else  if  (e->isFermata () || e->isFiguredBass () || e->isFretDiagram () || e->isJump ()) {
6364-                     //  handled separately by chordAttributes(), figuredBass(), findFretDiagram() or ignored
6365-                 } else  {
6366-                     LOGD (" direction type %s at tick %d not implemented" 
6367-                          e->typeName (), seg->tick ().ticks ());
6368-                 }
6369-             }
6332+ // ---------------------------------------------------------
6333+ //   harmonies
6334+ // ---------------------------------------------------------
6335+ 
6336+ /* 
6337+  * Helper method to export harmonies and chord diagrams for a single segment. 
6338+  */  
6339+ 
6340+ static  void  segmentHarmonies (ExportMusicXml* exp, track_idx_t  track, Segment* seg, size_t  offset)
6341+ {
6342+     const  std::vector<EngravingItem*> diagrams = seg->findAnnotations (ElementType::FRET_DIAGRAM, track, track);
6343+     std::vector<EngravingItem*> harmonies = seg->findAnnotations (ElementType::HARMONY, track, track);
6344+ 
6345+     for  (const  EngravingItem* d : diagrams) {
6346+         const  FretDiagram* diagram = toFretDiagram (d);
6347+         const  Harmony* harmony = diagram->harmony ();
6348+         if  (harmony) {
6349+             exp->harmony (harmony, diagram, offset);
6350+         } else  if  (!harmonies.empty ()) {
6351+             const  EngravingItem* defaultHarmony = harmonies.back ();
6352+             exp->harmony (toHarmony (defaultHarmony), diagram, offset);
6353+             harmonies.pop_back ();
6354+         } else  {
6355+             //  Found a fret diagram with no harmony, ignore
6356+             LOGD (" segmentHarmonies() seg %p found fretboard diagram %p w/o harmony: cannot write" 
63706357        }
6371-         if  (fd) {
6372-             //  found fd but no harmony, cannot write (MusicXML would be invalid)
6373-             LOGD (" seg %p found fretboard diagram %p w/o harmony: cannot write" 
6374-                  seg, fd);
6358+ 
6359+         for  (const  EngravingItem* h : harmonies) {
6360+             exp->harmony (toHarmony (h), 0 , offset);
6361+         }
6362+     }
6363+ }
6364+ 
6365+ /* 
6366+  * Write harmonies and fret diagrams that are attached to chords or rests. 
6367+  * 
6368+  * There are fondamental differences between the ways Musescore and MusicXML handle harmonies (Chord symbols) 
6369+  * and fretboard diagrams. 
6370+  * 
6371+  * In MuseScore, the Harmony element is now a child of FretboardDiagram BUT in previous versions, 
6372+  * both elements were independant siblings so we have to handle both cases. 
6373+  * In MusicXML, fretboard diagram is always contained in a harmony element. 
6374+  * 
6375+  * In MuseScore, Harmony elements are not always linked to notes, and each Harmony will be contained 
6376+  * in a `ChordRest` Segment. 
6377+  * In MusicXML, those successive Harmony elements must be exported before the note with different offsets. 
6378+  * 
6379+  * Edge cases that we simply cannot handle: 
6380+  *  - as of MusicXML 3.1, there is no way to represent a diagram without an associated chord symbol, 
6381+  * so when we encounter such an object in MuseScore, we simply cannot export it. 
6382+  *  - If a ChordRest segment contans a FretboardDiagram with no harmonies and several different Harmony siblings, 
6383+  * we simply have to pick a random one to export. 
6384+  */  
6385+ 
6386+ static  void  harmonies (ExportMusicXml* exp, track_idx_t  track, Segment* seg, int  divisions)
6387+ {
6388+     size_t  offset = 0 ;
6389+     segmentHarmonies (exp, track, seg, offset);
6390+ 
6391+     //  Edge case: find remaining `harmony` elements.
6392+     //  Suppose you have one single whole note in the measure but several chord symbols.
6393+     //  In MuseScore, each `Harmony` object will be stored in a `ChordRest` Segment that contains
6394+     //  no other Chords.
6395+     //  But in MusicXML, you are supposed to output all `harmony` elements before the first `note`,
6396+     //  with different `offset` parameters.
6397+     // 
6398+     //  That's why we need to explore the remaining segments to find
6399+     //  `Harmony` and `FretDiagram` elements in Segments without Chords and output them now.
6400+     for  (auto  seg1 = seg->next (); seg1; seg1 = seg1->next ()) {
6401+         if  (!seg1->isChordRestType ()) {
6402+             continue ;
63756403        }
6404+ 
6405+         const  auto  el1 = seg1->element (track);
6406+         if  (el1) { //  found a ChordRest, next harmony will be attached to this one
6407+             break ;
6408+         }
6409+         offset = (seg1->tick () - seg->tick ()).ticks () / divisions;
6410+         segmentHarmonies (exp, track, seg1, offset);
63766411    }
63776412}
63786413
@@ -7875,21 +7910,8 @@ void ExportMusicXml::writeMeasureTracks(const Measure* const m,
78757910                    }
78767911                    m_tboxesBelowWritten = true ;
78777912                }
7913+                 harmonies (this , track, seg, m_div);
78787914                annotations (this , strack, etrack, track, partRelStaffNo, seg);
7879-                 //  look for more harmony
7880-                 for  (auto  seg1 = seg->next (); seg1; seg1 = seg1->next ()) {
7881-                     if  (seg1->isChordRestType ()) {
7882-                         const  auto  el1 = seg1->element (track);
7883-                         if  (el1) {           //  found a ChordRest, next harmony will be attach to this one
7884-                             break ;
7885-                         }
7886-                         for  (auto  annot : seg1->annotations ()) {
7887-                             if  (annot->isHarmony () && annot->track () == track) {
7888-                                 harmony (toHarmony (annot), 0 , (seg1->tick () - seg->tick ()).ticks () / m_div);
7889-                             }
7890-                         }
7891-                     }
7892-                 }
78937915                figuredBass (m_xml, strack, etrack, track, static_cast <const  ChordRest*>(el), fbMap, m_div);
78947916                spannerStart (this , strack, etrack, track, partRelStaffNo, seg);
78957917            }
@@ -8419,7 +8441,7 @@ static void writeMusicXML(const FretDiagram* item, XmlWriter& xml)
84198441//    harmony
84208442// ---------------------------------------------------------
84218443
8422- void  ExportMusicXml::harmony (Harmony const * const  h, FretDiagram const * const  fd, int  offset)
8444+ void  ExportMusicXml::harmony (Harmony const * const  h, FretDiagram const * const  fd, size_t  offset)
84238445{
84248446    //  this code was probably in place to allow chord symbols shifted *right* to export with offset
84258447    //  since this was at once time the only way to get a chord to appear over beat 3 in an empty 4/4 measure
0 commit comments