diff --git a/.circleci/Dockerfile.test b/.circleci/Dockerfile.test
index cf4c4e461..0071a1157 100644
--- a/.circleci/Dockerfile.test
+++ b/.circleci/Dockerfile.test
@@ -3,5 +3,13 @@ FROM matthewjamesbriggs/mxci:v001
ENV BASEDIR="/mx"
WORKDIR $BASEDIR
COPY . .
-CMD cmake -DMX_BUILD_TESTS=on -DMX_BUILD_CORE_TESTS=on . && make -j12 && ./MxTest
+CMD cmake \
+ -DMX_BUILD_TESTS=on \
+ -DMX_BUILD_CORE_TESTS=on \
+ -DMX_BUILD_EXAMPLES=on \
+ . && \
+ make -j12 && \
+ ./MxTest && \
+ ./MxRead && \
+ ./MxWrite
# CMD cmake --version
\ No newline at end of file
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 63c11796a..f7cee6eaf 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -47,13 +47,17 @@ jobs:
name: Cmake
command: |
[ "$(git rev-parse --abbrev-ref HEAD)" == "master" ] && \
- cmake -DMX_BUILD_TESTS=on -DMX_BUILD_CORE_TESTS=on . || \
- cmake -DMX_BUILD_TESTS=on -DMX_BUILD_CORE_TESTS=off .
+ cmake -DMX_BUILD_EXAMPLES=on -DMX_BUILD_TESTS=on -DMX_BUILD_CORE_TESTS=on . || \
+ cmake -DMX_BUILD_EXAMPLES=on -DMX_BUILD_TESTS=on -DMX_BUILD_CORE_TESTS=off .
- run:
name: Build
command: make
+ - run:
+ name: Run Examples
+ command: ./MxRead && ./MxWrite
+
- run:
name: Test
command: ./MxTest
diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 000000000..a6a59ca01
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+Mx
\ No newline at end of file
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 000000000..31c5ccdce
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 000000000..79ee123c2
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 000000000..8822db8f1
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 000000000..3c89db64a
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/mx.iml b/.idea/mx.iml
new file mode 100644
index 000000000..f08604bb6
--- /dev/null
+++ b/.idea/mx.iml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 000000000..94a25f7f4
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ec7caa949..8a11fe605 100755
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -91,3 +91,22 @@ if(MX_BUILD_TESTS)
else()
message("tests will not be compiled")
endif()
+
+# MxExamples
+if(MX_BUILD_EXAMPLES)
+ message("examples will be compiled")
+ find_package( Threads )
+ add_executable(MxRead ${SOURCE}/mx/examples/Read.cpp)
+ add_executable(MxWrite ${SOURCE}/mx/examples/Write.cpp)
+ target_link_libraries(MxRead Mx ${CMAKE_THREAD_LIBS_INIT})
+ target_link_libraries(MxWrite Mx ${CMAKE_THREAD_LIBS_INIT})
+ set_property(TARGET MxRead PROPERTY CXX_STANDARD 14)
+ set_property(TARGET MxWrite PROPERTY CXX_STANDARD 14)
+
+ # TODO - create a proper 'include' directory
+ target_include_directories(MxRead PRIVATE ${SOURCE}/mx/api)
+ target_include_directories(MxWrite PRIVATE ${SOURCE}/mx/api)
+
+else()
+ message("examples will not be compiled")
+endif()
diff --git a/README.md b/README.md
index 27b92ac87..ae9162387 100755
--- a/README.md
+++ b/README.md
@@ -56,6 +56,242 @@ Visual Studio will open with a solution file and be ready to compile.
```
## Usage
+
+The `mx::api` namespace is intended to be a (somewhat) simplified wrapping `api` for MusicXML. It should be slightly more intuitive than manipulating the DOM representation directly.
+
+#### Writing MusicXML with `mx::api`
+```C++
+#include
+#include
+#include
+#include
+
+#include "mx/api/DocumentManager.h"
+#include "mx/api/ScoreData.h"
+
+// set this to 1 if you want to see the xml in your console
+#define MX_WRITE_THIS_TO_THE_CONSOLE 1
+
+int main(int argc, const char * argv[])
+{
+ using namespace mx::api;
+ const auto qticks = 4;
+
+ // create a score
+ auto score = ScoreData{};
+ score.workTitle = "Mx Example";
+ score.composer = "Matthew James Briggs";
+ score.copyright = "Copyright (c) 2019";
+ score.ticksPerQuarter = qticks;
+
+ // create a part
+ score.parts.emplace_back( PartData{} );
+ auto& part = score.parts.back();
+
+ // give the part a name
+ part.name = "Flute";
+ part.abbreviation = "Fl.";
+ part.displayName = "Flute";
+ part.displayAbbreviation = "Fl.";
+
+ // give the part an instrument
+ part.instrumentData.soundID = SoundID::windFlutesFlute;
+ part.instrumentData.midiData.channel = 1;
+ part.instrumentData.midiData.program = 74;
+
+ // add a measure
+ part.measures.emplace_back( MeasureData{} );
+ auto& measure = part.measures.back();
+ measure.timeSignature.beats = 4;
+ measure.timeSignature.beatType = 4;
+ measure.timeSignature.isImplicit = false;
+
+ // add a staff
+ measure.staves.emplace_back( StaffData{} );
+ auto& staff = measure.staves.back();
+
+ // set the clef
+ auto clef = ClefData{};
+ clef.setTreble();
+ staff.clefs.emplace_back( clef );
+
+ // add a voice
+ staff.voices[0] = VoiceData{};
+ auto& voice = staff.voices.at( 0 );
+
+ const auto quarter = qticks;
+ const auto half = qticks * 2;
+ const auto eighth = qticks / 2;
+
+ // add a few notes
+ auto currentTime = 0;
+ auto note = NoteData{};
+ note.pitchData.step = Step::d;
+ note.pitchData.alter = 1;
+ note.pitchData.octave = 5;
+ note.pitchData.accidental = Accidental::sharp;
+ note.durationData.durationName = DurationName::half;
+ note.durationData.durationTimeTicks = half;
+ note.tickTimePosition = currentTime;
+ voice.notes.push_back( note );
+
+ // advance our time
+ currentTime += half;
+
+ note.pitchData.step = Step::e;
+ note.pitchData.alter = 0;
+ note.pitchData.octave = 5;
+ note.pitchData.accidental = Accidental::none;
+ note.durationData.durationName = DurationName::eighth;
+ note.durationData.durationTimeTicks = eighth;
+ note.tickTimePosition = currentTime;
+ // beams are handled explicitly in musicxml
+ note.beams.push_back( Beam::begin ); // start an eighth-note beam
+ voice.notes.push_back( note );
+ currentTime += eighth;
+
+ note.pitchData.step = Step::f;
+ note.pitchData.alter = 0;
+ note.pitchData.octave = 5;
+ note.pitchData.accidental = Accidental::none;
+ note.durationData.durationName = DurationName::eighth;
+ note.tickTimePosition = currentTime;
+ note.durationData.durationTimeTicks = eighth;
+ note.beams.clear();
+ note.beams.push_back( Beam::end ); // end the eighth-note beam
+ voice.notes.push_back( note );
+ currentTime += eighth;
+
+ note.pitchData.step = Step::e;
+ note.pitchData.alter = 0;
+ note.pitchData.octave = 5;
+ note.pitchData.accidental = Accidental::none;
+ note.durationData.durationName = DurationName::quarter;
+ note.durationData.durationTimeTicks = quarter;
+ note.tickTimePosition = currentTime;
+ note.beams.clear();
+ voice.notes.push_back( note );
+
+ // the document manager is the liaison between our score data and the MusicXML DOM.
+ // it completely hides the MusicXML DOM from us when using mx::api
+ auto& mgr = DocumentManager::getInstance();
+ const auto documentID = mgr.createFromScore( score );
+
+ // write to the console
+ #if MX_WRITE_THIS_TO_THE_CONSOLE
+ mgr.writeToStream( documentID, std::cout );
+ std::cout << std::endl;
+ #endif
+
+ // write to a file
+ mgr.writeToFile( documentID, "./example.musicxml" );
+
+ return 0;
+}
+```
+
+#### Reading MusicXML with `mx::api`
+
+```C++
+#include "mx/api/DocumentManager.h"
+#include "mx/api/ScoreData.h"
+
+#include
+#include
+#include
+#include
+
+#define MX_IS_A_SUCCESS 0
+#define MX_IS_A_FAILURE 1
+
+constexpr const char* const xml = R"(
+
+
+
+
+
+ Music
+
+
+
+
+
+ 1
+
+ 0
+
+
+
+ G
+ 2
+
+
+
+
+ C
+ 4
+
+ 4
+ whole
+
+
+
+
+)";
+
+int main(int argc, const char * argv[])
+{
+ using namespace mx::api;
+
+ // create a reference to the singleton which holds documents in memory for us
+ auto& mgr = DocumentManager::getInstance();
+
+ // place the xml from above into a stream object
+ std::istringstream istr{ xml };
+
+ // ask the document manager to parse the xml into memory for us, returns a document ID.
+ const auto documentID = mgr.createFromStream( istr );
+
+ // get the structural representation of the score from the document manager
+ const auto score = mgr.getData( documentID );
+
+ // we need to explicitly destroy the document from memory
+ mgr.destroyDocument(documentID);
+
+ // make sure we have exactly one part
+ if( score.parts.size() != 1 )
+ {
+ return MX_IS_A_FAILURE;
+ }
+
+ // drill down into the data structure to retrieve the note
+ const auto& part = score.parts.at( 0 );
+ const auto& measure = part.measures.at( 0 );
+ const auto& staff = measure.staves.at( 0 );
+ const auto& voice = staff.voices.at( 0 );
+ const auto& note = voice.notes.at( 0 );
+
+ if( note.durationData.durationName != DurationName::whole )
+ {
+ return MX_IS_A_FAILURE;
+ }
+
+ if( note.pitchData.step != Step::c )
+ {
+ return MX_IS_A_FAILURE;
+ }
+
+ return MX_IS_A_SUCCESS;
+}
+```
+
+## Core Implementation Details
+
The MusicXML classes are tightly bound to the musicxml.xsd specification. MusicXML can be challenging to use and the mx class structure mirrors the complexity of the MusicXML specification. A facade or api for simplifying the interactions with MusicXML documents is planned for future development.
##### Namespaces
@@ -417,24 +653,22 @@ int main(int argc, const char * argv[])
### Unit Test Framework
-An executable program named MxTest is also included in the project. MxTest utilizes the CppUnitLite macro library by Michael Feathers. Licensing of this library is not clear, here is a link to the source of this library http://c2.com/cgi/wiki?CppUnitLite
+An executable program named MxTest is also included in the project. MxTest utilizes the CppUnitLite macro library by Michael Feathers. Licensing of this library is not clear, [here is a link](http://c2.com/cgi/wiki?CppUnitLite) to the source of this library.
CppUnitLite appears to be abandonware, but it is very useful.
Here are some additional CppUnitLite links
-http://www.objectmentor.com/resources/downloads.html
-https://github.com/smikes/CppUnitLite
+ * [http://www.objectmentor.com/resources/downloads.html](http://www.objectmentor.com/resources/downloads.html)
+ * [https://github.com/smikes/CppUnitLite](https://github.com/smikes/CppUnitLite)
+ * [https://github.com/webern/CppUnitLite](https://github.com/webern/CppUnitLite)
The tests are slow to compile, see the *Compiling* section for more info on how to skip compilation of the tests.
-### Known Issues and Todo List
-- There is no easy way to "deep copy" anything. Each element and attributes class needs a "clone" function.
-- XsID uniqueness is not constrained. Should it be?
-- XsIDREF's, especially in the ScorePartList are not constrained to relate to the PartList XsID's. Thus it is possible to create an unparsable MusicXML document. A feature could be added to enforce this one-to-one constraint. Should this feature be added in the core model? Maybe not.
-- A simplified and stable facade API should be added to make it much, much easier to understand and interact with MusicXML documents.
+### Release Notes
-### Historical Notes and Release Notes
-**Historical Note: October 6, 2016:** Significant progress has been made on the `api` namespace, which is a simplified set of data structures to represent a MusicXML document. These are being implemented as mostly-POD structs. Currently the importing of data into these structures is well-underway, but the exporting from these data structures has not been implemented.
+ * **Version 1.0** April 2019: A more-or-less stable version of `mx::api`
+ * **Version 0.3** Early 2019
+ * **Version 0.2** August 21, 2016 Adds the ability to import MusicXML documents into MusicXML Classes.
-**Release: Version 0.2 August 21, 2016** Adds the ability to import MusicXML documents into MusicXML Classes.
+**Historical Note: October 6, 2016:** Significant progress has been made on the `api` namespace, which is a simplified set of data structures to represent a MusicXML document. These are being implemented as mostly-POD structs. Currently the importing of data into these structures is well-underway, but the exporting from these data structures has not been implemented.
**Historical Note: August 16, 2016:** All tests are passing (core, xml and import). The remaining items to do on the ximport feature are
- search for all `\\TODO's` and fix those that can be fixed
diff --git a/Sourcecode/mx/examples/Read.cpp b/Sourcecode/mx/examples/Read.cpp
new file mode 100644
index 000000000..2a60ff8f6
--- /dev/null
+++ b/Sourcecode/mx/examples/Read.cpp
@@ -0,0 +1,102 @@
+#include
+#include
+#include
+#include
+
+// TODO - create a proper include folder
+// normally we would want to include like this
+// #include "mx/api/DocumentManager.h"
+// however because our cmake system is a little bit
+// broken, we are importing from the mx/api directory
+// directly.
+
+#include "DocumentManager.h"
+#include "ScoreData.h"
+
+#define MX_IS_A_SUCCESS 0
+#define MX_IS_A_FAILURE 1
+
+constexpr const char* const xml = R"(
+
+
+
+
+
+ Music
+
+
+
+
+
+ 1
+
+ 0
+
+
+
+ G
+ 2
+
+
+
+
+ C
+ 4
+
+ 4
+ whole
+
+
+
+
+)";
+
+int main(int argc, const char * argv[])
+{
+ using namespace mx::api;
+
+ // create a reference to the singleton which holds documents in memory for us
+ auto& mgr = DocumentManager::getInstance();
+
+ // place the xml from above into a stream object
+ std::istringstream istr{ xml };
+
+ // ask the document manager to parse the xml into memory for us, returns a document ID.
+ const auto documentID = mgr.createFromStream( istr );
+
+ // get the structural representation of the score from the document manager
+ const auto score = mgr.getData( documentID );
+
+ // we need to explicitly destroy the document from memory
+ mgr.destroyDocument(documentID);
+
+ // make sure we have exactly one part
+ if( score.parts.size() != 1 )
+ {
+ return MX_IS_A_FAILURE;
+ }
+
+ // drill down into the data structure to retrieve the note
+ const auto& part = score.parts.at( 0 );
+ const auto& measure = part.measures.at( 0 );
+ const auto& staff = measure.staves.at( 0 );
+ const auto& voice = staff.voices.at( 0 );
+ const auto& note = voice.notes.at( 0 );
+
+ if( note.durationData.durationName != DurationName::whole )
+ {
+ return MX_IS_A_FAILURE;
+ }
+
+ if( note.pitchData.step != Step::c )
+ {
+ return MX_IS_A_FAILURE;
+ }
+
+ return MX_IS_A_SUCCESS;
+}
\ No newline at end of file
diff --git a/Sourcecode/mx/examples/Write.cpp b/Sourcecode/mx/examples/Write.cpp
new file mode 100644
index 000000000..2dc1cf05d
--- /dev/null
+++ b/Sourcecode/mx/examples/Write.cpp
@@ -0,0 +1,134 @@
+#include
+#include
+#include
+#include
+
+// TODO - create a proper include folder
+// normally we would want to include like this
+// #include "mx/api/DocumentManager.h"
+// however because our cmake system is a little bit
+// broken, we are importing from the mx/api directory
+// directly.
+
+#include "DocumentManager.h"
+#include "ScoreData.h"
+
+// set this to 1 if you want to see the xml in your console
+#define MX_WRITE_THIS_TO_THE_CONSOLE 1
+
+int main(int argc, const char * argv[])
+{
+ using namespace mx::api;
+ const auto qticks = 4;
+
+ // create a score
+ auto score = ScoreData{};
+ score.workTitle = "Mx Example";
+ score.composer = "Matthew James Briggs";
+ score.copyright = "Copyright (c) 2019";
+ score.ticksPerQuarter = qticks;
+
+ // create a part
+ score.parts.emplace_back( PartData{} );
+ auto& part = score.parts.back();
+
+ // give the part a name
+ part.name = "Flute";
+ part.abbreviation = "Fl.";
+ part.displayName = "Flute";
+ part.displayAbbreviation = "Fl.";
+
+ // give the part an instrument
+ part.instrumentData.soundID = SoundID::windFlutesFlute;
+ part.instrumentData.midiData.channel = 1;
+ part.instrumentData.midiData.program = 74;
+
+ // add a measure
+ part.measures.emplace_back( MeasureData{} );
+ auto& measure = part.measures.back();
+ measure.timeSignature.beats = 4;
+ measure.timeSignature.beatType = 4;
+ measure.timeSignature.isImplicit = false;
+
+ // add a staff
+ measure.staves.emplace_back( StaffData{} );
+ auto& staff = measure.staves.back();
+
+ // set the clef
+ auto clef = ClefData{};
+ clef.setTreble();
+ staff.clefs.emplace_back( clef );
+
+ // add a voice
+ staff.voices[0] = VoiceData{};
+ auto& voice = staff.voices.at( 0 );
+
+ const auto quarter = qticks;
+ const auto half = qticks * 2;
+ const auto eighth = qticks / 2;
+
+ // add a few notes
+ auto currentTime = 0;
+ auto note = NoteData{};
+ note.pitchData.step = Step::d;
+ note.pitchData.alter = 1;
+ note.pitchData.octave = 5;
+ note.pitchData.accidental = Accidental::sharp;
+ note.durationData.durationName = DurationName::half;
+ note.durationData.durationTimeTicks = half;
+ note.tickTimePosition = currentTime;
+ voice.notes.push_back( note );
+
+ // advance our time
+ currentTime += half;
+
+ note.pitchData.step = Step::e;
+ note.pitchData.alter = 0;
+ note.pitchData.octave = 5;
+ note.pitchData.accidental = Accidental::none;
+ note.durationData.durationName = DurationName::eighth;
+ note.durationData.durationTimeTicks = eighth;
+ note.tickTimePosition = currentTime;
+ // beams are handled explicitly in musicxml
+ note.beams.push_back( Beam::begin ); // start an eighth-note beam
+ voice.notes.push_back( note );
+ currentTime += eighth;
+
+ note.pitchData.step = Step::f;
+ note.pitchData.alter = 0;
+ note.pitchData.octave = 5;
+ note.pitchData.accidental = Accidental::none;
+ note.durationData.durationName = DurationName::eighth;
+ note.tickTimePosition = currentTime;
+ note.durationData.durationTimeTicks = eighth;
+ note.beams.clear();
+ note.beams.push_back( Beam::end ); // end the eighth-note beam
+ voice.notes.push_back( note );
+ currentTime += eighth;
+
+ note.pitchData.step = Step::e;
+ note.pitchData.alter = 0;
+ note.pitchData.octave = 5;
+ note.pitchData.accidental = Accidental::none;
+ note.durationData.durationName = DurationName::quarter;
+ note.durationData.durationTimeTicks = quarter;
+ note.tickTimePosition = currentTime;
+ note.beams.clear();
+ voice.notes.push_back( note );
+
+ // the document manager is the liaison between our score data and the MusicXML DOM.
+ // it completely hides the MusicXML DOM from us when using mx::api
+ auto& mgr = DocumentManager::getInstance();
+ const auto documentID = mgr.createFromScore( score );
+
+ // write to the console
+ #if MX_WRITE_THIS_TO_THE_CONSOLE
+ mgr.writeToStream( documentID, std::cout );
+ std::cout << std::endl;
+ #endif
+
+ // write to a file
+ mgr.writeToFile( documentID, "./example.musicxml" );
+
+ return 0;
+}
\ No newline at end of file
diff --git a/Sourcecode/mxtest/file/PathRoot.h b/Sourcecode/mxtest/file/PathRoot.h
index b2de5688b..d078801ca 100644
--- a/Sourcecode/mxtest/file/PathRoot.h
+++ b/Sourcecode/mxtest/file/PathRoot.h
@@ -1,13 +1,13 @@
-// This file is auto generated
-// To override these paths, pass in these macro definitions to the compiler.
+// This file is auto generated by CMake
+// To override these paths, pass in macro definitions to the compiler.
#pragma once
// The absolute path to the root of the repository.
#ifndef MX_REPO_ROOT_PATH
-#define MX_REPO_ROOT_PATH "/Users/mjb/Documents/repos/mx"
+#define MX_REPO_ROOT_PATH "/Users/mjb/repos/mx"
#endif
// The absolute path to the binary output directory.
#ifndef MX_BINARY_OUTPUT_PATH
-#define MX_BINARY_OUTPUT_PATH "/Users/mjb/Documents/repos/mx/cmake-build-debug"
+#define MX_BINARY_OUTPUT_PATH "/Users/mjb/repos/mx/cmake-build-debug"
#endif
\ No newline at end of file