1
package org.herac.tuxguitar.io.musicxml;
3
import java.io.OutputStream;
4
import java.util.Iterator;
6
import javax.xml.parsers.DocumentBuilder;
7
import javax.xml.parsers.DocumentBuilderFactory;
8
import javax.xml.transform.OutputKeys;
9
import javax.xml.transform.Result;
10
import javax.xml.transform.Source;
11
import javax.xml.transform.Transformer;
12
import javax.xml.transform.TransformerFactory;
13
import javax.xml.transform.dom.DOMSource;
14
import javax.xml.transform.stream.StreamResult;
16
import org.herac.tuxguitar.io.base.TGFileFormatException;
17
import org.herac.tuxguitar.player.base.MidiInstrument;
18
import org.herac.tuxguitar.song.managers.TGSongManager;
19
import org.herac.tuxguitar.song.models.TGBeat;
20
import org.herac.tuxguitar.song.models.TGDuration;
21
import org.herac.tuxguitar.song.models.TGMeasure;
22
import org.herac.tuxguitar.song.models.TGNote;
23
import org.herac.tuxguitar.song.models.TGSong;
24
import org.herac.tuxguitar.song.models.TGString;
25
import org.herac.tuxguitar.song.models.TGTempo;
26
import org.herac.tuxguitar.song.models.TGTimeSignature;
27
import org.herac.tuxguitar.song.models.TGTrack;
28
import org.herac.tuxguitar.song.models.TGTupleto;
29
import org.w3c.dom.Attr;
30
import org.w3c.dom.Document;
31
import org.w3c.dom.Node;
33
public class MusicXMLWriter {
35
private static final String[] NOTE_NAMES = new String[]{"C","D","E","F","G","A","B"};
37
private static final int NOTE_SHARPS[] = new int[]{0,0,1,1,2,3,3,4,4,5,5,6};
39
private static final int NOTE_FLATS[] = new int[]{0,1,1,2,2,3,4,4,5,5,6,6};
41
private static final boolean[] NOTE_ALTERATIONS = new boolean[]{false,true,false,true,false,false,true,false,true,false,true,false};
43
private static final String[] DURATION_NAMES = new String[]{ "whole", "half", "quarter", "eighth", "16th", "32nd", "64th", };
45
private static final int DURATION_DIVISIONS = (int)TGDuration.QUARTER_TIME;
47
private static final int[] DURATION_VALUES = new int[]{
48
DURATION_DIVISIONS * 4, // WHOLE
49
DURATION_DIVISIONS * 2, // HALF
50
DURATION_DIVISIONS * 1, // QUARTER
51
DURATION_DIVISIONS / 2, // EIGHTH
52
DURATION_DIVISIONS / 4, // SIXTEENTH
53
DURATION_DIVISIONS / 8, // THIRTY_SECOND
54
DURATION_DIVISIONS / 16, // SIXTY_FOURTH
57
private TGSongManager manager;
59
private OutputStream stream;
61
private Document document;
63
public MusicXMLWriter(OutputStream stream){
67
public void writeSong(TGSong song) throws TGFileFormatException{
69
this.manager = new TGSongManager();
70
this.manager.setSong(song);
71
this.document = newDocument();
73
Node node = this.addNode(this.document,"score-partwise");
74
this.writeHeaders(node);
80
}catch(Throwable throwable){
81
throw new TGFileFormatException("Could not write song!.",throwable);
85
private void writeHeaders(Node parent){
86
this.writeWork(parent);
87
this.writeIdentification(parent);
90
private void writeWork(Node parent){
91
this.addNode(this.addNode(parent,"work"),"work-title",this.manager.getSong().getName());
94
private void writeIdentification(Node parent){
95
Node identification = this.addNode(parent,"identification");
96
this.addNode(this.addNode(identification,"encoding"), "software", "TuxGuitar");
97
this.addAttribute(this.addNode(identification,"creator",this.manager.getSong().getAuthor()),"type","composer");
100
private void writeSong(Node parent){
101
this.writePartList(parent);
102
this.writeParts(parent);
105
private void writePartList(Node parent){
106
Node partList = this.addNode(parent,"part-list");
108
Iterator tracks = this.manager.getSong().getTracks();
109
while(tracks.hasNext()){
110
TGTrack track = (TGTrack)tracks.next();
112
Node scoreParts = this.addNode(partList,"score-part");
113
this.addAttribute(scoreParts, "id", "P" + track.getNumber());
115
this.addNode(scoreParts, "part-name", track.getName());
117
Node scoreInstrument = this.addAttribute(this.addNode(scoreParts, "score-instrument"), "id", "P" + track.getNumber() + "-I1");
118
this.addNode(scoreInstrument, "instrument-name",MidiInstrument.INSTRUMENT_LIST[track.getChannel().getInstrument()].getName());
120
Node midiInstrument = this.addAttribute(this.addNode(scoreParts, "midi-instrument"), "id", "P" + track.getNumber() + "-I1");
121
this.addNode(midiInstrument, "midi-channel",Integer.toString(track.getChannel().getChannel() + 1));
122
this.addNode(midiInstrument, "midi-program",Integer.toString(track.getChannel().getInstrument() + 1));
126
private void writeParts(Node parent){
127
Iterator tracks = this.manager.getSong().getTracks();
128
while(tracks.hasNext()){
129
TGTrack track = (TGTrack)tracks.next();
130
Node part = this.addAttribute(this.addNode(parent,"part"), "id", "P" + track.getNumber());
132
TGMeasure previous = null;
134
Iterator measures = track.getMeasures();
135
while(measures.hasNext()){
136
TGMeasure measure = (TGMeasure)measures.next();
137
Node measureNode = this.addAttribute(this.addNode(part,"measure"), "number",Integer.toString(measure.getNumber()));
139
this.writeMeasureAttributes(measureNode, measure, previous);
140
this.writeDirection(measureNode, measure, previous);
141
this.writeBeats(measureNode, measure);
148
private void writeMeasureAttributes(Node parent,TGMeasure measure, TGMeasure previous){
149
boolean divisionChanges = (previous == null);
150
boolean keyChanges = (previous == null || measure.getKeySignature() != previous.getKeySignature());
151
boolean clefChanges = (previous == null || measure.getClef() != previous.getClef());
152
boolean timeSignatureChanges = (previous == null || !measure.getTimeSignature().isEqual(previous.getTimeSignature()));
153
boolean tuningChanges = (measure.getNumber() == 1);
154
if(divisionChanges || keyChanges || clefChanges || timeSignatureChanges){
155
Node measureAttributes = this.addNode(parent,"attributes");
157
this.addNode(measureAttributes,"divisions",Integer.toString(DURATION_DIVISIONS));
160
this.writeKeySignature(measureAttributes, measure.getKeySignature());
163
this.writeClef(measureAttributes,measure.getClef());
165
if(timeSignatureChanges){
166
this.writeTimeSignature(measureAttributes,measure.getTimeSignature());
169
this.writeTuning(measureAttributes, measure.getTrack());
174
private void writeTuning(Node parent, TGTrack track){
175
Node staffDetailsNode = this.addNode(parent,"staff-details");
176
this.addNode(staffDetailsNode, "staff-lines", Integer.toString( track.stringCount() ));
177
for( int i = track.stringCount() ; i > 0 ; i --){
178
TGString string = track.getString( i );
179
Node stringNode = this.addNode(staffDetailsNode, "staff-tuning");
180
this.addAttribute(stringNode, "line", Integer.toString( (track.stringCount() - string.getNumber()) + 1 ) );
181
this.addNode(stringNode, "tuning-step", NOTE_NAMES[ NOTE_SHARPS[ (string.getValue() % 12) ] ] );
182
this.addNode(stringNode, "tuning-octave", Integer.toString(string.getValue() / 12) );
186
private void writeTimeSignature(Node parent, TGTimeSignature ts){
187
Node node = this.addNode(parent,"time");
188
this.addNode(node,"beats",Integer.toString(ts.getNumerator()));
189
this.addNode(node,"beat-type",Integer.toString(ts.getDenominator().getValue()));
192
private void writeKeySignature(Node parent, int ks){
195
value = ( (((ks - 1) % 7) + 1) * ( ks > 7?-1:1));
197
Node key = this.addNode(parent,"key");
198
this.addNode(key,"fifths",Integer.toString( value ));
199
this.addNode(key,"mode","major");
202
private void writeClef(Node parent, int clef){
203
Node node = this.addNode(parent,"clef");
204
if(clef == TGMeasure.CLEF_TREBLE){
205
this.addNode(node,"sign","G");
206
this.addNode(node,"line","2");
208
else if(clef == TGMeasure.CLEF_BASS){
209
this.addNode(node,"sign","F");
210
this.addNode(node,"line","4");
212
else if(clef == TGMeasure.CLEF_TENOR){
213
this.addNode(node,"sign","G");
214
this.addNode(node,"line","2");
216
else if(clef == TGMeasure.CLEF_ALTO){
217
this.addNode(node,"sign","G");
218
this.addNode(node,"line","2");
222
private void writeDirection(Node parent, TGMeasure measure, TGMeasure previous){
223
boolean tempoChanges = (previous == null || measure.getTempo().getValue() != previous.getTempo().getValue());
226
Node direction = this.addAttribute(this.addNode(parent,"direction"),"placement","above");
227
this.writeMeasureTempo(direction, measure.getTempo());
231
private void writeMeasureTempo(Node parent,TGTempo tempo){
232
this.addAttribute(this.addNode(parent,"sound"),"tempo",Integer.toString(tempo.getValue()));
235
private void writeBeats(Node parent, TGMeasure measure){
236
int ks = measure.getKeySignature();
237
int beatCount = measure.countBeats();
238
for(int b = 0; b < beatCount; b ++){
239
TGBeat beat = measure.getBeat( b );
241
if(beat.isRestBeat()){
242
Node noteNode = this.addNode(parent,"note");
243
this.addNode(noteNode,"rest");
244
this.addNode(noteNode,"voice","1");
245
this.writeDuration(noteNode, beat.getDuration());
248
int noteCount = beat.countNotes();
249
for(int n = 0; n < noteCount; n ++){
250
TGNote note = beat.getNote( n );
252
Node noteNode = this.addNode(parent,"note");
253
int value = (note.getBeat().getMeasure().getTrack().getString(note.getString()).getValue() + note.getValue());
255
Node pitchNode = this.addNode(noteNode,"pitch");
256
this.addNode(pitchNode,"step",NOTE_NAMES[ (ks <= 7 ? NOTE_SHARPS[value % 12] : NOTE_FLATS[value % 12] )]);
257
this.addNode(pitchNode,"octave",Integer.toString(value / 12));
258
if(NOTE_ALTERATIONS[ value % 12 ]){
259
this.addNode(pitchNode,"alter", ( ks <= 7 ? "1" : "-1" ) );
262
Node technicalNode = this.addNode(this.addNode(noteNode, "notations"), "technical");
263
this.addNode(technicalNode,"fret", Integer.toString( note.getValue() ));
264
this.addNode(technicalNode,"string", Integer.toString( note.getString() ));
266
this.addNode(noteNode,"voice","1");
267
this.writeDuration(noteNode, beat.getDuration());
269
if(note.isTiedNote()){
270
this.addAttribute(this.addNode(noteNode,"tie"),"type","stop");
273
this.addNode(noteNode,"chord");
280
private void writeDuration(Node parent, TGDuration duration){
281
int index = duration.getIndex();
282
if( index >=0 && index <= 6 ){
283
int value = (DURATION_VALUES[ index ] * duration.getTupleto().getTimes() / duration.getTupleto().getEnters());
284
if(duration.isDotted()){
285
value += (value / 2);
287
else if(duration.isDoubleDotted()){
288
value += ((value / 4) * 3);
291
this.addNode(parent,"duration",Integer.toString(value));
292
this.addNode(parent,"type",DURATION_NAMES[ index ]);
294
if(duration.isDotted()){
295
this.addNode(parent,"dot");
297
else if(duration.isDoubleDotted()){
298
this.addNode(parent,"dot");
299
this.addNode(parent,"dot");
302
if(!duration.getTupleto().isEqual(TGTupleto.NORMAL)){
303
Node tupleto = this.addNode(parent,"time-modification");
304
this.addNode(tupleto,"actual-notes",Integer.toString(duration.getTupleto().getEnters()));
305
this.addNode(tupleto,"normal-notes",Integer.toString(duration.getTupleto().getTimes()));
310
private Node addAttribute(Node node, String name, String value){
311
Attr attribute = this.document.createAttribute(name);
312
attribute.setNodeValue(value);
313
node.getAttributes().setNamedItem(attribute);
317
private Node addNode(Node parent, String name){
318
Node node = this.document.createElement(name);
319
parent.appendChild(node);
323
private Node addNode(Node parent, String name, String content){
324
Node node = this.addNode(parent, name);
325
node.setTextContent(content);
329
private Document newDocument() {
331
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
332
DocumentBuilder builder = factory.newDocumentBuilder();
333
Document document = builder.newDocument();
335
}catch(Throwable throwable){
336
throwable.printStackTrace();
341
private void saveDocument() {
343
TransformerFactory xformFactory = TransformerFactory.newInstance();
344
Transformer idTransform = xformFactory.newTransformer();
345
Source input = new DOMSource(this.document);
346
Result output = new StreamResult(this.stream);
347
idTransform.setOutputProperty(OutputKeys.INDENT, "yes");
348
idTransform.transform(input, output);
349
}catch(Throwable throwable){
350
throwable.printStackTrace();