You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
rosegarden/src/document/io/MusicXmlExporter.cpp

554 lines
19 KiB

/*
Rosegarden
A MIDI and audio sequencer and musical notation editor.
This program is Copyright 2000-2008
Guillaume Laurent <glaurent@telegraph-road.org>,
Chris Cannam <cannam@all-day-breakfast.com>,
Richard Bown <richard.bown@ferventsoftware.com>
This file is Copyright 2002
Hans Kieserman <hkieserman@mail.com>
with heavy lifting from csoundio as it was on 13/5/2002.
The moral rights of Guillaume Laurent, Chris Cannam, and Richard
Bown to claim authorship of this work have been asserted.
Other copyrights also apply to some parts of this work. Please
see the AUTHORS file and individual file headers for details.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version. See the file
COPYING included with this distribution for more information.
*/
#include "MusicXmlExporter.h"
#include "base/BaseProperties.h"
#include "base/Composition.h"
#include "base/CompositionTimeSliceAdapter.h"
#include "base/Event.h"
#include "base/Instrument.h"
#include "base/NotationTypes.h"
#include "base/XmlExportable.h"
#include "document/RosegardenGUIDoc.h"
#include "gui/application/RosegardenApplication.h"
#include "gui/general/ProgressReporter.h"
#include <tqobject.h>
namespace Rosegarden
{
using namespace BaseProperties;
MusicXmlExporter::MusicXmlExporter(TQObject *parent,
RosegardenGUIDoc *doc,
std::string fileName) :
ProgressReporter(parent, "musicXmlExporter"),
m_doc(doc),
m_fileName(fileName)
{
// nothing else
}
MusicXmlExporter::~MusicXmlExporter()
{
// nothing
}
void
MusicXmlExporter::writeNote(Event *e, timeT lastNoteTime,
AccidentalTable &accTable,
const Clef &clef,
const Rosegarden::Key &key,
std::ofstream &str)
{
str << "\t\t\t<note>" << std::endl;
Pitch pitch(64);
Accidental acc;
Accidental displayAcc;
bool cautionary;
Accidental processedDisplayAcc;
if (e->isa(Note::EventRestType)) {
str << "\t\t\t\t<rest/>" << std::endl;
} else {
// Order of MusicXML elements within a note:
// chord
// pitch
// duration
// tie
// instrument
// voice
// type
// dot(s)
// accidental
// time modification
// stem
// notehead
// staff
// beam
// notations
// lyric
if (e->getNotationAbsoluteTime() == lastNoteTime) {
str << "\t\t\t\t<chord/>" << std::endl;
} else {
accTable.update();
}
str << "\t\t\t\t<pitch>" << std::endl;
long p = 0;
e->get<Int>(PITCH, p);
pitch = p;
str << "\t\t\t\t\t<step>" << pitch.getNoteName(key) << "</step>" << std::endl;
acc = pitch.getAccidental(key.isSharp());
displayAcc = pitch.getDisplayAccidental(key);
cautionary = false;
processedDisplayAcc =
accTable.processDisplayAccidental
(displayAcc, pitch.getHeightOnStaff(clef, key), cautionary);
// don't handle cautionary accidentals here:
if (cautionary)
processedDisplayAcc = Accidentals::NoAccidental;
if (acc == Accidentals::DoubleFlat) {
str << "\t\t\t\t\t<alter>-2</alter>" << std::endl;
} else if (acc == Accidentals::Flat) {
str << "\t\t\t\t\t<alter>-1</alter>" << std::endl;
} else if (acc == Accidentals::Sharp) {
str << "\t\t\t\t\t<alter>1</alter>" << std::endl;
} else if (acc == Accidentals::DoubleSharp) {
str << "\t\t\t\t\t<alter>2</alter>" << std::endl;
}
int octave = pitch.getOctave( -1);
str << "\t\t\t\t\t<octave>" << octave << "</octave>" << std::endl;
str << "\t\t\t\t</pitch>" << std::endl;
}
// Since there's no way to provide the performance absolute time
// for a note, there's also no point in providing the performance
// duration, even though it might in principle be of interest
str << "\t\t\t\t<duration>" << e->getNotationDuration() << "</duration>" << std::endl;
if (!e->isa(Note::EventRestType)) {
if (e->has(TIED_BACKWARD) &&
e->get
<Bool>(TIED_BACKWARD)) {
str << "\t\t\t\t<tie type=\"stop\"/>" << std::endl;
}
if (e->has(TIED_FORWARD) &&
e->get
<Bool>(TIED_FORWARD)) {
str << "\t\t\t\t<tie type=\"start\"/>" << std::endl;
}
// Incomplete: will RG ever use this?
str << "\t\t\t\t<voice>" << "1" << "</voice>" << std::endl;
}
Note note = Note::getNearestNote(e->getNotationDuration());
static const char *noteNames[] = {
"64th", "32nd", "16th", "eighth", "quarter", "half", "whole", "breve"
};
int noteType = note.getNoteType();
if (noteType < 0 || noteType >= int(sizeof(noteNames) / sizeof(noteNames[0]))) {
std::cerr << "WARNING: MusicXmlExporter::writeNote: bad note type "
<< noteType << std::endl;
noteType = 4;
}
str << "\t\t\t\t<type>" << noteNames[noteType] << "</type>" << std::endl;
for (int i = 0; i < note.getDots(); ++i) {
str << "\t\t\t\t<dot/>" << std::endl;
}
if (!e->isa(Note::EventRestType)) {
if (processedDisplayAcc == Accidentals::DoubleFlat) {
str << "\t\t\t\t<accidental>flat-flat</accidental>" << std::endl;
} else if (processedDisplayAcc == Accidentals::Flat) {
str << "\t\t\t\t<accidental>flat</accidental>" << std::endl;
} else if (processedDisplayAcc == Accidentals::Natural) {
str << "\t\t\t\t<accidental>natural</accidental>" << std::endl;
} else if (processedDisplayAcc == Accidentals::Sharp) {
str << "\t\t\t\t<accidental>sharp</accidental>" << std::endl;
} else if (processedDisplayAcc == Accidentals::DoubleSharp) {
str << "\t\t\t\t<accidental>double-sharp</accidental>" << std::endl;
}
bool haveNotations = false;
if (e->has(TIED_BACKWARD) &&
e->get
<Bool>(TIED_BACKWARD)) {
if (!haveNotations) {
str << "\t\t\t\t<notations>" << std::endl;
haveNotations = true;
}
str << "\t\t\t\t\t<tied type=\"stop\"/>" << std::endl;
}
if (e->has(TIED_FORWARD) &&
e->get
<Bool>(TIED_FORWARD)) {
if (!haveNotations) {
str << "\t\t\t\t<notations>" << std::endl;
haveNotations = true;
}
str << "\t\t\t\t\t<tied type=\"start\"/>" << std::endl;
}
if (haveNotations) {
str << "\t\t\t\t</notations>" << std::endl;
}
}
// could also do <stem>down</stem> if you wanted
str << "\t\t\t</note>" << std::endl;
}
void
MusicXmlExporter::writeKey(Rosegarden::Key whichKey, std::ofstream &str)
{
str << "\t\t\t\t<key>" << std::endl;
str << "\t\t\t\t<fifths>"
<< (whichKey.isSharp() ? "" : "-")
<< (whichKey.getAccidentalCount()) << "</fifths>" << std::endl;
str << "\t\t\t\t<mode>";
if (whichKey.isMinor()) {
str << "minor";
} else {
str << "major";
}
str << "</mode>" << std::endl;
str << "\t\t\t\t</key>" << std::endl;
}
void
MusicXmlExporter::writeTime(TimeSignature timeSignature, std::ofstream &str)
{
str << "\t\t\t\t<time>" << std::endl;
str << "\t\t\t\t<beats>" << timeSignature.getNumerator() << "</beats>" << std::endl;
str << "\t\t\t\t<beat-type>" << timeSignature.getDenominator() << "</beat-type>" << std::endl;
str << "\t\t\t\t</time>" << std::endl;
}
void
MusicXmlExporter::writeClef(Clef whichClef, std::ofstream &str)
{
str << "\t\t\t\t<clef>" << std::endl;
if (whichClef == Clef::Treble) {
str << "\t\t\t\t<sign>G</sign>" << std::endl;
str << "\t\t\t\t<line>2</line>" << std::endl;
} else if (whichClef == Clef::Alto) {
str << "\t\t\t\t<sign>C</sign>" << std::endl;
str << "\t\t\t\t<line>3</line>" << std::endl;
} else if (whichClef == Clef::Tenor) {
str << "\t\t\t\t<sign>C</sign>" << std::endl;
str << "\t\t\t\t<line>4</line>" << std::endl;
} else if (whichClef == Clef::Bass) {
str << "\t\t\t\t<sign>F</sign>" << std::endl;
str << "\t\t\t\t<line>4</line>" << std::endl;
}
str << "\t\t\t\t</clef>" << std::endl;
}
std::string
MusicXmlExporter::numToId(int num)
{
int base = num % 52;
char c;
if (base < 26) c = 'A' + char(base);
else c = 'a' + char(base - 26);
std::string s;
s += c;
while (num / 52 > 0) {
s += c;
num /= 52;
}
return s;
}
bool
MusicXmlExporter::write()
{
Composition *composition = &m_doc->getComposition();
std::ofstream str(m_fileName.c_str(), std::ios::out);
if (!str) {
std::cerr << "MusicXmlExporter::write() - can't write file " << m_fileName << std::endl;
return false;
}
// XML header information
str << "<?xml version=\"1.0\"?>" << std::endl;
str << "<!DOCTYPE score-partwise PUBLIC \"-//Recordare//DTD MusicXML 1.1 Partwise//EN\" \"http://www.musicxml.org/dtds/partwise.dtd\">" << std::endl;
// MusicXml header information
str << "<score-partwise>" << std::endl;
str << "\t<work> <work-title>" << XmlExportable::encode(m_fileName)
<< "</work-title></work> " << std::endl;
// Movement, etc. info goes here
str << "\t<identification> " << std::endl;
if (composition->getCopyrightNote() != "") {
str << "\t\t<rights>"
<< XmlExportable::encode(composition->getCopyrightNote())
<< "</rights>" << std::endl;
}
str << "\t\t<encoding>" << std::endl;
// Incomplete: Insert date!
// str << "\t\t\t<encoding-date>" << << "</encoding-date>" << std::endl;
str << "\t\t\t<software>Rosegarden v" VERSION "</software>" << std::endl;
str << "\t\t</encoding>" << std::endl;
str << "\t</identification> " << std::endl;
// MIDI information
str << "\t<part-list>" << std::endl;
Composition::trackcontainer& tracks = composition->getTracks();
int trackNo = 0;
timeT lastNoteTime = -1;
for (Composition::trackiterator i = tracks.begin();
i != tracks.end(); ++i) {
// Incomplete: What about all the other Midi stuff?
// Incomplete: (Future) GUI to set labels if they're not already
Instrument * trackInstrument = (&m_doc->getStudio())->getInstrumentById((*i).second->getInstrument());
str << "\t\t<score-part id=\"" << numToId((*i).first) << "\">" << std::endl;
str << "\t\t\t<part-name>" << XmlExportable::encode((*i).second->getLabel()) << "</part-name>" << std::endl;
if (trackInstrument) {
/*
Removing this stuff for now. It doesn't work, because the ids are
are expected to be non-numeric names that refer to elements
elsewhere that define the actual instruments. I think.
str << "\t\t\t<score-instrument id=\"" << trackInstrument->getName() << "\">" << std::endl;
str << "\t\t\t\t<instrument-name>" << trackInstrument->getType() << "</instrument-name>" << std::endl;
str << "\t\t\t</score-instrument>" << std::endl;
str << "\t\t\t<midi-instrument id=\"" << trackInstrument->getName() << "\">" << std::endl;
str << "\t\t\t\t<midi-channel>" << ((unsigned int)trackInstrument->getMidiChannel() + 1) << "</midi-channel>" << std::endl;
if (trackInstrument->sendsProgramChange()) {
str << "\t\t\t\t<midi-program>" << ((unsigned int)trackInstrument->getProgramChange() + 1) << "</midi-program>" << std::endl;
}
str << "\t\t\t</midi-instrument>" << std::endl;
*/
}
str << "\t\t</score-part>" << std::endl;
emit setProgress(int(double(trackNo++) / double(tracks.size()) * 20.0));
rgapp->refreshGUI(50);
} // end track iterator
str << "\t</part-list>" << std::endl;
// Notes!
// Write out all segments for each Track
trackNo = 0;
for (Composition::trackiterator j = tracks.begin();
j != tracks.end(); ++j) {
bool startedPart = false;
// Code courtesy docs/code/iterators.txt
CompositionTimeSliceAdapter::TrackSet trackSet;
// Incomplete: get the track info for each track (i.e. this should
// be in an iterator loop) into the track set
trackSet.insert((*j).first);
CompositionTimeSliceAdapter adapter(composition, trackSet);
int oldMeasureNumber = -1;
bool startedAttributes = false;
Rosegarden::Key key;
Clef clef;
AccidentalTable accTable(key, clef);
TimeSignature prevTimeSignature;
bool timeSigPending = false;
bool keyPending = false;
bool clefPending = false;
for (CompositionTimeSliceAdapter::iterator k = adapter.begin();
k != adapter.end(); ++k) {
Event *event = *k;
timeT absoluteTime = event->getNotationAbsoluteTime();
if (!startedPart) {
str << "\t<part id=\"" << numToId((*j).first) << "\">" << std::endl;
startedPart = true;
}
// Open a new measure if necessary
// Incomplete: How does MusicXML handle non-contiguous measures?
int measureNumber = composition->getBarNumber(absoluteTime);
TimeSignature timeSignature = composition->getTimeSignatureAt(absoluteTime);
if (measureNumber != oldMeasureNumber) {
if (startedAttributes) {
// rather bizarrely, MusicXML appears to require
// key, time, clef in that order
if (keyPending) {
writeKey(key, str);
keyPending = false;
}
if (timeSigPending) {
writeTime(prevTimeSignature, str);
timeSigPending = false;
}
if (clefPending) {
writeClef(clef, str);
clefPending = false;
}
str << "\t\t\t</attributes>" << std::endl;
startedAttributes = false;
}
while (measureNumber > oldMeasureNumber) {
bool first = (oldMeasureNumber < 0);
if (!first) {
if (startedAttributes) {
str << "\t\t\t</attributes>" << std::endl;
}
str << "\t\t</measure>\n" << std::endl;
}
++oldMeasureNumber;
str << "\t\t<measure number=\"" << (oldMeasureNumber + 1) << "\">" << std::endl;
if (first) {
str << "\t\t\t<attributes>" << std::endl;
// Divisions is divisions of crotchet (quarter-note) on which all
// note-lengths are based
str << "\t\t\t\t<divisions>" << Note(Note::Crotchet).getDuration() << "</divisions>" << std::endl;
startedAttributes = true;
timeSigPending = true;
}
}
accTable = AccidentalTable(key, clef);
}
oldMeasureNumber = measureNumber;
if (timeSignature != prevTimeSignature) {
prevTimeSignature = timeSignature;
timeSigPending = true;
if (!startedAttributes) {
str << "\t\t\t<attributes>" << std::endl;
startedAttributes = true;
}
}
// process event
if (event->isa(Rosegarden::Key::EventType)) {
if (!startedAttributes) {
str << "\t\t\t<attributes>" << std::endl;
startedAttributes = true;
}
key = Rosegarden::Key(*event);
keyPending = true;
accTable = AccidentalTable(key, clef);
} else if (event->isa(Clef::EventType)) {
if (!startedAttributes) {
str << "\t\t\t<attributes>" << std::endl;
startedAttributes = true;
}
clef = Clef(*event);
clefPending = true;
accTable = AccidentalTable(key, clef);
} else if (event->isa(Note::EventRestType) ||
event->isa(Note::EventType)) {
if (startedAttributes) {
if (keyPending) {
writeKey(key, str);
keyPending = false;
}
if (timeSigPending) {
writeTime(prevTimeSignature, str);
timeSigPending = false;
}
if (clefPending) {
writeClef(clef, str);
clefPending = false;
}
str << "\t\t\t</attributes>" << std::endl;
startedAttributes = false;
}
writeNote(event, lastNoteTime, accTable, clef, key, str);
if (event->isa(Note::EventType)) {
lastNoteTime = event->getNotationAbsoluteTime();
} else if (event->isa(Note::EventRestType)) {
lastNoteTime = -1;
}
}
}
if (startedPart) {
if (startedAttributes) {
if (keyPending) {
writeKey(key, str);
keyPending = false;
}
if (timeSigPending) {
writeTime(prevTimeSignature, str);
timeSigPending = false;
}
if (clefPending) {
writeClef(clef, str);
clefPending = false;
}
str << "\t\t\t</attributes>" << std::endl;
startedAttributes = false;
}
str << "\t\t</measure>" << std::endl;
str << "\t</part>" << std::endl;
}
emit setProgress(20 +
int(double(trackNo++) / double(tracks.size()) * 80.0));
rgapp->refreshGUI(50);
}
str << "</score-partwise>" << std::endl;
str.close();
return true;
}
}