diff --git a/Verovio.xcodeproj/project.pbxproj b/Verovio.xcodeproj/project.pbxproj index 150bf5abad..758f6b8e36 100644 --- a/Verovio.xcodeproj/project.pbxproj +++ b/Verovio.xcodeproj/project.pbxproj @@ -332,6 +332,12 @@ 4D64137E2035F68600BB630E /* mdiv.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4D64137D2035F68600BB630E /* mdiv.cpp */; }; 4D64137F2035F68600BB630E /* mdiv.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4D64137D2035F68600BB630E /* mdiv.cpp */; }; 4D6413802035F68600BB630E /* mdiv.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4D64137D2035F68600BB630E /* mdiv.cpp */; }; + 4D6479372C9881B800CD9539 /* iocmme.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4D6479362C9881B800CD9539 /* iocmme.cpp */; }; + 4D6479392C98825900CD9539 /* iocmme.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4D6479362C9881B800CD9539 /* iocmme.cpp */; }; + 4D64793A2C98825A00CD9539 /* iocmme.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4D6479362C9881B800CD9539 /* iocmme.cpp */; }; + 4D64793B2C98825A00CD9539 /* iocmme.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4D6479362C9881B800CD9539 /* iocmme.cpp */; }; + 4D64793C2C98826600CD9539 /* iocmme.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D6479382C9881DB00CD9539 /* iocmme.h */; }; + 4D64793D2C98826600CD9539 /* iocmme.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D6479382C9881DB00CD9539 /* iocmme.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4D64793F2C9C0F1C00CD9539 /* genericlayerelement.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4D64793E2C9C0F1C00CD9539 /* genericlayerelement.cpp */; }; 4D6479412C9C0F4200CD9539 /* genericlayerelement.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4D64793E2C9C0F1C00CD9539 /* genericlayerelement.cpp */; }; 4D6479422C9C0F4300CD9539 /* genericlayerelement.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4D64793E2C9C0F1C00CD9539 /* genericlayerelement.cpp */; }; @@ -1885,6 +1891,8 @@ 4D6413772035F58200BB630E /* pages.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = pages.cpp; path = src/pages.cpp; sourceTree = ""; }; 4D64137B2035F67C00BB630E /* mdiv.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = mdiv.h; path = include/vrv/mdiv.h; sourceTree = ""; }; 4D64137D2035F68600BB630E /* mdiv.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = mdiv.cpp; path = src/mdiv.cpp; sourceTree = ""; }; + 4D6479362C9881B800CD9539 /* iocmme.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = iocmme.cpp; path = src/iocmme.cpp; sourceTree = ""; }; + 4D6479382C9881DB00CD9539 /* iocmme.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = iocmme.h; path = include/vrv/iocmme.h; sourceTree = ""; }; 4D64793E2C9C0F1C00CD9539 /* genericlayerelement.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = genericlayerelement.cpp; path = src/genericlayerelement.cpp; sourceTree = ""; }; 4D6479402C9C0F2D00CD9539 /* genericlayerelement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = genericlayerelement.h; path = include/vrv/genericlayerelement.h; sourceTree = ""; }; 4D674B3E255F40AC008AEF4C /* plica.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = plica.h; path = include/vrv/plica.h; sourceTree = ""; }; @@ -2895,6 +2903,8 @@ 8F59291718854BF800FE51AD /* iobase.h */, 402197931F2E09DA00182DF1 /* ioabc.cpp */, 402197921F2E09CB00182DF1 /* ioabc.h */, + 4D6479382C9881DB00CD9539 /* iocmme.h */, + 4D6479362C9881B800CD9539 /* iocmme.cpp */, 8F086EC1188539540037FD8E /* iodarms.cpp */, 8F59291818854BF800FE51AD /* iodarms.h */, 4DA20ED01D0F151900706C4A /* iohumdrum.cpp */, @@ -3461,6 +3471,7 @@ E7901659298BCA97008FDB4E /* calcalignmentxposfunctor.h in Headers */, BDEF9ECC26725248008A3A47 /* caesura.h in Headers */, E7ADB3AB29D1921300825D5D /* adjustarticfunctor.h in Headers */, + 4D64793C2C98826600CD9539 /* iocmme.h in Headers */, E73E86252A069C640089DF74 /* transposefunctor.h in Headers */, 4DEC4DD921C8295700D1D273 /* rdg.h in Headers */, 4DB3D8D01F83D11800B5FC2B /* mordent.h in Headers */, @@ -3584,6 +3595,7 @@ 4D4CDEA82C079026005621E9 /* adjustneumexfunctor.h in Headers */, BB4C4B5222A932D7001F6AF0 /* ftrem.h in Headers */, E7E9C11329B0A1E200CFCE2F /* adjustaccidxfunctor.h in Headers */, + 4D64793D2C98826600CD9539 /* iocmme.h in Headers */, 4D88AD0B289673F50006D7DA /* symbol.h in Headers */, 4D1EB6A72A2A40CB00AF2F98 /* textlayoutelement.h in Headers */, 4D2E759022BC2B71004C51F0 /* course.h in Headers */, @@ -4110,6 +4122,7 @@ 40C2E4222052A6F60003625F /* pb.cpp in Sources */, 4DA0EAF322BB77C300A7EBEB /* facsimileinterface.cpp in Sources */, 4D1694241E3A44F300569BF4 /* positioninterface.cpp in Sources */, + 4D6479392C98825900CD9539 /* iocmme.cpp in Sources */, 4D1694251E3A44F300569BF4 /* timestamp.cpp in Sources */, 4D1694261E3A44F300569BF4 /* MidiEventList.cpp in Sources */, 4DA0EAC822BB779400A7EBEB /* facsimile.cpp in Sources */, @@ -4449,6 +4462,7 @@ 4D79642126C167100026288B /* pagemilestone.cpp in Sources */, 4D2073FB22A3BCE000E0765F /* tabgrp.cpp in Sources */, 4DEC4DBA21C8288900D1D273 /* choice.cpp in Sources */, + 4D6479372C9881B800CD9539 /* iocmme.cpp in Sources */, 4DACC9882990F29A00B55913 /* atts_mei.cpp in Sources */, 4D2073F522A3BCE000E0765F /* tuning.cpp in Sources */, 8F086F06188539540037FD8E /* view_beam.cpp in Sources */, @@ -4691,6 +4705,7 @@ 8F3DD35418854B2E0051330C /* slur.cpp in Sources */, 40C2E4232052A6F70003625F /* pb.cpp in Sources */, 4D64137A2035F58200BB630E /* pages.cpp in Sources */, + 4D64793A2C98825A00CD9539 /* iocmme.cpp in Sources */, 4D1694741E3A455200569BF4 /* humlib.cpp in Sources */, 4DB3D8C31F83D0EF00B5FC2B /* anchoredtext.cpp in Sources */, 4DB3D8E71F83D16A00B5FC2B /* ftrem.cpp in Sources */, @@ -4982,6 +4997,7 @@ BB4C4B6122A932D7001F6AF0 /* mrpt.cpp in Sources */, BB4C4AE722A932BC001F6AF0 /* damage.cpp in Sources */, 4DA0EACA22BB779400A7EBEB /* facsimile.cpp in Sources */, + 4D64793B2C98825A00CD9539 /* iocmme.cpp in Sources */, BB4C4B4B22A932D7001F6AF0 /* custos.cpp in Sources */, BB4C4BB822A932FC001F6AF0 /* Binasc.cpp in Sources */, BB4C4B7B22A932D7001F6AF0 /* tuplet.cpp in Sources */, diff --git a/include/vrv/alignfunctor.h b/include/vrv/alignfunctor.h index 2b9aa62733..a1728f2f31 100644 --- a/include/vrv/alignfunctor.h +++ b/include/vrv/alignfunctor.h @@ -12,9 +12,6 @@ namespace vrv { -class Mensur; -class MeterSig; - //---------------------------------------------------------------------------- // AlignMeterParams //---------------------------------------------------------------------------- @@ -22,8 +19,10 @@ class MeterSig; * Regroup pointers to meterSig, mensur and proport objects */ struct AlignMeterParams { - const MeterSig *meterSig; - const Mensur *mensur; + const MeterSig *meterSig = NULL; + const Mensur *mensur = NULL; + // Not const since we are cumulating proportion + Proport *proport = NULL; }; //---------------------------------------------------------------------------- diff --git a/include/vrv/drawinginterface.h b/include/vrv/drawinginterface.h index 49b85b6c42..59d1ed918e 100644 --- a/include/vrv/drawinginterface.h +++ b/include/vrv/drawinginterface.h @@ -14,6 +14,7 @@ #include "mensur.h" #include "metersig.h" #include "metersiggrp.h" +#include "proport.h" #include "vrvdef.h" namespace vrv { @@ -239,7 +240,7 @@ class StaffDefDrawingInterface { void SetDrawClef(bool drawClef) { m_drawClef = drawClef; } bool DrawKeySig() const { return (m_drawKeySig); } void SetDrawKeySig(bool drawKeySig) { m_drawKeySig = drawKeySig; } - bool DrawMensur() const { return (m_drawMensur && m_currentMensur.HasSign()); } + bool DrawMensur() const { return (m_drawMensur && (m_currentMensur.HasSign() || m_currentMensur.HasNum())); } void SetDrawMensur(bool drawMensur) { m_drawMensur = drawMensur; } bool DrawMeterSig() const { @@ -260,6 +261,7 @@ class StaffDefDrawingInterface { void SetCurrentMeterSig(const MeterSig *meterSig); void SetCurrentMeterSigGrp(const MeterSigGrp *meterSigGrp); void AlternateCurrentMeterSig(const Measure *measure); + void SetCurrentProport(const Proport *proport); ///@} /** @@ -277,6 +279,8 @@ class StaffDefDrawingInterface { const MeterSig *GetCurrentMeterSig() const { return &m_currentMeterSig; } MeterSigGrp *GetCurrentMeterSigGrp() { return &m_currentMeterSigGrp; } const MeterSigGrp *GetCurrentMeterSigGrp() const { return &m_currentMeterSigGrp; } + Proport *GetCurrentProport() { return &m_currentProport; } + const Proport *GetCurrentProport() const { return &m_currentProport; } ///@} private: @@ -290,6 +294,8 @@ class StaffDefDrawingInterface { MeterSig m_currentMeterSig; /** The meter signature group */ MeterSigGrp m_currentMeterSigGrp; + /** The proport */ + Proport m_currentProport; /** * @name Flags for indicating whether the clef, keysig and mensur needs to be drawn or not diff --git a/include/vrv/genericlayerelement.h b/include/vrv/genericlayerelement.h index 2a42252751..6b93f50a14 100644 --- a/include/vrv/genericlayerelement.h +++ b/include/vrv/genericlayerelement.h @@ -39,6 +39,12 @@ class GenericLayerElement : public LayerElement { */ std::string GetMEIName() const { return m_meiName; } + /** + * Return the MEI element original name + */ + std::string GetContent() { return m_content; } + void SetContent(std::string content) { m_content = content; } + //----------// // Functors // //----------// @@ -58,6 +64,8 @@ class GenericLayerElement : public LayerElement { std::string m_className; /** The MEI element name */ std::string m_meiName; + /** The MEI element content */ + std::string m_content; public: // diff --git a/include/vrv/horizontalaligner.h b/include/vrv/horizontalaligner.h index 30f470c7b2..887fe49e73 100644 --- a/include/vrv/horizontalaligner.h +++ b/include/vrv/horizontalaligner.h @@ -43,7 +43,9 @@ enum AlignmentType { ALIGNMENT_KEYSIG, ALIGNMENT_MENSUR, ALIGNMENT_METERSIG, + ALIGNMENT_PROPORT, ALIGNMENT_DOT, + ALIGNMENT_CUSTOS, ALIGNMENT_ACCID, ALIGNMENT_GRACENOTE, ALIGNMENT_BARLINE, diff --git a/include/vrv/iocmme.h b/include/vrv/iocmme.h new file mode 100644 index 0000000000..f74831ca7d --- /dev/null +++ b/include/vrv/iocmme.h @@ -0,0 +1,125 @@ +///////////////////////////////////////////////////////////////////////////// +// Name: iocmme.h +// Author: Laurent Pugin +// Created: 2024 +// Copyright (c) Authors and others. All rights reserved. +///////////////////////////////////////////////////////////////////////////// + +#ifndef __VRV_IOCMME_H__ +#define __VRV_IOCMME_H__ + +#include +#include +#include + +//---------------------------------------------------------------------------- + +#include "iobase.h" +#include "pugixml.hpp" +#include "vrvdef.h" + +namespace vrv { + +class Chord; +class Clef; +class KeySig; +class Layer; +class Measure; +class Note; +class Score; + +namespace cmme { + + struct mensInfo { + int prolatio = 2; + int tempus = 2; + int modusminor = 2; + int modusmaior = 2; + int proportNum = 1; + int proportDen = 1; + }; + +} // namespace cmme + +//---------------------------------------------------------------------------- +// CmmeInput +//---------------------------------------------------------------------------- + +class CmmeInput : public Input { +public: + // constructors and destructors + CmmeInput(Doc *doc); + virtual ~CmmeInput(); + + bool Import(const std::string &cmme) override; + +private: + void CreateSection(pugi::xml_node musicSectionNode); + void CreateStaff(pugi::xml_node voiceNode); + void CreateApp(pugi::xml_node appNode); + void CreateLemOrRdg(pugi::xml_node lemOrRdgNode, bool isFirst); + + void ReadEvents(pugi::xml_node eventsNode); + void ReadEditorialCommentary(pugi::xml_node evenNode, Object *object); + + void CreateAccid(pugi::xml_node accidNode); + void CreateBarline(pugi::xml_node barlineNode); + void CreateBreak(pugi::xml_node breakNode); + void CreateChord(pugi::xml_node chordNode); + void CreateClef(pugi::xml_node clefNode); + void CreateCustos(pugi::xml_node custosNode); + void CreateDot(pugi::xml_node dotNode); + void CreateEllipsis(); + void CreateKeySig(pugi::xml_node keyNode); + void CreateLacuna(pugi::xml_node lacunaNode); + void CreateMensuration(pugi::xml_node mensurationNode); + void CreateOriginalText(pugi::xml_node originalTextNode); + void CreateProport(pugi::xml_node proportNode); + void CreateNote(pugi::xml_node noteNode); + void CreateRest(pugi::xml_node restNode); + + void CreateVerse(pugi::xml_node verseNode); + + data_DURATION ReadDuration(pugi::xml_node durationNode, int &num, int &numbase) const; + bool IsClef(pugi::xml_node clefNode) const; + + /** + * Helper methods for accessing and converting text in elements. + * Return "" or VRV_UNSET if the node is NULL or the node or the text not found + */ + ///@{ + std::string AsString(const pugi::xml_node node) const; + std::string ChildAsString(const pugi::xml_node node, const std::string &child) const; + int AsInt(const pugi::xml_node node) const; + int ChildAsInt(const pugi::xml_node node, const std::string &child) const; + ///@} + +public: + // +private: + /** The current score (only one) */ + Score *m_score; + /** The current un-measured measure acting a a MEI section */ + Measure *m_currentSection; + /** The current layer (or container) to which the layer elements have to be added */ + Object *m_currentContainer; + /** The current key signature to which extra flats might be added */ + KeySig *m_currentSignature; + /** The current note */ + Note *m_currentNote; + /** The syllable is not the first */ + bool m_isInSyllable; + /** The mensural infos for all voices */ + std::vector m_mensInfos; + /** The mensural info for the current voice */ + cmme::mensInfo *m_mensInfo; + + /** The number of voices as given in the general data */ + int m_numVoices; + /** The name of the voices - if any */ + std::vector m_voices; +}; + +} // namespace vrv + +#endif diff --git a/include/vrv/layer.h b/include/vrv/layer.h index c450619d86..6b268a8e19 100644 --- a/include/vrv/layer.h +++ b/include/vrv/layer.h @@ -169,6 +169,8 @@ class Layer : public Object, const Mensur *GetCurrentMensur() const; MeterSig *GetCurrentMeterSig(); const MeterSig *GetCurrentMeterSig() const; + Proport *GetCurrentProport(); + const Proport *GetCurrentProport() const; ///@} void ResetStaffDefObjects(); diff --git a/include/vrv/proport.h b/include/vrv/proport.h index ad3db4c2f2..8aa0c240ca 100644 --- a/include/vrv/proport.h +++ b/include/vrv/proport.h @@ -34,6 +34,11 @@ class Proport : public LayerElement, public AttDurationRatio { std::string GetClassName() const override { return "Proport"; } ///@} + int GetCumulatedNum() const; + int GetCumulatedNumbase() const; + + void Cumulate(const Proport *proport); + /** Override the method since alignment is required */ bool HasToBeAligned() const override { return true; } @@ -52,6 +57,9 @@ class Proport : public LayerElement, public AttDurationRatio { public: // private: + /** the cumulated num and numbase */ + int m_cumulatedNum; + int m_cumulatedNumbase; }; } // namespace vrv diff --git a/include/vrv/setscoredeffunctor.h b/include/vrv/setscoredeffunctor.h index 60e3d9af41..6c4eaa18a1 100644 --- a/include/vrv/setscoredeffunctor.h +++ b/include/vrv/setscoredeffunctor.h @@ -136,6 +136,7 @@ class ScoreDefSetCurrentFunctor : public DocFunctor { FunctorCode VisitMeasure(Measure *measure) override; FunctorCode VisitMensur(Mensur *mensur) override; FunctorCode VisitPage(Page *page) override; + FunctorCode VisitProport(Proport *proport) override; FunctorCode VisitScore(Score *score) override; FunctorCode VisitScoreDef(ScoreDef *scoreDef) override; FunctorCode VisitStaff(Staff *staff) override; diff --git a/include/vrv/toolkitdef.h b/include/vrv/toolkitdef.h index 246a4c1bc7..254c844980 100644 --- a/include/vrv/toolkitdef.h +++ b/include/vrv/toolkitdef.h @@ -19,6 +19,7 @@ enum FileFormat { HUMMIDI, PAE, ABC, + CMME, DARMS, VOLPIANO, MUSICXML, diff --git a/src/adjustaccidxfunctor.cpp b/src/adjustaccidxfunctor.cpp index 7bc8b01512..cd57103ade 100644 --- a/src/adjustaccidxfunctor.cpp +++ b/src/adjustaccidxfunctor.cpp @@ -60,6 +60,9 @@ FunctorCode AdjustAccidXFunctor::VisitAlignmentReference(AlignmentReference *ali for (Accid *accid : accids) { // Skip any accid that was already adjusted if (m_adjustedAccids.count(accid) > 0) continue; + // Skip accid not descendant of a note (e.g., mensural) + if (!accid->GetFirstAncestor(NOTE)) continue; + auto range = octaveEquivalence.equal_range(accid); // Handle at least two octave accids without unisons int octaveAccidCount = 0; diff --git a/src/alignfunctor.cpp b/src/alignfunctor.cpp index f28c7baded..f838825000 100644 --- a/src/alignfunctor.cpp +++ b/src/alignfunctor.cpp @@ -18,6 +18,7 @@ #include "nc.h" #include "neume.h" #include "page.h" +#include "proport.h" #include "rend.h" #include "rest.h" #include "runningelement.h" @@ -41,8 +42,6 @@ AlignHorizontallyFunctor::AlignHorizontallyFunctor(Doc *doc) : DocFunctor(doc) { m_measureAligner = NULL; m_time = 0; - m_currentParams.mensur = NULL; - m_currentParams.meterSig = NULL; m_notationType = NOTATIONTYPE_cmn; m_scoreDefRole = SCOREDEF_NONE; m_isFirstMeasure = false; @@ -53,6 +52,7 @@ FunctorCode AlignHorizontallyFunctor::VisitLayer(Layer *layer) { m_currentParams.mensur = layer->GetCurrentMensur(); m_currentParams.meterSig = layer->GetCurrentMeterSig(); + m_currentParams.proport = layer->GetCurrentProport(); // We are starting a new layer, reset the time; // We set it to -1.0 for the scoreDef attributes since they have to be aligned before any timestamp event (-1.0) @@ -234,6 +234,16 @@ FunctorCode AlignHorizontallyFunctor::VisitLayerElement(LayerElement *layerEleme type = ALIGNMENT_SCOREDEF_METERSIG; } } + else if (layerElement->Is(PROPORT)) { + // replace the current proport + const Proport *previous = (m_currentParams.proport) ? (m_currentParams.proport) : NULL; + m_currentParams.proport = vrv_cast(layerElement); + assert(m_currentParams.proport); + if (previous) { + m_currentParams.proport->Cumulate(previous); + } + type = ALIGNMENT_PROPORT; + } else if (layerElement->Is({ MULTIREST, MREST, MRPT })) { type = ALIGNMENT_FULLMEASURE; } @@ -251,6 +261,9 @@ FunctorCode AlignHorizontallyFunctor::VisitLayerElement(LayerElement *layerEleme type = ALIGNMENT_DOT; } } + else if (layerElement->Is(CUSTOS)) { + type = ALIGNMENT_CUSTOS; + } else if (layerElement->Is(ACCID)) { // accid within note was already taken into account by noteParent type = ALIGNMENT_ACCID; diff --git a/src/drawinginterface.cpp b/src/drawinginterface.cpp index 09f209df45..1e35d7ef69 100644 --- a/src/drawinginterface.cpp +++ b/src/drawinginterface.cpp @@ -651,6 +651,14 @@ void StaffDefDrawingInterface::AlternateCurrentMeterSig(const Measure *measure) } } +void StaffDefDrawingInterface::SetCurrentProport(const Proport *proport) +{ + if (proport) { + m_currentProport = *proport; + m_currentProport.CloneReset(); + } +} + //---------------------------------------------------------------------------- // StemmedDrawingInterface //---------------------------------------------------------------------------- diff --git a/src/durationinterface.cpp b/src/durationinterface.cpp index 075c3ab2dd..00fa886757 100644 --- a/src/durationinterface.cpp +++ b/src/durationinterface.cpp @@ -136,9 +136,6 @@ Fraction DurationInterface::GetInterfaceAlignmentMensuralDuration( } // Any other case (minor, perfecta in tempus perfectum, and imperfecta in tempus imperfectum) follows the // mensuration and has no @num and @numbase attributes - if (currentMensur->HasNum()) num *= currentMensur->GetNum(); - if (currentMensur->HasNumbase()) numBase *= currentMensur->GetNumbase(); - int ratio = 0; Fraction duration(DURATION_breve); switch (noteDur) { diff --git a/src/iocmme.cpp b/src/iocmme.cpp new file mode 100644 index 0000000000..87f530c3e6 --- /dev/null +++ b/src/iocmme.cpp @@ -0,0 +1,1141 @@ +///////////////////////////////////////////////////////////////////////////// +// Name: iocmme.cpp +// Author: Laurent Pugin +// Created: 2024 +// Copyright (c) Authors and others. All rights reserved. +///////////////////////////////////////////////////////////////////////////// + +#include "iocmme.h" + +//---------------------------------------------------------------------------- + +#include +#include +#include +#include +#include + +//---------------------------------------------------------------------------- + +#include "accid.h" +#include "annot.h" +#include "app.h" +#include "barline.h" +#include "chord.h" +#include "clef.h" +#include "custos.h" +#include "doc.h" +#include "dot.h" +#include "genericlayerelement.h" +#include "keyaccid.h" +#include "keysig.h" +#include "label.h" +#include "layer.h" +#include "lem.h" +#include "ligature.h" +#include "mdiv.h" +#include "measure.h" +#include "mensur.h" +#include "note.h" +#include "proport.h" +#include "rdg.h" +#include "rest.h" +#include "score.h" +#include "section.h" +#include "space.h" +#include "staff.h" +#include "staffdef.h" +#include "staffgrp.h" +#include "supplied.h" +#include "syl.h" +#include "text.h" +#include "verse.h" +#include "vrv.h" + +//---------------------------------------------------------------------------- + +namespace vrv { + +//---------------------------------------------------------------------------- +// CmmeInput +//---------------------------------------------------------------------------- + +CmmeInput::CmmeInput(Doc *doc) : Input(doc) +{ + m_score = NULL; + m_currentSection = NULL; + m_currentContainer = NULL; + m_currentSignature = NULL; + m_currentNote = NULL; + m_isInSyllable = false; + m_mensInfo = NULL; +} + +CmmeInput::~CmmeInput() {} + +////////////////////////////////////////////////////////////////////////// + +bool CmmeInput::Import(const std::string &cmme) +{ + try { + m_doc->Reset(); + m_doc->SetType(Raw); + + // Genereate the header and add a comment to the project description + m_doc->GenerateMEIHeader(false); + pugi::xml_node projectDesc = m_doc->m_header.first_child().select_node("//projectDesc").node(); + if (projectDesc) { + pugi::xml_node p1 = projectDesc.append_child("p"); + p1.text().set("Converted from CMME XML"); + } + + pugi::xml_document doc; + doc.load_string(cmme.c_str(), (pugi::parse_comments | pugi::parse_default) & ~pugi::parse_eol); + pugi::xml_node root = doc.first_child(); + + // The mDiv + Mdiv *mdiv = new Mdiv(); + mdiv->m_visibility = Visible; + m_doc->AddChild(mdiv); + // The score + m_score = new Score(); + mdiv->AddChild(m_score); + + // We asssume that there is always as many Voice elements than given in NumVoices + pugi::xpath_node_set voices = root.select_nodes("/Piece/VoiceData/Voice"); + for (pugi::xpath_node voiceNode : voices) { + m_numVoices++; + // Get the voice name if any + std::string name = ChildAsString(voiceNode.node(), "Name"); + m_voices.push_back(name); + } + // Allocatate the mensural infos initialized with everything binary + m_mensInfos.resize(m_numVoices); + + pugi::xpath_node_set musicSections = root.select_nodes("/Piece/MusicSection/*"); + + for (pugi::xpath_node musicSectionNode : musicSections) { + CreateSection(musicSectionNode.node()); + } + + // add minimal scoreDef + StaffGrp *staffGrp = new StaffGrp(); + GrpSym *grpSym = new GrpSym(); + grpSym->SetSymbol(staffGroupingSym_SYMBOL_bracket); + staffGrp->AddChild(grpSym); + for (int i = 0; i < m_numVoices; ++i) { + StaffDef *staffDef = new StaffDef(); + staffDef->SetN(i + 1); + staffDef->SetLines(5); + staffDef->SetNotationtype(NOTATIONTYPE_mensural); + staffGrp->AddChild(staffDef); + // Label + if (!m_voices.at(i).empty()) { + Label *label = new Label(); + Text *text = new Text(); + text->SetText(UTF8to32(m_voices.at(i))); + label->AddChild(text); + staffDef->AddChild(label); + } + // Default mensur with everything binary in CMME + Mensur *mensur = new Mensur(); + mensur->SetProlatio(PROLATIO_2); + mensur->SetTempus(TEMPUS_2); + mensur->SetModusminor(MODUSMINOR_2); + mensur->SetModusmaior(MODUSMAIOR_2); + staffDef->AddChild(mensur); + } + + m_score->GetScoreDef()->AddChild(staffGrp); + + m_doc->ConvertToPageBasedDoc(); + } + catch (char *str) { + LogError("%s", str); + return false; + } + + return true; +} + +void CmmeInput::CreateSection(pugi::xml_node musicSectionNode) +{ + assert(m_score); + + std::string sectionType = musicSectionNode.name(); + + // Create a new section + Section *section = new Section(); + // Add the section type (MensuralMusic, Plainchant) to `@type` + section->SetType(sectionType); + m_score->AddChild(section); + + // Set the current section to an invisible unmeasured measure + m_currentSection = new Measure(UNMEASURED, 1); + section->AddChild(m_currentSection); + + // Loop through the number of voices and parse Voice or create an empty staff if not given + for (int i = 0; i < m_numVoices; ++i) { + std::string xpath = StringFormat("./Voice[VoiceNum[text()='%d']]", i + 1); + pugi::xpath_node voice = musicSectionNode.select_node(xpath.c_str()); + if (voice) { + CreateStaff(voice.node()); + } + else { + Staff *staff = new Staff(i + 1); + staff->SetVisible(BOOLEAN_false); + m_currentSection->AddChild(staff); + } + } +} + +void CmmeInput::CreateStaff(pugi::xml_node voiceNode) +{ + assert(m_currentSection); + + int numVoice = this->ChildAsInt(voiceNode, "VoiceNum"); + + Staff *staff = new Staff(numVoice); + Layer *layer = new Layer(); + layer->SetN(1); + m_currentContainer = layer; + + // (Re)-set the current mens info to the corresponding voice + m_mensInfo = &m_mensInfos.at(numVoice - 1); + // Reset the syllable position + m_isInSyllable = false; + m_currentSignature = NULL; + + staff->AddChild(m_currentContainer); + m_currentSection->AddChild(staff); + + // Loop through the event lists + ReadEvents(voiceNode.child("EventList")); +} + +void CmmeInput::CreateApp(pugi::xml_node appNode) +{ + assert(m_currentContainer); + + App *app = new App(EDITORIAL_LAYER); + m_currentContainer->AddChild(app); + m_currentContainer = app; + + // Loop through the event lists + pugi::xpath_node_set lemOrRdgs = appNode.select_nodes("./Reading"); + bool isFirst = true; + for (pugi::xpath_node lemOrRdg : lemOrRdgs) { + pugi::xml_node lemOrRdgNode = lemOrRdg.node(); + this->CreateLemOrRdg(lemOrRdgNode, isFirst); + isFirst = false; + } + + m_currentContainer = m_currentContainer->GetParent(); +} + +void CmmeInput::CreateLemOrRdg(pugi::xml_node lemOrRdgNode, bool isFirst) +{ + assert(m_currentContainer); + std::string versionId = this->ChildAsString(lemOrRdgNode, "VariantVersionID"); + + EditorialElement *lemOrRdg = NULL; + if (isFirst && (lemOrRdgNode.child("PreferredReading") || (versionId == "DEFAULT"))) { + lemOrRdg = new Lem(); + } + else { + lemOrRdg = new Rdg(); + } + lemOrRdg->m_visibility = (isFirst) ? Visible : Hidden; + + if (lemOrRdg->Is(RDG)) { + std::string label; + // Loop through the event lists + pugi::xpath_node_set variants = lemOrRdgNode.select_nodes("./VariantVersionID"); + bool isFirst = true; + for (pugi::xpath_node variant : variants) { + if (!isFirst) label += "; "; + label += this->AsString(variant.node()); + isFirst = false; + } + lemOrRdg->SetLabel(label); + } + + if (lemOrRdgNode.child("Error")) { + lemOrRdg->SetType("Error"); + } + else if (lemOrRdgNode.child("Lacuna")) { + lemOrRdg->SetType("Lacuna"); + } + + m_currentContainer->AddChild(lemOrRdg); + + m_currentContainer = lemOrRdg; + + ReadEvents(lemOrRdgNode.child("Music")); + + m_currentContainer = m_currentContainer->GetParent(); +} + +void CmmeInput::ReadEvents(pugi::xml_node eventsNode) +{ + assert(m_currentContainer); + + bool keySigFound = false; + + // Loop through the event lists + pugi::xpath_node_set events = eventsNode.select_nodes("./*"); + for (pugi::xpath_node event : events) { + pugi::xml_node eventNode = event.node(); + std::string name = eventNode.name(); + if (name == "Clef") { + if (this->IsClef(eventNode)) { + CreateClef(eventNode); + } + else if (eventNode.select_node("./Signature")) { + keySigFound = true; + CreateKeySig(eventNode); + } + else { + CreateAccid(eventNode); + } + } + else if (name == "Custos") { + CreateCustos(eventNode); + } + else if (name == "Dot") { + CreateDot(eventNode); + } + else if (name == "LineEnd") { + CreateBreak(eventNode); + } + else if (name == "Mensuration") { + CreateMensuration(eventNode); + } + else if (name == "MiscItem") { + /// Assuming that a MiscItem contains only one child + if (eventNode.select_node("./Barline")) { + pugi::xml_node barlineNode = eventNode.select_node("./Barline").node(); + CreateBarline(barlineNode); + } + else if (eventNode.child("Ellipsis")) { + CreateEllipsis(); + } + else if (eventNode.child("Lacuna")) { + CreateLacuna(eventNode.child("Lacuna")); + } + else { + LogWarning("Unsupported MiscItem content"); + } + } + else if (name == "MultiEvent") { + /// Assuming that a multievent contains a key signature, all events are key signatures + if (eventNode.select_node("./Clef/Signature")) { + m_currentSignature = NULL; + pugi::xpath_node_set clefs = eventNode.select_nodes("./Clef"); + for (pugi::xpath_node clef : clefs) { + pugi::xml_node clefNode = clef.node(); + CreateKeySig(clefNode); + } + } + else if (eventNode.select_node("./Note")) { + // Assuming that this only contains notes (and is a chord) + CreateChord(eventNode); + } + else { + LogWarning("Unsupported event '%s'", name.c_str()); + } + } + else if (name == "Note") { + CreateNote(eventNode); + } + else if (name == "OriginalText") { + CreateOriginalText(eventNode); + } + else if (name == "Proportion") { + CreateProport(eventNode); + } + else if (name == "Rest") { + CreateRest(eventNode); + } + else if (name == "VariantReadings") { + CreateApp(eventNode); + } + else { + LogWarning("Unsupported event '%s'", name.c_str()); + } + if (!keySigFound) { + m_currentSignature = NULL; + } + keySigFound = false; + } +} + +void CmmeInput::ReadEditorialCommentary(pugi::xml_node eventNode, Object *object) +{ + std::string commentary = this->ChildAsString(eventNode, "EditorialCommentary"); + + if (!commentary.empty()) { + Annot *annot = new Annot(); + Text *text = new Text(); + text->SetText(UTF8to32(commentary)); + annot->AddChild(text); + xsdAnyURI_List list; + list.push_back("#" + object->GetID()); + annot->SetPlist(list); + m_currentSection->AddChild(annot); + } +} + +void CmmeInput::CreateAccid(pugi::xml_node accidNode) +{ + static const std::map shapeMap{ + { "Bmol", ACCIDENTAL_WRITTEN_f }, // + { "BmolDouble", ACCIDENTAL_WRITTEN_f }, // + { "Bqua", ACCIDENTAL_WRITTEN_n }, // + { "Diesis", ACCIDENTAL_WRITTEN_s }, // + }; + + static const std::map pitchMap{ + { "C", PITCHNAME_c }, // + { "D", PITCHNAME_d }, // + { "E", PITCHNAME_e }, // + { "F", PITCHNAME_f }, // + { "G", PITCHNAME_g }, // + { "A", PITCHNAME_a }, // + { "B", PITCHNAME_b } // + }; + + assert(m_currentContainer); + + Accid *accid = new Accid(); + std::string appearance = this->ChildAsString(accidNode, "Appearance"); + data_ACCIDENTAL_WRITTEN accidWritten + = shapeMap.contains(appearance) ? shapeMap.at(appearance) : ACCIDENTAL_WRITTEN_f; + accid->SetAccid(accidWritten); + + std::string step = this->ChildAsString(accidNode, "Pitch/LetterName"); + // Default pitch to C + data_PITCHNAME ploc = pitchMap.contains(step) ? pitchMap.at(step) : PITCHNAME_c; + accid->SetPloc(ploc); + + int oct = this->ChildAsInt(accidNode, "Pitch/OctaveNum"); + if ((ploc != PITCHNAME_a) && (ploc != PITCHNAME_b)) oct += 1; + accid->SetOloc(oct); + + int staffLoc = this->ChildAsInt(accidNode, "StaffLoc"); + accid->SetLoc(staffLoc - 1); + + this->ReadEditorialCommentary(accidNode, accid); + + m_currentContainer->AddChild(accid); +} + +void CmmeInput::CreateBarline(pugi::xml_node barlineNode) +{ + assert(m_currentContainer); + + BarLine *barLine = new BarLine(); + + // Determine the barLine/@form based on the CMME 's and + int formNumLines = this->ChildAsInt(barlineNode, "NumLines"); + if (formNumLines == 1) { + barLine->SetForm(BARRENDITION_single); + } + else if (formNumLines == 2) { + barLine->SetForm(BARRENDITION_dbl); + } + else if (formNumLines != VRV_UNSET) { + LogWarning("Unsupported barline (with more than 2 lines)"); + } + + // `@form` is overwritten to 'rptboth' when is used + if (barlineNode.select_node("./RepeatSign")) { + barLine->SetForm(BARRENDITION_rptboth); + } + // Determine the barLine/@place + int bottomLine = this->ChildAsInt(barlineNode, "BottomStaffLine"); + if (bottomLine != VRV_UNSET) { + int place = bottomLine * 2; + barLine->SetPlace(place); + } + // Determine the barLine/@len + int numSpaces = this->ChildAsInt(barlineNode, "NumSpaces"); + if (numSpaces != VRV_UNSET) { + barLine->SetLen(numSpaces * 2); + } + + m_currentContainer->AddChild(barLine); +} + +void CmmeInput::CreateBreak(pugi::xml_node breakNode) +{ + assert(m_currentContainer); + + // This is either a system or page break (usually only + // in one part, so not easy to visualise in score) + if (breakNode.select_node("./PageEnd")) { + GenericLayerElement *pb = new GenericLayerElement("pb"); + m_currentContainer->AddChild(pb); + } + else { + GenericLayerElement *sb = new GenericLayerElement("sb"); + m_currentContainer->AddChild(sb); + } +} + +void CmmeInput::CreateChord(pugi::xml_node chordNode) +{ + assert(m_currentContainer); + + Chord *chord = new Chord(); + m_currentContainer->AddChild(chord); + m_currentContainer = chord; + pugi::xpath_node_set events = chordNode.select_nodes("./*"); + for (pugi::xpath_node event : events) { + pugi::xml_node eventNode = event.node(); + std::string name = eventNode.name(); + if (name == "Note") { + CreateNote(eventNode); + } + else { + LogWarning("Unsupported chord component: '%s'", name.c_str()); + } + } + m_currentContainer = m_currentContainer->GetParent(); +} + +void CmmeInput::CreateClef(pugi::xml_node clefNode) +{ + static const std::map shapeMap{ + { "C", CLEFSHAPE_C }, // + { "F", CLEFSHAPE_F }, // + { "G", CLEFSHAPE_G }, // + { "Frnd", CLEFSHAPE_F }, // + { "Fsqr", CLEFSHAPE_F }, // + }; + + assert(m_currentContainer); + + Clef *clef = new Clef(); + int staffLoc = this->ChildAsInt(clefNode, "StaffLoc"); + staffLoc = (staffLoc + 1) / 2; + clef->SetLine(staffLoc); + + std::string appearance = this->ChildAsString(clefNode, "Appearance"); + // Default clef to C + data_CLEFSHAPE shape = shapeMap.contains(appearance) ? shapeMap.at(appearance) : CLEFSHAPE_C; + clef->SetShape(shape); + + this->ReadEditorialCommentary(clefNode, clef); + + m_currentContainer->AddChild(clef); + + return; +} + +void CmmeInput::CreateCustos(pugi::xml_node custosNode) +{ + static const std::map pitchMap{ + { "C", PITCHNAME_c }, // + { "D", PITCHNAME_d }, // + { "E", PITCHNAME_e }, // + { "F", PITCHNAME_f }, // + { "G", PITCHNAME_g }, // + { "A", PITCHNAME_a }, // + { "B", PITCHNAME_b } // + }; + + assert(m_currentContainer); + + Custos *custos = new Custos(); + std::string step = this->ChildAsString(custosNode, "LetterName"); + // Default pitch to C + data_PITCHNAME pname = pitchMap.contains(step) ? pitchMap.at(step) : PITCHNAME_c; + custos->SetPname(pname); + + int oct = this->ChildAsInt(custosNode, "OctaveNum"); + if ((pname != PITCHNAME_a) && (pname != PITCHNAME_b)) oct += 1; + custos->SetOct(oct); + + this->ReadEditorialCommentary(custosNode, custos); + + m_currentContainer->AddChild(custos); + + return; +} + +void CmmeInput::CreateDot(pugi::xml_node dotNode) +{ + assert(m_currentContainer); + + Dot *dot = new Dot(); + m_currentContainer->AddChild(dot); + + this->ReadEditorialCommentary(dotNode, dot); + + return; +} + +void CmmeInput::CreateEllipsis() +{ + assert(m_currentContainer); + + GenericLayerElement *gap = new GenericLayerElement("gap"); + gap->SetType("cmme_ellipsis"); + gap->m_unsupported.push_back(std::make_pair("reason", "incipit")); + m_currentContainer->AddChild(gap); +} + +void CmmeInput::CreateKeySig(pugi::xml_node keyNode) +{ + static const std::map shapeMap{ + { "Bmol", ACCIDENTAL_WRITTEN_f }, // + { "BmolDouble", ACCIDENTAL_WRITTEN_f }, // + { "Bqua", ACCIDENTAL_WRITTEN_n }, // + { "Diesis", ACCIDENTAL_WRITTEN_s }, // + }; + + static const std::map pitchMap{ + { "C", PITCHNAME_c }, // + { "D", PITCHNAME_d }, // + { "E", PITCHNAME_e }, // + { "F", PITCHNAME_f }, // + { "G", PITCHNAME_g }, // + { "A", PITCHNAME_a }, // + { "B", PITCHNAME_b } // + }; + + assert(m_currentContainer); + + if (!m_currentSignature) { + m_currentSignature = new KeySig(); + m_currentContainer->AddChild(m_currentSignature); + } + + KeyAccid *keyAccid = new KeyAccid(); + std::string appearance = this->ChildAsString(keyNode, "Appearance"); + data_ACCIDENTAL_WRITTEN accid = shapeMap.contains(appearance) ? shapeMap.at(appearance) : ACCIDENTAL_WRITTEN_f; + keyAccid->SetAccid(accid); + + std::string step = this->ChildAsString(keyNode, "Pitch/LetterName"); + // Default pitch to C + data_PITCHNAME pname = pitchMap.contains(step) ? pitchMap.at(step) : PITCHNAME_c; + keyAccid->SetPname(pname); + + int oct = this->ChildAsInt(keyNode, "Pitch/OctaveNum"); + if ((pname != PITCHNAME_a) && (pname != PITCHNAME_b)) oct += 1; + keyAccid->SetOct(oct); + + int staffLoc = this->ChildAsInt(keyNode, "StaffLoc"); + keyAccid->SetLoc(staffLoc - 1); + + this->ReadEditorialCommentary(keyNode, keyAccid); + + m_currentSignature->AddChild(keyAccid); +} + +void CmmeInput::CreateLacuna(pugi::xml_node lacunaNode) +{ + // A lacuna is used in CMME to pad a part where + // the scribe's version is temporally incomplete. + // We use mei:space, but since this is not explicit + // in the source, we wrap it in mei:supplied + assert(m_currentContainer); + Space *space = new Space(); + Supplied *supplied = new Supplied(); + supplied->AddChild(space); + int num; + int numbase; + data_DURATION duration = this->ReadDuration(lacunaNode, num, numbase); + space->SetDur(duration); + space->SetType("cmme_lacuna"); + supplied->SetType("cmme_lacuna"); + m_currentContainer->AddChild(supplied); +} + +void CmmeInput::CreateMensuration(pugi::xml_node mensurationNode) +{ + assert(m_currentContainer); + assert(m_mensInfo); + + pugi::xml_node mensInfo = mensurationNode.child("MensInfo"); + if (mensInfo) { + m_mensInfo->prolatio = this->ChildAsInt(mensInfo, "Prolatio"); + m_mensInfo->tempus = this->ChildAsInt(mensInfo, "Tempus"); + m_mensInfo->modusminor = this->ChildAsInt(mensInfo, "ModusMinor"); + m_mensInfo->modusmaior = this->ChildAsInt(mensInfo, "ModusMaior"); + } + // If there is no then resets everything to binary + else { + m_mensInfo->prolatio = 2; + m_mensInfo->tempus = 2; + m_mensInfo->modusminor = 2; + m_mensInfo->modusmaior = 2; + } + + /// Mensuration: logical domain + Mensur *mensur = new Mensur(); + data_PROLATIO prolatio = (m_mensInfo->prolatio == 3) ? PROLATIO_3 : PROLATIO_2; + mensur->SetProlatio(prolatio); + data_TEMPUS tempus = (m_mensInfo->tempus == 3) ? TEMPUS_3 : TEMPUS_2; + mensur->SetTempus(tempus); + data_MODUSMINOR modusminor = (m_mensInfo->modusminor == 3) ? MODUSMINOR_3 : MODUSMINOR_2; + mensur->SetModusminor(modusminor); + data_MODUSMAIOR modusmaior = (m_mensInfo->modusmaior == 3) ? MODUSMAIOR_3 : MODUSMAIOR_2; + mensur->SetModusmaior(modusmaior); + + /// Mensuration: visual domain + /// Mensuration/Sign/MainSymbol to @sign + pugi::xml_node signNode = mensurationNode.child("Sign"); + std::string signValue = this->ChildAsString(signNode, "MainSymbol"); + if (signValue == "O") { + mensur->SetSign(MENSURATIONSIGN_O); + } + else if (signValue == "C") { + mensur->SetSign(MENSURATIONSIGN_C); + } + else if (signValue != "") { + LogWarning("Unsupported mesuration sign in CMME (not 'O' or 'C')"); + } + + /// Mensuration/Sign/Dot to @dot + if (signNode.child("Dot")) { + mensur->SetDot(BOOLEAN_true); + } + + /// Mensuration/Sign/Strokes to @slash + int strokes = this->ChildAsInt(signNode, "Strokes"); + if (strokes != VRV_UNSET) { + mensur->SetSlash(strokes); + } + + /// Mensuration/Sign/Orientation to @orient + static const std::map orientationMap{ + { "Reversed", ORIENTATION_reversed }, // + { "90CW", ORIENTATION_90CW }, // + { "90CCW", ORIENTATION_90CCW } // + }; + std::string orientation = this->ChildAsString(signNode, "Orientation"); + data_ORIENTATION orient = orientationMap.contains(orientation) ? orientationMap.at(orientation) : ORIENTATION_NONE; + mensur->SetOrient(orient); + + /// Mensuration/Small to @fontsize=small (not yet rendered in Verovio). + /// In the long run, we should add @size to att.mensur.vis because we have @mensur.size for , see class + /// att.mensural.vis + pugi::xml_node smallNode = mensurationNode.child("Small"); + if (smallNode != NULL) { + mensur->m_unsupported.push_back(std::make_pair("fontsize", "small")); + } + + /// Mesuration/NoScoreEffect to @type = cmme_no_score_effect + pugi::xml_node noScoreEffect = mensurationNode.child("NoScoreEffect"); + if (noScoreEffect != NULL) { + mensur->SetType("cmme_no_score_effect"); + } + + /// Mensuration/Number/Num to @num and Number/Den to @numbase + /// However, Number/Den cannot be entered in the CMME Editor. + /// It can only be added in the XML manually and imported into the CMME Editor, + /// where it won't render, but one can see it in the "Event Inspector." + pugi::xml_node numberNode = mensurationNode.child("Number"); + if (numberNode != NULL) { + int numValue = this->ChildAsInt(numberNode, "Num"); + int denValue = this->ChildAsInt(numberNode, "Den"); + if (numValue != VRV_UNSET and numValue != 0) { + mensur->SetNum(numValue); + } + if (denValue != VRV_UNSET and denValue != 0) { + mensur->SetNumbase(denValue); + } + } + + /// Mensuration/StaffLoc to @loc + int staffLoc = this->ChildAsInt(mensurationNode, "StaffLoc"); + if (staffLoc != VRV_UNSET) { + mensur->SetLoc(staffLoc); + } + + this->ReadEditorialCommentary(mensurationNode, mensur); + + m_currentContainer->AddChild(mensur); + + /// Proportion part coming from CMME in in . In this case, create an MEI + /// element that follows the MEI element and that contains the proport/@num and + /// proport/@numbase values of 'num' and 'den' + pugi::xml_node tempoChangeNode = mensInfo.child("TempoChange"); + if (tempoChangeNode != NULL) { + Proport *proport = new Proport(); + int numVal = this->ChildAsInt(tempoChangeNode, "Num"); + int denVal = this->ChildAsInt(tempoChangeNode, "Den"); + if (numVal != VRV_UNSET) { + proport->SetNum(numVal); + } + if (denVal != VRV_UNSET) { + proport->SetNumbase(denVal); + } + proport->SetType("cmme_tempo_change"); + m_currentContainer->AddChild(proport); + } + + return; +} + +void CmmeInput::CreateNote(pugi::xml_node noteNode) +{ + static const std::map pitchMap{ + { "C", PITCHNAME_c }, // + { "D", PITCHNAME_d }, // + { "E", PITCHNAME_e }, // + { "F", PITCHNAME_f }, // + { "G", PITCHNAME_g }, // + { "A", PITCHNAME_a }, // + { "B", PITCHNAME_b } // + }; + + static const std::map accidMap{ + { -3, ACCIDENTAL_WRITTEN_tf }, // + { -2, ACCIDENTAL_WRITTEN_ff }, // + { -1, ACCIDENTAL_WRITTEN_f }, // + { 0, ACCIDENTAL_WRITTEN_n }, // + { 1, ACCIDENTAL_WRITTEN_s }, // + { 2, ACCIDENTAL_WRITTEN_ss }, // + { 3, ACCIDENTAL_WRITTEN_sx }, // + }; + + static const std::map stemDirMap{ + { "Up", STEMDIRECTION_up }, // + { "Down", STEMDIRECTION_down }, // + { "Left", STEMDIRECTION_left }, // + { "Right", STEMDIRECTION_right }, // + }; + + assert(m_currentContainer); + + Note *note = new Note(); + std::string step = this->ChildAsString(noteNode, "LetterName"); + // Default pitch to C + data_PITCHNAME pname = pitchMap.contains(step) ? pitchMap.at(step) : PITCHNAME_c; + note->SetPname(pname); + + int num; + int numbase; + data_DURATION duration = this->ReadDuration(noteNode, num, numbase); + note->SetDur(duration); + if (num != VRV_UNSET && numbase != VRV_UNSET) { + note->SetNum(num); + note->SetNumbase(numbase); + } + + int oct = this->ChildAsInt(noteNode, "OctaveNum"); + if ((pname != PITCHNAME_a) && (pname != PITCHNAME_b)) oct += 1; + note->SetOct(oct); + + if (noteNode.child("Colored")) { + note->SetColored(BOOLEAN_true); + } + + if (noteNode.child("ModernText")) { + m_currentNote = note; + CreateVerse(noteNode.child("ModernText")); + m_currentNote = NULL; + } + + if (noteNode.child("Corona")) { + data_STAFFREL_basic position = STAFFREL_basic_above; + if (noteNode.select_node("Corona/Orientation[text()='Down']")) { + position = STAFFREL_basic_below; + } + note->SetFermata(position); + } + + if (noteNode.child("ModernAccidental")) { + Accid *accid = new Accid(); + int offset = this->ChildAsInt(noteNode.child("ModernAccidental"), "PitchOffset"); + offset = std::min(3, offset); + offset = std::max(-3, offset); + // Default pitch to C + data_ACCIDENTAL_WRITTEN accidWritten = accidMap.contains(offset) ? accidMap.at(offset) : ACCIDENTAL_WRITTEN_n; + accid->SetAccid(accidWritten); + accid->SetFunc(accidLog_FUNC_edit); + note->AddChild(accid); + } + + if (noteNode.child("Signum")) { + // MEI currently lacks signum congruentiae, so we warn and set not type + LogWarning("Signum Congruentiae in CMME mapped to @type"); + note->SetType("cmme_signum_congruentiae"); + } + + if (noteNode.child("Stem")) { + std::string dir = this->ChildAsString(noteNode.child("Stem"), "Dir"); + if (dir == "Barline") { + LogWarning("Unsupported 'Barline' stem direction"); + } + data_STEMDIRECTION stemDir = stemDirMap.contains(dir) ? stemDirMap.at(dir) : STEMDIRECTION_NONE; + note->SetStemDir(stemDir); + + std::string side = this->ChildAsString(noteNode.child("Stem"), "Side"); + if (side == "Left") { + note->SetStemPos(STEMPOSITION_left); + } + else if (side == "Right") { + note->SetStemPos(STEMPOSITION_right); + } + } + + if (noteNode.child("Lig")) { + std::string lig = this->ChildAsString(noteNode, "Lig"); + if (lig == "Retrorsum") { + LogWarning("Unsupported 'Retrorsum' ligature"); + } + data_LIGATUREFORM form = (lig == "Obliqua") ? LIGATUREFORM_obliqua : LIGATUREFORM_recta; + // First note of the ligature, create the ligature element + if (!m_currentContainer->Is(LIGATURE)) { + if (m_currentContainer->Is(CHORD)) { + LogWarning("Ligature within chord is not supported"); + } + else { + Ligature *ligature = new Ligature(); + ligature->SetForm(form); + m_currentContainer->AddChild(ligature); + m_currentContainer = ligature; + } + } + // Otherwise simply add the `@lig` to the note + else { + note->SetLig(form); + } + } + + this->ReadEditorialCommentary(noteNode, note); + + m_currentContainer->AddChild(note); + + // We have processed the last note of a ligature + if (m_currentContainer->Is(LIGATURE) && !noteNode.child("Lig")) { + m_currentContainer = m_currentContainer->GetParent(); + } + + return; +} + +void CmmeInput::CreateOriginalText(pugi::xml_node originalTextNode) +{ + return; +} + +void CmmeInput::CreateProport(pugi::xml_node proportNode) +{ + assert(m_currentContainer); + assert(m_mensInfo); + + /// Proportion part coming from CMME . In this case, create an MEI element is created alone + /// (not following an MEI element) + Proport *proport = new Proport(); + int numVal = this->ChildAsInt(proportNode, "Num"); + int denVal = this->ChildAsInt(proportNode, "Den"); + if (numVal != VRV_UNSET) { + proport->SetNum(numVal); + // Cumulated it + m_mensInfo->proportNum *= numVal; + } + if (denVal != VRV_UNSET) { + proport->SetNumbase(denVal); + // Cumulated it + m_mensInfo->proportDen *= denVal; + } + Fraction::Reduce(m_mensInfo->proportNum, m_mensInfo->proportDen); + proport->SetType("cmme_proportion"); + m_currentContainer->AddChild(proport); + return; +} + +void CmmeInput::CreateRest(pugi::xml_node restNode) +{ + assert(m_currentContainer); + + Rest *rest = new Rest(); + int num; + int numbase; + data_DURATION duration = this->ReadDuration(restNode, num, numbase); + rest->SetDur(duration); + if (num != VRV_UNSET && numbase != VRV_UNSET) { + rest->SetNum(num); + rest->SetNumbase(numbase); + } + + this->ReadEditorialCommentary(restNode, rest); + + if (restNode.child("Signum")) { + // MEI currently lacks signum congruentiae, so we warn and set not type + LogWarning("Signum Congruentiae in CMME mapped to @type"); + rest->SetType("cmme_signum_congruentiae"); + } + + m_currentContainer->AddChild(rest); + + return; +} + +void CmmeInput::CreateVerse(pugi::xml_node verseNode) +{ + assert(m_currentNote); + + Verse *verse = new Verse(); + verse->SetN(1); + Syl *syl = new Syl(); + Text *text = new Text(); + std::string sylText = this->ChildAsString(verseNode, "Syllable"); + text->SetText(UTF8to32(sylText)); + + if (verseNode.child("WordEnd")) { + syl->SetWordpos(sylLog_WORDPOS_t); + m_isInSyllable = false; + } + else { + if (m_isInSyllable) { + syl->SetWordpos(sylLog_WORDPOS_m); + } + else { + syl->SetWordpos(sylLog_WORDPOS_i); + } + m_isInSyllable = true; + syl->SetCon(sylLog_CON_d); + } + + syl->AddChild(text); + verse->AddChild(syl); + m_currentNote->AddChild(verse); +} + +data_DURATION CmmeInput::ReadDuration(pugi::xml_node durationNode, int &num, int &numbase) const +{ + static const std::map durationMap{ + { "Maxima", DURATION_maxima }, // + { "Longa", DURATION_longa }, // + { "Brevis", DURATION_brevis }, // + { "Semibrevis", DURATION_semibrevis }, // + { "Minima", DURATION_minima }, // + { "Semiminima", DURATION_semiminima }, // + { "Fusa", DURATION_fusa }, // + { "Semifusa", DURATION_semifusa } // + }; + + assert(m_mensInfo); + + std::string type = this->ChildAsString(durationNode, "Type"); + // Default duration to brevis + data_DURATION duration = durationMap.contains(type) ? durationMap.at(type) : DURATION_brevis; + + num = VRV_UNSET; + numbase = VRV_UNSET; + + if (durationNode.child("Length")) { + int cmmeNum = this->ChildAsInt(durationNode.child("Length"), "Num"); + int cmmeDen = this->ChildAsInt(durationNode.child("Length"), "Den"); + + if ((cmmeNum == VRV_UNSET) || (cmmeDen == VRV_UNSET)) { + return duration; + } + + // Apply the proportion + cmmeNum *= m_mensInfo->proportNum; + cmmeDen *= m_mensInfo->proportDen; + + std::pair ratio = { 1, 1 }; + + if (type == "Maxima") { + ratio.first *= m_mensInfo->modusmaior * m_mensInfo->modusminor * m_mensInfo->tempus * m_mensInfo->prolatio; + } + else if (type == "Longa") { + ratio.first *= m_mensInfo->modusminor * m_mensInfo->tempus * m_mensInfo->prolatio; + } + else if (type == "Brevis") { + ratio.first *= m_mensInfo->tempus * m_mensInfo->prolatio; + } + else if (type == "Semibrevis") { + ratio.first *= m_mensInfo->prolatio; + } + else if (type == "Semiminima") { + ratio.second = 2; + } + else if (type == "Fusa") { + ratio.second = 4; + } + else if (type == "Semifusa") { + ratio.second = 8; + } + + cmmeNum *= ratio.second; + cmmeDen *= ratio.first; + + // MEI num and numabase are cmme den and num respectively + num = cmmeDen; + numbase = cmmeNum; + + Fraction::Reduce(num, numbase); + + if (num == numbase) { + num = VRV_UNSET; + numbase = VRV_UNSET; + } + } + + return duration; +} + +bool CmmeInput::IsClef(const pugi::xml_node clefNode) const +{ + static std::vector clefs = { "C", "F", "Fsqr", "Frnd", "G" }; + + // Checking this is not enough since it is somethimes missing in CMME files + if (clefNode.select_node("./Signature")) return false; + + // Also check the clef appearance + std::string appearance = this->ChildAsString(clefNode, "Appearance"); + return (std::find(clefs.begin(), clefs.end(), appearance) != clefs.end()); +} + +std::string CmmeInput::AsString(const pugi::xml_node node) const +{ + if (!node) return ""; + + if (node.text()) { + return std::string(node.text().as_string()); + } + return ""; +} + +std::string CmmeInput::ChildAsString(const pugi::xml_node node, const std::string &child) const +{ + if (!node) return ""; + + pugi::xpath_node childNode = node.select_node(child.c_str()); + if (childNode.node()) { + return this->AsString(childNode.node()); + } + return ""; +} + +int CmmeInput::AsInt(const pugi::xml_node node) const +{ + if (!node) return VRV_UNSET; + + if (node.text()) { + return (node.text().as_int()); + } + return VRV_UNSET; +} + +int CmmeInput::ChildAsInt(const pugi::xml_node node, const std::string &child) const +{ + if (!node) return VRV_UNSET; + + pugi::xpath_node childNode = node.select_node(child.c_str()); + if (childNode.node()) { + return this->AsInt(childNode.node()); + } + return VRV_UNSET; +} + +} // namespace vrv diff --git a/src/iomei.cpp b/src/iomei.cpp index 2fb82d8e41..1483969fbc 100644 --- a/src/iomei.cpp +++ b/src/iomei.cpp @@ -2522,6 +2522,13 @@ void MEIOutput::WriteGenericLayerElement(pugi::xml_node currentNode, GenericLaye currentNode.set_name(element->GetMEIName().c_str()); + // Reparse the original content stored as a string document + pugi::xml_document content; + content.load_string(element->GetContent().c_str()); + for (pugi::xml_node child : content.first_child().children()) { + currentNode.append_copy(child); + } + this->WriteLayerElement(currentNode, element); } @@ -2796,6 +2803,8 @@ void MEIOutput::WriteProport(pugi::xml_node currentNode, Proport *proport) assert(proport); this->WriteLayerElement(currentNode, proport); + + proport->WriteDurationRatio(currentNode); } void MEIOutput::WriteQuilisma(pugi::xml_node currentNode, Quilisma *quilisma) @@ -6263,6 +6272,9 @@ bool MEIInput::ReadLayerChildren(Object *parent, pugi::xml_node parentNode, Obje else if (elementName == "fTrem") { success = this->ReadFTrem(parent, xmlElement); } + else if (elementName == "gap") { + success = this->ReadGenericLayerElement(parent, xmlElement); + } else if (elementName == "graceGrp") { success = this->ReadGraceGrp(parent, xmlElement); } @@ -6656,6 +6668,13 @@ bool MEIInput::ReadGenericLayerElement(Object *parent, pugi::xml_node element) GenericLayerElement *vrvElement = new GenericLayerElement(element.name()); this->ReadLayerElement(element, vrvElement); + // Store the content as a string document + pugi::xml_document content; + content.append_copy(element); + std::ostringstream oss; + content.save(oss); + vrvElement->SetContent(oss.str()); + parent->AddChild(vrvElement); this->ReadUnsupportedAttr(element, vrvElement); return true; diff --git a/src/layer.cpp b/src/layer.cpp index eaa30b8be3..54d9f1145b 100644 --- a/src/layer.cpp +++ b/src/layer.cpp @@ -542,6 +542,18 @@ const MeterSig *Layer::GetCurrentMeterSig() const return staff->m_drawingStaffDef->GetCurrentMeterSig(); } +Proport *Layer::GetCurrentProport() +{ + return const_cast(std::as_const(*this).GetCurrentProport()); +} + +const Proport *Layer::GetCurrentProport() const +{ + const Staff *staff = vrv_cast(this->GetFirstAncestor(STAFF)); + assert(staff && staff->m_drawingStaffDef); + return staff->m_drawingStaffDef->GetCurrentProport(); +} + void Layer::SetDrawingStaffDefValues(StaffDef *currentStaffDef) { if (!currentStaffDef) { diff --git a/src/layerelement.cpp b/src/layerelement.cpp index dbf3b45ba8..9351443a93 100644 --- a/src/layerelement.cpp +++ b/src/layerelement.cpp @@ -51,6 +51,7 @@ #include "neume.h" #include "note.h" #include "page.h" +#include "proport.h" #include "rest.h" #include "slur.h" #include "smufl.h" @@ -677,6 +678,18 @@ Fraction LayerElement::GetAlignmentDuration( return Fraction(0, 1); } + // Mensural chords are aligned looking at the duration of the notes + if (this->Is(CHORD) && IsMensuralType(notationType)) { + Fraction duration = 0; + ListOfConstObjects notes = this->FindAllDescendantsByType(NOTE); + for (const Object *object : notes) { + const Note *note = vrv_cast(object); + Fraction noteDuration = note->GetAlignmentDuration(params, notGraceOnly, notationType); + duration = std::max(duration, noteDuration); + } + return duration; + } + // Only resolve simple sameas links to avoid infinite recursion const LayerElement *sameas = dynamic_cast(this->GetSameasLink()); if (sameas && !sameas->HasSameasLink()) { @@ -686,6 +699,13 @@ Fraction LayerElement::GetAlignmentDuration( if (this->HasInterface(INTERFACE_DURATION)) { int num = 1; int numbase = 1; + + if (params.proport) { + // Proportion are applied reversly - higher ratio means shorter values + if (params.proport->HasNum()) num *= params.proport->GetCumulatedNum(); + if (params.proport->HasNumbase()) numbase *= params.proport->GetCumulatedNumbase(); + } + const Tuplet *tuplet = vrv_cast(this->GetFirstAncestor(TUPLET, MAX_TUPLET_DEPTH)); if (tuplet) { ListOfConstObjects objects; @@ -764,8 +784,6 @@ Fraction LayerElement::GetAlignmentDuration( Fraction LayerElement::GetAlignmentDuration(bool notGraceOnly, data_NOTATIONTYPE notationType) const { AlignMeterParams params; - params.meterSig = NULL; - params.mensur = NULL; return this->GetAlignmentDuration(params, notGraceOnly, notationType); } @@ -807,8 +825,6 @@ Fraction LayerElement::GetContentAlignmentDuration( Fraction LayerElement::GetContentAlignmentDuration(bool notGraceOnly, data_NOTATIONTYPE notationType) const { AlignMeterParams params; - params.meterSig = NULL; - params.mensur = NULL; return this->GetContentAlignmentDuration(params, notGraceOnly, notationType); } diff --git a/src/midifunctor.cpp b/src/midifunctor.cpp index a3b4a4c8fa..7f2f42555b 100644 --- a/src/midifunctor.cpp +++ b/src/midifunctor.cpp @@ -44,8 +44,6 @@ InitOnsetOffsetFunctor::InitOnsetOffsetFunctor() : Functor() { m_currentScoreTime = 0; m_currentRealTimeSeconds = 0.0; - m_meterParams.mensur = NULL; - m_meterParams.meterSig = NULL; m_notationType = NOTATIONTYPE_cmn; m_currentTempo = MIDI_TEMPO; } diff --git a/src/options.cpp b/src/options.cpp index 395e53b47c..b978eac5fd 100644 --- a/src/options.cpp +++ b/src/options.cpp @@ -914,7 +914,8 @@ Options::Options() m_baseOptions.AddOption(&m_allPages); m_inputFrom.SetInfo("Input from", - "Select input format from: \"abc\", \"darms\", \"esac\", \"humdrum\", \"mei\", \"pae\", \"volpiano\", \"xml\" " + "Select input format from: \"abc\", \"cmme.xml\", \"darms\", \"esac\", \"humdrum\", \"mei\", \"pae\", " + "\"volpiano\", \"xml\" " "(musicxml), \"musicxml-hum\" (musicxml via humdrum)"); m_inputFrom.Init("mei"); m_inputFrom.SetKey("inputFrom"); diff --git a/src/proport.cpp b/src/proport.cpp index 0ee5336561..44466ca8a5 100644 --- a/src/proport.cpp +++ b/src/proport.cpp @@ -32,6 +32,33 @@ void Proport::Reset() { LayerElement::Reset(); this->ResetDurationRatio(); + + m_cumulatedNum = VRV_UNSET; + m_cumulatedNumbase = VRV_UNSET; +} + +int Proport::GetCumulatedNum() const +{ + return (m_cumulatedNum != VRV_UNSET) ? m_cumulatedNum : this->GetNum(); +} + +int Proport::GetCumulatedNumbase() const +{ + return (m_cumulatedNumbase != VRV_UNSET) ? m_cumulatedNumbase : this->GetNumbase(); +} + +void Proport::Cumulate(const Proport *proport) +{ + // Unset values are not cumulated + if (proport->HasNum() && this->HasNum()) { + m_cumulatedNum = this->GetNum() * proport->GetCumulatedNum(); + } + if (proport->HasNumbase() && this->HasNumbase()) { + m_cumulatedNumbase = this->GetNumbase() * proport->GetCumulatedNumbase(); + } + if ((m_cumulatedNum != VRV_UNSET) && (m_cumulatedNumbase != VRV_UNSET)) { + Fraction::Reduce(m_cumulatedNum, m_cumulatedNumbase); + } } FunctorCode Proport::Accept(Functor &functor) diff --git a/src/setscoredeffunctor.cpp b/src/setscoredeffunctor.cpp index 7eaeee62d0..bfb13ce8e2 100644 --- a/src/setscoredeffunctor.cpp +++ b/src/setscoredeffunctor.cpp @@ -230,6 +230,15 @@ FunctorCode ScoreDefSetCurrentFunctor::VisitPage(Page *page) return FUNCTOR_CONTINUE; } +FunctorCode ScoreDefSetCurrentFunctor::VisitProport(Proport *proport) +{ + assert(m_currentStaffDef); + StaffDef *upcomingStaffDef = m_upcomingScoreDef.GetStaffDef(m_currentStaffDef->GetN()); + assert(upcomingStaffDef); + upcomingStaffDef->SetCurrentProport(proport); + return FUNCTOR_CONTINUE; +} + FunctorCode ScoreDefSetCurrentFunctor::VisitScore(Score *score) { m_currentScore = score; diff --git a/src/toolkit.cpp b/src/toolkit.cpp index 2d314b5cb5..0d031038af 100644 --- a/src/toolkit.cpp +++ b/src/toolkit.cpp @@ -23,6 +23,7 @@ #include "filereader.h" #include "findfunctor.h" #include "ioabc.h" +#include "iocmme.h" #include "iodarms.h" #include "iohumdrum.h" #include "iomei.h" @@ -205,6 +206,9 @@ bool Toolkit::SetInputFrom(std::string const &inputFrom) else if (inputFrom == "volpiano") { m_inputFrom = VOLPIANO; } + else if (inputFrom == "cmme.xml") { + m_inputFrom = CMME; + } else if ((inputFrom == "humdrum") || (inputFrom == "hum")) { m_inputFrom = HUMDRUM; } @@ -295,6 +299,9 @@ FileFormat Toolkit::IdentifyInputFrom(const std::string &data) if (std::regex_search(initial, std::regex("<(!DOCTYPE )?(score-partwise|opus|score-timewise)[\\s\\n>]"))) { return musicxmlDefault; } + if (std::regex_search(initial, std::regex("<(Piece xmlns=\"http://www.cmme.org\")[\\s\\n>]"))) { + return CMME; + } LogWarning("Warning: Trying to load unknown XML data which cannot be identified."); return UNKNOWN; } @@ -563,6 +570,9 @@ bool Toolkit::LoadData(const std::string &data, bool resetLogBuffer) else if (inputFormat == VOLPIANO) { input = new VolpianoInput(&m_doc); } + else if (inputFormat == CMME) { + input = new CmmeInput(&m_doc); + } #ifndef NO_HUMDRUM_SUPPORT else if (inputFormat == HUMDRUM) { // LogInfo("Importing Humdrum data"); diff --git a/src/view_mensural.cpp b/src/view_mensural.cpp index 5222b8d916..6973520115 100644 --- a/src/view_mensural.cpp +++ b/src/view_mensural.cpp @@ -85,7 +85,7 @@ void View::DrawMensur(DeviceContext *dc, LayerElement *element, Layer *layer, St Mensur *mensur = vrv_cast(element); assert(mensur); - if (!mensur->HasSign()) { + if (!mensur->HasSign() && !mensur->HasNum()) { // only react to visual attributes return; } @@ -562,39 +562,12 @@ void View::DrawProportFigures(DeviceContext *dc, int x, int y, int num, int numB void View::DrawProport(DeviceContext *dc, LayerElement *element, Layer *layer, Staff *staff, Measure *measure) { + assert(element); assert(layer); assert(staff); - assert(dynamic_cast(element)); // Element must be a Proport" - - int x1, x2, y1, y2; - - Proport *proport = dynamic_cast(element); dc->StartGraphic(element, "", element->GetID()); - int y = staff->GetDrawingY() - (m_doc->GetDrawingUnit(staff->m_drawingStaffSize) * 4); - int x = element->GetDrawingX(); - - x1 = x + 120; - x2 = x1 + 150; // ??TEST: JUST DRAW AN ARBITRARY RECTANGLE - y1 = y; - y2 = y + 50 + (50 * proport->GetNum()); - // DrawFilledRectangle(dc,x1,y1,x2,y2); - this->DrawPartFilledRectangle(dc, x1, y1, x2, y2, 0); - - if (proport->HasNum()) { - x = element->GetDrawingX(); - // if (proport->GetSign() || proport->HasTempus()) // ??WHAT SHOULD THIS BE? - { - x += m_doc->GetDrawingUnit(staff->m_drawingStaffSize) - * 5; // step forward because we have a sign or a meter symbol - } - int numbase = proport->HasNumbase() ? proport->GetNumbase() : 0; - this->DrawProportFigures(dc, x, - staff->GetDrawingY() - m_doc->GetDrawingUnit(staff->m_drawingStaffSize) * (staff->m_drawingLines - 1), - proport->GetNum(), numbase, staff); - } - dc->EndGraphic(element, this); }