diff --git a/src/importexport/musicxml/imusicxmlconfiguration.h b/src/importexport/musicxml/imusicxmlconfiguration.h index 106aabe314039..cd4df3cecb515 100644 --- a/src/importexport/musicxml/imusicxmlconfiguration.h +++ b/src/importexport/musicxml/imusicxmlconfiguration.h @@ -19,8 +19,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef MU_IMPORTEXPORT_IMUSICXMLCONFIGURATION_H -#define MU_IMPORTEXPORT_IMUSICXMLCONFIGURATION_H +#pragma once #include "modularity/imoduleinterface.h" #include "io/path.h" @@ -67,5 +66,3 @@ class IMusicXmlConfiguration : MODULE_EXPORT_INTERFACE virtual void setInferTextTypeOverride(std::optional value) = 0; }; } - -#endif // MU_IMPORTEXPORT_IMUSICXMLCONFIGURATION_H diff --git a/src/importexport/musicxml/internal/musicxml/exportxml.cpp b/src/importexport/musicxml/internal/musicxml/exportxml.cpp index 00fab5c16acdd..eb5ea6a487bed 100644 --- a/src/importexport/musicxml/internal/musicxml/exportxml.cpp +++ b/src/importexport/musicxml/internal/musicxml/exportxml.cpp @@ -124,9 +124,9 @@ #include "engraving/dom/volta.h" #include "engraving/dom/whammybar.h" -#include "musicxml.h" #include "musicxmlfonthandler.h" #include "musicxmlsupport.h" +#include "musicxmltypes.h" #include "modularity/ioc.h" #include "../../imusicxmlconfiguration.h" @@ -157,6 +157,8 @@ namespace mu::engraving { #define clefDebug(...) {} #endif +constexpr int MAX_PART_GROUPS = 8; + //--------------------------------------------------------- // typedefs //--------------------------------------------------------- diff --git a/src/importexport/musicxml/internal/musicxml/exportxml.h b/src/importexport/musicxml/internal/musicxml/exportxml.h index d6d488ae71fa6..1c29d85a83994 100644 --- a/src/importexport/musicxml/internal/musicxml/exportxml.h +++ b/src/importexport/musicxml/internal/musicxml/exportxml.h @@ -20,18 +20,14 @@ * along with this program. If not, see . */ -#ifndef MU_IMPORTEXPORT_EXPORTXML_H -#define MU_IMPORTEXPORT_EXPORTXML_H +#pragma once #include "io/iodevice.h" #include "global/types/string.h" namespace mu::engraving { class Score; - bool saveMxl(Score*, muse::io::IODevice*); bool saveXml(Score*, muse::io::IODevice*); bool saveXml(Score*, const muse::String&); } - -#endif // MU_IMPORTEXPORT_EXPORTXML_H diff --git a/src/importexport/musicxml/internal/musicxml/importmxml.h b/src/importexport/musicxml/internal/musicxml/importmxml.h index f7813d4c1be56..7a476d085ec48 100644 --- a/src/importexport/musicxml/internal/musicxml/importmxml.h +++ b/src/importexport/musicxml/internal/musicxml/importmxml.h @@ -20,8 +20,7 @@ * along with this program. If not, see . */ -#ifndef __IMPORTMXML_H__ -#define __IMPORTMXML_H__ +#pragma once #include "global/types/string.h" #include "engravingerrors.h" @@ -31,5 +30,3 @@ class Score; Err importMusicXMLfromBuffer(Score* score, const muse::String&, const muse::ByteArray& data); } - -#endif diff --git a/src/importexport/musicxml/internal/musicxml/importmxmllogger.h b/src/importexport/musicxml/internal/musicxml/importmxmllogger.h index 71ba0885b57f2..fbca301eae13d 100644 --- a/src/importexport/musicxml/internal/musicxml/importmxmllogger.h +++ b/src/importexport/musicxml/internal/musicxml/importmxmllogger.h @@ -20,8 +20,7 @@ * along with this program. If not, see . */ -#ifndef __IMPORTMXMLLOGGER_H__ -#define __IMPORTMXMLLOGGER_H__ +#pragma once #include "global/types/string.h" @@ -45,5 +44,3 @@ class MxmlLogger Level m_level = Level::MXML_INFO; }; } // namespace Ms - -#endif diff --git a/src/importexport/musicxml/internal/musicxml/importmxmlnoteduration.h b/src/importexport/musicxml/internal/musicxml/importmxmlnoteduration.h index 50610a48917cf..f343c7b84e110 100644 --- a/src/importexport/musicxml/internal/musicxml/importmxmlnoteduration.h +++ b/src/importexport/musicxml/internal/musicxml/importmxmlnoteduration.h @@ -20,8 +20,7 @@ * along with this program. If not, see . */ -#ifndef __IMPORTMXMLNOTEDURATION_H__ -#define __IMPORTMXMLNOTEDURATION_H__ +#pragma once #include "engraving/dom/durationtype.h" #include "engraving/types/fraction.h" @@ -66,5 +65,3 @@ class MxmlNoteDuration MxmlLogger* m_logger = nullptr; ///< Error logger }; } // namespace Ms - -#endif diff --git a/src/importexport/musicxml/internal/musicxml/importmxmlnotepitch.h b/src/importexport/musicxml/internal/musicxml/importmxmlnotepitch.h index 3bdb09e32d97a..dacec58ab7ccf 100644 --- a/src/importexport/musicxml/internal/musicxml/importmxmlnotepitch.h +++ b/src/importexport/musicxml/internal/musicxml/importmxmlnotepitch.h @@ -20,8 +20,7 @@ * along with this program. If not, see . */ -#ifndef __IMPORTMXMLNOTEPITCH_H__ -#define __IMPORTMXMLNOTEPITCH_H__ +#pragma once #include "global/serialization/xmlstreamreader.h" #include "engraving/dom/accidental.h" @@ -69,5 +68,3 @@ class MxmlNotePitch MxmlLogger* m_logger = nullptr; // Error logger }; } // namespace Ms - -#endif diff --git a/src/importexport/musicxml/internal/musicxml/importmxmlpass1.cpp b/src/importexport/musicxml/internal/musicxml/importmxmlpass1.cpp index 41febe37340ec..422386fa0fd4f 100644 --- a/src/importexport/musicxml/internal/musicxml/importmxmlpass1.cpp +++ b/src/importexport/musicxml/internal/musicxml/importmxmlpass1.cpp @@ -43,6 +43,7 @@ #include "importmxmllogger.h" #include "importmxmlnoteduration.h" #include "importmxmlpass1.h" +#include "musicxmltypes.h" #include "modularity/ioc.h" #include "importexport/musicxml/imusicxmlconfiguration.h" @@ -78,6 +79,170 @@ static bool musicxmlImportLayout() } namespace mu::engraving { +//--------------------------------------------------------- +// NoteList +//--------------------------------------------------------- + +/** + List of note start/stop times in a voice in all staves. +*/ + +class NoteList +{ +public: + NoteList(); + void addNote(const int startTick, const int endTick, const size_t staff); + void dump(const int& voice) const; + bool stavesOverlap(const int staff1, const int staff2) const; + bool anyStaffOverlaps() const; +private: + std::vector _staffNoteLists; // The note start/stop times in all staves + bool notesOverlap(const StartStop& n1, const StartStop& n2) const; +}; + +//--------------------------------------------------------- +// VoiceOverlapDetector +//--------------------------------------------------------- + +/** + Detect overlap in a voice, which is when a voice has two or more notes + active at the same time. In theory this should not happen, as voices + only move forward in time, but Sibelius 7 reuses voice numbers in multi- + staff parts, which leads to overlap. + + Current implementation does not detect voice overlap within a staff, + but only between staves. +*/ + +class VoiceOverlapDetector +{ +public: + VoiceOverlapDetector(); + void addNote(const int startTick, const int endTick, const int& voice, const int staff); + void dump() const; + void newMeasure(); + bool stavesOverlap(const int& voice) const; +private: + std::map _noteLists; // The notelists for all the voices +}; + +NoteList::NoteList() +{ + _staffNoteLists.reserve(MAX_STAVES); + for (int i = 0; i < MAX_STAVES; ++i) { + _staffNoteLists.push_back(StartStopList()); + } +} + +void NoteList::addNote(const int startTick, const int endTick, const size_t staff) +{ + if (staff < _staffNoteLists.size()) { + _staffNoteLists[staff].push_back(StartStop(startTick, endTick)); + } +} + +void NoteList::dump(const int& voice) const +{ + // dump contents + for (int i = 0; i < MAX_STAVES; ++i) { + printf("voice %d staff %d:", voice, i); + for (size_t j = 0; j < _staffNoteLists.at(i).size(); ++j) { + printf(" %d-%d", _staffNoteLists.at(i).at(j).first, _staffNoteLists.at(i).at(j).second); + } + printf("\n"); + } + // show overlap + printf("overlap voice %d:", voice); + for (int i = 0; i < MAX_STAVES - 1; ++i) { + for (int j = i + 1; j < MAX_STAVES; ++j) { + stavesOverlap(i, j); + } + } + printf("\n"); +} + +/** + Determine if notes n1 and n2 overlap. + This is NOT the case if + - n1 starts when or after n2 stops + - or n2 starts when or after n1 stops + */ + +bool NoteList::notesOverlap(const StartStop& n1, const StartStop& n2) const +{ + return !(n1.first >= n2.second || n1.second <= n2.first); +} + +/** + Determine if any note in staff1 and staff2 overlaps. + */ + +bool NoteList::stavesOverlap(const int staff1, const int staff2) const +{ + for (size_t i = 0; i < _staffNoteLists.at(staff1).size(); ++i) { + for (size_t j = 0; j < _staffNoteLists.at(staff2).size(); ++j) { + if (notesOverlap(_staffNoteLists.at(staff1).at(i), _staffNoteLists.at(staff2).at(j))) { + //printf(" %d-%d", staff1, staff2); + return true; + } + } + } + return false; +} + +/** + Determine if any note in any staff overlaps. + */ + +bool NoteList::anyStaffOverlaps() const +{ + for (int i = 0; i < MAX_STAVES - 1; ++i) { + for (int j = i + 1; j < MAX_STAVES; ++j) { + if (stavesOverlap(i, j)) { + return true; + } + } + } + return false; +} + +VoiceOverlapDetector::VoiceOverlapDetector() +{ + // LOGD("VoiceOverlapDetector::VoiceOverlapDetector(staves %d)", MAX_STAVES); +} + +void VoiceOverlapDetector::addNote(const int startTick, const int endTick, const int& voice, const int staff) +{ + // if necessary, create the note list for voice + if (!muse::contains(_noteLists, voice)) { + _noteLists.insert({ voice, NoteList() }); + } + _noteLists[voice].addNote(startTick, endTick, staff); +} + +void VoiceOverlapDetector::dump() const +{ + // LOGD("VoiceOverlapDetector::dump()"); + for (auto& p : _noteLists) { + p.second.dump(p.first); + } +} + +void VoiceOverlapDetector::newMeasure() +{ + // LOGD("VoiceOverlapDetector::newMeasure()"); + _noteLists.clear(); +} + +bool VoiceOverlapDetector::stavesOverlap(const int& voice) const +{ + if (muse::contains(_noteLists, voice)) { + return _noteLists.at(voice).anyStaffOverlaps(); + } else { + return false; + } +} + //--------------------------------------------------------- // allocateStaves //--------------------------------------------------------- @@ -3100,252 +3265,6 @@ void MusicXMLParserPass1::notations(MxmlStartStop& tupletStartStop) } } -//--------------------------------------------------------- -// smallestTypeAndCount -//--------------------------------------------------------- - -/** - Determine the smallest note type and the number of those - present in a ChordRest. - For a note without dots the type equals the note type - and count is one. - For a single dotted note the type equals half the note type - and count is three. - A double dotted note is similar. - Note: code assumes when duration().type() is incremented, - the note length is divided by two, checked by tupletAssert(). - */ - -static void smallestTypeAndCount(const TDuration durType, int& type, int& count) -{ - type = int(durType.type()); - count = 1; - switch (durType.dots()) { - case 0: - // nothing to do - break; - case 1: - type += 1; // next-smaller type - count = 3; - break; - case 2: - type += 2; // next-next-smaller type - count = 7; - break; - default: - LOGD("smallestTypeAndCount() does not support more than 2 dots"); - } -} - -//--------------------------------------------------------- -// matchTypeAndCount -//--------------------------------------------------------- - -/** - Given two note types and counts, if the types are not equal, - make them equal by successively doubling the count of the - largest type. - */ - -static void matchTypeAndCount(int& type1, int& count1, int& type2, int& count2) -{ - while (type1 < type2) { - type1++; - count1 *= 2; - } - while (type2 < type1) { - type2++; - count2 *= 2; - } -} - -//--------------------------------------------------------- -// addDurationToTuplet -//--------------------------------------------------------- - -/** - Add duration to tuplet duration - Determine type and number of smallest notes in the tuplet - */ - -void MxmlTupletState::addDurationToTuplet(const Fraction dur, const Fraction timeMod) -{ - /* - LOGD("1 duration %s timeMod %s -> state.tupletType %d state.tupletCount %d state.actualNotes %d state.normalNotes %d", - muPrintable(duration.print()), - muPrintable(timeMod.print()), - m_tupletType, - m_tupletCount, - m_actualNotes, - m_normalNotes - ); - */ - if (duration <= Fraction(0, 1)) { - // first note: init variables - actualNotes = timeMod.denominator(); - normalNotes = timeMod.numerator(); - smallestTypeAndCount(dur / timeMod, tupletType, tupletCount); - } else { - int noteType = 0; - int noteCount = 0; - smallestTypeAndCount(dur / timeMod, noteType, noteCount); - // match the types - matchTypeAndCount(tupletType, tupletCount, noteType, noteCount); - tupletCount += noteCount; - } - duration += dur; - /* - LOGD("2 duration %s -> state.tupletType %d state.tupletCount %d state.actualNotes %d state.normalNotes %d", - muPrintable(duration.print()), - m_tupletType, - m_tupletCount, - m_actualNotes, - m_normalNotes - ); - */ -} - -//--------------------------------------------------------- -// determineTupletFractionAndFullDuration -//--------------------------------------------------------- - -/** - Split duration into two factors where fullDuration is note sized - (i.e. the denominator is a power of 2), 1/2 < fraction <= 1/1 - and fraction * fullDuration equals duration. - */ - -void determineTupletFractionAndFullDuration(const Fraction duration, Fraction& fraction, Fraction& fullDuration) -{ - fraction = duration; - fullDuration = Fraction(1, 1); - // move denominator's powers of 2 from fraction to fullDuration - while (fraction.denominator() % 2 == 0) { - fraction *= 2; - fraction.reduce(); - fullDuration *= Fraction(1, 2); - } - // move numerator's powers of 2 from fraction to fullDuration - while (fraction.numerator() % 2 == 0) { - fraction *= Fraction(1, 2); - fraction.reduce(); - fullDuration *= 2; - fullDuration.reduce(); - } - // make sure 1/2 < fraction <= 1/1 - while (fraction <= Fraction(1, 2)) { - fullDuration *= Fraction(1, 2); - fraction *= 2; - } - fullDuration.reduce(); - fraction.reduce(); - - /* - Examples (note result when denominator is not a power of two): - 3:2 tuplet of 1/4 results in fraction 1/1 and fullDuration 1/2 - 2:3 tuplet of 1/4 results in fraction 3/1 and fullDuration 1/4 - 4:3 tuplet of 1/4 results in fraction 3/1 and fullDuration 1/4 - 3:4 tuplet of 1/4 results in fraction 1/1 and fullDuration 1/1 - - Bring back fraction in 1/2 .. 1/1 range. - */ - - if (fraction > Fraction(1, 1) && fraction.denominator() == 1) { - fullDuration *= fraction; - fullDuration.reduce(); - fraction = Fraction(1, 1); - } - - /* - LOGD("duration %s fraction %s fullDuration %s", - muPrintable(duration.toString()), - muPrintable(fraction.toString()), - muPrintable(fullDuration.toString()) - ); - */ -} - -//--------------------------------------------------------- -// isTupletFilled -//--------------------------------------------------------- - -/** - Determine if the tuplet is completely filled, - because either (1) it is at least the same duration - as the specified number of the specified normal type notes - or (2) the duration adds up to a normal note duration. - - Example (1): a 3:2 tuplet with a 1/4 and a 1/8 note - is filled if normal type is 1/8, - it is not filled if normal type is 1/4. - - Example (2): a 3:2 tuplet with a 1/4 and a 1/8 note is filled. - */ - -static bool isTupletFilled(const MxmlTupletState& state, const TDuration normalType, const Fraction timeMod) -{ - UNUSED(timeMod); - bool res = false; - const int actualNotes = state.actualNotes; - /* - const auto normalNotes = state.m_normalNotes; - LOGD("duration %s normalType %s timeMod %s normalNotes %d actualNotes %d", - muPrintable(state.m_duration.toString()), - muPrintable(normalType.fraction().toString()), - muPrintable(timeMod.toString()), - normalNotes, - actualNotes - ); - */ - - int tupletType = state.tupletType; - int tupletCount = state.tupletCount; - - if (normalType.isValid()) { - int matchedNormalType = int(normalType.type()); - int matchedNormalCount = actualNotes; - // match the types - matchTypeAndCount(tupletType, tupletCount, matchedNormalType, matchedNormalCount); - // ... result scenario (1) - res = tupletCount >= matchedNormalCount; - /* - LOGD("normalType valid tupletType %d tupletCount %d matchedNormalType %d matchedNormalCount %d res %d", - tupletType, - tupletCount, - matchedNormalType, - matchedNormalCount, - res - ); - */ - } else { - // ... result scenario (2) - res = tupletCount >= actualNotes; - /* - LOGD("normalType not valid tupletCount %d actualNotes %d res %d", - tupletCount, - actualNotes, - res - ); - */ - } - return res; -} - -//--------------------------------------------------------- -// missingTupletDuration -//--------------------------------------------------------- - -Fraction missingTupletDuration(const Fraction duration) -{ - Fraction tupletFraction; - Fraction tupletFullDuration; - - determineTupletFractionAndFullDuration(duration, tupletFraction, tupletFullDuration); - Fraction missing = (Fraction(1, 1) - tupletFraction) * tupletFullDuration; - - return missing; -} - //--------------------------------------------------------- // voiceToInt //--------------------------------------------------------- @@ -3369,103 +3288,6 @@ int MusicXMLParserPass1::voiceToInt(const String& voice) return voiceInt; } -//--------------------------------------------------------- -// determineTupletAction -//--------------------------------------------------------- - -/** - Update tuplet state using parse result tupletDesc. - Tuplets with and but without - are handled correctly. - TODO Nested tuplets are not (yet) supported. - */ - -MxmlTupletFlags MxmlTupletState::determineTupletAction(const Fraction noteDuration, - const Fraction timeMod, - const MxmlStartStop tupletStartStop, - const TDuration normalType, - Fraction& missingPreviousDuration, - Fraction& missingCurrentDuration) -{ - const int actNotes = timeMod.denominator(); - const int norNotes = timeMod.numerator(); - MxmlTupletFlags res = MxmlTupletFlag::NONE; - - // check for unexpected termination of previous tuplet - if (inTuplet && timeMod == Fraction(1, 1)) { - // recover by simply stopping the current tuplet first - if (!isTupletFilled(*this, normalType, timeMod)) { - missingPreviousDuration = missingTupletDuration(duration); - //LOGD("tuplet incomplete, missing %s", muPrintable(missingPreviousDuration.print())); - } - *this = {}; - res |= MxmlTupletFlag::STOP_PREVIOUS; - } - - // check for obvious errors - if (inTuplet && tupletStartStop == MxmlStartStop::START) { - LOGD("tuplet already started"); - // recover by simply stopping the current tuplet first - if (!isTupletFilled(*this, normalType, timeMod)) { - missingPreviousDuration = missingTupletDuration(duration); - //LOGD("tuplet incomplete, missing %s", muPrintable(missingPreviousDuration.print())); - } - *this = {}; - res |= MxmlTupletFlag::STOP_PREVIOUS; - } - if (tupletStartStop == MxmlStartStop::STOP && !inTuplet) { - LOGD("tuplet stop but no tuplet started"); // TODO - // recovery handled later (automatically, no special case needed) - } - - // Tuplet are either started by the tuplet start - // or when the time modification is first found. - if (!inTuplet) { - if (tupletStartStop == MxmlStartStop::START - || (!inTuplet && (actNotes != 1 || norNotes != 1))) { - if (tupletStartStop != MxmlStartStop::START) { - implicit = true; - } else { - implicit = false; - } - // create a new tuplet - inTuplet = true; - res |= MxmlTupletFlag::START_NEW; - } - } - - // Add chord to the current tuplet. - // Must also check for actual/normal notes to prevent - // adding one chord too much if tuplet stop is missing. - if (inTuplet && !(actNotes == 1 && norNotes == 1)) { - addDurationToTuplet(noteDuration, timeMod); - res |= MxmlTupletFlag::ADD_CHORD; - } - - // Tuplets are stopped by the tuplet stop - // or when the tuplet is filled completely - // (either with knowledge of the normal type - // or as a last resort calculated based on - // actual and normal notes plus total duration) - // or when the time-modification is not found. - - if (inTuplet) { - if (tupletStartStop == MxmlStartStop::STOP - || (implicit && isTupletFilled(*this, normalType, timeMod)) - || (actNotes == 1 && norNotes == 1)) { // incorrect ??? check scenario incomplete tuplet w/o start - if (actNotes > norNotes && !isTupletFilled(*this, normalType, timeMod)) { - missingCurrentDuration = missingTupletDuration(duration); - LOGD("current tuplet incomplete, missing %s", muPrintable(missingCurrentDuration.toString())); - } - - *this = {}; - res |= MxmlTupletFlag::STOP_CURRENT; - } - } - - return res; -} - //--------------------------------------------------------- // note //--------------------------------------------------------- diff --git a/src/importexport/musicxml/internal/musicxml/importmxmlpass1.h b/src/importexport/musicxml/internal/musicxml/importmxmlpass1.h index a3f6465a7d4cf..acbc98cc720a9 100644 --- a/src/importexport/musicxml/internal/musicxml/importmxmlpass1.h +++ b/src/importexport/musicxml/internal/musicxml/importmxmlpass1.h @@ -20,22 +20,22 @@ * along with this program. If not, see . */ -#ifndef __IMPORTMXMLPASS1_H__ -#define __IMPORTMXMLPASS1_H__ +#pragma once #include "global/serialization/xmlstreamreader.h" #include "global/containers.h" -#include "global/types/flags.h" #include "draw/types/geometry.h" -#include "importxmlfirstpass.h" -#include "musicxml.h" // for the creditwords and MusicXmlPartGroupList definitions #include "musicxmlsupport.h" +#include "musicxmltypes.h" +#include "musicxmltupletstate.h" +#include "musicxmlpart.h" #include "engraving/engravingerrors.h" namespace mu::engraving { class Score; +class VoiceOverlapDetector; //--------------------------------------------------------- // PageFormat @@ -53,8 +53,8 @@ struct PageFormat { bool twosided = false; }; -typedef std::map PartMap; -typedef std::map MusicXmlPartGroupMap; +typedef std::pair StartStop; +typedef std::vector StartStopList; //--------------------------------------------------------- // MxmlOctaveShiftDesc @@ -75,53 +75,54 @@ struct MxmlOctaveShiftDesc { }; //--------------------------------------------------------- -// MxmlStartStop (also used in pass 2) +// MusicXmlPartGroup //--------------------------------------------------------- -enum class MxmlStartStop : char { - NONE, START, STOP -}; - -enum class MxmlTupletFlag : char { - NONE = 0, - STOP_PREVIOUS = 1, - START_NEW = 2, - ADD_CHORD = 4, - STOP_CURRENT = 8 +struct MusicXmlPartGroup { + int span = 0; + int start = 0; + BracketType type = BracketType::NO_BRACKET; + bool barlineSpan = false; + int column = 0; }; +typedef std::vector MusicXmlPartGroupList; +typedef std::map PartMap; +typedef std::map MusicXmlPartGroupMap; -enum class MusicXMLExporterSoftware : char { - SIBELIUS, - DOLET6, - DOLET8, - FINALE, - NOTEFLIGHT, - OTHER -}; +//--------------------------------------------------------- +// CreditWords +// a single parsed MusicXML credit-words element +//--------------------------------------------------------- -typedef muse::Flags MxmlTupletFlags; - -struct MxmlTupletState { - void addDurationToTuplet(const Fraction duration, const Fraction timeMod); - MxmlTupletFlags determineTupletAction(const Fraction noteDuration, const Fraction timeMod, const MxmlStartStop tupletStartStop, - const TDuration normalType, Fraction& missingPreviousDuration, Fraction& missingCurrentDuration); - bool inTuplet = false; - bool implicit = false; - int actualNotes = 1; - int normalNotes = 1; - Fraction duration { 0, 1 }; - int tupletType = 0; // smallest note type in the tuplet // TODO_NOW rename ? - int tupletCount = 0; // number of smallest notes in the tuplet // TODO_NOW rename ? +struct CreditWords { + int page = 0; + String type; + double defaultX = 0.0; + double defaultY = 0.0; + double fontSize = 0.0; + String justify; + String hAlign; + String vAlign; + String words; + CreditWords(int p, String tp, double dx, double dy, double fs, String j, String ha, String va, String w) + { + page = p; + type = tp; + defaultX = dx; + defaultY = dy; + fontSize = fs; + justify = j; + hAlign = ha; + vAlign = va; + words = w; + } }; - -using MxmlTupletStates = std::map; +typedef std::vector CreditWordsList; //--------------------------------------------------------- // declarations //--------------------------------------------------------- -void determineTupletFractionAndFullDuration(const Fraction duration, Fraction& fraction, Fraction& fullDuration); -Fraction missingTupletDuration(const Fraction duration); bool isLikelyCreditText(const String& text, const bool caseInsensitive); bool isLikelySubtitleText(const String& text, const bool caseInsensitive); @@ -241,4 +242,3 @@ class MusicXMLParserPass1 std::set m_seenDenominators; // Denominators seen. Used for rounding errors. }; } // namespace Ms -#endif diff --git a/src/importexport/musicxml/internal/musicxml/importmxmlpass2.cpp b/src/importexport/musicxml/internal/musicxml/importmxmlpass2.cpp index d3594d09fc6d9..9f4bf2c0ba1e1 100644 --- a/src/importexport/musicxml/internal/musicxml/importmxmlpass2.cpp +++ b/src/importexport/musicxml/internal/musicxml/importmxmlpass2.cpp @@ -91,6 +91,7 @@ #include "importmxmlpass2.h" #include "musicxmlfonthandler.h" #include "musicxmlsupport.h" +#include "musicxmltypes.h" #include "modularity/ioc.h" #include "importexport/musicxml/imusicxmlconfiguration.h" @@ -121,6 +122,8 @@ static std::shared_ptr engravingFonts() //#define DEBUG_VOICE_MAPPER true +constexpr int MAX_LYRICS = 16; + //--------------------------------------------------------- // function declarations //--------------------------------------------------------- diff --git a/src/importexport/musicxml/internal/musicxml/importmxmlpass2.h b/src/importexport/musicxml/internal/musicxml/importmxmlpass2.h index 2b3dc6608be0f..11af49954a51a 100644 --- a/src/importexport/musicxml/internal/musicxml/importmxmlpass2.h +++ b/src/importexport/musicxml/internal/musicxml/importmxmlpass2.h @@ -20,15 +20,14 @@ * along with this program. If not, see . */ -#ifndef __IMPORTMXMLPASS2_H__ -#define __IMPORTMXMLPASS2_H__ +#pragma once #include #include "importmxmlpass1.h" -#include "importxmlfirstpass.h" #include "internal/musicxml/musicxmlsupport.h" -#include "musicxml.h" // a.o. for Slur +#include "musicxmltypes.h" +#include "musicxmltupletstate.h" #include "engraving/dom/instrument.h" #include "engraving/dom/types.h" @@ -46,15 +45,6 @@ using FiguredBassList = std::vector; using Tuplets = std::map; using Beams = std::map; -//--------------------------------------------------------- -// MxmlStartStop -//--------------------------------------------------------- -/* -enum class MxmlStartStop : char { - START, STOP, NONE - }; - */ - //--------------------------------------------------------- // MusicXmlSlash //--------------------------------------------------------- @@ -80,6 +70,33 @@ struct MusicXmlTupletDesc { TupletNumberType shownumber; }; +//--------------------------------------------------------- +// SlurDesc +//--------------------------------------------------------- + +/** + The description of Slurs being handled + */ + +class SlurDesc +{ +public: + enum class State : char { + NONE, START, STOP + }; + SlurDesc() + : m_slur(0), m_state(State::NONE) {} + Slur* slur() const { return m_slur; } + void start(Slur* slur) { m_slur = slur; m_state = State::START; } + void stop(Slur* slur) { m_slur = slur; m_state = State::STOP; } + bool isStart() const { return m_state == State::START; } + bool isStop() const { return m_state == State::STOP; } +private: + Slur* m_slur = nullptr; + State m_state; +}; +typedef std::map > MusicXmlSpannerMap; + //--------------------------------------------------------- // MusicXmlSpannerDesc //--------------------------------------------------------- @@ -109,6 +126,30 @@ struct MusicXmlExtendedSpannerDesc { String toString() const; }; +//--------------------------------------------------------- +// HarmonyDesc +//--------------------------------------------------------- + +/** + The description of a chord symbol with or without a fret diagram + */ + +struct HarmonyDesc +{ + track_idx_t m_track; + bool fretDiagramVisible() const { return m_fretDiagram ? m_fretDiagram->visible() : false; } + Harmony* m_harmony; + FretDiagram* m_fretDiagram; + + HarmonyDesc(track_idx_t m_track, Harmony* m_harmony, FretDiagram* m_fretDiagram) + : m_track(m_track), m_harmony(m_harmony), + m_fretDiagram(m_fretDiagram) {} + + HarmonyDesc() + : m_track(0), m_harmony(nullptr), m_fretDiagram(nullptr) {} +}; +using HarmonyMap = std::multimap; + //--------------------------------------------------------- // MusicXmlLyricsExtend //--------------------------------------------------------- @@ -134,6 +175,22 @@ struct GraceNoteLyrics { : lyric(lyric), extend(extend), no(no) {} }; +struct InferredPercInstr { + int pitch; + track_idx_t track; + String name; + Fraction tick; + + InferredPercInstr(int pitch, track_idx_t track, String name, Fraction tick) + : pitch(pitch), track(track), name(name), tick(tick) {} + + InferredPercInstr() + : pitch(-1), track(muse::nidx), name(u""), tick(Fraction(0, -1)) {} +}; +typedef std::vector InferredPercList; + +typedef std::map > MetronomeTextMap; + //--------------------------------------------------------- // MusicXMLParserLyric //--------------------------------------------------------- @@ -242,6 +299,9 @@ using SpannerSet = std::set; using DelayedArpMap = std::map; using SegnoStack = std::map; using SystemElements = std::multimap; + +// Ties are identified by the pitch and track of their first note +typedef std::pair TieLocation; using MusicXMLTieMap = std::map; //--------------------------------------------------------- @@ -613,4 +673,3 @@ class MusicXMLInferredFingering void addToNotes(std::vector& notes) const; }; } // namespace Ms -#endif diff --git a/src/importexport/musicxml/internal/musicxml/importxml.cpp b/src/importexport/musicxml/internal/musicxml/importxml.cpp index a6a6009e26b7f..a5b1bc9153160 100644 --- a/src/importexport/musicxml/internal/musicxml/importxml.cpp +++ b/src/importexport/musicxml/internal/musicxml/importxml.cpp @@ -183,72 +183,4 @@ Err importCompressedMusicXml(MasterScore* score, const String& name, bool forceM // and import it return doValidateAndImport(score, name, data, forceMode); } - -//--------------------------------------------------------- -// VoiceDesc -//--------------------------------------------------------- - -// TODO: move somewhere else - -VoiceDesc::VoiceDesc() - : m_staff(-1), m_voice(-1), m_overlaps(false) -{ - for (int i = 0; i < MAX_STAVES; ++i) { - m_chordRests[i] = 0; - m_staffAlloc[i] = -1; - m_voices[i] = -1; - } -} - -void VoiceDesc::incrChordRests(int s) -{ - if (0 <= s && s < MAX_STAVES) { - m_chordRests[s]++; - } -} - -int VoiceDesc::numberChordRests() const -{ - int res = 0; - for (int i = 0; i < MAX_STAVES; ++i) { - res += m_chordRests[i]; - } - return res; -} - -int VoiceDesc::preferredStaff() const -{ - int max = 0; - int res = 0; - for (int i = 0; i < MAX_STAVES; ++i) { - if (m_chordRests[i] > max) { - max = m_chordRests[i]; - res = i; - } - } - return res; -} - -String VoiceDesc::toString() const -{ - String res = u"["; - for (int i = 0; i < MAX_STAVES; ++i) { - res += String(u" %1").arg(m_chordRests[i]); - } - res += String(u" ] overlaps %1").arg(m_overlaps); - if (m_overlaps) { - res += u" staffAlloc ["; - for (int i = 0; i < MAX_STAVES; ++i) { - res += String(u" %1").arg(m_staffAlloc[i]); - } - res += u" ] voices ["; - for (int i = 0; i < MAX_STAVES; ++i) { - res += String(u" %1").arg(m_voices[i]); - } - res += u" ]"; - } else { - res += String(u" staff %1 voice %2").arg(m_staff + 1).arg(m_voice + 1); - } - return res; -} } // namespace Ms diff --git a/src/importexport/musicxml/internal/musicxml/musicxml.cmake b/src/importexport/musicxml/internal/musicxml/musicxml.cmake index 09e0c1145a2da..ec89cc56654b4 100644 --- a/src/importexport/musicxml/internal/musicxml/musicxml.cmake +++ b/src/importexport/musicxml/internal/musicxml/musicxml.cmake @@ -15,13 +15,18 @@ set (MUSICXML_SRC ${CMAKE_CURRENT_LIST_DIR}/importmxmlpass2.cpp ${CMAKE_CURRENT_LIST_DIR}/importmxmlpass2.h ${CMAKE_CURRENT_LIST_DIR}/importxml.cpp - ${CMAKE_CURRENT_LIST_DIR}/importxmlfirstpass.cpp - ${CMAKE_CURRENT_LIST_DIR}/importxmlfirstpass.h - ${CMAKE_CURRENT_LIST_DIR}/musicxml.h ${CMAKE_CURRENT_LIST_DIR}/musicxmlfonthandler.cpp ${CMAKE_CURRENT_LIST_DIR}/musicxmlfonthandler.h + ${CMAKE_CURRENT_LIST_DIR}/musicxmlpart.cpp + ${CMAKE_CURRENT_LIST_DIR}/musicxmlpart.h ${CMAKE_CURRENT_LIST_DIR}/musicxmlsupport.cpp ${CMAKE_CURRENT_LIST_DIR}/musicxmlsupport.h + ${CMAKE_CURRENT_LIST_DIR}/musicxmltupletstate.cpp + ${CMAKE_CURRENT_LIST_DIR}/musicxmltupletstate.h + ${CMAKE_CURRENT_LIST_DIR}/musicxmltypes.cpp + ${CMAKE_CURRENT_LIST_DIR}/musicxmltypes.h ${CMAKE_CURRENT_LIST_DIR}/musicxmlvalidation.cpp ${CMAKE_CURRENT_LIST_DIR}/musicxmlvalidation.h + ${CMAKE_CURRENT_LIST_DIR}/musicxmlvoicedesc.cpp + ${CMAKE_CURRENT_LIST_DIR}/musicxmlvoicedesc.h ) diff --git a/src/importexport/musicxml/internal/musicxml/musicxml.h b/src/importexport/musicxml/internal/musicxml/musicxml.h deleted file mode 100644 index 58eaa374fd689..0000000000000 --- a/src/importexport/musicxml/internal/musicxml/musicxml.h +++ /dev/null @@ -1,147 +0,0 @@ -/* - * SPDX-License-Identifier: GPL-3.0-only - * MuseScore-Studio-CLA-applies - * - * MuseScore Studio - * Music Composition & Notation - * - * Copyright (C) 2021 MuseScore Limited - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#ifndef __MUSICXML_H__ -#define __MUSICXML_H__ - -/** - \file - Definition of class MusicXML -*/ - -#include "engraving/types/fraction.h" -#include "engraving/dom/mscore.h" -#include "engraving/dom/pitchspelling.h" -#include "engraving/dom/line.h" -#include "importxmlfirstpass.h" -#include "musicxmlsupport.h" - -namespace mu::engraving { -//--------------------------------------------------------- -// MusicXmlPartGroup -//--------------------------------------------------------- - -struct MusicXmlPartGroup { - int span = 0; - int start = 0; - BracketType type = BracketType::NO_BRACKET; - bool barlineSpan = false; - int column = 0; -}; - -const int MAX_LYRICS = 16; -const int MAX_PART_GROUPS = 8; -const int MAX_NUMBER_LEVEL = 16; // maximum number of overlapping MusicXML objects - -//--------------------------------------------------------- -// CreditWords -// a single parsed MusicXML credit-words element -//--------------------------------------------------------- - -struct CreditWords { - int page = 0; - String type; - double defaultX = 0.0; - double defaultY = 0.0; - double fontSize = 0.0; - String justify; - String hAlign; - String vAlign; - String words; - CreditWords(int p, String tp, double dx, double dy, double fs, String j, String ha, String va, String w) - { - page = p; - type = tp; - defaultX = dx; - defaultY = dy; - fontSize = fs; - justify = j; - hAlign = ha; - vAlign = va; - words = w; - } -}; - -typedef std::vector CreditWordsList; -typedef CreditWordsList::iterator iCreditWords; -typedef CreditWordsList::const_iterator ciCreditWords; - -//--------------------------------------------------------- -// JumpMarkerDesc -//--------------------------------------------------------- - -/** - The description of Jumps and Markers to be added later -*/ - -class JumpMarkerDesc -{ -public: - JumpMarkerDesc(EngravingItem* el, Measure* meas) - : m_el(el), m_meas(meas) {} - EngravingItem* el() const { return m_el; } - Measure* meas() const { return m_meas; } - -private: - EngravingItem* m_el = nullptr; - Measure* m_meas = nullptr; -}; - -typedef std::vector JumpMarkerDescList; - -//--------------------------------------------------------- -// SlurDesc -//--------------------------------------------------------- - -/** - The description of Slurs being handled - */ - -class SlurDesc -{ -public: - enum class State : char { - NONE, START, STOP - }; - SlurDesc() - : m_slur(0), m_state(State::NONE) {} - Slur* slur() const { return m_slur; } - void start(Slur* slur) { m_slur = slur; m_state = State::START; } - void stop(Slur* slur) { m_slur = slur; m_state = State::STOP; } - bool isStart() const { return m_state == State::START; } - bool isStop() const { return m_state == State::STOP; } -private: - Slur* m_slur = nullptr; - State m_state; -}; - -// Ties are identified by the pitch and track of their first note -typedef std::pair TieLocation; - -//--------------------------------------------------------- -// MusicXml -//--------------------------------------------------------- - -typedef std::vector MusicXmlPartGroupList; -typedef std::map > MusicXmlSpannerMap; -} // namespace Ms -#endif diff --git a/src/importexport/musicxml/internal/musicxml/musicxmlfonthandler.h b/src/importexport/musicxml/internal/musicxml/musicxmlfonthandler.h index b015d0f9675e0..370ab3aa03c33 100644 --- a/src/importexport/musicxml/internal/musicxml/musicxmlfonthandler.h +++ b/src/importexport/musicxml/internal/musicxml/musicxmlfonthandler.h @@ -20,8 +20,7 @@ * along with this program. If not, see . */ -#ifndef __MUSICXMLFONTHANDLER_H__ -#define __MUSICXMLFONTHANDLER_H__ +#pragma once #include "engraving/dom/text.h" @@ -50,5 +49,3 @@ class MScoreTextToMXML String musicalTextFont; }; } // namespace Ms - -#endif diff --git a/src/importexport/musicxml/internal/musicxml/importxmlfirstpass.cpp b/src/importexport/musicxml/internal/musicxml/musicxmlpart.cpp similarity index 63% rename from src/importexport/musicxml/internal/musicxml/importxmlfirstpass.cpp rename to src/importexport/musicxml/internal/musicxml/musicxmlpart.cpp index 5938522275e2d..ba99ab14bdb05 100644 --- a/src/importexport/musicxml/internal/musicxml/importxmlfirstpass.cpp +++ b/src/importexport/musicxml/internal/musicxml/musicxmlpart.cpp @@ -5,7 +5,7 @@ * MuseScore Studio * Music Composition & Notation * - * Copyright (C) 2021 MuseScore Limited + * Copyright (C) 2024 MuseScore Limited * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -20,12 +20,10 @@ * along with this program. If not, see . */ -#include "importxmlfirstpass.h" +#include "musicxmlpart.h" +#include "dom/mscore.h" -#include "log.h" - -namespace mu::engraving { -// TODO: move somewhere else +using namespace mu::engraving; static const std::vector vocalInstrumentNames = { u"Voice", @@ -59,6 +57,57 @@ static const std::vector percussionInstrumentNames = { u"Stamp" }; +int MusicXmlOctaveShiftList::octaveShift(const Fraction f) const +{ + if (empty()) { + return 0; + } + auto i = upper_bound(f); + if (i == begin()) { + return 0; + } + --i; + return i->second; +} + +void MusicXmlOctaveShiftList::addOctaveShift(const int shift, const Fraction f) +{ + IF_ASSERT_FAILED(Fraction(0, 1) <= f) { + return; + } + + //LOGD("addOctaveShift(shift %d f %s)", shift, muPrintable(f.print())); + auto i = find(f); + if (i == end()) { + //LOGD("addOctaveShift: not found, inserting"); + insert({ f, shift }); + } else { + //LOGD("addOctaveShift: found %d, adding", (*this)[f]); + (*this)[f] += shift; + //LOGD("addOctaveShift: res %d", (*this)[f]); + } +} + +void MusicXmlOctaveShiftList::calcOctaveShiftShifts() +{ + /* + for (auto i = cbegin(); i != cend(); ++i) + LOGD(" [%s : %d]", muPrintable((*i).first.print()), (*i).second); + */ + + // to each MusicXmlOctaveShiftList entry, add the sum of all previous ones + int currentShift = 0; + for (auto i = begin(); i != end(); ++i) { + currentShift += i->second; + i->second = currentShift; + } + + /* + for (auto i = cbegin(); i != cend(); ++i) + LOGD(" [%s : %d]", muPrintable((*i).first.print()), (*i).second); + */ +} + MusicXmlPart::MusicXmlPart(String id, String name) : m_id(id), m_name(name) { @@ -181,171 +230,3 @@ bool MusicXmlPart::isPercussionStaff() const } return false; } - -//--------------------------------------------------------- -// interval -//--------------------------------------------------------- - -Interval MusicXmlIntervalList::interval(const Fraction f) const -{ - if (empty()) { - return {}; - } - auto i = upper_bound(f); - if (i == begin()) { - return {}; - } - --i; - return i->second; -} - -//--------------------------------------------------------- -// instrument -//--------------------------------------------------------- - -const String MusicXmlInstrList::instrument(const Fraction f) const -{ - if (empty()) { - return String(); - } - auto i = upper_bound(f); - if (i == begin()) { - return String(); - } - --i; - return i->second; -} - -//--------------------------------------------------------- -// setInstrument -//--------------------------------------------------------- - -void MusicXmlInstrList::setInstrument(const String instr, const Fraction f) -{ - // TODO determine how to handle multiple instrument changes at the same time - // current implementation keeps the first one - if (!insert({ f, instr }).second) { - LOGD() << "element already exists, instr: " << instr - << ", tick: " << f.toString() << "(" << f.ticks() << ")"; - } - //(*this)[f] = instr; -} - -int MusicXmlOctaveShiftList::octaveShift(const Fraction f) const -{ - if (empty()) { - return 0; - } - auto i = upper_bound(f); - if (i == begin()) { - return 0; - } - --i; - return i->second; -} - -void MusicXmlOctaveShiftList::addOctaveShift(const int shift, const Fraction f) -{ - IF_ASSERT_FAILED(Fraction(0, 1) <= f) { - return; - } - - //LOGD("addOctaveShift(shift %d f %s)", shift, muPrintable(f.print())); - auto i = find(f); - if (i == end()) { - //LOGD("addOctaveShift: not found, inserting"); - insert({ f, shift }); - } else { - //LOGD("addOctaveShift: found %d, adding", (*this)[f]); - (*this)[f] += shift; - //LOGD("addOctaveShift: res %d", (*this)[f]); - } -} - -void MusicXmlOctaveShiftList::calcOctaveShiftShifts() -{ - /* - for (auto i = cbegin(); i != cend(); ++i) - LOGD(" [%s : %d]", muPrintable((*i).first.print()), (*i).second); - */ - - // to each MusicXmlOctaveShiftList entry, add the sum of all previous ones - int currentShift = 0; - for (auto i = begin(); i != end(); ++i) { - currentShift += i->second; - i->second = currentShift; - } - - /* - for (auto i = cbegin(); i != cend(); ++i) - LOGD(" [%s : %d]", muPrintable((*i).first.print()), (*i).second); - */ -} - -//--------------------------------------------------------- -// LyricNumberHandler -// collect lyric numbering information and determine order -// -// MusicXML lyrics may contain name and number attributes, -// plus position information (typically default-y). -// Name and number are simply tokens with no specified usage. -// Default-y cannot easily be used to determine the lyrics -// line, as it tends to differ per system depending on the -// actual notes present. -// -// Simply collecting all possible lyric number attributes -// within a MusicXML part and assigning lyrics position -// based on alphabetically sorting works well for all -// common MusicXML files. -//--------------------------------------------------------- - -//--------------------------------------------------------- -// addNumber -//--------------------------------------------------------- - -void LyricNumberHandler::addNumber(const String& number) -{ - if (m_numberToNo.find(number) == m_numberToNo.end()) { - m_numberToNo[number] = -1; // unassigned - } -} - -//--------------------------------------------------------- -// toString -//--------------------------------------------------------- - -String LyricNumberHandler::toString() const -{ - String res; - for (const auto& p : m_numberToNo) { - if (!res.isEmpty()) { - res += u" "; - } - res += String(u"%1:%2").arg(p.first).arg(p.second); - } - return res; -} - -//--------------------------------------------------------- -// getLyricNo -//--------------------------------------------------------- - -int LyricNumberHandler::getLyricNo(const String& number) const -{ - const auto it = m_numberToNo.find(number); - return it == m_numberToNo.end() ? 0 : it->second; -} - -//--------------------------------------------------------- -// determineLyricNos -//--------------------------------------------------------- - -void LyricNumberHandler::determineLyricNos() -{ - int i = 0; - for (auto& p : m_numberToNo) { - p.second = i; - ++i; - } -} -} diff --git a/src/importexport/musicxml/internal/musicxml/importxmlfirstpass.h b/src/importexport/musicxml/internal/musicxml/musicxmlpart.h similarity index 76% rename from src/importexport/musicxml/internal/musicxml/importxmlfirstpass.h rename to src/importexport/musicxml/internal/musicxml/musicxmlpart.h index 370dd694a2a48..9404973697fe3 100644 --- a/src/importexport/musicxml/internal/musicxml/importxmlfirstpass.h +++ b/src/importexport/musicxml/internal/musicxml/musicxmlpart.h @@ -5,7 +5,7 @@ * MuseScore Studio * Music Composition & Notation * - * Copyright (C) 2021 MuseScore Limited + * Copyright (C) 2024 MuseScore Limited * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -19,33 +19,14 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +#pragma once -#ifndef __IMPORTXMLFIRSTPASS_H__ -#define __IMPORTXMLFIRSTPASS_H__ - -#include "engraving/types/fraction.h" -#include "engraving/dom/interval.h" -#include "musicxmlsupport.h" +#include "types/fraction.h" +#include "dom/interval.h" +#include "musicxmltypes.h" +#include "musicxmlvoicedesc.h" namespace mu::engraving { -typedef std::map VoiceList; -//using Intervals = std::map; - -class MusicXmlIntervalList : public std::map -{ -public: - MusicXmlIntervalList() {} - Interval interval(const Fraction f) const; -}; - -class MusicXmlInstrList : public std::map -{ -public: - MusicXmlInstrList() {} - const String instrument(const Fraction f) const; - void setInstrument(const String instr, const Fraction f); -}; - class MusicXmlOctaveShiftList : public std::map { public: @@ -55,18 +36,6 @@ class MusicXmlOctaveShiftList : public std::map void calcOctaveShiftShifts(); }; -class LyricNumberHandler -{ -public: - LyricNumberHandler() {} - void addNumber(const String& number); - String toString() const; - int getLyricNo(const String& number) const; - void determineLyricNos(); -private: - std::map m_numberToNo; -}; - class MusicXmlPart { public: @@ -114,7 +83,6 @@ class MusicXmlPart int m_maxStaff = -1; // maximum staff value found (0 based), -1 = none bool m_hasLyrics = false; std::map m_staffNumberToIndex; // Mapping from staff number to index in staff list. - // Only for when staves are discarded in MusicXMLParserPass1::attributes. + // Only for when staves are discarded in MusicXMLParserPass1::attributes. }; -} // namespace Ms -#endif +} diff --git a/src/importexport/musicxml/internal/musicxml/musicxmlsupport.cpp b/src/importexport/musicxml/internal/musicxml/musicxmlsupport.cpp index bdbfe65ab7717..dd83f835a409e 100644 --- a/src/importexport/musicxml/internal/musicxml/musicxmlsupport.cpp +++ b/src/importexport/musicxml/internal/musicxml/musicxmlsupport.cpp @@ -27,198 +27,13 @@ #include "global/serialization/xmlstreamreader.h" #include "translation.h" -#include "engraving/dom/accidental.h" #include "engraving/dom/articulation.h" #include "engraving/dom/chord.h" #include "types/symnames.h" - +#include "musicxmltypes.h" #include "musicxmlsupport.h" -#include "log.h" - -using AccidentalType = mu::engraving::AccidentalType; -using SymId = mu::engraving::SymId; - -static const std::map smuflAccidentalTypes { - { u"accidentalDoubleFlatOneArrowDown", AccidentalType::DOUBLE_FLAT_ONE_ARROW_DOWN }, - { u"accidentalFlatOneArrowDown", AccidentalType::FLAT_ONE_ARROW_DOWN }, - { u"accidentalNaturalOneArrowDown", AccidentalType::NATURAL_ONE_ARROW_DOWN }, - { u"accidentalSharpOneArrowDown", AccidentalType::SHARP_ONE_ARROW_DOWN }, - { u"accidentalDoubleSharpOneArrowDown", AccidentalType::DOUBLE_SHARP_ONE_ARROW_DOWN }, - { u"accidentalDoubleFlatOneArrowUp", AccidentalType::DOUBLE_FLAT_ONE_ARROW_UP }, - { u"accidentalFlatOneArrowUp", AccidentalType::FLAT_ONE_ARROW_UP }, - { u"accidentalNaturalOneArrowUp", AccidentalType::NATURAL_ONE_ARROW_UP }, - { u"accidentalSharpOneArrowUp", AccidentalType::SHARP_ONE_ARROW_UP }, - { u"accidentalDoubleSharpOneArrowUp", AccidentalType::DOUBLE_SHARP_ONE_ARROW_UP }, - { u"accidentalDoubleFlatTwoArrowsDown", AccidentalType::DOUBLE_FLAT_TWO_ARROWS_DOWN }, - { u"accidentalFlatTwoArrowsDown", AccidentalType::FLAT_TWO_ARROWS_DOWN }, - { u"accidentalNaturalTwoArrowsDown", AccidentalType::NATURAL_TWO_ARROWS_DOWN }, - { u"accidentalSharpTwoArrowsDown", AccidentalType::SHARP_TWO_ARROWS_DOWN }, - { u"accidentalDoubleSharpTwoArrowsDown", AccidentalType::DOUBLE_SHARP_TWO_ARROWS_DOWN }, - { u"accidentalDoubleFlatTwoArrowsUp", AccidentalType::DOUBLE_FLAT_TWO_ARROWS_UP }, - { u"accidentalFlatTwoArrowsUp", AccidentalType::FLAT_TWO_ARROWS_UP }, - { u"accidentalNaturalTwoArrowsUp", AccidentalType::NATURAL_TWO_ARROWS_UP }, - { u"accidentalSharpTwoArrowsUp", AccidentalType::SHARP_TWO_ARROWS_UP }, - { u"accidentalDoubleSharpTwoArrowsUp", AccidentalType::DOUBLE_SHARP_TWO_ARROWS_UP }, - { u"accidentalDoubleFlatThreeArrowsDown", AccidentalType::DOUBLE_FLAT_THREE_ARROWS_DOWN }, - { u"accidentalFlatThreeArrowsDown", AccidentalType::FLAT_THREE_ARROWS_DOWN }, - { u"accidentalNaturalThreeArrowsDown", AccidentalType::NATURAL_THREE_ARROWS_DOWN }, - { u"accidentalSharpThreeArrowsDown", AccidentalType::SHARP_THREE_ARROWS_DOWN }, - { u"accidentalDoubleSharpThreeArrowsDown", AccidentalType::DOUBLE_SHARP_THREE_ARROWS_DOWN }, - { u"accidentalDoubleFlatThreeArrowsUp", AccidentalType::DOUBLE_FLAT_THREE_ARROWS_UP }, - { u"accidentalFlatThreeArrowsUp", AccidentalType::FLAT_THREE_ARROWS_UP }, - { u"accidentalNaturalThreeArrowsUp", AccidentalType::NATURAL_THREE_ARROWS_UP }, - { u"accidentalSharpThreeArrowsUp", AccidentalType::SHARP_THREE_ARROWS_UP }, - { u"accidentalDoubleSharpThreeArrowsUp", AccidentalType::DOUBLE_SHARP_THREE_ARROWS_UP }, - { u"accidentalLowerOneSeptimalComma", AccidentalType::LOWER_ONE_SEPTIMAL_COMMA }, - { u"accidentalRaiseOneSeptimalComma", AccidentalType::RAISE_ONE_SEPTIMAL_COMMA }, - { u"accidentalLowerTwoSeptimalCommas", AccidentalType::LOWER_TWO_SEPTIMAL_COMMAS }, - { u"accidentalRaiseTwoSeptimalCommas", AccidentalType::RAISE_TWO_SEPTIMAL_COMMAS }, - { u"accidentalLowerOneUndecimalQuartertone", AccidentalType::LOWER_ONE_UNDECIMAL_QUARTERTONE }, - { u"accidentalRaiseOneUndecimalQuartertone", AccidentalType::RAISE_ONE_UNDECIMAL_QUARTERTONE }, - { u"accidentalLowerOneTridecimalQuartertone", AccidentalType::LOWER_ONE_TRIDECIMAL_QUARTERTONE }, - { u"accidentalRaiseOneTridecimalQuartertone", AccidentalType::RAISE_ONE_TRIDECIMAL_QUARTERTONE }, - { u"accidentalDoubleFlatEqualTempered", AccidentalType::DOUBLE_FLAT_EQUAL_TEMPERED }, - { u"accidentalFlatEqualTempered", AccidentalType::FLAT_EQUAL_TEMPERED }, - { u"accidentalNaturalEqualTempered", AccidentalType::NATURAL_EQUAL_TEMPERED }, - { u"accidentalSharpEqualTempered", AccidentalType::SHARP_EQUAL_TEMPERED }, - { u"accidentalDoubleSharpEqualTempered", AccidentalType::DOUBLE_SHARP_EQUAL_TEMPERED }, - { u"accidentalQuarterFlatEqualTempered", AccidentalType::QUARTER_FLAT_EQUAL_TEMPERED }, - { u"accidentalQuarterSharpEqualTempered", AccidentalType::QUARTER_SHARP_EQUAL_TEMPERED } -}; - namespace mu::engraving { -NoteList::NoteList() -{ - _staffNoteLists.reserve(MAX_STAVES); - for (int i = 0; i < MAX_STAVES; ++i) { - _staffNoteLists.push_back(StartStopList()); - } -} - -void NoteList::addNote(const int startTick, const int endTick, const size_t staff) -{ - if (staff < _staffNoteLists.size()) { - _staffNoteLists[staff].push_back(StartStop(startTick, endTick)); - } -} - -void NoteList::dump(const int& voice) const -{ - // dump contents - for (int i = 0; i < MAX_STAVES; ++i) { - printf("voice %d staff %d:", voice, i); - for (size_t j = 0; j < _staffNoteLists.at(i).size(); ++j) { - printf(" %d-%d", _staffNoteLists.at(i).at(j).first, _staffNoteLists.at(i).at(j).second); - } - printf("\n"); - } - // show overlap - printf("overlap voice %d:", voice); - for (int i = 0; i < MAX_STAVES - 1; ++i) { - for (int j = i + 1; j < MAX_STAVES; ++j) { - stavesOverlap(i, j); - } - } - printf("\n"); -} - -/** - Determine if notes n1 and n2 overlap. - This is NOT the case if - - n1 starts when or after n2 stops - - or n2 starts when or after n1 stops - */ - -static bool notesOverlap(const StartStop& n1, const StartStop& n2) -{ - return !(n1.first >= n2.second || n1.second <= n2.first); -} - -/** - Determine if any note in staff1 and staff2 overlaps. - */ - -bool NoteList::stavesOverlap(const int staff1, const int staff2) const -{ - for (size_t i = 0; i < _staffNoteLists.at(staff1).size(); ++i) { - for (size_t j = 0; j < _staffNoteLists.at(staff2).size(); ++j) { - if (notesOverlap(_staffNoteLists.at(staff1).at(i), _staffNoteLists.at(staff2).at(j))) { - //printf(" %d-%d", staff1, staff2); - return true; - } - } - } - return false; -} - -/** - Determine if any note in any staff overlaps. - */ - -bool NoteList::anyStaffOverlaps() const -{ - for (int i = 0; i < MAX_STAVES - 1; ++i) { - for (int j = i + 1; j < MAX_STAVES; ++j) { - if (stavesOverlap(i, j)) { - return true; - } - } - } - return false; -} - -VoiceOverlapDetector::VoiceOverlapDetector() -{ - // LOGD("VoiceOverlapDetector::VoiceOverlapDetector(staves %d)", MAX_STAVES); -} - -void VoiceOverlapDetector::addNote(const int startTick, const int endTick, const int& voice, const int staff) -{ - // if necessary, create the note list for voice - if (!muse::contains(_noteLists, voice)) { - _noteLists.insert({ voice, NoteList() }); - } - _noteLists[voice].addNote(startTick, endTick, staff); -} - -void VoiceOverlapDetector::dump() const -{ - // LOGD("VoiceOverlapDetector::dump()"); - for (auto p : _noteLists) { - p.second.dump(p.first); - } -} - -void VoiceOverlapDetector::newMeasure() -{ - // LOGD("VoiceOverlapDetector::newMeasure()"); - _noteLists.clear(); -} - -bool VoiceOverlapDetector::stavesOverlap(const int& voice) const -{ - if (muse::contains(_noteLists, voice)) { - return _noteLists.at(voice).anyStaffOverlaps(); - } else { - return false; - } -} - -String MusicXMLInstrument::toString() const -{ - return String(u"chan %1 prog %2 vol %3 pan %4 unpitched %5 name '%6' sound '%7' head %8 line %9 stemDir %10") - .arg(midiChannel) - .arg(midiProgram) - .arg(midiVolume) - .arg(midiPan) - .arg(unpitched) - .arg(name, sound) - .arg(int(notehead)) - .arg(line) - .arg(int(stemDirection)); -} - //--------------------------------------------------------- // errorStringWithLocation //--------------------------------------------------------- @@ -623,6 +438,54 @@ String accidentalType2MxmlString(const AccidentalType type) return s; } +static const std::map smuflAccidentalTypes { + { u"accidentalDoubleFlatOneArrowDown", AccidentalType::DOUBLE_FLAT_ONE_ARROW_DOWN }, + { u"accidentalFlatOneArrowDown", AccidentalType::FLAT_ONE_ARROW_DOWN }, + { u"accidentalNaturalOneArrowDown", AccidentalType::NATURAL_ONE_ARROW_DOWN }, + { u"accidentalSharpOneArrowDown", AccidentalType::SHARP_ONE_ARROW_DOWN }, + { u"accidentalDoubleSharpOneArrowDown", AccidentalType::DOUBLE_SHARP_ONE_ARROW_DOWN }, + { u"accidentalDoubleFlatOneArrowUp", AccidentalType::DOUBLE_FLAT_ONE_ARROW_UP }, + { u"accidentalFlatOneArrowUp", AccidentalType::FLAT_ONE_ARROW_UP }, + { u"accidentalNaturalOneArrowUp", AccidentalType::NATURAL_ONE_ARROW_UP }, + { u"accidentalSharpOneArrowUp", AccidentalType::SHARP_ONE_ARROW_UP }, + { u"accidentalDoubleSharpOneArrowUp", AccidentalType::DOUBLE_SHARP_ONE_ARROW_UP }, + { u"accidentalDoubleFlatTwoArrowsDown", AccidentalType::DOUBLE_FLAT_TWO_ARROWS_DOWN }, + { u"accidentalFlatTwoArrowsDown", AccidentalType::FLAT_TWO_ARROWS_DOWN }, + { u"accidentalNaturalTwoArrowsDown", AccidentalType::NATURAL_TWO_ARROWS_DOWN }, + { u"accidentalSharpTwoArrowsDown", AccidentalType::SHARP_TWO_ARROWS_DOWN }, + { u"accidentalDoubleSharpTwoArrowsDown", AccidentalType::DOUBLE_SHARP_TWO_ARROWS_DOWN }, + { u"accidentalDoubleFlatTwoArrowsUp", AccidentalType::DOUBLE_FLAT_TWO_ARROWS_UP }, + { u"accidentalFlatTwoArrowsUp", AccidentalType::FLAT_TWO_ARROWS_UP }, + { u"accidentalNaturalTwoArrowsUp", AccidentalType::NATURAL_TWO_ARROWS_UP }, + { u"accidentalSharpTwoArrowsUp", AccidentalType::SHARP_TWO_ARROWS_UP }, + { u"accidentalDoubleSharpTwoArrowsUp", AccidentalType::DOUBLE_SHARP_TWO_ARROWS_UP }, + { u"accidentalDoubleFlatThreeArrowsDown", AccidentalType::DOUBLE_FLAT_THREE_ARROWS_DOWN }, + { u"accidentalFlatThreeArrowsDown", AccidentalType::FLAT_THREE_ARROWS_DOWN }, + { u"accidentalNaturalThreeArrowsDown", AccidentalType::NATURAL_THREE_ARROWS_DOWN }, + { u"accidentalSharpThreeArrowsDown", AccidentalType::SHARP_THREE_ARROWS_DOWN }, + { u"accidentalDoubleSharpThreeArrowsDown", AccidentalType::DOUBLE_SHARP_THREE_ARROWS_DOWN }, + { u"accidentalDoubleFlatThreeArrowsUp", AccidentalType::DOUBLE_FLAT_THREE_ARROWS_UP }, + { u"accidentalFlatThreeArrowsUp", AccidentalType::FLAT_THREE_ARROWS_UP }, + { u"accidentalNaturalThreeArrowsUp", AccidentalType::NATURAL_THREE_ARROWS_UP }, + { u"accidentalSharpThreeArrowsUp", AccidentalType::SHARP_THREE_ARROWS_UP }, + { u"accidentalDoubleSharpThreeArrowsUp", AccidentalType::DOUBLE_SHARP_THREE_ARROWS_UP }, + { u"accidentalLowerOneSeptimalComma", AccidentalType::LOWER_ONE_SEPTIMAL_COMMA }, + { u"accidentalRaiseOneSeptimalComma", AccidentalType::RAISE_ONE_SEPTIMAL_COMMA }, + { u"accidentalLowerTwoSeptimalCommas", AccidentalType::LOWER_TWO_SEPTIMAL_COMMAS }, + { u"accidentalRaiseTwoSeptimalCommas", AccidentalType::RAISE_TWO_SEPTIMAL_COMMAS }, + { u"accidentalLowerOneUndecimalQuartertone", AccidentalType::LOWER_ONE_UNDECIMAL_QUARTERTONE }, + { u"accidentalRaiseOneUndecimalQuartertone", AccidentalType::RAISE_ONE_UNDECIMAL_QUARTERTONE }, + { u"accidentalLowerOneTridecimalQuartertone", AccidentalType::LOWER_ONE_TRIDECIMAL_QUARTERTONE }, + { u"accidentalRaiseOneTridecimalQuartertone", AccidentalType::RAISE_ONE_TRIDECIMAL_QUARTERTONE }, + { u"accidentalDoubleFlatEqualTempered", AccidentalType::DOUBLE_FLAT_EQUAL_TEMPERED }, + { u"accidentalFlatEqualTempered", AccidentalType::FLAT_EQUAL_TEMPERED }, + { u"accidentalNaturalEqualTempered", AccidentalType::NATURAL_EQUAL_TEMPERED }, + { u"accidentalSharpEqualTempered", AccidentalType::SHARP_EQUAL_TEMPERED }, + { u"accidentalDoubleSharpEqualTempered", AccidentalType::DOUBLE_SHARP_EQUAL_TEMPERED }, + { u"accidentalQuarterFlatEqualTempered", AccidentalType::QUARTER_FLAT_EQUAL_TEMPERED }, + { u"accidentalQuarterSharpEqualTempered", AccidentalType::QUARTER_SHARP_EQUAL_TEMPERED } +}; + //--------------------------------------------------------- // accidentalType2SmuflMxmlString //--------------------------------------------------------- diff --git a/src/importexport/musicxml/internal/musicxml/musicxmlsupport.h b/src/importexport/musicxml/internal/musicxml/musicxmlsupport.h index 0eac04013d6dd..b2a7916f6fce0 100644 --- a/src/importexport/musicxml/internal/musicxml/musicxmlsupport.h +++ b/src/importexport/musicxml/internal/musicxml/musicxmlsupport.h @@ -20,11 +20,9 @@ * along with this program. If not, see . */ -#ifndef __MUSICXMLSUPPORT_H__ -#define __MUSICXMLSUPPORT_H__ +#pragma once #include "engraving/types/fraction.h" -#include "engraving/dom/mscore.h" #include "engraving/dom/note.h" #include "engraving/dom/fret.h" @@ -33,217 +31,6 @@ class XmlStreamReader; } namespace mu::engraving { -class Chord; - -//--------------------------------------------------------- -// NoteList -//--------------------------------------------------------- - -/** - List of note start/stop times in a voice in a single staff. -*/ - -typedef std::pair StartStop; -typedef std::vector StartStopList; - -//--------------------------------------------------------- -// NoteList -//--------------------------------------------------------- - -/** - List of note start/stop times in a voice in all staves. -*/ - -class NoteList -{ -public: - NoteList(); - void addNote(const int startTick, const int endTick, const size_t staff); - void dump(const int& voice) const; - bool stavesOverlap(const int staff1, const int staff2) const; - bool anyStaffOverlaps() const; -private: - std::vector _staffNoteLists; // The note start/stop times in all staves -}; - -struct MusicXmlArpeggioDesc { - Arpeggio* arp; - int no; - - MusicXmlArpeggioDesc(Arpeggio* arp, int no) - : arp(arp), no(no) {} -}; -typedef std::multimap ArpeggioMap; - -/** - The description of a chord symbol with or without a fret diagram - */ - -struct HarmonyDesc -{ - track_idx_t m_track; - bool fretDiagramVisible() const { return m_fretDiagram ? m_fretDiagram->visible() : false; } - Harmony* m_harmony; - FretDiagram* m_fretDiagram; - - HarmonyDesc(track_idx_t m_track, Harmony* m_harmony, FretDiagram* m_fretDiagram) - : m_track(m_track), m_harmony(m_harmony), - m_fretDiagram(m_fretDiagram) {} - - HarmonyDesc() - : m_track(0), m_harmony(nullptr), m_fretDiagram(nullptr) {} -}; - -using HarmonyMap = std::multimap; - -//--------------------------------------------------------- -// VoiceDesc -//--------------------------------------------------------- - -/** - The description of a single voice in a MusicXML part. -*/ - -class VoiceDesc -{ -public: - VoiceDesc(); - void incrChordRests(int s); - int numberChordRests() const; - int numberChordRests(int s) const { return (s >= 0 && s < MAX_STAVES) ? m_chordRests[s] : 0; } - int preferredStaff() const; // Determine preferred staff for this voice - void setStaff(int s) - { - if (s >= 0) { - m_staff = s; - } - } - - int staff() const { return m_staff; } - void setVoice(int v) - { - if (v >= 0) { - m_voice = v; - } - } - - int voice() const { return m_voice; } - void setVoice(int s, int v) - { - if (s >= 0 && s < MAX_STAVES) { - m_voices[s] = v; - } - } - - int voice(int s) const { return (s >= 0 && s < MAX_STAVES) ? m_voices[s] : -1; } - void setOverlap(bool b) { m_overlaps = b; } - bool overlaps() const { return m_overlaps; } - void setStaffAlloc(int s, int i) - { - if (s >= 0 && s < MAX_STAVES) { - m_staffAlloc[s] = i; - } - } - - int staffAlloc(int s) const { return (s >= 0 && s < MAX_STAVES) ? m_staffAlloc[s] : -1; } - String toString() const; -private: - int m_chordRests[MAX_STAVES]; // The number of chordrests on each MusicXML staff - int m_staff; // The MuseScore staff allocated - int m_voice; // The MuseScore voice allocated - bool m_overlaps; // This voice contains active notes in multiple staves at the same time - int m_staffAlloc[MAX_STAVES]; // For overlapping voices: voice is allocated on these staves (note: -2=unalloc -1=undef 1=alloc) - int m_voices[MAX_STAVES]; // For every voice allocated on the staff, the voice number -}; - -//--------------------------------------------------------- -// VoiceOverlapDetector -//--------------------------------------------------------- - -/** - Detect overlap in a voice, which is when a voice has two or more notes - active at the same time. In theory this should not happen, as voices - only move forward in time, but Sibelius 7 reuses voice numbers in multi- - staff parts, which leads to overlap. - - Current implementation does not detect voice overlap within a staff, - but only between staves. -*/ - -class VoiceOverlapDetector -{ -public: - VoiceOverlapDetector(); - void addNote(const int startTick, const int endTick, const int& voice, const int staff); - void dump() const; - void newMeasure(); - bool stavesOverlap(const int& voice) const; -private: - std::map _noteLists; // The notelists for all the voices -}; - -//--------------------------------------------------------- -// MusicXMLInstrument -//--------------------------------------------------------- - -/** - A single instrument in a MusicXML part. - Used for both a drum part and a (non-drum) multi-instrument part - */ - -struct MusicXMLInstrument { - int unpitched; // midi-unpitched read from MusicXML - String name; // instrument-name read from MusicXML - String sound; // instrument-sound read from MusicXML - String virtLib; // virtual-library read from MusicXML - String virtName; // virtual-name read from MusicXML - int midiChannel; // midi-channel read from MusicXML - int midiPort; // port read from MusicXML - int midiProgram; // midi-program read from MusicXML - int midiVolume; // volume read from MusicXML - int midiPan; // pan value read from MusicXML - NoteHeadGroup notehead; // notehead symbol set - int line = 0; // place notehead onto this line - DirectionV stemDirection; - - String toString() const; - - MusicXMLInstrument() // required by std::map - : unpitched(-1), name(), midiChannel(-1), midiPort(-1), midiProgram(-1), midiVolume(100), midiPan(63), - notehead(NoteHeadGroup::HEAD_INVALID), line(0), stemDirection(DirectionV::AUTO) {} - MusicXMLInstrument(String s) - : unpitched(-1), name(s), midiChannel(-1), midiPort(-1), midiProgram(-1), midiVolume(100), midiPan(63), - notehead(NoteHeadGroup::HEAD_NORMAL), line(0), stemDirection(DirectionV::AUTO) {} - /* - MusicXMLInstrument(int p, String s, NoteHead::Group nh, int l, Direction d) - : unpitched(p), name(s), midiChannel(-1), midiPort(-1), midiProgram(-1), midiVolume(100), midiPan(63), - notehead(nh), line(l), stemDirection(d) {} - */ -}; - -struct InferredPercInstr { - int pitch; - track_idx_t track; - String name; - Fraction tick; - - InferredPercInstr(int pitch, track_idx_t track, String name, Fraction tick) - : pitch(pitch), track(track), name(name), tick(tick) {} - - InferredPercInstr() - : pitch(-1), track(muse::nidx), name(u""), tick(Fraction(0, -1)) {} -}; - -typedef std::vector InferredPercList; - -/** - A MusicXML drumset or set of instruments in a multi-instrument part. - */ - -typedef std::map MusicXMLInstruments; - -typedef std::map > MetronomeTextMap; - //--------------------------------------------------------- // MxmlSupport -- MusicXML import support functions //--------------------------------------------------------- @@ -269,4 +56,3 @@ extern const Articulation* findLaissezVibrer(const Chord* chord); extern String errorStringWithLocation(int line, int col, const String& error); extern String checkAtEndElement(const muse::XmlStreamReader& e, const String& expName); } // namespace Ms -#endif diff --git a/src/importexport/musicxml/internal/musicxml/musicxmltupletstate.cpp b/src/importexport/musicxml/internal/musicxml/musicxmltupletstate.cpp new file mode 100644 index 0000000000000..54557255949eb --- /dev/null +++ b/src/importexport/musicxml/internal/musicxml/musicxmltupletstate.cpp @@ -0,0 +1,364 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-Studio-CLA-applies + * + * MuseScore Studio + * Music Composition & Notation + * + * Copyright (C) 2024 MuseScore Limited + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "musicxmltupletstate.h" +#include "internal/musicxml/musicxmlsupport.h" + +using namespace mu::engraving; + +//--------------------------------------------------------- +// isTupletFilled +//--------------------------------------------------------- + +/** + Determine if the tuplet is completely filled, + because either (1) it is at least the same duration + as the specified number of the specified normal type notes + or (2) the duration adds up to a normal note duration. + + Example (1): a 3:2 tuplet with a 1/4 and a 1/8 note + is filled if normal type is 1/8, + it is not filled if normal type is 1/4. + + Example (2): a 3:2 tuplet with a 1/4 and a 1/8 note is filled. + */ + +bool MxmlTupletState::isTupletFilled(const TDuration normalType, const Fraction timeMod) +{ + UNUSED(timeMod); + bool res = false; + /* + const auto normalNotes = state.m_normalNotes; + LOGD("duration %s normalType %s timeMod %s normalNotes %d actualNotes %d", + muPrintable(state.m_duration.toString()), + muPrintable(normalType.fraction().toString()), + muPrintable(timeMod.toString()), + normalNotes, + actualNotes + ); + */ + + if (normalType.isValid()) { + int matchedNormalType = int(normalType.type()); + int matchedNormalCount = actualNotes; + // match the types + matchTypeAndCount(matchedNormalType, matchedNormalCount); + // ... result scenario (1) + res = smallestNoteCount >= matchedNormalCount; + /* + LOGD("normalType valid tupletType %d tupletCount %d matchedNormalType %d matchedNormalCount %d res %d", + tupletType, + tupletCount, + matchedNormalType, + matchedNormalCount, + res + ); + */ + } else { + // ... result scenario (2) + res = smallestNoteCount >= actualNotes; + /* + LOGD("normalType not valid tupletCount %d actualNotes %d res %d", + tupletCount, + actualNotes, + res + ); + */ + } + return res; +} + +//--------------------------------------------------------- +// smallestTypeAndCount +//--------------------------------------------------------- + +/** + Determine the smallest note type and the number of those + present in a ChordRest. + For a note without dots the type equals the note type + and count is one. + For a single dotted note the type equals half the note type + and count is three. + A double dotted note is similar. + Note: code assumes when duration().type() is incremented, + the note length is divided by two, checked by tupletAssert(). + */ + +void MxmlTupletState::smallestTypeAndCount(const TDuration durType, int& type, int& count) +{ + type = int(durType.type()); + count = 1; + switch (durType.dots()) { + case 0: + // nothing to do + break; + case 1: + type += 1; // next-smaller type + count = 3; + break; + case 2: + type += 2; // next-next-smaller type + count = 7; + break; + default: + LOGD("smallestTypeAndCount() does not support more than 2 dots"); + } +} + +//--------------------------------------------------------- +// matchTypeAndCount +//--------------------------------------------------------- + +/** + Given two note types and counts, if the types are not equal, + make them equal by successively doubling the count of the + largest type. + */ + +void MxmlTupletState::matchTypeAndCount(int& noteType, int& noteCount) +{ + while (smallestNoteType < noteType) { + smallestNoteType++; + smallestNoteCount *= 2; + } + while (noteType < smallestNoteType) { + noteType++; + noteCount *= 2; + } +} + +//--------------------------------------------------------- +// addDurationToTuplet +//--------------------------------------------------------- + +/** + Add duration to tuplet duration + Determine type and number of smallest notes in the tuplet + */ + +void MxmlTupletState::addDurationToTuplet(const Fraction dur, const Fraction timeMod) +{ + /* + LOGD("1 duration %s timeMod %s -> state.tupletType %d state.tupletCount %d state.actualNotes %d state.normalNotes %d", + muPrintable(duration.print()), + muPrintable(timeMod.print()), + m_tupletType, + m_tupletCount, + m_actualNotes, + m_normalNotes + ); + */ + if (duration <= Fraction(0, 1)) { + // first note: init variables + actualNotes = timeMod.denominator(); + normalNotes = timeMod.numerator(); + smallestTypeAndCount(dur / timeMod, smallestNoteType, smallestNoteCount); + } else { + int noteType = 0; + int noteCount = 0; + smallestTypeAndCount(dur / timeMod, noteType, noteCount); + // match the types + matchTypeAndCount(noteType, noteCount); + smallestNoteCount += noteCount; + } + duration += dur; + /* + LOGD("2 duration %s -> state.tupletType %d state.tupletCount %d state.actualNotes %d state.normalNotes %d", + muPrintable(duration.print()), + m_tupletType, + m_tupletCount, + m_actualNotes, + m_normalNotes + ); + */ +} + +//--------------------------------------------------------- +// determineTupletAction +//--------------------------------------------------------- + +/** + Update tuplet state using parse result tupletDesc. + Tuplets with and but without + are handled correctly. + TODO Nested tuplets are not (yet) supported. + */ + +MxmlTupletFlags MxmlTupletState::determineTupletAction(const Fraction noteDuration, + const Fraction timeMod, + const MxmlStartStop tupletStartStop, + const TDuration normalType, + Fraction& missingPreviousDuration, + Fraction& missingCurrentDuration) +{ + const int actNotes = timeMod.denominator(); + const int norNotes = timeMod.numerator(); + MxmlTupletFlags res = MxmlTupletFlag::NONE; + + // check for unexpected termination of previous tuplet + if (inTuplet && timeMod == Fraction(1, 1)) { + // recover by simply stopping the current tuplet first + if (!isTupletFilled(normalType, timeMod)) { + missingPreviousDuration = missingTupletDuration(duration); + //LOGD("tuplet incomplete, missing %s", muPrintable(missingPreviousDuration.print())); + } + *this = {}; + res |= MxmlTupletFlag::STOP_PREVIOUS; + } + + // check for obvious errors + if (inTuplet && tupletStartStop == MxmlStartStop::START) { + LOGD("tuplet already started"); + // recover by simply stopping the current tuplet first + if (!isTupletFilled(normalType, timeMod)) { + missingPreviousDuration = missingTupletDuration(duration); + //LOGD("tuplet incomplete, missing %s", muPrintable(missingPreviousDuration.print())); + } + *this = {}; + res |= MxmlTupletFlag::STOP_PREVIOUS; + } + if (tupletStartStop == MxmlStartStop::STOP && !inTuplet) { + LOGD("tuplet stop but no tuplet started"); // TODO + // recovery handled later (automatically, no special case needed) + } + + // Tuplet are either started by the tuplet start + // or when the time modification is first found. + if (!inTuplet) { + if (tupletStartStop == MxmlStartStop::START + || (!inTuplet && (actNotes != 1 || norNotes != 1))) { + if (tupletStartStop != MxmlStartStop::START) { + implicit = true; + } else { + implicit = false; + } + // create a new tuplet + inTuplet = true; + res |= MxmlTupletFlag::START_NEW; + } + } + + // Add chord to the current tuplet. + // Must also check for actual/normal notes to prevent + // adding one chord too much if tuplet stop is missing. + if (inTuplet && !(actNotes == 1 && norNotes == 1)) { + addDurationToTuplet(noteDuration, timeMod); + res |= MxmlTupletFlag::ADD_CHORD; + } + + // Tuplets are stopped by the tuplet stop + // or when the tuplet is filled completely + // (either with knowledge of the normal type + // or as a last resort calculated based on + // actual and normal notes plus total duration) + // or when the time-modification is not found. + + if (inTuplet) { + if (tupletStartStop == MxmlStartStop::STOP + || (implicit && isTupletFilled(normalType, timeMod)) + || (actNotes == 1 && norNotes == 1)) { // incorrect ??? check scenario incomplete tuplet w/o start + if (actNotes > norNotes && !isTupletFilled(normalType, timeMod)) { + missingCurrentDuration = missingTupletDuration(duration); + LOGD("current tuplet incomplete, missing %s", muPrintable(missingCurrentDuration.toString())); + } + + *this = {}; + res |= MxmlTupletFlag::STOP_CURRENT; + } + } + + return res; +} + +//--------------------------------------------------------- +// determineTupletFractionAndFullDuration +//--------------------------------------------------------- + +/** + Split duration into two factors where fullDuration is note sized + (i.e. the denominator is a power of 2), 1/2 < fraction <= 1/1 + and fraction * fullDuration equals duration. + */ + +void mu::engraving::determineTupletFractionAndFullDuration(const Fraction duration, Fraction& fraction, Fraction& fullDuration) +{ + fraction = duration; + fullDuration = Fraction(1, 1); + // move denominator's powers of 2 from fraction to fullDuration + while (fraction.denominator() % 2 == 0) { + fraction *= 2; + fraction.reduce(); + fullDuration *= Fraction(1, 2); + } + // move numerator's powers of 2 from fraction to fullDuration + while (fraction.numerator() % 2 == 0) { + fraction *= Fraction(1, 2); + fraction.reduce(); + fullDuration *= 2; + fullDuration.reduce(); + } + // make sure 1/2 < fraction <= 1/1 + while (fraction <= Fraction(1, 2)) { + fullDuration *= Fraction(1, 2); + fraction *= 2; + } + fullDuration.reduce(); + fraction.reduce(); + + /* + Examples (note result when denominator is not a power of two): + 3:2 tuplet of 1/4 results in fraction 1/1 and fullDuration 1/2 + 2:3 tuplet of 1/4 results in fraction 3/1 and fullDuration 1/4 + 4:3 tuplet of 1/4 results in fraction 3/1 and fullDuration 1/4 + 3:4 tuplet of 1/4 results in fraction 1/1 and fullDuration 1/1 + + Bring back fraction in 1/2 .. 1/1 range. + */ + + if (fraction > Fraction(1, 1) && fraction.denominator() == 1) { + fullDuration *= fraction; + fullDuration.reduce(); + fraction = Fraction(1, 1); + } + + /* + LOGD("duration %s fraction %s fullDuration %s", + muPrintable(duration.toString()), + muPrintable(fraction.toString()), + muPrintable(fullDuration.toString()) + ); + */ +} + +//--------------------------------------------------------- +// missingTupletDuration +//--------------------------------------------------------- + +Fraction mu::engraving::missingTupletDuration(const Fraction duration) +{ + Fraction tupletFraction; + Fraction tupletFullDuration; + + determineTupletFractionAndFullDuration(duration, tupletFraction, tupletFullDuration); + Fraction missing = (Fraction(1, 1) - tupletFraction) * tupletFullDuration; + + return missing; +} diff --git a/src/importexport/musicxml/internal/musicxml/musicxmltupletstate.h b/src/importexport/musicxml/internal/musicxml/musicxmltupletstate.h new file mode 100644 index 0000000000000..9df19463ced95 --- /dev/null +++ b/src/importexport/musicxml/internal/musicxml/musicxmltupletstate.h @@ -0,0 +1,60 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-Studio-CLA-applies + * + * MuseScore Studio + * Music Composition & Notation + * + * Copyright (C) 2024 MuseScore Limited + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include "musicxmltypes.h" +#include "dom/durationtype.h" + +namespace mu::engraving { +enum class MxmlTupletFlag : char { + NONE = 0, + STOP_PREVIOUS = 1, + START_NEW = 2, + ADD_CHORD = 4, + STOP_CURRENT = 8 +}; +typedef muse::Flags MxmlTupletFlags; + +class MxmlTupletState +{ +public: + MxmlTupletFlags determineTupletAction(const Fraction noteDuration, const Fraction timeMod, const MxmlStartStop tupletStartStop, + const TDuration normalType, Fraction& missingPreviousDuration, Fraction& missingCurrentDuration); +private: + void addDurationToTuplet(const Fraction duration, const Fraction timeMod); + void smallestTypeAndCount(const TDuration durType, int& type, int& count); + void matchTypeAndCount(int& noteType, int& noteCount); + bool isTupletFilled(const TDuration normalType, const Fraction timeMod); + + bool inTuplet = false; + bool implicit = false; + int actualNotes = 1; + int normalNotes = 1; + Fraction duration { 0, 1 }; + int smallestNoteType = 0; // smallest note type in the tuplet + int smallestNoteCount = 0; // number of smallest notes in the tuplet +}; +using MxmlTupletStates = std::map; + +void determineTupletFractionAndFullDuration(const Fraction duration, Fraction& fraction, Fraction& fullDuration); +Fraction missingTupletDuration(const Fraction duration); +} diff --git a/src/importexport/musicxml/internal/musicxml/musicxmltypes.cpp b/src/importexport/musicxml/internal/musicxml/musicxmltypes.cpp new file mode 100644 index 0000000000000..94300d8bc6c20 --- /dev/null +++ b/src/importexport/musicxml/internal/musicxml/musicxmltypes.cpp @@ -0,0 +1,155 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-Studio-CLA-applies + * + * MuseScore Studio + * Music Composition & Notation + * + * Copyright (C) 2024 MuseScore Limited + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "musicxmltypes.h" + +using namespace mu::engraving; + +String MusicXMLInstrument::toString() const +{ + return String(u"chan %1 prog %2 vol %3 pan %4 unpitched %5 name '%6' sound '%7' head %8 line %9 stemDir %10") + .arg(midiChannel) + .arg(midiProgram) + .arg(midiVolume) + .arg(midiPan) + .arg(unpitched) + .arg(name, sound) + .arg(int(notehead)) + .arg(line) + .arg(int(stemDirection)); +} + +//--------------------------------------------------------- +// interval +//--------------------------------------------------------- + +Interval MusicXmlIntervalList::interval(const Fraction f) const +{ + if (empty()) { + return {}; + } + auto i = upper_bound(f); + if (i == begin()) { + return {}; + } + --i; + return i->second; +} + +//--------------------------------------------------------- +// instrument +//--------------------------------------------------------- + +const String MusicXmlInstrList::instrument(const Fraction f) const +{ + if (empty()) { + return String(); + } + auto i = upper_bound(f); + if (i == begin()) { + return String(); + } + --i; + return i->second; +} + +//--------------------------------------------------------- +// setInstrument +//--------------------------------------------------------- + +void MusicXmlInstrList::setInstrument(const String instr, const Fraction f) +{ + // TODO determine how to handle multiple instrument changes at the same time + // current implementation keeps the first one + if (!insert({ f, instr }).second) { + LOGD() << "element already exists, instr: " << instr + << ", tick: " << f.toString() << "(" << f.ticks() << ")"; + } + //(*this)[f] = instr; +} + +//--------------------------------------------------------- +// LyricNumberHandler +// collect lyric numbering information and determine order +// +// MusicXML lyrics may contain name and number attributes, +// plus position information (typically default-y). +// Name and number are simply tokens with no specified usage. +// Default-y cannot easily be used to determine the lyrics +// line, as it tends to differ per system depending on the +// actual notes present. +// +// Simply collecting all possible lyric number attributes +// within a MusicXML part and assigning lyrics position +// based on alphabetically sorting works well for all +// common MusicXML files. +//--------------------------------------------------------- + +//--------------------------------------------------------- +// addNumber +//--------------------------------------------------------- + +void LyricNumberHandler::addNumber(const String& number) +{ + if (m_numberToNo.find(number) == m_numberToNo.end()) { + m_numberToNo[number] = -1; // unassigned + } +} + +//--------------------------------------------------------- +// toString +//--------------------------------------------------------- + +String LyricNumberHandler::toString() const +{ + String res; + for (const auto& p : m_numberToNo) { + if (!res.isEmpty()) { + res += u" "; + } + res += String(u"%1:%2").arg(p.first).arg(p.second); + } + return res; +} + +//--------------------------------------------------------- +// getLyricNo +//--------------------------------------------------------- + +int LyricNumberHandler::getLyricNo(const String& number) const +{ + const auto it = m_numberToNo.find(number); + return it == m_numberToNo.end() ? 0 : it->second; +} + +//--------------------------------------------------------- +// determineLyricNos +//--------------------------------------------------------- + +void LyricNumberHandler::determineLyricNos() +{ + int i = 0; + for (auto& p : m_numberToNo) { + p.second = i; + ++i; + } +} diff --git a/src/importexport/musicxml/internal/musicxml/musicxmltypes.h b/src/importexport/musicxml/internal/musicxml/musicxmltypes.h new file mode 100644 index 0000000000000..a6d480e6f94ce --- /dev/null +++ b/src/importexport/musicxml/internal/musicxml/musicxmltypes.h @@ -0,0 +1,122 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-Studio-CLA-applies + * + * MuseScore Studio + * Music Composition & Notation + * + * Copyright (C) 2024 MuseScore Limited + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include "dom/arpeggio.h" +#include "dom/interval.h" + +namespace mu::engraving { +const int MAX_NUMBER_LEVEL = 16; // maximum number of overlapping MusicXML objects + +enum class MusicXMLExporterSoftware : char { + SIBELIUS, + DOLET6, + DOLET8, + FINALE, + NOTEFLIGHT, + OTHER +}; + +//--------------------------------------------------------- +// MxmlStartStop +//--------------------------------------------------------- + +enum class MxmlStartStop : char { + NONE, START, STOP +}; + +struct MusicXmlArpeggioDesc { + Arpeggio* arp; + int no; + + MusicXmlArpeggioDesc(Arpeggio* arp, int no) + : arp(arp), no(no) {} +}; +typedef std::multimap ArpeggioMap; + +//--------------------------------------------------------- +// MusicXMLInstrument +//--------------------------------------------------------- + +/** + A single instrument in a MusicXML part. + Used for both a drum part and a (non-drum) multi-instrument part + */ + +struct MusicXMLInstrument { + int unpitched; // midi-unpitched read from MusicXML + String name; // instrument-name read from MusicXML + String sound; // instrument-sound read from MusicXML + String virtLib; // virtual-library read from MusicXML + String virtName; // virtual-name read from MusicXML + int midiChannel; // midi-channel read from MusicXML + int midiPort; // port read from MusicXML + int midiProgram; // midi-program read from MusicXML + int midiVolume; // volume read from MusicXML + int midiPan; // pan value read from MusicXML + NoteHeadGroup notehead; // notehead symbol set + int line = 0; // place notehead onto this line + DirectionV stemDirection; + + String toString() const; + + MusicXMLInstrument() // required by std::map + : unpitched(-1), name(), midiChannel(-1), midiPort(-1), midiProgram(-1), midiVolume(100), midiPan(63), + notehead(NoteHeadGroup::HEAD_INVALID), line(0), stemDirection(DirectionV::AUTO) {} + MusicXMLInstrument(String s) + : unpitched(-1), name(s), midiChannel(-1), midiPort(-1), midiProgram(-1), midiVolume(100), midiPan(63), + notehead(NoteHeadGroup::HEAD_NORMAL), line(0), stemDirection(DirectionV::AUTO) {} + /* + MusicXMLInstrument(int p, String s, NoteHead::Group nh, int l, Direction d) + : unpitched(p), name(s), midiChannel(-1), midiPort(-1), midiProgram(-1), midiVolume(100), midiPan(63), + notehead(nh), line(l), stemDirection(d) {} + */ +}; +typedef std::map MusicXMLInstruments; + +class MusicXmlIntervalList : public std::map +{ +public: + MusicXmlIntervalList() {} + Interval interval(const Fraction f) const; +}; + +class MusicXmlInstrList : public std::map +{ +public: + MusicXmlInstrList() {} + const String instrument(const Fraction f) const; + void setInstrument(const String instr, const Fraction f); +}; + +class LyricNumberHandler +{ +public: + LyricNumberHandler() {} + void addNumber(const String& number); + String toString() const; + int getLyricNo(const String& number) const; + void determineLyricNos(); +private: + std::map m_numberToNo; +}; +} diff --git a/src/importexport/musicxml/internal/musicxml/musicxmlvalidation.h b/src/importexport/musicxml/internal/musicxml/musicxmlvalidation.h index d59f95a86476e..e6e180c6a1795 100644 --- a/src/importexport/musicxml/internal/musicxml/musicxmlvalidation.h +++ b/src/importexport/musicxml/internal/musicxml/musicxmlvalidation.h @@ -20,8 +20,7 @@ * along with this program. If not, see . */ -#ifndef MU_MUSICXML_MUSICXMLVALIDATION_H -#define MU_MUSICXML_MUSICXMLVALIDATION_H +#pragma once #include "global/types/bytearray.h" #include "global/types/string.h" @@ -35,5 +34,3 @@ class MusicxmlValidation static engraving::Err validate(const muse::String& name, const muse::ByteArray& data); }; } - -#endif // MU_MUSICXML_MUSICXMLVALIDATION_H diff --git a/src/importexport/musicxml/internal/musicxml/musicxmlvoicedesc.cpp b/src/importexport/musicxml/internal/musicxml/musicxmlvoicedesc.cpp new file mode 100644 index 0000000000000..ec3e1f0980029 --- /dev/null +++ b/src/importexport/musicxml/internal/musicxml/musicxmlvoicedesc.cpp @@ -0,0 +1,90 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-Studio-CLA-applies + * + * MuseScore Studio + * Music Composition & Notation + * + * Copyright (C) 2024 MuseScore Limited + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "musicxmlvoicedesc.h" + +using namespace mu::engraving; +//--------------------------------------------------------- +// VoiceDesc +//--------------------------------------------------------- + +VoiceDesc::VoiceDesc() + : m_staff(-1), m_voice(-1), m_overlaps(false) +{ + for (int i = 0; i < MAX_STAVES; ++i) { + m_chordRests[i] = 0; + m_staffAlloc[i] = -1; + m_voices[i] = -1; + } +} + +void VoiceDesc::incrChordRests(int s) +{ + if (0 <= s && s < MAX_STAVES) { + m_chordRests[s]++; + } +} + +int VoiceDesc::numberChordRests() const +{ + int res = 0; + for (int i = 0; i < MAX_STAVES; ++i) { + res += m_chordRests[i]; + } + return res; +} + +int VoiceDesc::preferredStaff() const +{ + int max = 0; + int res = 0; + for (int i = 0; i < MAX_STAVES; ++i) { + if (m_chordRests[i] > max) { + max = m_chordRests[i]; + res = i; + } + } + return res; +} + +String VoiceDesc::toString() const +{ + String res = u"["; + for (int i = 0; i < MAX_STAVES; ++i) { + res += String(u" %1").arg(m_chordRests[i]); + } + res += String(u" ] overlaps %1").arg(m_overlaps); + if (m_overlaps) { + res += u" staffAlloc ["; + for (int i = 0; i < MAX_STAVES; ++i) { + res += String(u" %1").arg(m_staffAlloc[i]); + } + res += u" ] voices ["; + for (int i = 0; i < MAX_STAVES; ++i) { + res += String(u" %1").arg(m_voices[i]); + } + res += u" ]"; + } else { + res += String(u" staff %1 voice %2").arg(m_staff + 1).arg(m_voice + 1); + } + return res; +} diff --git a/src/importexport/musicxml/internal/musicxml/musicxmlvoicedesc.h b/src/importexport/musicxml/internal/musicxml/musicxmlvoicedesc.h new file mode 100644 index 0000000000000..19ef75f3fb7b6 --- /dev/null +++ b/src/importexport/musicxml/internal/musicxml/musicxmlvoicedesc.h @@ -0,0 +1,87 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-Studio-CLA-applies + * + * MuseScore Studio + * Music Composition & Notation + * + * Copyright (C) 2024 MuseScore Limited + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include "dom/mscore.h" + +namespace mu::engraving { +//--------------------------------------------------------- +// VoiceDesc +//--------------------------------------------------------- + +/** + The description of a single voice in a MusicXML part. +*/ + +class VoiceDesc +{ +public: + VoiceDesc(); + void incrChordRests(int s); + int numberChordRests() const; + int numberChordRests(int s) const { return (s >= 0 && s < MAX_STAVES) ? m_chordRests[s] : 0; } + int preferredStaff() const; // Determine preferred staff for this voice + void setStaff(int s) + { + if (s >= 0) { + m_staff = s; + } + } + + int staff() const { return m_staff; } + void setVoice(int v) + { + if (v >= 0) { + m_voice = v; + } + } + + int voice() const { return m_voice; } + void setVoice(int s, int v) + { + if (s >= 0 && s < MAX_STAVES) { + m_voices[s] = v; + } + } + + int voice(int s) const { return (s >= 0 && s < MAX_STAVES) ? m_voices[s] : -1; } + void setOverlap(bool b) { m_overlaps = b; } + bool overlaps() const { return m_overlaps; } + void setStaffAlloc(int s, int i) + { + if (s >= 0 && s < MAX_STAVES) { + m_staffAlloc[s] = i; + } + } + + int staffAlloc(int s) const { return (s >= 0 && s < MAX_STAVES) ? m_staffAlloc[s] : -1; } + String toString() const; +private: + int m_chordRests[MAX_STAVES]; // The number of chordrests on each MusicXML staff + int m_staff; // The MuseScore staff allocated + int m_voice; // The MuseScore voice allocated + bool m_overlaps; // This voice contains active notes in multiple staves at the same time + int m_staffAlloc[MAX_STAVES]; // For overlapping voices: voice is allocated on these staves (note: -2=unalloc -1=undef 1=alloc) + int m_voices[MAX_STAVES]; // For every voice allocated on the staff, the voice number +}; +typedef std::map VoiceList; +} diff --git a/src/importexport/musicxml/internal/musicxmlconfiguration.h b/src/importexport/musicxml/internal/musicxmlconfiguration.h index fbf9c82482a51..c2075c9950056 100644 --- a/src/importexport/musicxml/internal/musicxmlconfiguration.h +++ b/src/importexport/musicxml/internal/musicxmlconfiguration.h @@ -19,8 +19,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef MU_IMPORTEXPORT_MUSICXMLCONFIGURATION_H -#define MU_IMPORTEXPORT_MUSICXMLCONFIGURATION_H +#pragma once #include "../imusicxmlconfiguration.h" @@ -64,5 +63,3 @@ class MusicXmlConfiguration : public IMusicXmlConfiguration std::optional m_inferTextTypeOverride; }; } - -#endif // MU_IMPORTEXPORT_MUSICXMLCONFIGURATION_H diff --git a/src/importexport/musicxml/internal/musicxmlreader.h b/src/importexport/musicxml/internal/musicxmlreader.h index 25bcc04d53c4e..6c4f687532be1 100644 --- a/src/importexport/musicxml/internal/musicxmlreader.h +++ b/src/importexport/musicxml/internal/musicxmlreader.h @@ -19,8 +19,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef MU_IMPORTEXPORT_MUSICXMLREADER_H -#define MU_IMPORTEXPORT_MUSICXMLREADER_H +#pragma once #include "project/inotationreader.h" @@ -31,4 +30,3 @@ class MusicXmlReader : public project::INotationReader muse::Ret read(mu::engraving::MasterScore* score, const muse::io::path_t& path, const Options& options = Options()) override; }; } -#endif // MU_IMPORTEXPORT_MUSICXMLREADER_H diff --git a/src/importexport/musicxml/internal/musicxmlwriter.h b/src/importexport/musicxml/internal/musicxmlwriter.h index 23d22b5a7b05b..5a8bfba88722e 100644 --- a/src/importexport/musicxml/internal/musicxmlwriter.h +++ b/src/importexport/musicxml/internal/musicxmlwriter.h @@ -19,9 +19,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - -#ifndef MU_IMPORTEXPORT_MUSICXMLWRITER_H -#define MU_IMPORTEXPORT_MUSICXMLWRITER_H +#pragma once #include "project/inotationwriter.h" @@ -38,5 +36,3 @@ class MusicXmlWriter : public project::INotationWriter const Options& options = Options()) override; }; } - -#endif // MU_IMPORTEXPORT_MUSICXMLWRITER_H diff --git a/src/importexport/musicxml/internal/mxlwriter.h b/src/importexport/musicxml/internal/mxlwriter.h index f456fbe251ced..fa772f38e4140 100644 --- a/src/importexport/musicxml/internal/mxlwriter.h +++ b/src/importexport/musicxml/internal/mxlwriter.h @@ -19,9 +19,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - -#ifndef MU_IMPORTEXPORT_MXLWRITER_H -#define MU_IMPORTEXPORT_MXLWRITER_H +#pragma once #include "musicxmlwriter.h" @@ -32,5 +30,3 @@ class MxlWriter : public MusicXmlWriter muse::Ret write(notation::INotationPtr notation, muse::io::IODevice& dstDevice, const Options& options = Options()) override; }; } - -#endif // MU_IMPORTEXPORT_MXLWRITER_H diff --git a/src/importexport/musicxml/musicxmlmodule.h b/src/importexport/musicxml/musicxmlmodule.h index 40a6d19736d4e..7481c1fd6cbd6 100644 --- a/src/importexport/musicxml/musicxmlmodule.h +++ b/src/importexport/musicxml/musicxmlmodule.h @@ -19,8 +19,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef MU_IMPORTEXPORT_MUSICXMLMODULE_H -#define MU_IMPORTEXPORT_MUSICXMLMODULE_H +#pragma once #include @@ -42,5 +41,3 @@ class MusicXmlModule : public muse::modularity::IModuleSetup std::shared_ptr m_configuration; }; } - -#endif // MU_IMPORTEXPORT_MUSICXMLMODULE_H