Skip to content

Commit

Permalink
Merge pull request #22978 from rism-digital/develop-mei-mscore-ids
Browse files Browse the repository at this point in the history
Add support for MEI export and import of MuseScore IDs
  • Loading branch information
cbjeukendrup authored Jan 11, 2025
2 parents 262af63 + 5dd039b commit 4352e7e
Show file tree
Hide file tree
Showing 9 changed files with 155 additions and 29 deletions.
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();
}
// 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

0 comments on commit 4352e7e

Please sign in to comment.