Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for MEI export and import of MuseScore IDs #22978

Merged
merged 11 commits into from
Jan 11, 2025
3 changes: 3 additions & 0 deletions src/importexport/mei/imeiconfiguration.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ class IMeiConfiguration : MODULE_EXPORT_INTERFACE

virtual bool meiExportLayout() const = 0;
virtual void setMeiExportLayout(bool value) = 0;

virtual bool meiUseMuseScoreIds() const = 0;
virtual void setMeiUseMuseScoreIds(bool value) = 0;
};
}

Expand Down
12 changes: 12 additions & 0 deletions src/importexport/mei/internal/meiconfiguration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@ static const std::string module_name("iex_mei");

static const Settings::Key MEI_IMPORT_LAYOUT_KEY(module_name, "import/mei/importMeiLayout");
static const Settings::Key MEI_EXPORT_LAYOUT_KEY(module_name, "export/mei/exportMeiLayout");
static const Settings::Key MEI_USE_MUSESCORE_IDS_KEY(module_name, "export/mei/useMuseScoreIds");

void MeiConfiguration::init()
{
settings()->setDefaultValue(MEI_IMPORT_LAYOUT_KEY, Val(true));
settings()->setDefaultValue(MEI_EXPORT_LAYOUT_KEY, Val(true));
settings()->setDefaultValue(MEI_USE_MUSESCORE_IDS_KEY, Val(false));
}

bool MeiConfiguration::meiImportLayout() const
Expand All @@ -57,3 +59,13 @@ void MeiConfiguration::setMeiExportLayout(bool value)
{
settings()->setSharedValue(MEI_EXPORT_LAYOUT_KEY, Val(value));
}

bool MeiConfiguration::meiUseMuseScoreIds() const
{
return settings()->value(MEI_USE_MUSESCORE_IDS_KEY).toBool();
}

void MeiConfiguration::setMeiUseMuseScoreIds(bool value)
{
settings()->setSharedValue(MEI_USE_MUSESCORE_IDS_KEY, Val(value));
}
3 changes: 3 additions & 0 deletions src/importexport/mei/internal/meiconfiguration.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ class MeiConfiguration : public IMeiConfiguration

bool meiExportLayout() const override;
void setMeiExportLayout(bool value) override;

bool meiUseMuseScoreIds() const override;
void setMeiUseMuseScoreIds(bool value) override;
};
}

Expand Down
36 changes: 31 additions & 5 deletions src/importexport/mei/internal/meiexporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
#include "engraving/dom/laissezvib.h"
#include "engraving/dom/lyrics.h"
#include "engraving/dom/marker.h"
#include "engraving/dom/masterscore.h"
#include "engraving/dom/measure.h"
#include "engraving/dom/measurerepeat.h"
#include "engraving/dom/note.h"
Expand Down Expand Up @@ -93,6 +94,8 @@ using namespace mu::engraving;

