This utility extracts and converts Enigma XML from a Finale .musx file. It is a successor to the original denigma project by Chris Roode.
This project is not affiliated with or endorsed by Finale or its parent company.
- It is an independent open-source utility designed to help users access and convert their own data in the absence of Finale, which has been discontinued.
- It does not contain any Finale source code.
- It is not capable of writing Finale files, only reading them.
- Nothing in this repository circumvents digital copy protection on the Finale application.
Denigma is split into small reusable libraries as well as the CLI utility:
denigma_classifyprovides independent classification helpers for clefs, articulations, dynamics, and jumps.denigma_format_enigmaxmlhandles MUSX archive extraction and EnigmaXML pass-through.denigma_format_mnxconverts MUSX content to MNX.denigma_format_mssconverts MUSX content to MSS.denigma_format_musicxmlconverts MUSX content to MusicXML.denigma_format_svgconverts MUSX content to SVG.
The documentation site for the library API is https://openmusx.github.io/denigma/.
Denigma can also be consumed as libraries from another CMake project. Link only the narrow library or libraries you need:
target_link_libraries(my_tool PRIVATE denigma::classify)
target_link_libraries(my_tool PRIVATE denigma::gap-report)
target_link_libraries(my_tool PRIVATE denigma::mnx)
target_link_libraries(my_tool PRIVATE denigma::mss)
target_link_libraries(my_tool PRIVATE denigma::musicxml)
target_link_libraries(my_tool PRIVATE denigma::svg)
target_link_libraries(my_tool PRIVATE denigma::enigmaxml)Use denigma::classify when you only need classification helpers. Use one of the format targets when you need a specific converter.
Applications may register linked formats and call ConverterRegistry::convert when runtime format selection or owned output buffers are useful:
#include "denigma/formats/mnx.h"
denigma::ConverterRegistry registry;
denigma::formats::mnx::registerConverters(registry);
denigma::BufferRandomAccessReader input(musxBytes);
denigma::formats::mnx::Options options;
options.common.sourceName = "score.musx";
auto artifact = registry.convert(
denigma::FormatId::Musx,
denigma::FormatId::MnxJson,
input,
denigma::ConversionRequest{ &options });ConversionArtifact owns the generated documents in emission order and preserves the converter's ConversionResult. Multi-output converters retain each suggested filename. The format-specific typed converters and their convert overloads remain a first-class alternative.
Gap collection is opt-in. Supply a classify::GapCollector (from denigma/classify/gaps.h, part of denigma::classify) through CommonOptions::gapCollector, then inspect its typed gaps directly or serialize it with the separate denigma::gap-report library. Exporters depend only on the collector; none of them links the serializer, so a project that consumes a converter without wanting reports never pulls it in.
#include "denigma/gap_report.h"
denigma::classify::GapCollector gaps;
denigma::formats::mnx::Options options;
options.common.gapCollector = &gaps;
auto result = denigma::formats::mnx::MusxToMnxJsonConverter{}.convert(input, output, options);
auto report = denigma::serializeGapReport(gaps, { { "my-app", "1.0", "abc123" }, {} });When gapCollector is null, no gaps are classified or stored. The exporter hands the collector its parsed source document, which the collector keeps alive until it is destroyed, so a report can be serialized after the conversion returns. Serialization always produces a report, including an empty gaps array when nothing was collected. The second member of GapReportOptions is an optional denigma::GlyphMetricsFn; the CLI passes its FreeType-backed text measurer so that text inside an embedded arrowhead image is sized exactly, and a front end without one (the WebAssembly module) gets heuristic sizing.
Chord symbols, notehead shapes, expressions, and smart shapes are the gap types so far. Chords anchor to a part-measure ID plus position and optional staff. Noteheads anchor directly to the stable note ID already present in MNX. A smart shape MNX cannot carry (a custom line, a glissando, a trill or vibrato line, a keyboard pedal line, a beat-attached slur, a bend) spans from its anchor to an end: an entry-attached shape names the events or notes it runs between, and a beat-attached shape names part measures with positions. Its payload is the classified shape, and the caps of a line that carry an arrowhead reference a report-level arrowheads table of SVG images drawn in staff spaces with the shape's origin at the line end.
{
"schemaVersion": 1,
"producer": { "name": "denigma", "version": "4.0.0", "commit": "..." },
"gaps": [{
"type": "chord-symbol",
"anchor": "P1.m1",
"position": { "numerator": 0, "denominator": 1 },
"chord": {
"root": { "step": "C", "alteration": 0 },
"rootLowerCase": false,
"showRoot": true,
"showSuffix": true,
"suffix": {
"strings": [{ "text": "m7", "position": "inline" }],
"suffixText": "m7",
"quality": "minor-seventh",
"degrees": []
}
}
}]
}Serialized reports use these compatibility rules:
- Consumers should dispatch on
typeand ignore unknown fields and gap types. - Anchors are MNX object IDs and remain valid as consumers modify surrounding arrays.
- Consumers should resolve chord positions before changing measure timing.
- A gap with an
endspans from its anchor to that end;endhas the same shape as the anchor fields. arrowheadskeys are report-internal references, not MNX object IDs.- Split-instrument export reports a chord once in each generated MNX part that contains its source staff, with the corresponding part-measure anchor.
The WebAssembly API opts into collection and exposes serialized report bytes through denigma_result_gap_report_data and denigma_result_gap_report_size. CLI users can pass --gap-report with MNX output to write <output>.gaps.json beside the score.
The serializer library is built by default and can be excluded with denigma_BUILD_GAP_REPORT=OFF. In such a build the CLI warns and ignores --gap-report, and the WebAssembly accessors return an empty report, while every converter and the collector remain available. Code that must compile either way serializes through denigma::withGapReport(collector, [&]<typename Writer>(const Writer& writer) { ... writer.serialize(producer) ... }), which runs the callback only when the serializer is present; denigma::GAP_REPORT_AVAILABLE says which.
Gap payloads are not intended to duplicate every missing exporter feature. Features that the target standard can represent should normally be implemented directly in that exporter or its dependency. Typed payloads are reserved for durable target-format limitations with a demonstrated downstream recovery use case.
The companion denigma-examples repository demonstrates this from separate native and WebAssembly projects using CMake FetchContent or a local Denigma checkout.
When Denigma is added as a CMake subproject, its CLI and tests are disabled by default. Consumers can override either option before making Denigma available:
set(denigma_BUILD_CLI OFF CACHE BOOL "" FORCE)
set(denigma_BUILD_TESTING OFF CACHE BOOL "" FORCE)
set(denigma_BUILD_GAP_REPORT OFF CACHE BOOL "" FORCE) # optional: omit the gap report serializer
include(FetchContent)
FetchContent_Declare(
denigma
GIT_REPOSITORY https://github.com/openmusx/denigma.git
GIT_TAG <release-tag-or-commit>
)
FetchContent_MakeAvailable(denigma)Standalone Denigma builds continue to build the CLI and tests by default.
Use the --help option to get a full list of commands:
denigma --help
Clone the GitHub repository and clone all submodules.
Install the latest cmake:
brew install cmake
brew install ninjaYou must install cmake and xxd. The easiest way is with a package manager such as Choclatey (choco).
Choclatey install instructions
Install the latest cmake and xxd
choco install cmake
choco install ninja
choco install xxdcmake -P build.cmakeor (for Linux or macOS)
./build.cmakeYou can clean the build directory with
cmake -P build.cmake -- cleanor (for Linux or macOS)
./build.cmake -- cleanDenigma also builds a WebAssembly module exposing MUSX inspection and conversion to EnigmaXML, MusicXML, and MNX through a small C ABI (src/wasm/denigma_wasm.cpp). It is the module that denigma-online runs in the browser. Building it requires Emscripten (CI uses 5.0.7):
emcmake cmake -S . -B build-wasm -DCMAKE_BUILD_TYPE=MinSizeRel -DDENIGMA_CXX_STANDARD=20
cmake --build build-wasm --target denigma_wasm
node tests/wasm/smoke.mjs build-wasm/wasm/denigma.js build-wasm/wasm/denigma.wasmBuild the denigma_wasm target rather than all: the module links only the EnigmaXML, MusicXML, and MNX converters, and naming the target keeps the text-measuring converters (SVG and MSS) and their font dependencies out of the build. Under Emscripten the CLI and tests are off by default and denigma_BUILD_WASM is on. The module lands in build-wasm/wasm/ as denigma.js and denigma.wasm; CI builds and smoke-tests the module on every pull request, uploads the pair as the denigma-wasm artifact of every push to main, and attaches it to every published release as denigma.<tag>.wasm.zip alongside the native binaries.
See .vscode_template/README.md for OS-specific templates (macos, linux, windows) with launch.json and tasks.json.