Skip to content

[MEI] add support for fingered tremolo#32855

Open
rettinghaus wants to merge 7 commits intomusescore:masterfrom
rettinghaus:mei/tremolo
Open

[MEI] add support for fingered tremolo#32855
rettinghaus wants to merge 7 commits intomusescore:masterfrom
rettinghaus:mei/tremolo

Conversation

@rettinghaus
Copy link
Copy Markdown
Contributor

@rettinghaus rettinghaus commented Mar 30, 2026

This adds import/export for fingered tremolos to the MEI support.

Summary by CodeRabbit

  • New Features

    • Added full round‑trip support for fingered (two‑chord) tremolos in MEI import/export, including explicit first‑chord elements and unit‑duration mapping for tremolo strokes.
    • Import now recognizes fTrem elements and attaches chords to two‑chord tremolos; export writes matching fTrem elements.
  • Tests

    • Added MEI and MuseScore test fixtures and a unit test validating two‑chord tremolo import/export and unit‑duration mappings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 30, 2026

📝 Walkthrough

Walkthrough

Adds MEI fingered-tremolo (fTrem) support: new dur/unitdur conversion helpers, importer/exporter handling for TremoloTwoChord (fTrem read/write), test fixtures, and related API/signature updates.

Changes

Cohort / File(s) Summary
Conversion helpers
src/importexport/mei/internal/meiconverter.h, src/importexport/mei/internal/meiconverter.cpp
Added Convert::unitdurFromMEI() and Convert::unitdurToMEI() for tremolo unit-duration mapping; moved/reordered existing durFromMEI() / durToMEI() implementations within the file.
MEI exporter
src/importexport/mei/internal/meiexporter.h, src/importexport/mei/internal/meiexporter.cpp
Added MeiExporter::writeFTrem() and integrated opening/closing of <fTrem> into writeChord() for two-chord tremolos; sets @unitdur and xml:id.
MEI importer
src/importexport/mei/internal/meiimporter.h, src/importexport/mei/internal/meiimporter.cpp
Added readFTrem() to parse <fTrem> and create/register TremoloTwoChord; updated tremolo wiring so chords attach to TremoloTwoChord when appropriate; changed several read* signatures to use Fraction& and adjusted duration handling when inside fTrem.
Tests / fixtures
src/importexport/mei/tests/data/ftrem-01.mei, src/importexport/mei/tests/data/ftrem-01.mscx, src/importexport/mei/tests/mei_tests.cpp
Added MEI and MuseScore fixtures demonstrating fTrem unitdur variants and a new test case mei_ftrem_01 to exercise import of <fTrem>.

Sequence Diagram(s)

sequenceDiagram
    participant MEI_DOM as MEI DOM
    participant Importer as MeiImporter
    participant Converter as Convert
    participant Tremolo as TremoloTwoChord
    participant Score as Score/ObjectModel

    MEI_DOM->>Importer: readFTrem(fTremNode)
    Importer->>Converter: unitdurFromMEI(fTrem)
    Converter-->>Importer: TremoloType
    Importer->>Tremolo: create TremoloTwoChord (register UID)
    Importer->>Importer: readElements() (children)
    MEI_DOM->>Importer: readChord()/readNote()
    Importer->>Score: attach chord -> setChord1/setChord2 and setTremoloTwoChord
    Importer->>Importer: clear m_tremolo state