bool MeiExporter::write(std::string& meiData)
{
const bool useMuseScoreIds = configuration()->meiUseMuseScoreIds();

m_uids = UIDRegister::instance();
m_xmlIDCounter = 0;

Expand Down Expand Up @@ -129,10 +132,24 @@ bool MeiExporter::write(std::string& meiData)
m_mei = meiDoc.append_child("mei");
m_mei.append_attribute("xmlns") = "http://www.music-encoding.org/ns/mei";

// Save xml:id metaTag's as mei@xml:id
String xmlId = m_score->metaTag(u"xml:id");
if (!xmlId.isEmpty()) {
m_mei.append_attribute("xml:id") = xmlId.toStdString().c_str();
// Option to use MuseScore Ids has priority
if (useMuseScoreIds) {
std::stringstream xmlId;
EID eid = m_score->masterScore()->eid();
if (!eid.isValid()) {
eid = m_score->masterScore()->assignNewEID();
}
String eidStr = String::fromStdString(eid.toStdString().c_str());
xmlId << "mscore-" << eidStr.replace('/', '.').replace('+', '-').toStdString();
m_mei.append_attribute("xml:id") = xmlId.str().c_str();
lpugin marked this conversation as resolved.
Show resolved Hide resolved
}
// Otherwise check if we have a metaTag
else {
// Save xml:id metaTag's as mei@xml:id
String xmlId = m_score->metaTag(u"xml:id");
if (!xmlId.isEmpty()) {
m_mei.append_attribute("xml:id") = xmlId.toStdString().c_str();
}
}

libmei::AttConverter converter;
Expand Down Expand Up @@ -2539,7 +2556,16 @@ std::string MeiExporter::generateHashID()

std::string MeiExporter::getXmlIdFor(const EngravingItem* item, const char c)
{
if (m_uids->hasUid(item)) {
const bool useMuseScoreIds = configuration()->meiUseMuseScoreIds();

if (useMuseScoreIds) {
EID eid = item->eid();
if (!eid.isValid()) {
eid = item->assignNewEID();
}
String eidStr = String::fromStdString(eid.toStdString().c_str());
return "mscore-" + eidStr.replace('/', '.').replace('+', '-').toStdString();
} else if (m_uids->hasUid(item)) {
return m_uids->uid(item);
}

Expand Down
90 changes: 66 additions & 24 deletions src/importexport/mei/internal/meiimporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ bool MeiImporter::read(const muse::io::path_t& path)
{
m_uids = UIDRegister::instance();
m_uids->clear();
m_hasMuseScoreIds = false;

m_lastMeasure = nullptr;
m_tremoloId.clear();
Expand Down Expand Up @@ -134,12 +135,30 @@ bool MeiImporter::read(const muse::io::path_t& path)

success = success && this->readMeiHead(root);

success = success && this->readScore(root);

pugi::xml_attribute xmlId = root.attribute("xml:id");
bool hasRootXmlId = false;
if (xmlId && !String(xmlId.value()).empty()) {
m_score->setMetaTag(u"xml:id", String(xmlId.value()));
// Do not keep a xml:id map when having a xml:id seed.
hasRootXmlId = true;
String xmlIdStr = String(xmlId.value());
if (xmlIdStr.startsWith(u"mscore-")) {
// Keep a global flag since we are going to read them only if mei@xml:id is given with mscore EID
m_hasMuseScoreIds = true;
String valStr = xmlIdStr.remove(u"mscore-").replace('.', '/').replace('-', '+');
// The mei@xml:id store the score EID
EID eid = EID::fromStdString(valStr.toStdString());
if (eid.isValid()) {
m_score->setEID(eid);
}
} else {
// Keep it as a seed
m_score->setMetaTag(u"xml:id", xmlIdStr);
}
}

success = success && this->readScore(root);

if (hasRootXmlId) {
// Do not keep a xml:id map when having a xml:id seed or MscoreIds
m_uids->clear();
}

Expand Down Expand Up @@ -260,7 +279,11 @@ ChordRest* MeiImporter::addChordRest(pugi::xml_node node, Measure* measure, int
} else {
chordRest = Factory::createChord(segment);
}
m_uids->reg(chordRest, meiElement.m_xmlId);

// Do not use single note xml:id / EID for the ChordRest
if (!dynamic_cast<const libmei::Note*>(&meiElement)) {
this->readXmlId(chordRest, meiElement.m_xmlId);
}

if (m_startIdChordRests.count(meiElement.m_xmlId)) {
m_startIdChordRests[meiElement.m_xmlId] = chordRest;
Expand Down Expand Up @@ -458,7 +481,7 @@ EngravingItem* MeiImporter::addAnnotation(const libmei::Element& meiElement, Mea
} else {
return nullptr;
}
m_uids->reg(item, meiElement.m_xmlId);
this->readXmlId(item, meiElement.m_xmlId);

item->setTrack(chordRest->track());
segment->add(item);
Expand Down Expand Up @@ -504,7 +527,7 @@ Spanner* MeiImporter::addSpanner(const libmei::Element& meiElement, Measure* mea
} else {
return nullptr;
}
m_uids->reg(item, meiElement.m_xmlId);
this->readXmlId(item, meiElement.m_xmlId);

item->setTick(chordRest->tick());
item->setStartElement(chordRest);
Expand Down Expand Up @@ -549,7 +572,7 @@ EngravingItem* MeiImporter::addToChordRest(const libmei::Element& meiElement, Me
} else {
return nullptr;
}
m_uids->reg(item, meiElement.m_xmlId);
this->readXmlId(item, meiElement.m_xmlId);

item->setTrack(chordRest->track());
chordRest->add(item);
Expand Down Expand Up @@ -856,6 +879,23 @@ void MeiImporter::setOrnamentAccid(engraving::Ornament* ornament, const Convert:
}
}

void MeiImporter::readXmlId(engraving::EngravingItem* item, const std::string& meiUID)
{
String xmlIdStr = String::fromStdString(meiUID);
// We have a file that has MuseScore EIDs and one on this element
if (m_hasMuseScoreIds && xmlIdStr.startsWith(u"mscore-")) {
String valStr = xmlIdStr.remove(u"mscore-").replace('.', '/').replace('-', '+');
EID eid = EID::fromStdString(valStr.toStdString());
if (!eid.isValid()) {
Convert::logs.push_back(String("A valid MuseScore ID could not be extracted from '%1'").arg(xmlIdStr));
} else {
item->setEID(eid);
}
} else {
m_uids->reg(item, meiUID);
}
}

//---------------------------------------------------------
// parsing methods
//---------------------------------------------------------
Expand Down Expand Up @@ -1323,7 +1363,7 @@ bool MeiImporter::readEnding(pugi::xml_node endingNode)
} else {
Volta* volta = Factory::createVolta(m_score->dummy());
Convert::endingFromMEI(volta, meiEnding, warning);
m_uids->reg(volta, meiEnding.m_xmlId);
this->readXmlId(volta, meiEnding.m_xmlId);
volta->setTrack(0);
volta->setTrack2(0);
volta->setTick(m_endingStart->tick());
Expand Down Expand Up @@ -1356,7 +1396,7 @@ bool MeiImporter::readMeasure(pugi::xml_node measureNode)
Convert::MeasureStruct measureSt = Convert::measureFromMEI(meiMeasure, warning);

Measure* measure = Factory::createMeasure(m_score->dummy()->system());
m_uids->reg(measure, meiMeasure.m_xmlId);
this->readXmlId(measure, meiMeasure.m_xmlId);
measure->setTick(m_ticks);
measure->setTimesig(m_currentTimeSig);

Expand Down Expand Up @@ -1730,7 +1770,7 @@ bool MeiImporter::readChord(pugi::xml_node chordNode, Measure* measure, int trac
NOT_SUPPORTED;
} else {
TremoloSingleChord* tremolo = Factory::createTremoloSingleChord(chord);
m_uids->reg(tremolo, m_tremoloId);
this->readXmlId(tremolo, m_tremoloId);
tremolo->setTremoloType(ttype);
chord->add(tremolo);
}
Expand Down Expand Up @@ -1761,7 +1801,7 @@ bool MeiImporter::readClef(pugi::xml_node clefNode, Measure* measure, int track,
Segment* segment = measure->getSegment(SegmentType::Clef, ticks + measure->tick());
Clef* clef = Factory::createClef(segment);
Convert::colorFromMEI(clef, meiClef);
m_uids->reg(clef, meiClef.m_xmlId);
this->readXmlId(clef, meiClef.m_xmlId);
clef->setClefType(ClefTypeList(Convert::clefFromMEI(meiClef, warning)));
if (warning) {
this->addLog("clef", clefNode);
Expand Down Expand Up @@ -1828,7 +1868,7 @@ bool MeiImporter::readMRest(pugi::xml_node mRestNode, Measure* measure, int trac
Segment* segment = measure->getSegment(SegmentType::ChordRest, ticks + measure->tick());
Rest* rest = Factory::createRest(segment, TDuration(DurationType::V_MEASURE));
Convert::colorFromMEI(rest, meiMRest);
m_uids->reg(rest, meiMRest.m_xmlId);
this->readXmlId(rest, meiMRest.m_xmlId);
rest->setTicks(m_currentTimeSig);
rest->setDurationType(DurationType::V_MEASURE);
rest->setTrack(track);
Expand Down Expand Up @@ -1868,7 +1908,7 @@ bool MeiImporter::readMRpt(pugi::xml_node mRptNode, Measure* measure, int track,
Segment* segment = measure->getSegment(SegmentType::ChordRest, ticks + measure->tick());
MeasureRepeat* measureRepeat = Factory::createMeasureRepeat(segment);
Convert::colorFromMEI(measureRepeat, meiMRpt);
m_uids->reg(measureRepeat, meiMRpt.m_xmlId);
this->readXmlId(measureRepeat, meiMRpt.m_xmlId);
measureRepeat->setTrack(track);
measureRepeat->setTicks(measure->ticks());
measureRepeat->setNumMeasures(1);
Expand Down Expand Up @@ -1904,6 +1944,8 @@ bool MeiImporter::readNote(pugi::xml_node noteNode, Measure* measure, int track,
} else {
// Support for non MEI-Basic accid and accid.ges encoded in <note> - this is not academic...
meiAccid.Read(noteNode);
// Remove the xml:id read from the note in that case
meiAccid.m_xmlId = "";
}

Staff* staff = m_score->staff(track2staff(track));
Expand All @@ -1926,7 +1968,7 @@ bool MeiImporter::readNote(pugi::xml_node noteNode, Measure* measure, int track,
NOT_SUPPORTED;
} else {
TremoloSingleChord* tremolo = Factory::createTremoloSingleChord(chord);
m_uids->reg(tremolo, m_tremoloId);
this->readXmlId(tremolo, m_tremoloId);
tremolo->setTremoloType(ttype);
chord->add(tremolo);
}
Expand All @@ -1935,7 +1977,7 @@ bool MeiImporter::readNote(pugi::xml_node noteNode, Measure* measure, int track,

Note* note = Factory::createNote(chord);
Convert::colorFromMEI(note, meiNote);
m_uids->reg(note, meiNote.m_xmlId);
this->readXmlId(note, meiNote.m_xmlId);

// If there is a reference to the note in the MEI, add it the maps (e.g., for ties)
if (m_startIdChordRests.count(meiNote.m_xmlId)) {
Expand All @@ -1950,7 +1992,7 @@ bool MeiImporter::readNote(pugi::xml_node noteNode, Measure* measure, int track,

Accidental* accid = Factory::createAccidental(note);
Convert::colorFromMEI(accid, meiAccid);
m_uids->reg(accid, meiAccid.m_xmlId);
this->readXmlId(accid, meiAccid.m_xmlId);
accid->setAccidentalType(pitchSt.accidType);
//accid->setBracket(AccidentalBracket::BRACKET); // Not supported in MEI-Basic
accid->setRole(pitchSt.accidRole);
Expand Down Expand Up @@ -2046,7 +2088,7 @@ bool MeiImporter::readTuplet(pugi::xml_node tupletNode, Measure* measure, int tr
meiTuplet.Read(tupletNode);

m_tuplet = Factory::createTuplet(measure);
m_uids->reg(m_tuplet, meiTuplet.m_xmlId);
this->readXmlId(m_tuplet, meiTuplet.m_xmlId);
Convert::tupletFromMEI(m_tuplet, meiTuplet, warning);
if (warning) {
this->addLog("tuplet", tupletNode);
Expand Down Expand Up @@ -2123,7 +2165,7 @@ bool MeiImporter::readVerse(pugi::xml_node verseNode, Chord* chord)
}

Lyrics* lyrics = Factory::createLyrics(chord);
m_uids->reg(lyrics, meiVerse.m_xmlId);
this->readXmlId(lyrics, meiVerse.m_xmlId);
Convert::colorFromMEI(lyrics, meiVerse);

bool success = true;
Expand Down Expand Up @@ -2402,7 +2444,7 @@ bool MeiImporter::readF(pugi::xml_node fNode, engraving::FiguredBass* figuredBas

const int line = static_cast<int>(figuredBass->itemsCount());
FiguredBassItem* figuredBassItem = figuredBass->createItem(line);
m_uids->reg(figuredBassItem, meiF.m_xmlId);
this->readXmlId(figuredBassItem, meiF.m_xmlId);
figuredBassItem->setTrack(figuredBass->track());
figuredBassItem->setParent(figuredBass);

Expand Down Expand Up @@ -2444,7 +2486,7 @@ bool MeiImporter::readFb(pugi::xml_node harmNode, Measure* measure)
return true;
}
// Needs to be registered by hand because we pass meiHarm to MeiImporter::addAnnotation
m_uids->reg(figuredBass, meiFb.m_xmlId);
this->readXmlId(figuredBass, meiFb.m_xmlId);

Convert::fbFromMEI(figuredBass, meiHarm, meiFb, warning);

Expand Down Expand Up @@ -2478,7 +2520,7 @@ bool MeiImporter::readFermata(pugi::xml_node fermataNode, Measure* measure)
if (fermataPos == measure->ticks()) {
Segment* segment = measure->getSegment(SegmentType::EndBarLine, measure->tick() + measure->ticks());
fermata = Factory::createFermata(segment);
m_uids->reg(fermata, meiFermata.m_xmlId);
this->readXmlId(fermata, meiFermata.m_xmlId);
const int staffIdx
= (meiFermata.HasStaff() && meiFermata.GetStaff().size() > 0) ? this->getStaffIndex(meiFermata.GetStaff().at(0)) : 0;
fermata->setTrack(staffIdx * VOICES);
Expand Down Expand Up @@ -2791,7 +2833,7 @@ bool MeiImporter::readRepeatMark(pugi::xml_node repeatMarkNode, Measure* measure
item = Factory::createMarker(measure);
Convert::markerFromMEI(dynamic_cast<Marker*>(item), meiRepeatMark, warning);
}
m_uids->reg(item, meiRepeatMark.m_xmlId);
this->readXmlId(item, meiRepeatMark.m_xmlId);
item->setTrack(0);
measure->add(item);

Expand Down Expand Up @@ -2874,7 +2916,7 @@ bool MeiImporter::readTie(pugi::xml_node tieNode, Measure* measure)
}

Tie* tie = new Tie(m_score->dummy());
m_uids->reg(tie, meiTie.m_xmlId);
this->readXmlId(tie, meiTie.m_xmlId);
startNote->setTieFor(tie);
tie->setStartNote(startNote);
tie->setTrack(startNote->track());
Expand Down
6 changes: 6 additions & 0 deletions src/importexport/mei/internal/meiimporter.h
Original file line number Diff line number Diff line change
Expand Up @@ -198,12 +198,18 @@ class MeiImporter
void extendLyrics();
void setOrnamentAccid(engraving::Ornament* ornament, const Convert::OrnamStruct& ornamSt);

/** Read the xmlId and process it appropriately */
void readXmlId(engraving::EngravingItem* item, const std::string& meiUID);

/** The Score pointer */
engraving::Score* m_score = nullptr;

/** The uid register */
UIDRegister* m_uids;

/** A flag indicating the file has MuseScore EIDs as xml:ids */
bool m_hasMuseScoreIds;

engraving::Fraction m_ticks;
int m_lastMeasureN;
engraving::Measure* m_lastMeasure;
Expand Down
Loading
Loading