LCOV - code coverage report
Current view: top level - src/engraving/rw/write - writer.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 88.6 % 184 163
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-Studio-CLA-applies
       4              :  *
       5              :  * MuseScore Studio
       6              :  * Music Composition & Notation
       7              :  *
       8              :  * Copyright (C) 2021 MuseScore Limited and others
       9              :  *
      10              :  * This program is free software: you can redistribute it and/or modify
      11              :  * it under the terms of the GNU General Public License version 3 as
      12              :  * published by the Free Software Foundation.
      13              :  *
      14              :  * This program is distributed in the hope that it will be useful,
      15              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      16              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17              :  * GNU General Public License for more details.
      18              :  *
      19              :  * You should have received a copy of the GNU General Public License
      20              :  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
      21              :  */
      22              : #include "writer.h"
      23              : 
      24              : #include "../types/types.h"
      25              : 
      26              : #include "dom/score.h"
      27              : #include "dom/masterscore.h"
      28              : #include "dom/part.h"
      29              : #include "dom/excerpt.h"
      30              : #include "dom/staff.h"
      31              : #include "dom/segment.h"
      32              : 
      33              : #include "global/io/buffer.h"
      34              : 
      35              : #include "../xmlwriter.h"
      36              : #include "../inoutdata.h"
      37              : 
      38              : #include "twrite.h"
      39              : #include "staffwrite.h"
      40              : 
      41              : using namespace muse;
      42              : using namespace mu::engraving;
      43              : using namespace mu::engraving::write;
      44              : 
      45         1938 : Writer::Writer()
      46              : {
      47         1938 : }
      48              : 
      49         1249 : bool Writer::writeScore(Score* score, io::IODevice* device, rw::WriteInOutData* inout)
      50              : {
      51         1259 :     TRACEFUNC;
      52              : 
      53         1249 :     XmlWriter xml(device);
      54         1249 :     WriteContext ctx(score);
      55         1249 :     if (inout) {
      56            3 :         ctx = inout->ctx;
      57              :     }
      58              : 
      59         1249 :     xml.startDocument();
      60              : 
      61         3747 :     xml.startElement("museScore", { { "version", Constants::MSC_VERSION_STR } });
      62              : 
      63         1249 :     if (!MScore::testMode) {
      64            0 :         xml.tag("programVersion", application()->version().toString());
      65            0 :         xml.tag("programRevision", application()->revision());
      66              :     }
      67              : 
      68              :     compat::WriteScoreHook hook;
      69         1249 :     write(score, xml, ctx, hook);
      70              : 
      71         1249 :     xml.endElement();
      72         1249 :     xml.flush();
      73              : 
      74         1249 :     if (!inout || !inout->ctx.shouldWriteRange()) {
      75              :         //update version values for i.e. plugin access
      76         1249 :         score->m_mscoreVersion = application()->version().toString();
      77         1249 :         score->m_mscoreRevision = application()->revision().toInt(nullptr, 16);
      78         1249 :         score->m_mscVersion = Constants::MSC_VERSION;
      79              :     }
      80              : 
      81         1249 :     if (inout) {
      82            3 :         inout->ctx = ctx;
      83              :     }
      84              : 
      85         1249 :     return true;
      86         2498 : }
      87              : 
      88         1388 : void Writer::write(Score* score, XmlWriter& xml, WriteContext& ctx, compat::WriteScoreHook& hook)
      89              : {
      90         1388 :     TRACEFUNC;
      91              : 
      92         1388 :     xml.startElement(score);
      93              : 
      94         1388 :     TWrite::writeItemEid(score, xml);
      95              : 
      96         1388 :     if (Excerpt* e = score->excerpt()) {
      97          145 :         if (!e->name().empty()) {
      98          141 :             xml.tag("name", e->name());
      99              :         }
     100              : 
     101          145 :         const TracksMap& tracks = e->tracksMapping();
     102          145 :         if (!(tracks.size() == e->nstaves() * VOICES) && !tracks.empty()) {
     103           26 :             for (auto it = tracks.begin(); it != tracks.end(); ++it) {
     104           84 :                 xml.tag("Tracklist", { { "sTrack", it->first }, { "dstTrack", it->second } });
     105              :             }
     106              :         }
     107              : 
     108          145 :         if (e->initialPartId().isValid()) {
     109           23 :             xml.tag("initialPartId", e->initialPartId().toUint64());
     110              :         }
     111              :     }
     112              : 
     113         1388 :     if (score->isLayoutMode(LayoutMode::LINE)) {
     114            0 :         xml.tag("layoutMode", "line");
     115              :     }
     116         1388 :     if (score->isLayoutMode(LayoutMode::SYSTEM)) {
     117            0 :         xml.tag("layoutMode", "system");
     118              :     }
     119              : 
     120         1388 :     if (score->isMaster() && !MScore::testMode) {
     121            0 :         score->m_synthesizerState.write(xml);
     122              :     }
     123              : 
     124         1388 :     if (score->pageNumberOffset()) {
     125            1 :         xml.tag("page-offset", score->pageNumberOffset());
     126              :     }
     127         1388 :     xml.tag("Division", Constants::DIVISION);
     128         1388 :     ctx.setCurTrack(muse::nidx);
     129              : 
     130         1388 :     hook.onWriteStyle302(score, xml);
     131              : 
     132         1388 :     xml.tag("showInvisible", score->m_showInvisible);
     133         1388 :     xml.tag("showUnprintable", score->m_showUnprintable);
     134         1388 :     xml.tag("showFrames", score->m_showFrames);
     135         1388 :     xml.tag("showMargins", score->m_showPageborders);
     136         1388 :     xml.tag("markIrregularMeasures", score->m_markIrregularMeasures, true);
     137              : 
     138         1388 :     if (!score->m_showSoundFlags) { // true by default
     139            0 :         xml.tag("showSoundFlags", score->m_showSoundFlags);
     140              :     }
     141              : 
     142         1388 :     if (score->m_isOpen) {
     143           90 :         xml.tag("open", score->m_isOpen);
     144              :     }
     145              : 
     146        16832 :     for (const auto& t : score->m_metaTags) {
     147              :         // do not output "platform" and "creationDate" in test and save template mode
     148        15444 :         if ((!MScore::testMode && !MScore::saveTemplateMode) || (t.first != "platform" && t.first != "creationDate")) {
     149        51832 :             xml.tag("metaTag", { { "name", t.first.toXmlEscaped() } }, t.second);
     150              :         }
     151              :     }
     152              : 
     153         1388 :     if (score->m_scoreOrder.isValid()) {
     154           62 :         ScoreOrder order = score->m_scoreOrder;
     155           62 :         order.updateInstruments(score);
     156           62 :         order.write(xml);
     157           62 :     }
     158              : 
     159         1388 :     staff_idx_t staffStart = 0;
     160         1388 :     staff_idx_t staffEnd = 0;
     161         1388 :     MeasureBase* measureStart = nullptr;
     162         1388 :     MeasureBase* measureEnd = nullptr;
     163              : 
     164         1388 :     if (ctx.shouldWriteRange()) {
     165            0 :         const WriteRange& r = ctx.range().value();
     166            0 :         staffStart = r.startStaffIdx;
     167            0 :         staffEnd = r.endStaffIdx;
     168            0 :         measureStart = r.startMeasure;
     169            0 :         measureEnd = r.endMeasure;
     170              :     } else {
     171         1388 :         staffEnd     = score->nstaves();
     172         1388 :         measureStart = score->first();
     173              :     }
     174              : 
     175         1388 :     if (!score->m_systemObjectStaves.empty()) {
     176            4 :         bool saveSysObjStaves = false;
     177            4 :         for (const Staff* s : score->m_systemObjectStaves) {
     178            4 :             IF_ASSERT_FAILED(s->idx() != muse::nidx) {
     179            0 :                 continue;
     180              :             }
     181            4 :             saveSysObjStaves = true;
     182            4 :             break;
     183              :         }
     184              : 
     185            4 :         if (saveSysObjStaves) {
     186            4 :             xml.startElement("SystemObjects");
     187           18 :             for (const Staff* s : score->m_systemObjectStaves) {
     188           14 :                 const staff_idx_t idx = s->idx();
     189           14 :                 IF_ASSERT_FAILED(idx != muse::nidx) {
     190            0 :                     continue;
     191              :                 }
     192              : 
     193           14 :                 if (ctx.shouldWriteRange()) {
     194            0 :                     if (idx < staffStart || idx >= staffEnd) {
     195            0 :                         continue;
     196              :                     }
     197              :                 }
     198              : 
     199           42 :                 xml.tag("Instance", { { "staffId", idx + 1 } });
     200              :             }
     201            4 :             xml.endElement();
     202              :         }
     203              :     }
     204              : 
     205         1388 :     ctx.setCurTrack(0);
     206              : 
     207         1388 :     if (score->isMaster()) {
     208              :         // Let's decide: write midi mapping to a file or not
     209         1243 :         score->masterScore()->checkMidiMapping();
     210              :     }
     211              : 
     212         1738 :     auto shouldWritePart = [&ctx, score, staffStart, staffEnd](const Part* part) {
     213         1738 :         if (!ctx.shouldWriteRange()) {
     214         1738 :             return true;
     215              :         }
     216              : 
     217            0 :         const staff_idx_t firstStaffIdx = score->staffIdx(part);
     218            0 :         const staff_idx_t lastStaffIdx = firstStaffIdx + part->nstaves() - 1;
     219              : 
     220            0 :         return (firstStaffIdx >= staffStart && firstStaffIdx < staffEnd)
     221            0 :                || (lastStaffIdx >= staffStart && lastStaffIdx < staffEnd);
     222         1388 :     };
     223              : 
     224         3126 :     for (const Part* part : score->m_parts) {
     225         1738 :         if (shouldWritePart(part)) {
     226         1738 :             TWrite::write(part, xml, ctx);
     227              :         }
     228              :     }
     229              : 
     230         1388 :     ctx.setCurTrack(0);
     231         1388 :     ctx.setTrackDiff(-static_cast<int>(staffStart * VOICES));
     232         1388 :     if (measureStart) {
     233         3357 :         for (staff_idx_t staffIdx = staffStart; staffIdx < staffEnd; ++staffIdx) {
     234         1969 :             const Staff* st = score->staff(staffIdx);
     235         1969 :             StaffWrite::writeStaff(st, xml, ctx, measureStart, measureEnd, staffStart, staffIdx);
     236              :         }
     237              :     }
     238         1388 :     ctx.setCurTrack(muse::nidx);
     239              : 
     240         1388 :     TWrite::writeScoreSpanners(score, xml, ctx);
     241              : 
     242         1388 :     hook.onWriteExcerpts302(score, xml, ctx);
     243              : 
     244         1388 :     TWrite::writePageLocks(score, xml);
     245         1388 :     TWrite::writeSystemLocks(score, xml);
     246         1388 :     TWrite::writeSystemDividers(score, xml, ctx);
     247              : 
     248         1388 :     xml.endElement(); // score
     249        14381 : }
     250              : 
     251           84 : void Writer::writeSegments(XmlWriter& xml, WriteContext& ctx, track_idx_t strack, track_idx_t etrack,
     252              :                            Segment* sseg, Segment* eseg, bool writeSystemElements, bool forceTimeSig)
     253              : {
     254           84 :     ctx.setCurTrack(strack);
     255           84 :     TWrite::writeSegments(xml, ctx, strack, etrack, sseg, eseg, writeSystemElements, forceTimeSig);
     256           84 : }
     257              : 
     258          706 : void Writer::doWriteItem(const EngravingItem* item, XmlWriter& xml)
     259              : {
     260          706 :     WriteContext ctx(item->score(), /* clipboardMode = */ true);
     261          706 :     TWrite::writeItem(item, xml, ctx);
     262          706 : }
     263              : 
     264           81 : muse::ByteArray Writer::writeStaffSelection(Score* score, const SelectionFilter& filter, staff_idx_t staffStart, staff_idx_t staffEnd,
     265              :                                             const Fraction& tickStart, const Fraction& tickEnd, Segment* startSegment,
     266              :                                             Segment* endSegment)
     267              : {
     268           81 :     auto buffer = io::Buffer::opened(io::IODevice::WriteOnly);
     269           81 :     XmlWriter xml(&buffer);
     270              : 
     271           81 :     xml.startDocument();
     272              : 
     273           81 :     Fraction ticks = tickEnd - tickStart;
     274           81 :     int staves = static_cast<int>(staffEnd - staffStart);
     275              : 
     276              :     XmlWriter::Attributes staffListAttributes = {
     277           81 :         { "version", (MScore::testMode ? "2.00" : Constants::MSC_VERSION_STR) },
     278            0 :         { "tick", tickStart.toString() },
     279           81 :         { "len", ticks.toString() },
     280              :         { "staff", staffStart },
     281              :         { "staves", staves },
     282          567 :     };
     283              : 
     284              :     // Note: canCopy() ensures that the whole selection has a single time stretch ratio.
     285           81 :     Fraction timeStretch = score->staff(staffStart)->timeStretch(tickStart);
     286           81 :     if (timeStretch != Fraction(1, 1)) {
     287            0 :         staffListAttributes.push_back({ "timeStretch", timeStretch.toString() });
     288              :     }
     289              : 
     290           81 :     xml.startElement("StaffList", staffListAttributes);
     291              : 
     292           81 :     WriteContext ctx(score, /* clipboardMode = */ true);
     293           81 :     ctx.setFilter(filter);
     294           81 :     ctx.setFirstClipboardTick(tickStart);
     295              : 
     296          165 :     for (staff_idx_t staffIdx = staffStart; staffIdx < staffEnd; ++staffIdx) {
     297           84 :         track_idx_t startTrack = staffIdx * VOICES;
     298           84 :         track_idx_t endTrack   = startTrack + VOICES;
     299              : 
     300          252 :         xml.startElement("Staff", { { "id", staffIdx } });
     301              : 
     302           84 :         Staff* staff = score->staff(staffIdx);
     303           84 :         Part* part = staff->part();
     304           84 :         Interval interval = part->instrument(startSegment->tick())->transpose();
     305           84 :         if (interval.chromatic) {
     306            1 :             xml.tag("transposeChromatic", interval.chromatic);
     307              :         }
     308           84 :         if (interval.diatonic) {
     309            1 :             xml.tag("transposeDiatonic", interval.diatonic);
     310              :         }
     311           84 :         writeSegments(xml, ctx, startTrack, endTrack, startSegment, endSegment, false, false);
     312           84 :         xml.endElement();
     313              :     }
     314              : 
     315           81 :     TWrite::writeScoreSpanners(score, staff2track(staffStart), staff2track(staffEnd), startSegment, endSegment, xml, ctx);
     316              : 
     317           81 :     xml.endElement();
     318           81 :     xml.flush();
     319          162 :     return buffer.data();
     320          327 : }
     321              : 
     322           11 : muse::ByteArray Writer::writeSymbolListSelection(track_idx_t fromTrack, track_idx_t toTrack,
     323              :                                                  const std::vector<SelectedSymbol>& symbols)
     324              : {
     325           11 :     auto buffer = io::Buffer::opened(io::IODevice::WriteOnly);
     326           11 :     XmlWriter xml(&buffer);
     327              : 
     328           11 :     xml.startDocument();
     329              : 
     330           55 :     xml.startElement("SymbolList", { { "version", Constants::MSC_VERSION_STR },
     331              :                          { "fromtrack", fromTrack },
     332              :                          { "totrack", toTrack } });
     333              : 
     334           11 :     track_idx_t currTrack = muse::nidx;
     335          120 :     for (const SelectedSymbol& symbol : symbols) {
     336          109 :         if (currTrack != symbol.track) {
     337           12 :             xml.tag("trackOffset", static_cast<int>(symbol.track - fromTrack));
     338           12 :             currTrack = symbol.track;
     339              :         }
     340          109 :         xml.tag("tickOffset", symbol.tickOffset);
     341          109 :         xml.tag("segDelta", symbol.segDelta);
     342          109 :         doWriteItem(symbol.element, xml);
     343              :     }
     344              : 
     345           11 :     xml.endElement();
     346           11 :     xml.flush();
     347           11 :     buffer.close();
     348           22 :     return buffer.data();
     349           22 : }
        

Generated by: LCOV version 2.0-1