Loading
sequenceDiagram
    participant Score as Score/ObjectModel
    participant Exporter as MeiExporter
    participant Converter as Convert
    participant MEI_DOM as MEI DOM

    Score->>Exporter: writeChord(chord)
    Exporter->>Exporter: detect TremoloFirstChord
    Exporter->>Converter: unitdurToMEI(tremolo)
    Converter-->>Exporter: data_DURATION
    Exporter->>MEI_DOM: create <fTrem> with `@unitdur` and `@xml:id`
    Exporter->>MEI_DOM: write child notes
    Exporter->>Exporter: detect TremoloSecondChord
    Exporter->>MEI_DOM: close </fTrem>
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is minimal and lacks most required template sections including issue resolution, detailed motivation, CLA checkbox verification, commit message confirmation, and testing verification. Fill out the PR template completely: add issue number (Resolves: #NNNNN), provide detailed motivation for changes, check all applicable checkboxes, confirm commits follow guidelines, verify code quality, and document any unit/vtest cases created.
Docstring Coverage ⚠️ Warning Docstring coverage is 30.77% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title '[MEI] add support for fingered tremolo' directly and specifically describes the main change in the PR, which adds import/export support for fingered tremolos (fTrem elements) to the MEI module.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: ca4f29a3-70be-40af-a23a-2d7c8427e7e5

📥 Commits

Reviewing files that changed from the base of the PR and between c3842f5 and 063154d.

📒 Files selected for processing (9)
  • src/importexport/mei/internal/meiconverter.cpp
  • src/importexport/mei/internal/meiconverter.h
  • src/importexport/mei/internal/meiexporter.cpp
  • src/importexport/mei/internal/meiexporter.h
  • src/importexport/mei/internal/meiimporter.cpp
  • src/importexport/mei/internal/meiimporter.h
  • src/importexport/mei/tests/data/ftrem-01.mei
  • src/importexport/mei/tests/data/ftrem-01.mscx
  • src/importexport/mei/tests/mei_tests.cpp

Comment on lines +1840 to +1844
if (isNode(chordNode.parent(), u"fTrem")) {
TremoloTwoChord* tremolo = Factory::createTremoloTwoChord(chord);
m_uids->reg(tremolo, m_tremoloId);
tremolo->setTremoloType(m_tremoloType);
chord->add(tremolo);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Build one shared TremoloTwoChord for the whole <fTrem>.

Both branches allocate a fresh TremoloTwoChord and attach it to the current chord. src/engraving/dom/tremolotwochord.h expects one object with chord1/chord2 set, and src/engraving/dom/chord.cpp:2361-2376 derives tremoloChordType() from that shared pairing. As written, imported fingered tremolos are left unpaired, so first/second chord classification—and therefore layout/re-export—will be wrong. Please keep a single tremolo instance for the <fTrem> and wire it with setChord1() / setChord2() (or setChords()), then feed its id through readXmlId() rather than m_uids->reg().

Also applies to: 2068-2072

Comment on lines +177 to +179
TEST_F(Mei_Tests, mei_ftrem_01) {
meiReadTest("ftrem-01");
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Add one multi-note chord case here as well.

The new fixture only exercises <fTrem> with <note> children, so the <chord> path added in MeiImporter::readChord() and MeiExporter::writeChord() still isn't covered.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
src/importexport/mei/internal/meiimporter.cpp (2)

1842-1846: ⚠️ Potential issue | 🔴 Critical

Build one shared TremoloTwoChord for the whole <fTrem>.

This still allocates a separate TremoloTwoChord per chord and never wires chord1/chord2. Chord::tremoloChordType(), layout, and re-export expect one paired object. Please mirror src/importexport/mnx/internal/import/mnximportpartmeasures.cpp:626-633 and src/importexport/musicxml/internal/import/importmusicxmlpass2.cpp:6737-6747: create one tremolo, call setChords(...), attach it only on the first chord, and route the MEI id through readXmlId().

Also applies to: 2070-2074


1911-1914: ⚠️ Potential issue | 🟡 Minor

Don't drop unsupported unitdur warnings.

Convert::unitdurFromMEI() can already report invalid unitdur values, but this path ignores warning, so malformed <fTrem unitdur="..."> input imports silently and then proceeds with INVALID_TREMOLO. Please call addLog("tremolo unit duration", fTremNode) before recursing.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 251fc559-9532-4491-a403-bd0b31da8d64

📥 Commits

Reviewing files that changed from the base of the PR and between 063154d and afa28e3.

📒 Files selected for processing (2)
  • src/importexport/mei/internal/meiconverter.cpp
  • src/importexport/mei/internal/meiimporter.cpp

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0ec43401-d2b3-43dc-9ee8-a878ab3595cb

📥 Commits

Reviewing files that changed from the base of the PR and between afa28e3 and 9d3f204.

📒 Files selected for processing (2)
  • src/importexport/mei/internal/meiimporter.cpp
  • src/importexport/mei/internal/meiimporter.h

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
src/importexport/mei/internal/meiimporter.cpp (1)

1896-1917: ⚠️ Potential issue | 🔴 Critical

Critical: readFTrem() will not compile and has logic errors.

Multiple issues in this function:

  1. Line 1908: chord is undefined - There is no chord variable in scope. This code will not compile.

  2. Line 1908: Local variable shadows member - TremoloTwoChord* m_tremolo = ... declares a local variable that shadows the class member m_tremolo. The child elements read via readElements() will see the member (which is uninitialized/nullptr), not this local variable. The tremolo will never be attached to the chords.

  3. Lines 1907-1910: Warning not logged - Convert::unitdurFromMEI() sets warning=true for unsupported values, but it's never checked or logged.

  4. Line 1909: Inconsistent ID registration - Uses m_uids->reg() instead of this->readXmlId() which is used elsewhere and handles MuseScore EIDs properly.

🐛 Proposed fix
 bool MeiImporter::readFTrem(pugi::xml_node fTremNode, Measure* measure, int track, Fraction& ticks)
 {
     IF_ASSERT_FAILED(measure) {
         return false;
     }

     bool success = true;

     libmei::FTrem meiFTrem;
     meiFTrem.Read(fTremNode);

     bool warning = false;
-    TremoloTwoChord* m_tremolo = Factory::createTremoloTwoChord(chord);
-    m_uids->reg(m_tremolo, meiFTrem.m_xmlId);
-    m_tremolo->setTremoloType(Convert::unitdurFromMEI(meiFTrem, warning));
+    m_tremolo = Factory::createTremoloTwoChord(m_score->dummy()->chord());
+    this->readXmlId(m_tremolo, meiFTrem.m_xmlId);
+    TremoloType tremoloType = Convert::unitdurFromMEI(meiFTrem, warning);
+    if (warning) {
+        this->addLog("tremolo unit duration", fTremNode);
+    }
+    m_tremolo->setTremoloType(tremoloType);

     success = readElements(fTremNode, measure, track, ticks);

     m_tremolo = nullptr;

     return success;
 }
src/importexport/mei/internal/meiimporter.h (1)

255-256: ⚠️ Potential issue | 🔴 Critical

Missing initialization of m_tremolo member.

The m_tremolo pointer is declared without a default initializer. In MeiImporter::read() (line 109 in the .cpp), m_tremoloId.clear() is called but m_tremolo is never initialized to nullptr. This leaves the pointer with an indeterminate value, which will cause undefined behavior when checked in readChord()/readNote() with if (m_tremolo).

🐛 Proposed fix
-    engraving::TremoloTwoChord* m_tremolo;
+    engraving::TremoloTwoChord* m_tremolo = nullptr;

Additionally, in meiimporter.cpp read() function around line 109, add initialization:

m_tremoloId.clear();
m_tremolo = nullptr;  // Add this line

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1b984829-9a5f-495a-b5be-0101a9e9b8ba

📥 Commits

Reviewing files that changed from the base of the PR and between 9d3f204 and 8fcc01d.

📒 Files selected for processing (2)
  • src/importexport/mei/internal/meiimporter.cpp
  • src/importexport/mei/internal/meiimporter.h

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant