Skip to content

Commit 0d86401

Browse files
committed
Rewrite the xml harmonies export algorithm
fix #303623
1 parent a5c92e2 commit 0d86401

File tree

4 files changed

+1895
-65
lines changed

4 files changed

+1895
-65
lines changed

importexport/musicxml/exportxml.cpp

Lines changed: 89 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -4544,27 +4544,6 @@ static void measureStyle(XmlWriter& xml, Attributes& attr, const Measure* const
45444544
}
45454545
}
45464546

4547-
//---------------------------------------------------------
4548-
// findFretDiagram
4549-
//---------------------------------------------------------
4550-
4551-
static const FretDiagram* findFretDiagram(int strack, int etrack, int track, Segment* seg)
4552-
{
4553-
if (seg->segmentType() == SegmentType::ChordRest) {
4554-
for (const Element* e : seg->annotations()) {
4555-
4556-
int wtrack = -1; // track to write annotation
4557-
4558-
if (strack <= e->track() && e->track() < etrack)
4559-
wtrack = findTrackForAnnotations(e->track(), seg);
4560-
4561-
if (track == wtrack && e->type() == ElementType::FRET_DIAGRAM)
4562-
return static_cast<const FretDiagram*>(e);
4563-
}
4564-
}
4565-
return 0;
4566-
}
4567-
45684547
//---------------------------------------------------------
45694548
// commonAnnotations
45704549
//---------------------------------------------------------
@@ -4601,47 +4580,103 @@ static bool commonAnnotations(ExportMusicXml* exp, const Element* e, int sstaff)
46014580
// annotations
46024581
//---------------------------------------------------------
46034582

4604-
/*
4605-
* Write annotations that are attached to chords or rests
4606-
*/
4607-
4608-
// In MuseScore, Element::FRET_DIAGRAM and Element::HARMONY are separate annotations,
4609-
// in MusicXML they are combined in the harmony element. This means they have to be matched.
4610-
// TODO: replace/repair current algorithm (which can only handle one FRET_DIAGRAM and one HARMONY)
4583+
// Only handle common annotations, others are handled elsewhere
46114584

