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 : }
|