Skip to content

Commit 36dc487

Browse files
thibaultJojo-Schmitz
authored andcommitted
Fix #303623, GH#16902: Fix multiple issues with chord diagrams in MusicXML export
Port/rebase of @thibault's musescore#5958 For some reason that PR's test files (testHarmony6.mscx, testHarmony6_ref.xml) were already in master (as testHarmony7.mscx, testHarmony7_ref.xml), but not used in the unit tests.
1 parent e5e6582 commit 36dc487

File tree

2 files changed

+95
-70
lines changed

2 files changed

+95
-70
lines changed

src/importexport/musicxml/internal/musicxml/exportxml.cpp

Lines changed: 92 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -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

63436315
static 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", seg, diagram);
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

src/importexport/musicxml/tests/musicxml_tests.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,9 @@ TEST_F(Musicxml_Tests, harmony5) {
659659
TEST_F(Musicxml_Tests, harmony6) {
660660
mxmlMscxExportTestRef("testHarmony6");
661661
}
662+
TEST_F(Musicxml_Tests, harmony7) {
663+
mxmlMscxExportTestRef("testHarmony7");
664+
}
662665
TEST_F(Musicxml_Tests, harmony8) {
663666
mxmlIoTest("testHarmony8");
664667
}

0 commit comments

Comments
 (0)