46124585
static void annotations(ExportMusicXml* exp, int strack, int etrack, int track, int sstaff, Segment* seg)
46134586
{
4614-
if (seg->segmentType() == SegmentType::ChordRest) {
4587+
for (const Element* e : seg->annotations()) {
46154588

4616-
const FretDiagram* fd = findFretDiagram(strack, etrack, track, seg);
4617-
// if (fd) qDebug("annotations seg %p found fretboard diagram %p", seg, fd);
4589+
int wtrack = -1; // track to write annotation
46184590

4619-
for (const Element* e : seg->annotations()) {
4591+
if (strack <= e->track() && e->track() < etrack)
4592+
wtrack = findTrackForAnnotations(e->track(), seg);
46204593

4621-
int wtrack = -1; // track to write annotation
4594+
if (track == wtrack) {
4595+
if (commonAnnotations(exp, e, sstaff))
4596+
; // already handled
4597+
}
4598+
}
4599+
}
46224600

4623-
if (strack <= e->track() && e->track() < etrack)
4624-
wtrack = findTrackForAnnotations(e->track(), seg);
4601+
//---------------------------------------------------------
4602+
// harmonies
4603+
//---------------------------------------------------------
46254604

4626-
if (track == wtrack) {
4627-
if (commonAnnotations(exp, e, sstaff))
4628-
; // already handled
4629-
else if (e->isHarmony()) {
4630-
// qDebug("annotations seg %p found harmony %p", seg, e);
4631-
exp->harmony(toHarmony(e), fd);
4632-
fd = nullptr; // make sure to write only once ...
4633-
}
4634-
else if (e->isFermata() || e->isFiguredBass() || e->isFretDiagram() || e->isJump())
4635-
; // handled separately by chordAttributes(), figuredBass(), findFretDiagram() or ignored
4636-
else
4637-
qDebug("direction type %s at tick %d not implemented",
4638-
Element::name(e->type()), seg->tick().ticks());
4639-
}
4605+
/*
4606+
* Helper method to export harmonies and chord diagrams for a single segment.
4607+
*/
4608+
4609+
static void segmentHarmonies(ExportMusicXml* exp, int track, Segment* seg, int offset)
4610+
{
4611+
const std::vector<Element*> diagrams = seg->findAnnotations(ElementType::FRET_DIAGRAM, track, track);
4612+
std::vector<Element*> harmonies = seg->findAnnotations(ElementType::HARMONY, track, track);
4613+
4614+
for (const Element* e : diagrams) {
4615+
const FretDiagram* diagram = toFretDiagram(e);
4616+
const Harmony* harmony = diagram->harmony();
4617+
if (harmony) {
4618+
exp->harmony(harmony, diagram, offset);
4619+
}
4620+
else if (! harmonies.empty()) {
4621+
const Element* defaultHarmony = harmonies.back();
4622+
exp->harmony(toHarmony(defaultHarmony), diagram, offset);
4623+
harmonies.pop_back();
4624+
}
4625+
else {
4626+
// Found a fret diagram with no harmony, ignore
4627+
qDebug("segmentHarmonies() seg %p found fretboard diagram %p w/o harmony: cannot write", seg, diagram);
46404628
}
4641-
if (fd)
4642-
// found fd but no harmony, cannot write (MusicXML would be invalid)
4643-
qDebug("seg %p found fretboard diagram %p w/o harmony: cannot write",
4644-
seg, fd);
4629+
}
4630+
4631+
for (const Element* e: harmonies)
4632+
exp->harmony(toHarmony(e), 0, offset);
4633+
}
4634+
4635+
/*
4636+
* Write harmonies and fret diagrams that are attached to chords or rests.
4637+
*
4638+
* There are fondamental differences between the ways Musescore and MusicXML handle harmonies (Chord symbols)
4639+
* and fretboard diagrams.
4640+
*
4641+
* In MuseScore, the Harmony element is now a child of FretboardDiagram BUT in previous versions,
4642+
* both elements were independant siblings so we have to handle both cases.
4643+
* In MusicXML, fretboard diagram is always contained in a harmony element.
4644+
*
4645+
* In MuseScore, Harmony elements are not always linked to notes, and each Harmony will be contained
4646+
* in a `ChordRest` Segment.
4647+
* In MusicXML, those successive Harmony elements must be exported before the note with different offsets.
4648+
*
4649+
* Edge cases that we simply cannot handle:
4650+
* - as of MusicXML 3.1, there is no way to represent a diagram without an associated chord symbol,
4651+
* so when we encounter such an object in MuseScore, we simply cannot export it.
4652+
* - If a ChordRest segment contans a FretboardDiagram with no harmonies and several different Harmony siblings,
4653+
* we simply have to pick a random one to export.
4654+
*/
4655+
4656+
static void harmonies(ExportMusicXml* exp, int track, Segment* seg, int divisions)
4657+
{
4658+
int offset = 0;
4659+
segmentHarmonies(exp, track, seg, offset);
4660+
4661+
// Edge case: find remaining `harmony` elements.
4662+
// Suppose you have one single whole note in the measure but several chord symbols.
4663+
// In MuseScore, each `Harmony` object will be stored in a `ChordRest` Segment that contains
4664+
// no other Chords.
4665+
// But in MusicXML, you are supposed to output all `harmony` elements before the first `note`,
4666+
// with different `offset` parameters.
4667+
//
4668+
// That's why we need to explore the remaining segments to find
4669+
// `Harmony` and `FretDiagram` elements in Segments without Chords and output them now.
4670+
for (auto seg1 = seg->next(); seg1; seg1 = seg1->next()) {
4671+
if (!seg1->isChordRestType())
4672+
continue;
4673+
4674+
const auto el1 = seg1->element(track);
4675+
if (el1) // found a ChordRest, next harmony will be attached to this one
4676+
break;
4677+
4678+
offset = (seg1->tick() - seg->tick()).ticks() / divisions;
4679+
segmentHarmonies(exp, track, seg1, offset);
46454680
}
46464681
}
46474682

@@ -5886,19 +5921,8 @@ void ExportMusicXml::writeMeasureTracks(const Measure* const m,
58865921
}
58875922
tboxesBelowWritten = true;
58885923
}
5924+
harmonies(this, st, seg, div);
58895925
annotations(this, strack, etrack, st, sstaff, seg);
5890-
// look for more harmony
5891-
for (auto seg1 = seg->next(); seg1; seg1 = seg1->next()) {
5892-
if (seg1->isChordRestType()) {
5893-
const auto el1 = seg1->element(st);
5894-
if (el1) // found a ChordRest, next harmony will be attach to this one
5895-
break;
5896-
for (auto annot : seg1->annotations()) {
5897-
if (annot->isHarmony() && annot->track() == st)
5898-
harmony(toHarmony(annot), 0, (seg1->tick() - seg->tick()).ticks() / div);
5899-
}
5900-
}
5901-
}
59025926
figuredBass(_xml, strack, etrack, st, static_cast<const ChordRest*>(el), fbMap, div);
59035927
spannerStart(this, strack, etrack, st, sstaff, seg);
59045928
}

0 commit comments

Comments
 (0)