{"id":16867000,"url":"https://github.com/webern/mx","last_synced_at":"2025-07-12T15:07:45.941Z","repository":{"id":24224319,"uuid":"27616590","full_name":"webern/mx","owner":"webern","description":"C++ binding for MusicXML.","archived":false,"fork":false,"pushed_at":"2023-06-25T02:09:24.000Z","size":39254,"stargazers_count":88,"open_issues_count":18,"forks_count":35,"subscribers_count":9,"default_branch":"master","last_synced_at":"2025-04-02T04:11:16.221Z","etag":null,"topics":["c-plus-plus","music","music-notation","musicxml","xml","xml-dom"],"latest_commit_sha":null,"homepage":null,"language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/webern.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2014-12-06T00:41:21.000Z","updated_at":"2024-12-29T15:43:26.000Z","dependencies_parsed_at":"2024-10-27T12:23:34.087Z","dependency_job_id":null,"html_url":"https://github.com/webern/mx","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/webern/mx","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webern%2Fmx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webern%2Fmx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webern%2Fmx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webern%2Fmx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/webern","download_url":"https://codeload.github.com/webern/mx/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webern%2Fmx/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265009213,"owners_count":23697154,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["c-plus-plus","music","music-notation","musicxml","xml","xml-dom"],"created_at":"2024-10-13T14:52:21.888Z","updated_at":"2025-07-12T15:07:45.918Z","avatar_url":"https://github.com/webern.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"MusicXML Class Library\r\n======================\r\n\r\n- Author: Matthew James Briggs\r\n- License: MIT\r\n- Version: 1.0\r\n- Supported MusicXML Version: 3.0\r\n- Language: C++17\r\n\r\n-----------------------------------------\r\n\r\n[![CircleCI](https://circleci.com/gh/webern/mx.svg?style=svg)](https://circleci.com/gh/webern/mx)\r\n\r\n## Introduction\r\n\r\nThis project is a C++ library for working with MusicXML.\r\n\r\n# Build\r\n\r\nBuilding and running tests should be as simple as:\r\n\r\n```\r\ngit clone https://github.com/webern/mx.git mx\r\nmkdir build\r\ncd build\r\ncmake ../mx -DMX_BUILD_TESTS=on -DMX_BUILD_CORE_TESTS=off -DMX_BUILD_EXAMPLES=on\r\nmake -j6\r\n./mxtest\r\n```\r\n\r\n### Cmake Options\r\n\r\nThere are three `cmake` options:\r\n\r\n```\r\n    -DMX_BUILD_TESTS=on\r\n    -DMX_BUILD_CORE_TESTS=off\r\n    -DMX_BUILD_EXAMPLES=on\r\n```\r\n\r\nThe configuration shown above is the recommended configuration for development.\r\nIf you just need the lib then turn off all three of the `cmake` options.\r\nThe 'core tests' take a long time to compile.\r\nYou only need to run them if you make changes in the `mx::core` namespace.\r\n\r\n### Build Tenets\r\n\r\n* `mx` should not depend on any outside libraries (no deps).\r\n* `mx` third-party code should be kept to a minimum.\r\n* `mx` third-party code should be checked into the `mx` repo and compiled as part of the `mx` library.\r\n* `mx` should not depened on any package manager, though it may be published into any package manager.\r\n\r\n### Using `mx` in a Cmake Project\r\n\r\nThe following script demonstrates how you can start a new cmake project that uses `mx` by commiting its sourcecode into your project:\r\n\r\n```sh\r\n#!/bin/bash\r\nse -eou pipefail\r\n\r\n# this script demonstrates how to depend on mx by including it in your\r\n# sourcecode tree.\r\n\r\n# if given, the first argument is a path to directory where the new\r\n# project will be created.\r\nREPO=\"${1:-/tmp/$(uuidgen)}\"\r\necho \"Creating a new project in: ${REPO}\"\r\n\r\n# create a new git repository for your project\r\nmkdir -p \"${REPO}\"\r\ncd \"${REPO}\"\r\ngit init\r\n# bring the mx sourcecode into your project into a temporary location\r\ngit clone https://github.com/webern/mx.git mxtemp\r\n\r\n# copy only what we need.  all we need is the Sourcode directory, the\r\n# cmake file, the license, and the .gitignore file (helpful since there\r\n# is one generated file.)\r\nmkdir mx\r\nmv mxtemp/Sourcecode mx/Sourcecode\r\nmv mxtemp/.gitignore mx/.gitignore\r\nmv mxtemp/LICENSE.txt mx/LICENSE.txt\r\nmv mxtemp/CMakeLists.txt mx/CMakeLists.txt\r\n# we don't need the test code, either\r\nrm -rf mx/Sourcecode/private/mxtest\r\nrm -rf mxtemp\r\n\r\n# commit the mx sourcecode to our project repo\r\ngit add --all \u0026\u0026 git commit -m'mx sourcecode'\r\n\r\n# create a main.cpp file\r\ncat \u003c\u003c- \"EOF\" \u003e main.cpp\r\n#include \u003ciostream\u003e\r\n#include \"mx/api/ScoreData.h\"\r\n#include \"mx/api/DocumentManager.h\"\r\n\r\nint main () {\r\n    using namespace mx::api;\r\n    ScoreData score{};\r\n    score.workTitle = \"Hello World\";\r\n    NoteData note{};\r\n    note.durationData.durationName = DurationName::quarter;\r\n    note.pitchData.step = Step::d;\r\n    VoiceData voiceData{};\r\n    voiceData.notes.push_back(note);\r\n    StaffData staff{};\r\n    staff.voices[0] = voiceData;\r\n    MeasureData measure{};\r\n    measure.staves.push_back(staff);\r\n    PartData part{};\r\n    part.measures.push_back(measure);\r\n    score.parts.push_back(part);\r\n    auto\u0026 mgr = DocumentManager::getInstance();\r\n    const auto id = mgr.createFromScore(score);\r\n    mgr.writeToStream(id, std::cout);\r\n    mgr.destroyDocument(id);\r\n}\r\nEOF\r\n\r\n# create a cmake file\r\ncat \u003c\u003c- \"EOF\" \u003e CMakeLists.txt\r\ncmake_minimum_required(VERSION 3.17)\r\nproject(my-musicxml-proj)\r\nset(CMAKE_CXX_STANDARD 17)\r\nset(CPP_VERSION 17)\r\n\r\nadd_executable(my-musicxml-proj main.cpp)\r\nadd_subdirectory(mx)\r\ntarget_link_libraries(my-musicxml-proj mx)\r\ntarget_include_directories(my-musicxml-proj PRIVATE mx/Sourcecode/include)\r\nEOF\r\n\r\n# create a .gitignore file to ignore a build directory\r\ncat \u003c\u003c- \"EOF\" \u003e .gitignore\r\nbuild/\r\nEOF\r\n\r\ngit add --all \u0026\u0026 git commit -m'musicxml hello world'\r\n\r\n# create a build directory\r\nmkdir build\r\n\r\n# build your project\r\ncd build\r\ncmake .. \u0026\u0026 make -j10\r\n# run your executable\r\n./my-musicxml-proj\r\n```\r\n\r\n### Xcode Project\r\n\r\nThe Xcode project (checked-in to the repo) has targets for iOS and macOS frameworks and dylibs.\r\nThese are not specified in the cmake file.\r\nContributors are not required to keep the Xcode project up-to-date.\r\nIf you add, move or remove files from the codebase, it is likely that the Xcode CI run will fail.\r\nThis will not prevent a contribution from being merged, the maintainer will fix the project after-the-fact.\r\n\r\n\r\n# Using `mx`\r\n\r\n## API\r\n\r\nThe `mx::api` namespace is intended to be a simplified structural representation of MusicXML.\r\nIt should be more intuitive than manipulating the DOM representation directly.\r\nIn particular, voices and time positions are more explicitly managed.\r\nSome complexities, on the other hand, are retained in `mx::api`, such as the need to manage beam starts and stops explicitly.\r\n\r\n#### Writing MusicXML with `mx::api`\r\n\r\n```C++\r\n#include \u003cstring\u003e\r\n#include \u003ciostream\u003e\r\n#include \u003ccstdint\u003e\r\n#include \u003csstream\u003e\r\n\r\n#include \"mx/api/DocumentManager.h\"\r\n#include \"mx/api/ScoreData.h\"\r\n\r\n// set this to 1 if you want to see the xml in your console\r\n#define MX_WRITE_THIS_TO_THE_CONSOLE 0\r\n\r\nint main(int argc, const char * argv[])\r\n{\r\n    using namespace mx::api;\r\n    const auto qticks = 4;\r\n\r\n    // create a score\r\n    auto score = ScoreData{};\r\n    score.workTitle = \"Mx Example\";\r\n    score.composer = \"Matthew James Briggs\";\r\n    score.copyright = \"Copyright (c) 2019\";\r\n    score.ticksPerQuarter = qticks;\r\n\r\n    // create a part\r\n    score.parts.emplace_back( PartData{} );\r\n    auto\u0026 part = score.parts.back();\r\n\r\n    // give the part a name\r\n    part.name = \"Flute\";\r\n    part.abbreviation = \"Fl.\";\r\n    part.displayName = \"Flute\";\r\n    part.displayAbbreviation = \"Fl.\";\r\n\r\n    // give the part an instrument\r\n    part.instrumentData.soundID = SoundID::windFlutesFlute;\r\n    part.instrumentData.midiData.channel = 1;\r\n    part.instrumentData.midiData.program = 74;\r\n\r\n    // add a measure\r\n    part.measures.emplace_back( MeasureData{} );\r\n    auto\u0026 measure = part.measures.back();\r\n    measure.timeSignature.beats = 4;\r\n    measure.timeSignature.beatType = 4;\r\n    measure.timeSignature.isImplicit = false;\r\n\r\n    // add a staff\r\n    measure.staves.emplace_back( StaffData{} );\r\n    auto\u0026 staff = measure.staves.back();\r\n\r\n    // set the clef\r\n    auto clef = ClefData{};\r\n    clef.setTreble();\r\n    staff.clefs.emplace_back( clef );\r\n\r\n    // add a voice\r\n    staff.voices[0] = VoiceData{};\r\n    auto\u0026 voice = staff.voices.at( 0 );\r\n\r\n    const auto quarter = qticks;\r\n    const auto half = qticks * 2;\r\n    const auto eighth = qticks / 2;\r\n\r\n    // add a few notes\r\n    auto currentTime = 0;\r\n    auto note = NoteData{};\r\n    note.pitchData.step = Step::d;\r\n    note.pitchData.alter = 1;\r\n    note.pitchData.octave = 5;\r\n    note.pitchData.accidental = Accidental::sharp;\r\n    note.durationData.durationName = DurationName::half;\r\n    note.durationData.durationTimeTicks = half;\r\n    note.tickTimePosition = currentTime;\r\n    voice.notes.push_back( note );\r\n\r\n    // advance our time\r\n    currentTime += half;\r\n\r\n    note.pitchData.step = Step::e;\r\n    note.pitchData.alter = 0;\r\n    note.pitchData.octave = 5;\r\n    note.pitchData.accidental = Accidental::none;\r\n    note.durationData.durationName = DurationName::eighth;\r\n    note.durationData.durationTimeTicks = eighth;\r\n    note.tickTimePosition = currentTime;\r\n    // beams are handled explicitly in musicxml\r\n    note.beams.push_back( Beam::begin ); // start an eighth-note beam\r\n    voice.notes.push_back( note );\r\n    currentTime += eighth;\r\n\r\n    note.pitchData.step = Step::f;\r\n    note.pitchData.alter = 0;\r\n    note.pitchData.octave = 5;\r\n    note.pitchData.accidental = Accidental::none;\r\n    note.durationData.durationName = DurationName::eighth;\r\n    note.tickTimePosition = currentTime;\r\n    note.durationData.durationTimeTicks = eighth;\r\n    note.beams.clear();\r\n    note.beams.push_back( Beam::end ); // end the eighth-note beam\r\n    voice.notes.push_back( note );\r\n    currentTime += eighth;\r\n\r\n    note.pitchData.step = Step::e;\r\n    note.pitchData.alter = 0;\r\n    note.pitchData.octave = 5;\r\n    note.pitchData.accidental = Accidental::none;\r\n    note.durationData.durationName = DurationName::quarter;\r\n    note.durationData.durationTimeTicks = quarter;\r\n    note.tickTimePosition = currentTime;\r\n    note.beams.clear();\r\n    voice.notes.push_back( note );\r\n\r\n    // the document manager is the liaison between our score data and the MusicXML DOM.\r\n    // it completely hides the MusicXML DOM from us when using mx::api\r\n    auto\u0026 mgr = DocumentManager::getInstance();\r\n    const auto documentID = mgr.createFromScore( score );\r\n\r\n    // write to the console\r\n    #if MX_WRITE_THIS_TO_THE_CONSOLE\r\n    mgr.writeToStream( documentID, std::cout );\r\n    std::cout \u003c\u003c std::endl;\r\n    #endif\r\n\r\n    // write to a file\r\n    mgr.writeToFile( documentID, \"./example.musicxml\" );\r\n\r\n    // we need to explicitly delete the object held by the manager\r\n    mgr.destroyDocument( documentID );\r\n\r\n    return 0;\r\n}\r\n```\r\n\r\n#### Reading MusicXML with `mx::api`\r\n\r\n```C++\r\n#include \"mx/api/DocumentManager.h\"\r\n#include \"mx/api/ScoreData.h\"\r\n\r\n#include \u003cstring\u003e\r\n#include \u003ciostream\u003e\r\n#include \u003ccstdint\u003e\r\n#include \u003csstream\u003e\r\n\r\n#define MX_IS_A_SUCCESS 0\r\n#define MX_IS_A_FAILURE 1\r\n\r\nconstexpr const char* const xml = R\"(\r\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?\u003e\r\n\u003c!DOCTYPE score-partwise PUBLIC\r\n    \"-//Recordare//DTD MusicXML 3.1 Partwise//EN\"\r\n    \"http://www.musicxml.org/dtds/partwise.dtd\"\u003e\r\n\u003cscore-partwise version=\"3.1\"\u003e\r\n  \u003cpart-list\u003e\r\n    \u003cscore-part id=\"P1\"\u003e\r\n      \u003cpart-name\u003eMusic\u003c/part-name\u003e\r\n    \u003c/score-part\u003e\r\n  \u003c/part-list\u003e\r\n  \u003cpart id=\"P1\"\u003e\r\n    \u003cmeasure number=\"1\"\u003e\r\n      \u003cattributes\u003e\r\n        \u003cdivisions\u003e1\u003c/divisions\u003e\r\n        \u003ckey\u003e\r\n          \u003cfifths\u003e0\u003c/fifths\u003e\r\n        \u003c/key\u003e\r\n        \u003ctime\u003e\r\n          \u003cbeats\u003e4\u003c/beats\u003e\r\n          \u003cbeat-type\u003e4\u003c/beat-type\u003e\r\n        \u003c/time\u003e\r\n        \u003cclef\u003e\r\n          \u003csign\u003eG\u003c/sign\u003e\r\n          \u003cline\u003e2\u003c/line\u003e\r\n        \u003c/clef\u003e\r\n      \u003c/attributes\u003e\r\n      \u003cnote\u003e\r\n        \u003cpitch\u003e\r\n          \u003cstep\u003eC\u003c/step\u003e\r\n          \u003coctave\u003e4\u003c/octave\u003e\r\n        \u003c/pitch\u003e\r\n        \u003cduration\u003e4\u003c/duration\u003e\r\n        \u003ctype\u003ewhole\u003c/type\u003e\r\n      \u003c/note\u003e\r\n    \u003c/measure\u003e\r\n  \u003c/part\u003e\r\n\u003c/score-partwise\u003e\r\n)\";\r\n\r\nint main(int argc, const char * argv[])\r\n{\r\n    using namespace mx::api;\r\n\r\n    // create a reference to the singleton which holds documents in memory for us\r\n    auto\u0026 mgr = DocumentManager::getInstance();\r\n\r\n    // place the xml from above into a stream object\r\n    std::istringstream istr{ xml };\r\n\r\n    // ask the document manager to parse the xml into memory for us, returns a document ID.\r\n    const auto documentID = mgr.createFromStream( istr );\r\n\r\n    // get the structural representation of the score from the document manager\r\n    const auto score = mgr.getData( documentID );\r\n\r\n    // we need to explicitly destroy the document from memory\r\n    mgr.destroyDocument(documentID);\r\n\r\n    // make sure we have exactly one part\r\n    if( score.parts.size() != 1 )\r\n    {\r\n        return MX_IS_A_FAILURE;\r\n    }\r\n\r\n    // drill down into the data structure to retrieve the note\r\n    const auto\u0026 part = score.parts.at( 0 );\r\n    const auto\u0026 measure = part.measures.at( 0 );\r\n    const auto\u0026 staff = measure.staves.at( 0 );\r\n    const auto\u0026 voice = staff.voices.at( 0 );\r\n    const auto\u0026 note = voice.notes.at( 0 );\r\n\r\n    if( note.durationData.durationName != DurationName::whole )\r\n    {\r\n        return MX_IS_A_FAILURE;\r\n    }\r\n\r\n    if( note.pitchData.step != Step::c )\r\n    {\r\n        return MX_IS_A_FAILURE;\r\n    }\r\n\r\n    return MX_IS_A_SUCCESS;\r\n}\r\n```\r\n\r\n# Implementation Details\r\n\r\nThe MusicXML classes in `mx::core` are tightly bound to the `musicxml.xsd` specification.\r\nMusicXML can be challenging to use and the `mx::core` class structure mirrors the complexity of the MusicXML specification.\r\nA simplified representation is available in `mx::api`.\r\nIt is possible to work with a subset of MusicXML using only `mx::api`, without delving into `mx::core`.\r\n\r\n##### Namespaces\r\n\r\n```C++\r\nusing namespace mx::api;    // an easier interface for reading and writing MusicXML\r\nusing namespace mx::core;   // a direct representation of a musicxml document in C++ classes\r\nusing namespace mx::impl    // the logic that translates between mx::api and mx::core\r\nusing namespace mx::utility // a typical catch-all for generic stuff like logging macros\r\nusing namespace ezxml;      // generic serialization and deserialization of xml\r\n```\r\n\r\n##### `mx::api`\r\n\r\nThe `mx::api` namespace is a set of 'plain old data' structs that represent a simplified model of MusicXML.\r\nFor example, here is the `ScoreData.h`, which represents the top level of the object heirarchy:\r\n\r\n```C++\r\nclass ScoreData\r\n{\r\npublic:\r\n    MusicXmlVersion musicXmlVersion;\r\n    std::string musicXmlType;\r\n    std::string workTitle;\r\n    std::string workNumber;\r\n    std::string movementTitle;\r\n    std::string movementNumber;\r\n    std::string composer;\r\n    std::string lyricist;\r\n    std::string arranger;\r\n    std::string publisher;\r\n    std::string copyright;\r\n    EncodingData encoding;\r\n    std::vector\u003cPageTextData\u003e pageTextItems;\r\n    DefaultsData defaults;\r\n    std::vector\u003cPartData\u003e parts;\r\n    std::vector\u003cPartGroupData\u003e partGroups;\r\n    int ticksPerQuarter;\r\n    std::map\u003cMeasureIndex, LayoutData\u003e layout;\r\n};\r\n```\r\n\r\n`mx::api` and `mx::core` are kept completely separate.\\\r\nThat is, `mx::api` data is serialized into `mx::core` data, which is then serialized into MusicXML.\r\nThe `mx::api` struct allow us to simplify things like specifying a note's tick time position, and allowing the serialization process to take care of details such as `\u003cforward\u003e` `\u003cbackup\u003e` elements.\r\n\r\n##### `mx::core`\r\n\r\nThe `mx::core` namespace contains the MusicXML representation objects such as elements and attributes.\r\n`mx::core` was mostly generated from `musicxml.xsd` with plenty of intervention by hand.\r\n\r\n###### XML Choices and Groups\r\n\r\nIn the `musicxml.xsd` there are many cases of `xs:choice` or `xs:group` being used.\r\nThese constructs are typically represented in the `mx::core` class structure the same way that they are found in the `musicxml.xsd` specification.\r\nThe interfaces in this namespace are relatively stable, however they are tightly bound to MusicXML's specification and thus they will change when it comes time to support a future version of MusicXML.\r\n\r\n##### `mx::impl`\r\n\r\n`mx::impl` is the translation layer between `mx::api` and `mx::core`.\r\n\r\n##### `mx::utility`\r\n\r\nThis namespace is small.\r\nIt mostly contains macros and small, generic functions.\r\n\r\n##### `ezxml`\r\n\r\nThe `ezxml` namespace contains generic XML DOM functionality.\r\nUnder the hood [pugixml](http://pugixml.org/) is being used.\r\nSee the XML DOM section for more information.\r\nNote that, even though `ezxml` can stand alone as a useful abstraction, we build it as if it were entirely owned by the `mx` project.\r\nAdditionally, we check the `pugixml` library in and build it as if it were part of the `mx` project.\r\nThis is in keeping with the build tenets [above](#build-tenets)\r\n\r\n##### Partwise vs. Timewise\r\nThere are two types of MusicXML documents, `partwise` and `timewise`.\r\nA partwise document consists of a set of parts which contain measures.\r\nA timewise document consists of a set of measures which contain parts.\r\nPartwise is used more often by MusicXML applications while Timewise documents seem to be rare or even nonresistant.\r\nNonetheless *MusicXML Class Library* implements both Timewise and Partwise.\r\nThe class `mx::core::Document` can hold *either* a Partwise *or* a Timewise score.\r\nNote that it actually holds both, but only one or the other is 'active' (this is similar to how `xsd` `choice` constructs are handled).\r\nYou can check the inner document type with the getChoice function.\r\nYou can convert between Partwise and Timewise with the convertContents function.\r\n\r\n##### Elements\r\nEach XML element is represented by a class which derives from ElementInterface.\r\nElements are created and used by way of shared pointers.\r\nEach element comes with a set of using/typedef statements as well as a convenience function for making the shared pointers.\r\n\r\n##### Shared Pointers\r\nMany elements contain other elements.\r\nWhen they do, these data members will also be shared pointers.\r\nGet/set functions will allow access to the data members by accepting and returning shared pointers.\r\nIf you attempt to set a data member to a nullptr, the setter function will silently do nothing.\r\nThus we can be reasonably assured our objects will never return nullptr.\r\n\r\nFor example\r\n\r\n```C++\r\nstd::shared_ptr\u003cFoo\u003e foo; /* nullptr! */\r\nbar-\u003esetFoo( foo );       /* no-op because you passed a nullptr */\r\nauto x = bar-\u003egetFoo();   /* guaranteed not to be null */\r\nx-\u003esomefuntion();         /* OK to dereference without checking for nullptr */\r\n```\r\n\r\n##### Optional Member Data\r\nMany of the elements in MusicXML are optional.\r\nIn these cases there is a bool which indicates whether or not the element is present.\r\nThe bool serves as a flag indicating whether or not the optional element will be output when you stream out your MusicXML document.\r\nThe bool has no side-effect on the element who's presence/absence it represents.\r\nSo for example we may set some data:\r\n\r\n```C++\r\nfoo-\u003esetValue( \"hello\" );\r\nbar-\u003esetFoo( foo );\r\n```\r\n\r\nBut in this example, if Foo is an optional member of Bar, then we must also set hasFoo to *true* or else foo will not be in the XML output.\r\n\r\n```C++\r\nbar-\u003etoStream(...); /* Foo is not in the output! */\r\nbar-\u003esetHasFoo( true );\r\nbar-\u003etoStream(...); /* Now we see \u003cfoo\u003ehello\u003c/foo\u003e in the output. */\r\n```\r\n\r\nAlso note that setting HasFoo to *false* does not mean that Foo's value is gone.\r\n\r\n```C++\r\nbar-\u003esetHasFoo( false ); /* The XML document no longer has a Foo */\r\nbar-\u003egetFoo()-\u003egetValue() == \"hello\"; /* True! The value still exists but is not present in the XML. */\r\n```\r\n\r\n##### Optional Member Data with Unbounded Occurrences\r\nSometimes an element may contain zero, one, or many occurrences of another element.\r\nFor example\r\n\r\n```xml\r\n\u003cxs:element name=\"key\" type=\"key\" minOccurs=\"0\" maxOccurs=\"unbounded\"\u003e\r\n```\r\n\r\nIn this case there will be a collection of Key objects and the getter/setters will look like this, where `KeySet` is a typedef of `std::vector\u003cKey\u003e`.\r\n\r\n```C++\r\nconst KeySet\u0026 getKeySet() const;\r\nvoid addKey( const KeyPtr\u0026 value );\r\nvoid removeKey( const KeySetIterConst\u0026 value );\r\nvoid clearKeySet();\r\nKeyPtr getKey( const KeySetIterConst\u0026 setIterator ) const;\r\n```\r\n\r\n##### Required Member Data with Unbounded Occurrences\r\nSometimes an element is required, but you may optionally have more than one.\r\nFor example\r\n\r\n```xml\r\n\u003cxs:element name=\"direction-type\" type=\"direction-type\" maxOccurs=\"unbounded\"/\u003e\r\n```\r\n\r\nIn this case, minOccurs=\"1\" (by default per XSD language rules).\r\nThe functions will look just like the previous example, but they will behave differently\r\n\r\n```C++\r\nconst DirectionTypeSet\u0026 getDirectionTypeSet() const;\r\nvoid addDirectionType( const DirectionTypePtr\u0026 value );\r\nvoid removeDirectionType( const DirectionTypeSetIterConst\u0026 value );\r\nvoid clearDirectionTypeSet();\r\nDirectionTypePtr getDirectionType( const DirectionTypeSetIterConst\u0026 setIterator ) const;\r\n```\r\n\r\nWhen the containing element is constructed, a single DirectionType will be default constructed and pushed onto the vector.\r\nThus you will have one default constructed DirectionType in the set upon construction.\r\n\r\nIf you try to call removeDirectionType with only one DirectionType in the set (size==1) nothing will happen.\r\nYou will still have a single DirectionType in the collection.\r\n\r\nWhen you call clearDirectionTypeSet vector.clear() will be called but it will follow up by pushing a default constructed DirectionType onto the vector so you will still have size==1.\r\n\r\nAs it turns out, this design choice tends to be annoying in practice.\r\nOn the upside, it does guarantee that your MusicXML document will be valid, even if you forget to add a required element.\r\nThe downside is that it means you have to deal with the fact that a default constructed element always exists in the set, so you must replace or remove the first element.\r\nFurthermore, you cannot remove the existing element until another one has been added.\r\nHere are the two patterns I have used for this (pseudocode).\r\n\r\n**Pattern 1:** Replace the first element by dereferencing the begin() iterator:\r\n\r\n```C++\r\nbool isFirstAdded = false;\r\nfor( auto stuffElement : stuffElementsIWantToAdd )\r\n{\r\n    if( !isFirstAdded )\r\n    {\r\n        *( myElementIWantToAddThemTo-\u003egetStuffSet().begin() ) = stuffElement;\r\n        isFirstAdded = true;\r\n    }\r\n    else\r\n    {\r\n        myElementIWantToAddThemTo-\u003eaddStuff( stuffElement );\r\n    }\r\n}\r\n```\r\n\r\n**Pattern 2:** Remove the default element *After* adding a replacement:\r\n\r\n```C++\r\nbool isFirstAdded = false;\r\nfor( auto stuffElement : stuffIWantToAdd )\r\n{\r\n    myElementIWantToAddThemTo-\u003eaddStuff( stuffElement );\r\n    if( !isFirstAdded )\r\n    {\r\n        myElementIWantToAddThemTo-\u003eremoveStuff( myElementIWantToAddThemTo-\u003egetStuffSet().cbegin() )\r\n        isFirstAdded = true;\r\n    }\r\n}\r\n```\r\n\r\nPattern 1 always works, even if you're not sure whether or not the `minOccurs=\"1\"` or `\"0\"`.\r\nPattern 2 only works when `minOccurs=\"1\"`.\r\nThere are no cases where `minOccurs` is greater than 1.\r\n\r\n##### Member Data with Bounded maxOccurs\r\n```xml\r\n\u003cxs:element name=\"beam\" type=\"beam\" minOccurs=\"0\" maxOccurs=\"8\"/\u003e\r\n```\r\nIn this case if you call addBeam when there are already 8 beams in the vector, nothing will happen.\r\n\r\n##### xs:groups\r\nFor an xs:group there is usually a single 'element' class which represents the group of elements.\r\nFor example this XSD snippet:\r\n\r\n```xml\r\n\u003cxs:group name=\"editorial\"\u003e\r\n\t\u003cxs:sequence\u003e\r\n\t\t\u003cxs:group ref=\"footnote\" minOccurs=\"0\"/\u003e\r\n\t\t\u003cxs:group ref=\"level\" minOccurs=\"0\"/\u003e\r\n\t\u003c/xs:sequence\u003e\r\n\u003c/xs:group\u003e\r\n```\r\n\r\nis represented by this class:\r\n\r\n```C++\r\nclass EditorialGroup : public ElementInterface\r\n{\r\npublic:\r\n    EditorialGroup();\r\n\r\n    /* ... other stuff ... */\r\n\r\n    /* _________ Footnote minOccurs = 0, maxOccurs = 1 _________ */\r\n    FootnotePtr getFootnote() const;\r\n    void setFootnote( const FootnotePtr\u0026 value );\r\n    bool getHasFootnote() const;\r\n    void setHasFootnote( const bool value );\r\n\r\n    /* _________ Level minOccurs = 0, maxOccurs = 1 _________ */\r\n    LevelPtr getLevel() const;\r\n    void setLevel( const LevelPtr\u0026 value );\r\n    bool getHasLevel() const;\r\n    void setHasLevel( const bool value );\r\n    \r\n    bool fromXElement( std::ostream\u0026 message, xml::XElement\u0026 xelement );\r\n\r\nprivate:\r\n    FootnotePtr myFootnote;\r\n    bool myHasFootnote;\r\n    LevelPtr myLevel;\r\n    bool myHasLevel;\r\n};\r\n```\r\n\r\n##### xs:choices\r\n\r\nThere are a few exceptions (mistakes) but for the most part, `xs:choice` constructs are represented by a class with a name ending in 'Choice'.\r\nThe element will have an enum named 'Choice' in the public scope of the class.\r\nEach of the possible 'choices' will exist as data members of the class, but only one of them will be 'active' (was present in, or will be written to, XML).\r\nFor example, this xsd construct:\r\n\r\n```xml\r\n\u003cxs:choice minOccurs=\"0\"\u003e\r\n    \u003cxs:element name=\"pre-bend\" type=\"empty\"\u003e\r\n        \u003cxs:annotation\u003e\r\n            \u003cxs:documentation\u003eThe pre-bend element indicates that this is a pre-bend rather than a normal bend or a release.\u003c/xs:documentation\u003e\r\n        \u003c/xs:annotation\u003e\r\n    \u003c/xs:element\u003e\r\n    \u003cxs:element name=\"release\" type=\"empty\"\u003e\r\n        \u003cxs:annotation\u003e\r\n            \u003cxs:documentation\u003eThe release element indicates that this is a release rather than a normal bend or pre-bend.\u003c/xs:documentation\u003e\r\n        \u003c/xs:annotation\u003e\r\n    \u003c/xs:element\u003e\r\n\u003c/xs:choice\u003e\r\n```\r\n\r\nIs represented by this class:\r\n\r\n```C++\r\nclass BendChoice : public ElementInterface\r\n{\r\npublic:\r\n    enum class Choice\r\n    {\r\n        preBend = 1,\r\n        release = 2\r\n    };\r\n    BendChoice();\r\n\r\n\t/* ... other stuff ... */\r\n    \r\n    BendChoice::Choice getChoice() const;\r\n    void setChoice( BendChoice::Choice value );\r\n\r\n    /* _________ PreBend minOccurs = 1, maxOccurs = 1 _________ */\r\n    PreBendPtr getPreBend() const;\r\n    void setPreBend( const PreBendPtr\u0026 value );\r\n\r\n    /* _________ Release minOccurs = 1, maxOccurs = 1 _________ */\r\n    ReleasePtr getRelease() const;\r\n    void setRelease( const ReleasePtr\u0026 value );\r\n\r\n    bool fromXElement( std::ostream\u0026 message, xml::XElement\u0026 xelement );\r\n\r\nprivate:\r\n    Choice myChoice;\r\n    PreBendPtr myPreBend;\r\n    ReleasePtr myRelease;\r\n};\r\n```\r\n\r\nWhen `getChoice() == BendChoice::Choice::preBend` then we will see `\u003cpre-bend/\u003e` in the XML, but when `getChoice() == BendChoice::Choice::postBend` then we will see `\u003cpost-bend/\u003e` in the XML.\r\n\r\n### XML DOM (::ezxml::)\r\n\r\nAny XML document can be read and manipulated with the classes in the `::ezxml::` namespace.\r\nMost notably, look at the following pure virtual interfaces XDoc, XElement, XAttribute.\r\nAlso look at the STL-compliant iterators XElementIterator and XAttributeIterator.\r\n\r\nThese interfaces are designed to wrap any underlying XML DOM software so that `mx::core` does not care or know about the XML DOM code.\r\nA set of implementation classes wrapping pugixml are provided, but if you need to use, say Xerces or RapidXML, you can look at the PugiElement, PugiDoc, etc classes and wrap whatever library you need.\r\n\r\nHere's how you can read a MusicXML document into `mx::core` classes by way of `::ezxml::`.\r\n\r\n```C++\r\n#include \"mx/core/Document.h\"\r\n#include \"mx/utility/Utility.h\"\r\n#include \"functions.h\"\r\n#include \"ezxml/XFactory.h\"\r\n#include \"ezxml/XDoc.h\"\r\n\r\n#include \u003ciostream\u003e\r\n#include \u003cstring\u003e\r\n#include \u003csstream\u003e\r\n\r\nint main(int argc, const char *argv[])\r\n{\r\n    // allocate the objects\r\n    mx::core::DocumentPtr mxDoc = makeDocument();\r\n    ::ezxml::::XDocPtr xmlDoc = ::ezxml::::XFactory::makeXDoc();\r\n    \r\n    // read a MusicXML file into the XML DOM structure\r\n    xmlDoc-\u003eloadFile( \"music.xml\" );\r\n\r\n    // create an ostream to receive any parsing messages\r\n    std::stringstream parseMessages;\r\n    \r\n    // convert the XML DOM into MusicXML Classes\r\n    bool isSuccess = mxDoc-\u003efromXDoc( parseMessages, *xmlDoc );\r\n    \r\n    if( !isSuccess )\r\n    {\r\n        std::cout \u003c\u003c \"Parsing of the MusicXML document failed with the following message(s):\" \u003c\u003c std::endl;\r\n        std::cout \u003c\u003c parseMessages.str() \u003c\u003c std::endl;\r\n        return -1;\r\n    }\r\n    \r\n    // maybe the document was timewise document. if so, convert it to partwise\r\n    if( mxDoc-\u003egetChoice() == mx::core::DocumentChoice::timewise )\r\n    {\r\n        mxDoc-\u003econvertContents();\r\n    }\r\n    \r\n    // get the root\r\n    auto scorePartwise = mxDoc-\u003egetScorePartwise();\r\n    \r\n    // change the title\r\n    scorePartwise-\u003egetScoreHeaderGroup()-\u003esetHasWork( true );\r\n    scorePartwise-\u003egetScoreHeaderGroup()-\u003egetWork()-\u003esetHasWorkTitle( true );\r\n    scorePartwise-\u003egetScoreHeaderGroup()-\u003egetWork()-\u003egetWorkTitle()-\u003esetValue( mx::core::XsString( \"New Title\" ) );\r\n    \r\n    // write it back out to disk\r\n    mxDoc-\u003etoXDoc( *xmlDoc );\r\n    xmlDoc-\u003ewrite( \"newtitle.xml\" );\r\n    \r\n    return 0;\r\n}\r\n```\r\n\r\n### Hello World using mx::core\r\nOn the MusicXML home page there is an example of a \"Hello World\" simple MusicXML file.\r\nHere is a main function that would output this \"Hello World\" MusicXML example to std::cout.\r\n\r\n```C++\r\n#include \u003ciostream\u003e\r\n#include \"DocumentPartwise.h\"\r\n#include \"Elements.h\"\r\n\r\nusing namespace mx::core;\r\nusing namespace std;\r\n\r\nint main(int argc, const char * argv[])\r\n{\r\n    auto doc = makeDocumentPartwise();\r\n    auto s = doc-\u003egetScorePartwise();\r\n    s-\u003egetAttributes()-\u003ehasVersion = true;\r\n    s-\u003egetAttributes()-\u003eversion = XsToken( \"3.0\" );\r\n    auto header = s-\u003egetScoreHeaderGroup();\r\n    header-\u003egetPartList()-\u003egetScorePart()-\u003egetAttributes()-\u003eid = XsID( \"P1\" );\r\n    header-\u003egetPartList()-\u003egetScorePart()-\u003egetPartName()-\u003esetValue( XsString( \"Music\" ) );\r\n    auto part = *( s-\u003egetPartwisePartSet().cbegin() );\r\n    part-\u003egetAttributes()-\u003eid = XsIDREF( \"P1\" );\r\n    auto measure = *( part-\u003egetPartwiseMeasureSet().cbegin() );\r\n    measure-\u003egetAttributes()-\u003enumber = XsToken( \"1\" );\r\n    auto propertiesChoice = makeMusicDataChoice();\r\n    propertiesChoice-\u003esetChoice( MusicDataChoice::Choice::properties );\r\n    auto properties = propertiesChoice-\u003egetProperties();\r\n    properties-\u003esetHasDivisions( true );\r\n    properties-\u003egetDivisions()-\u003esetValue( PositiveDivisionsValue( 1 ) );\r\n    properties-\u003eaddKey( makeKey() );\r\n    auto time = makeTime();\r\n    time-\u003egetTimeChoice()-\u003esetChoice( TimeChoice::Choice::timeSignature );\r\n    time-\u003egetTimeChoice()-\u003egetTimeSignature()-\u003egetBeats()-\u003esetValue( XsString( \"4\" ) );\r\n    time-\u003egetTimeChoice()-\u003egetTimeSignature()-\u003egetBeatType()-\u003esetValue( XsString( \"4\" ) );\r\n    properties-\u003eaddTime( time );\r\n    auto clef = makeClef();\r\n    clef-\u003egetSign()-\u003esetValue( ClefSign::g );\r\n    clef-\u003esetHasLine( true );\r\n    clef-\u003egetLine()-\u003esetValue( StaffLine( 2 ) );\r\n    properties-\u003eaddClef( clef );\r\n    measure-\u003egetMusicDataGroup()-\u003eaddMusicDataChoice( propertiesChoice );\r\n    auto noteData = makeMusicDataChoice();\r\n    noteData-\u003esetChoice( MusicDataChoice::Choice::note );\r\n    noteData-\u003egetNote()-\u003egetNoteChoice()-\u003esetChoice( NoteChoice::Choice::normal );\r\n    noteData-\u003egetNote()-\u003egetNoteChoice()-\u003egetNormalNoteGroup()-\u003egetFullNoteGroup()-\u003egetFullNoteTypeChoice()-\u003esetChoice( FullNoteTypeChoice::Choice::pitch );\r\n    noteData-\u003egetNote()-\u003egetNoteChoice()-\u003egetNormalNoteGroup()-\u003egetFullNoteGroup()-\u003egetFullNoteTypeChoice()-\u003egetPitch()-\u003egetStep()-\u003esetValue( StepEnum::c );\r\n    noteData-\u003egetNote()-\u003egetNoteChoice()-\u003egetNormalNoteGroup()-\u003egetFullNoteGroup()-\u003egetFullNoteTypeChoice()-\u003egetPitch()-\u003egetOctave()-\u003esetValue( OctaveValue( 4 ) );\r\n    noteData-\u003egetNote()-\u003egetNoteChoice()-\u003egetNormalNoteGroup()-\u003egetDuration()-\u003esetValue( PositiveDivisionsValue( 4 ) );\r\n    noteData-\u003egetNote()-\u003egetType()-\u003esetValue( NoteTypeValue::whole );\r\n    measure-\u003egetMusicDataGroup()-\u003eaddMusicDataChoice( noteData );\r\n    \r\n    doc-\u003etoStream( cout ); /* print Hello World MusicXML document to console */\r\n    return 0;\r\n}\r\n```\r\n\r\n### Unit Test Framework\r\n\r\nAn executable program named mxtest is also included in the project.\r\nmxtest utilizes the Catch2 test framework.\r\nThe core tests are slow to compile, see the [`cmake` options](#cmake-options) section for more info on how to skip compilation of the tests.\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwebern%2Fmx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwebern%2Fmx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwebern%2Fmx/lists"}