1 /* This file is part of the KDE project
2 * Copyright (C) 2007 Marijn Kruisselbrink <mkruisselbrink@kde.org>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB. If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19 #include "MusicXmlWriter.h"
20 #include "Global.h"
21 #include "Sheet.h"
22 #include "Part.h"
23 #include "PartGroup.h"
24 #include "VoiceElement.h"
25 #include "Chord.h"
26 #include "VoiceBar.h"
27 #include "Bar.h"
28 #include "Note.h"
29 #include "Clef.h"
30 #include "KeySignature.h"
31 #include "TimeSignature.h"
32 #include "Staff.h"
33
34 #include <KoXmlWriter.h>
35
36 using namespace MusicCore;
37
MusicXmlWriter()38 MusicXmlWriter::MusicXmlWriter()
39 {
40 }
41
~MusicXmlWriter()42 MusicXmlWriter::~MusicXmlWriter()
43 {
44 }
45
writePartGroup(KoXmlWriter & w,int id,PartGroup * group)46 static void writePartGroup(KoXmlWriter& w, int id, PartGroup* group)
47 {
48 w.startElement("music:part-group");
49 w.addAttribute("type", "start");
50 w.addAttribute("number", id);
51
52 if (!group->name().isNull()) {
53 w.startElement("music:group-name");
54 w.addTextNode(group->name());
55 w.endElement(); // music:group-name
56 }
57 if (!group->shortName(false).isNull()) {
58 w.startElement("music:group-abbreviation");
59 w.addTextNode(group->shortName());
60 w.endElement(); // music:group-abbreviation
61 }
62
63 if (group->symbol() != PartGroup::None) {
64 w.startElement("music:group-symbol");
65 switch (group->symbol()) {
66 case PartGroup::None: w.addTextNode("none"); break;
67 case PartGroup::Brace: w.addTextNode("brace"); break;
68 case PartGroup::Line: w.addTextNode("line"); break;
69 case PartGroup::Bracket: w.addTextNode("bracket"); break;
70 }
71 w.endElement(); // music:group-symbol
72 }
73
74 w.startElement("music:group-barline");
75 w.addTextNode(group->commonBarLines() ? "yes" : "no");
76 w.endElement(); // music:group-barline
77
78 w.endElement(); // music:part-group
79 }
80
writePartDesc(KoXmlWriter & w,int id,Part * part)81 static void writePartDesc(KoXmlWriter& w, int id, Part* part)
82 {
83 w.startElement("music:score-part");
84 w.addAttribute("id", QString("P%1").arg(id));
85
86 w.startElement("music:part-name");
87 w.addTextNode(part->name());
88 w.endElement(); // music:part-name
89
90 QString abbr = part->shortName(false);
91 if (!abbr.isNull()) {
92 w.startElement("music:part-abbreviation");
93 w.addTextNode(abbr);
94 w.endElement(); // music:part-abbreviation
95 }
96
97 w.endElement(); // music:score-part
98 }
99
writeChord(KoXmlWriter & w,Chord * chord,Voice * voice,Part * part,int bar)100 static void writeChord(KoXmlWriter& w, Chord* chord, Voice* voice, Part* part, int bar)
101 {
102 if (!chord->noteCount()) {
103 w.startElement("music:note");
104
105 w.startElement("music:rest");
106 w.endElement(); // music:rest
107
108 w.startElement("music:duration");
109 w.addTextNode(QString::number(chord->length()));
110 w.endElement(); // music:duration
111
112 w.startElement("music:voice");
113 w.addTextNode(QString::number(part->indexOfVoice(voice) + 1));
114 w.endElement(); // music:voice
115
116 w.startElement("music:type");
117 w.addTextNode(durationToString(chord->duration()));
118 w.endElement(); // music:type
119
120 for (int i = 0; i < chord->dots(); i++) {
121 w.startElement("music:dot");
122 w.endElement(); // music:dot
123 }
124
125 if (part->staffCount() > 1) {
126 // only write staff info when more than one staff exists
127 Staff* s = chord->staff();
128 w.startElement("music:staff");
129 w.addTextNode(QString::number(part->indexOfStaff(s) + 1));
130 w.endElement(); //music:staff
131 }
132 w.endElement(); // music:note
133 } else for (int n = 0; n < chord->noteCount(); n++) {
134 Staff* staff = chord->note(n)->staff();
135 w.startElement("music:note");
136
137 if (n > 0) {
138 w.startElement("music:chord");
139 w.endElement(); // music:chord
140 }
141
142 w.startElement("music:pitch");
143 w.startElement("music:step");
144 int pitch = chord->note(n)->pitch();
145 char note = 'A' + ((((pitch + 2) % 7) + 7) % 7);
146 w.addTextNode(QString(note));
147 w.endElement(); // music:step
148
149 if (chord->note(n)->accidentals()) {
150 w.startElement("music:alter");
151 w.addTextNode(QString::number(chord->note(n)->accidentals()));
152 w.endElement(); // music:alter
153 }
154
155 w.startElement("music:octave");
156 w.addTextNode(QString::number((pitch + 4*7) / 7)); // first add, than divide to get proper rounding
157 w.endElement(); // music:octave
158 w.endElement(); // music:pitch
159 w.startElement("music:duration");
160 w.addTextNode(QString::number(chord->length()));
161 w.endElement(); // music:duration
162
163 w.startElement("music:voice");
164 w.addTextNode(QString::number(part->indexOfVoice(voice) + 1));
165 w.endElement(); // music:voice
166
167 w.startElement("music:type");
168 w.addTextNode(durationToString(chord->duration()));
169 w.endElement(); // music:type
170
171 for (int i = 0; i < chord->dots(); i++) {
172 w.startElement("music:dot");
173 w.endElement(); // music:dot
174 }
175
176 int activeAccidentals = 0;
177 KeySignature* ks = staff->lastKeySignatureChange(bar);
178 if (ks) activeAccidentals = ks->accidentals(chord->note(n)->pitch());
179 VoiceBar* vb = chord->voiceBar();
180 // next check the bar for the last previous note in the same voice with the same pitch
181 for (int e = 0; e < vb->elementCount(); e++) {
182 Chord* c = dynamic_cast<Chord*>(vb->element(e));
183 if (!c) continue;
184 if (c == chord) break;
185 for (int nid = 0; nid < c->noteCount(); nid++) {
186 Note* note = c->note(nid);
187 if (note->staff() != staff) continue;
188 if (note->pitch() == chord->note(n)->pitch()) {
189 activeAccidentals = note->accidentals();
190 }
191 }
192 }
193
194 if (chord->note(n)->accidentals() != activeAccidentals) {
195 w.startElement("music:accidental");
196 switch (chord->note(n)->accidentals()) {
197 case -2: w.addTextNode("flat-flat"); break;
198 case -1: w.addTextNode("flat"); break;
199 case 0: w.addTextNode("natural"); break;
200 case 1: w.addTextNode("sharp"); break;
201 case 2: w.addTextNode("double-sharp"); break;
202 }
203 w.endElement(); // music:accidental
204 }
205
206 if (part->staffCount() > 1) {
207 // only write staff info when more than one staff exists
208 Staff* s = chord->note(n)->staff();
209 w.startElement("music:staff");
210 w.addTextNode(QString::number(part->indexOfStaff(s) + 1));
211 w.endElement(); //music:staff
212 }
213 w.endElement(); // music:note
214 }
215 }
216
writeClef(KoXmlWriter & w,Clef * clef,Part * part)217 static void writeClef(KoXmlWriter& w, Clef* clef, Part* part)
218 {
219 w.startElement("music:clef");
220
221 if (part->staffCount() > 1) {
222 // only write staff info when more than one staff exists
223 Staff* s = clef->staff();
224 w.addAttribute("number", QString::number(part->indexOfStaff(s) + 1));
225 }
226
227 w.startElement("music:sign");
228 switch (clef->shape()) {
229 case Clef::GClef: w.addTextNode("G"); break;
230 case Clef::FClef: w.addTextNode("F"); break;
231 case Clef::CClef: w.addTextNode("C"); break;
232 }
233 w.endElement(); // music:sign
234
235 w.endElement(); // music:clef
236 }
237
writeKeySignature(KoXmlWriter & w,KeySignature * ks,Part * part)238 static void writeKeySignature(KoXmlWriter& w, KeySignature* ks, Part* part)
239 {
240 w.startElement("music:key");
241
242 if (part->staffCount() > 1) {
243 // only write staff info when more than one staff exists
244 Staff* s = ks->staff();
245 w.addAttribute("number", QString::number(part->indexOfStaff(s) + 1));
246 }
247
248 w.startElement("music:fifths");
249 w.addTextNode(QString::number(ks->accidentals()));
250 w.endElement(); // music:fifths
251
252 w.endElement(); // music:key
253 }
254
writeTimeSignature(KoXmlWriter & w,TimeSignature * ts,Part * part)255 static void writeTimeSignature(KoXmlWriter& w, TimeSignature* ts, Part* part)
256 {
257 w.startElement("music:time");
258
259 if (part->staffCount() > 1) {
260 // only write staff info when more than one staff exists
261 Staff* s = ts->staff();
262 w.addAttribute("number", QString::number(part->indexOfStaff(s) + 1));
263 }
264
265 w.startElement("music:beats");
266 w.addTextNode(QString::number(ts->beats()));
267 w.endElement(); // music:beats
268
269 w.startElement("music:beat-type");
270 w.addTextNode(QString::number(ts->beat()));
271 w.endElement(); // music:beat-type
272
273 w.endElement(); // music:time
274 }
275
writePart(KoXmlWriter & w,int id,Part * part)276 static void writePart(KoXmlWriter& w, int id, Part* part)
277 {
278 w.startElement("music:part");
279 w.addAttribute("id", QString("P%1").arg(id));
280
281 for (int i = 0; i < part->sheet()->barCount(); i++) {
282 Bar* bar = part->sheet()->bar(i);
283
284 w.startElement("music:measure");
285 w.addAttribute("number", i+1);
286
287 bool inAttributes = false;
288 if (i == 0) {
289 w.startElement("music:attributes");
290 w.startElement("music:divisions");
291 w.addTextNode(QString::number(QuarterLength));
292 w.endElement(); // music:divisions
293 inAttributes = true;
294 }
295
296 for (int st = 0; st < part->staffCount(); st++) {
297 Staff* staff = part->staff(st);
298 for (int e = 0; e < bar->staffElementCount(staff); e++) {
299 StaffElement* se = bar->staffElement(staff, e);
300
301 KeySignature* ks = dynamic_cast<KeySignature*>(se);
302 if (ks) {
303 if (!inAttributes) {
304 w.startElement("music:attributes");
305 inAttributes = true;
306 }
307 writeKeySignature(w, ks, part);
308 }
309 }
310 }
311 for (int st = 0; st < part->staffCount(); st++) {
312 Staff* staff = part->staff(st);
313 for (int e = 0; e < bar->staffElementCount(staff); e++) {
314 StaffElement* se = bar->staffElement(staff, e);
315
316 TimeSignature* ts = dynamic_cast<TimeSignature*>(se);
317 if (ts) {
318 if (!inAttributes) {
319 w.startElement("music:attributes");
320 inAttributes = true;
321 }
322 writeTimeSignature(w, ts, part);
323 }
324 }
325 }
326
327 if (i == 0 && part->staffCount() != 1) {
328 w.startElement("music:staves");
329 w.addTextNode(QString::number(part->staffCount()));
330 w.endElement(); // music:staves
331 }
332
333 for (int st = 0; st < part->staffCount(); st++) {
334 Staff* staff = part->staff(st);
335 for (int e = 0; e < bar->staffElementCount(staff); e++) {
336 StaffElement* se = bar->staffElement(staff, e);
337
338 Clef* c = dynamic_cast<Clef*>(se);
339 if (c) {
340 if (!inAttributes) {
341 w.startElement("music:attributes");
342 inAttributes = true;
343 }
344 writeClef(w, c, part);
345 }
346 }
347 }
348
349 if (inAttributes) {
350 w.endElement(); // music:attributes
351 }
352
353 int curTime = 0;
354 for (int voice = 0; voice < part->voiceCount(); voice++) {
355 if (curTime != 0) {
356 w.startElement("music:backup");
357 w.startElement("music:duration");
358 w.addTextNode(QString::number(curTime));
359 w.endElement(); // music:duration
360 w.endElement(); // music:backup
361 }
362
363 Voice* v = part->voice(voice);
364 VoiceBar* vb = part->sheet()->bar(i)->voice(v);
365 for (int e = 0; e < vb->elementCount(); e++) {
366 VoiceElement* ve = vb->element(e);
367 curTime += ve->length();
368
369 Chord* c = dynamic_cast<Chord*>(ve);
370 if(c) writeChord(w, c, v, part, i);
371 }
372 }
373 w.endElement(); // music:measure
374 }
375
376 w.endElement(); // music:part
377 }
378
writeSheet(KoXmlWriter & w,Sheet * sheet,bool writeNamespaceDef)379 void MusicXmlWriter::writeSheet(KoXmlWriter& w, Sheet* sheet, bool writeNamespaceDef)
380 {
381 // w.startDocument("score-partwise", "-//Recordare//DTD MusicXML 1.1 Partwise//EN",
382 // "http://www.musicxml.org/dtds/partwise.dtd");
383 w.startElement("music:score-partwise");
384 if (writeNamespaceDef) {
385 w.addAttribute("xmlns:music", "http://www.calligra.org/music");
386 }
387 w.addAttribute("version", "1.1");
388
389 w.startElement("music:part-list");
390 for (int i = 0; i < sheet->partCount(); i++) {
391 for (int pg = 0; pg < sheet->partGroupCount(); pg++) {
392 if (sheet->partGroup(pg)->firstPart() == i) {
393 writePartGroup(w, pg+1, sheet->partGroup(pg));
394 }
395 }
396 writePartDesc(w, i, sheet->part(i));
397 for (int pg = 0; pg < sheet->partGroupCount(); pg++) {
398 if (sheet->partGroup(pg)->lastPart() == i) {
399 w.startElement("music:part-group");
400 w.addAttribute("type", "stop");
401 w.addAttribute("number", pg+1);
402 w.endElement(); // music:part-group
403 }
404 }
405 }
406 w.endElement(); // music:part-list
407
408 for (int i = 0; i < sheet->partCount(); i++) {
409 writePart(w, i, sheet->part(i));
410 }
411
412 w.endElement(); // music:score-partwise
413 // w.endDocument();
414 }
415
416