Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
71424bf
update to latest mnxdom with latest schema (15)
rpatters1 Apr 30, 2026
d95f30e
refactored existing code to latest mnx schema changes
rpatters1 Apr 30, 2026
d9b6282
implement bow direction (with tests)
rpatters1 Apr 30, 2026
51d8082
import fermatas
rpatters1 May 1, 2026
60e9dac
export working (with test)
rpatters1 May 1, 2026
e7d7a01
fix breath marks
rpatters1 May 1, 2026
ed58d8d
code formatting issues
rpatters1 May 1, 2026
6eb8ebd
coderabbit review changes
rpatters1 May 1, 2026
0fcf0a7
add clarifying comment about barline fermata exports
rpatters1 May 1, 2026
af88503
update comment about solitary sequences to match revised code behavior
rpatters1 May 2, 2026
886373f
improve test coverage for breath marks, with fixes
rpatters1 May 3, 2026
ae65286
better fermata test coverage and code cleanup
rpatters1 May 3, 2026
91be8f1
coderabbit fixes:
rpatters1 May 3, 2026
df81423
fix some comments and a compiler warning.
rpatters1 May 12, 2026
87d845e
update test files with latest twriter
rpatters1 Jun 1, 2026
10e5498
fix code formatting issue
rpatters1 Jun 1, 2026
64a40db
[schema 17] MNX arpeggio support
rpatters1 Jun 20, 2026
4872c54
Provisionally support dynamics for pre-v19 schemas
rpatters1 Jun 20, 2026
7559b92
Set beam direction from MNX stem direction (since MNX has no separate…
rpatters1 Jun 20, 2026
caa905e
[schema 19] MNX dynamics
rpatters1 Jun 21, 2026
9ae2ab8
Fix issue with diatonic delta calculation. The normal keysig path was…
rpatters1 Jun 24, 2026
2c13742
Add comprehensive test of dynamics with hairpins, prefixes, and suffi…
rpatters1 Jun 24, 2026
a78f3a3
Report provenance in exported mnx file.
rpatters1 Jun 25, 2026
097ddb0
Code review changes. One code review item surfaced a serious issue wi…
rpatters1 Jun 25, 2026
081245a
Rename resolveVoiceTrack to be not specific to dynamics
rpatters1 Jun 26, 2026
0ab9a77
Update mnxdom commit to use vendored mnx schema and examples instead …
rpatters1 Jun 26, 2026
1321506
- mnxdom cmake no longer requires xxd at build time
rpatters1 Jul 9, 2026
b4fbaac
remove mnx exclusion since this PR eliminates the dependency on xxd i…
rpatters1 Jul 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions SetupConfigure.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -376,14 +376,6 @@ if (MUSE_MODULE_NETWORK_WEBSOCKET)
set(QT_ADD_WEBSOCKET ON)
endif()

if (MUE_BUILD_IMPEXP_MNX_MODULE)
find_program(XXD_PROGRAM xxd QUIET)
if (NOT XXD_PROGRAM)
message(WARNING "xxd not found, disabling MNX import/export module")
set(MUE_BUILD_IMPEXP_MNX_MODULE OFF)
endif()
endif()

###########################################
# Unit tests
###########################################
Expand Down
5 changes: 4 additions & 1 deletion src/importexport/mnx/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,14 @@ if(MSS_USE_SYSTEM_MNXDOM)
target_compile_definitions(iex_mnx PRIVATE MNXDOM_SYSTEM)
message(STATUS "Using system-installed mnxdom")
else()
set(MNXDOM_GIT_TAG 12d09d298d347e29f56098e3f10d900c5f55be79)
string(SUBSTRING "${MNXDOM_GIT_TAG}" 0 7 MNXDOM_GIT_COMMIT)
FetchContent_Declare(
mnxdom
GIT_REPOSITORY https://github.com/rpatters1/mnxdom.git
GIT_TAG e7c947bf768caccf315426dcae0dfac02caf738b
GIT_TAG ${MNXDOM_GIT_TAG}
)
set(MNXDOM_GIT_COMMIT "${MNXDOM_GIT_COMMIT}" CACHE STRING "mnxdom source control revision to embed in provenance" FORCE)
FetchContent_MakeAvailable(mnxdom)
set(MNXDOM_LIB mnxdom)
message(STATUS "Using fetched mnxdom")
Expand Down
98 changes: 98 additions & 0 deletions src/importexport/mnx/internal/export/mnxexporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,15 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "mnxexporter.h"
#include "internal/shared/mnxtypesconv.h"

#include <stdexcept>

#include "engraving/dom/masterscore.h"
#include "engraving/dom/score.h"
#include "global/iapplication.h"
#include "io/path.h"
#include "modularity/ioc.h"
#include "log.h"
#include "translation.h"

Expand Down Expand Up @@ -85,6 +90,69 @@ std::pair<size_t, int> MnxExporter::mnxPartStaffFromStaffIdx(engraving::staff_id
return it->second;
}

//---------------------------------------------------------
// mnxFermataFromFermata
//---------------------------------------------------------

mnx::Fermata MnxExporter::mnxFermataFromFermata(const Fermata* fermata)
{
mnx::Fermata result;
result.set_or_clear_duration(toMnxFermataDuration(fermata->fermataType()));
result.set_or_clear_symbol(toMnxFermataSymbol(fermata->symId()));
result.set_or_clear_orient(toMnxOrientation(fermata->placement()));
return result;
}

//---------------------------------------------------------
// mnxChordTargetPosition
// calculate exact MNX position for an object targeting
// a chord, including grace-note targets
//---------------------------------------------------------

std::optional<MnxChordTargetPosition> MnxExporter::mnxChordTargetPosition(const Chord* chord, const Measure* measure)
{
IF_ASSERT_FAILED(chord && measure) {
return std::nullopt;
}

auto positionFromTick = [measure](const Fraction& tick) {
return MnxChordTargetPosition {
toMnxFractionValue(tick - measure->tick()).reduced(),
std::nullopt
};
};

if (!chord->isGrace()) {
MnxChordTargetPosition position = positionFromTick(chord->tick());
if (!chord->graceNotes().empty()) {
position.graceIndex = 0;
}
return position;
}

EngravingObject* parent = chord->explicitParent();
if (!parent || !parent->isChord()) {
LOGW() << "Skipping grace-note target with missing main chord.";
return std::nullopt;
}

const Chord* mainChord = toChord(parent);
const GraceNotesGroup& graceNotes = chord->isGraceAfter()
? mainChord->graceNotesAfter()
: mainChord->graceNotesBefore();
const auto graceIt = std::find(graceNotes.begin(), graceNotes.end(), chord);
if (graceIt == graceNotes.end()) {
LOGW() << "Skipping grace-note target whose grace index could not be resolved.";
return std::nullopt;
}

const size_t index = static_cast<size_t>(std::distance(graceNotes.begin(), graceIt));
MnxChordTargetPosition position = positionFromTick(chord->isGraceAfter() ? mainChord->tick() + mainChord->ticks()
: mainChord->tick());
position.graceIndex = static_cast<unsigned>(graceNotes.size() - index);
return position;
}

//---------------------------------------------------------
// exportMnx
//---------------------------------------------------------
Expand All @@ -93,6 +161,36 @@ muse::Ret MnxExporter::exportMnx()
{
LOGI() << "MNX export started: schema version=" << m_mnxDocument.mnx().version();
// Header
static muse::GlobalInject<muse::IApplication> application;
auto client = m_mnxDocument.mnx().ensure_mnxdom().ensure_client();
muse::String version = application()->version().toString();
const muse::String revision = application()->revision();
const muse::String build = application()->build();
if (!revision.isEmpty()) {
version += u" rev. " + revision;
}
client.set_name(application()->title().toStdString());
client.set_version(version.toStdString());
if (!build.isEmpty()) {
client.set_build(build.toStdString());
}
if (m_score && m_score->masterScore()) {
IFileInfoProviderPtr fileInfo = m_score->masterScore()->fileInfo();
if (fileInfo) {
const muse::io::path_t fileName = fileInfo->fileName();
const std::string format = muse::io::suffix(fileName);
if (!format.empty() || !fileName.empty()) {
auto source = m_mnxDocument.mnx().ensure_mnxdom().ensure_source();
if (!format.empty()) {
source.set_format(format);
}
if (!fileName.empty()) {
source.set_filename(fileName.toStdString());
}
}
}
}

if (m_exportBeams) {
mnx::MnxMetaData::Support support = m_mnxDocument.mnx().ensure_support();
support.set_useBeams(true);
Expand Down
33 changes: 26 additions & 7 deletions src/importexport/mnx/internal/export/mnxexporter.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,22 +35,33 @@
#include "engraving/types/types.h"

namespace mu::engraving {
class Chord;
class ChordRest;
class EID;
class EngravingItem;
class EngravingObject;
class Fermata;
class Hairpin;
class GraceNotesGroup;
class Instrument;
class Measure;
class Note;
class Part;
class Ottava;
class Score;
class Slur;
class Staff;
class Volta;
class TremoloTwoChord;
class Tuplet;
} // namespace mu::engraving

namespace mu::iex::mnxio {
struct MnxChordTargetPosition {
mnx::FractionValue fraction;
std::optional<unsigned> graceIndex;
};

class MnxExporter
{
public:
Expand All @@ -68,6 +79,8 @@ class MnxExporter
std::optional<mnx::sequence::Event> mnxEventFromCR(const engraving::ChordRest* cr);
size_t mnxMeasureIndexFromMeasure(const engraving::Measure* measure) const;
std::pair<size_t, int> mnxPartStaffFromStaffIdx(engraving::staff_idx_t staffIdx) const;
static mnx::Fermata mnxFermataFromFermata(const engraving::Fermata* fermata);
std::optional<MnxChordTargetPosition> mnxChordTargetPosition(const engraving::Chord* chord, const engraving::Measure* measure);

private:
enum class ContentContext {
Expand Down Expand Up @@ -105,20 +118,26 @@ class MnxExporter
bool createParts();
void createLayout(const std::vector<engraving::Staff*>& staves, const std::string& layoutId);
void createSequences(const engraving::Part* part, const engraving::Measure* measure, mnx::part::Measure& mnxMeasure);
void appendContent(mnx::ContentArray content, ExportContext& ctx, const std::vector<engraving::ChordRest*>& chordRests,
void createArpeggios(const engraving::Part* part, const engraving::Measure* measure, mnx::part::Measure& mnxMeasure);
void createVolta(const engraving::Volta* volta);
void createSlur(const engraving::Slur* slur);
void createHairpin(const engraving::Hairpin* hairpin);
void createOttava(const engraving::Ottava* ottava);
void appendContent(mnx::sequence::SequenceContent content, ExportContext& ctx, const std::vector<engraving::ChordRest*>& chordRests,
ContentContext context);
void appendGrace(mnx::ContentArray content, ExportContext& ctx, engraving::GraceNotesGroup& graceNotes);
void appendGrace(mnx::sequence::SequenceContent content, ExportContext& ctx, engraving::GraceNotesGroup& graceNotes);
void createBeam(ExportContext& ctx, engraving::ChordRest* chordRest);
size_t appendTuplet(mnx::ContentArray content, ExportContext& ctx, const std::vector<engraving::ChordRest*>& chordRests, size_t idx,
engraving::ChordRest* chordRest, const engraving::Tuplet* tuplet);
size_t appendTremolo(mnx::ContentArray content, ExportContext& ctx, const std::vector<engraving::ChordRest*>& chordRests, size_t idx,
engraving::ChordRest* chordRest);
bool appendEvent(mnx::ContentArray content, ExportContext& ctx, engraving::ChordRest* chordRest);
size_t appendTuplet(mnx::sequence::SequenceContent, ExportContext & ctx, const std::vector<engraving::ChordRest*>& chordRests,
size_t idx, engraving::ChordRest* chordRest, const engraving::Tuplet* tuplet);
size_t appendTremolo(mnx::sequence::SequenceContent content, ExportContext& ctx, const std::vector<engraving::ChordRest*>& chordRests,
size_t idx, engraving::ChordRest* chordRest);
bool appendEvent(mnx::sequence::SequenceContent, ExportContext & ctx, engraving::ChordRest* chordRest);
bool createRest(mnx::sequence::Event& mnxEvent, engraving::ChordRest* chordRest);
bool createNotes(mnx::sequence::Event& mnxEvent, engraving::ChordRest* chordRest);
void createTies(mnx::sequence::NoteBase& mnxNote, engraving::Note* note);
const engraving::Tuplet* findTopTuplet(engraving::ChordRest* chordRest, const ExportContext& ctx) const;
void exportDrumsetKit(const engraving::Part* part, const engraving::Instrument* instrument, mnx::Part& mnxPart);
void createMarkings(mnx::sequence::Event& mnxEvent, engraving::ChordRest* cr);

void exportSpanners();

Expand Down
21 changes: 19 additions & 2 deletions src/importexport/mnx/internal/export/mnxexportglobal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -286,8 +286,8 @@ static void exportMeasureElements(mnx::global::Measure& mnxMeasure, const Measur
}
}

for (Segment* segment = measure->first(); segment; segment = segment->next()) {
for (EngravingItem* item : segment->annotations()) {
for (const Segment* segment = measure->first(); segment; segment = segment->next()) {
for (const EngravingItem* item : segment->annotations()) {
IF_ASSERT_FAILED(item) {
continue;
}
Expand All @@ -302,6 +302,23 @@ static void exportMeasureElements(mnx::global::Measure& mnxMeasure, const Measur
}
}
}

if (const BarLine* barLine = measure->endBarLine()) {
if (const Segment* seg = barLine->segment()) {
for (const EngravingItem* item : seg->annotations()) {
IF_ASSERT_FAILED(item) {
continue;
}
if (item->isFermata()) {
const Fermata* fermata = toFermata(item);
DO_ASSERT(fermata);
if (fermata) {
mnxMeasure.set_fermata(MnxExporter::mnxFermataFromFermata(fermata));
}
}
}
}
}
}

//---------------------------------------------------------
Expand Down
8 changes: 4 additions & 4 deletions src/importexport/mnx/internal/export/mnxexportlayouts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ static void buildGroupNodes(const std::vector<LayoutGroupSpan>& groups, std::vec
// appendLayoutStaff
//---------------------------------------------------------

static void appendLayoutStaff(LayoutBuildContext& ctx, mnx::ContentArray content, size_t staffIdx)
static void appendLayoutStaff(LayoutBuildContext& ctx, mnx::layout::LayoutContent content, size_t staffIdx)
{
Staff* staff = ctx.staves->at(staffIdx);
if (!staff) {
Expand Down Expand Up @@ -236,7 +236,7 @@ static void appendLayoutStaff(LayoutBuildContext& ctx, mnx::ContentArray content
LOGW() << "Skipping layout staff " << staffIdx << " with no part id.";
return;
}
auto mnxStaff = content.append<mnx::layout::Staff>();
auto mnxStaff = content.appendStaff();
auto source = mnxStaff.sources().append(partId);
source.set_staff(staffNum);
}
Expand Down Expand Up @@ -317,7 +317,7 @@ static mnx::StaffGroupBarlineOverride calcGroupBarlineOverride(const LayoutBuild
// buildContent
//---------------------------------------------------------

static void buildContent(LayoutBuildContext& ctx, mnx::ContentArray content,
static void buildContent(LayoutBuildContext& ctx, mnx::layout::LayoutContent content,
size_t start, size_t end, const std::vector<size_t>& children)
{
size_t staffIdx = start;
Expand All @@ -331,7 +331,7 @@ static void buildContent(LayoutBuildContext& ctx, mnx::ContentArray content,
if (childPos < children.size()
&& nodes[children[childPos]].span.start == staffIdx) {
const LayoutGroupNode& node = nodes[children[childPos]];
auto mnxGroup = content.append<mnx::layout::Group>();
auto mnxGroup = content.appendGroup();
switch (calcGroupBarlineOverride(ctx, node.span)) {
case mnx::StaffGroupBarlineOverride::None:
mnxGroup.set_or_clear_barlineStyle(mnx::StaffGroupBarlineStyle::Individual);
Expand Down
Loading
Loading