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