/* Rosegarden A MIDI and audio sequencer and musical notation editor. This program is Copyright 2000-2008 Guillaume Laurent , Chris Cannam , Richard Bown This file is Copyright 2002 Hans Kieserman 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 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" << std::endl; Pitch pitch(64); Accidental acc; Accidental displayAcc; bool cautionary; Accidental processedDisplayAcc; if (e->isa(Note::EventRestType)) { str << "\t\t\t\t" << 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" << std::endl; } else { accTable.update(); } str << "\t\t\t\t" << std::endl; long p = 0; e->get(PITCH, p); pitch = p; str << "\t\t\t\t\t" << pitch.getNoteName(key) << "" << 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-2" << std::endl; } else if (acc == Accidentals::Flat) { str << "\t\t\t\t\t-1" << std::endl; } else if (acc == Accidentals::Sharp) { str << "\t\t\t\t\t1" << std::endl; } else if (acc == Accidentals::DoubleSharp) { str << "\t\t\t\t\t2" << std::endl; } int octave = pitch.getOctave( -1); str << "\t\t\t\t\t" << octave << "" << std::endl; str << "\t\t\t\t" << 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" << e->getNotationDuration() << "" << std::endl; if (!e->isa(Note::EventRestType)) { if (e->has(TIED_BACKWARD) && e->get (TIED_BACKWARD)) { str << "\t\t\t\t" << std::endl; } if (e->has(TIED_FORWARD) && e->get (TIED_FORWARD)) { str << "\t\t\t\t" << std::endl; } // Incomplete: will RG ever use this? str << "\t\t\t\t" << "1" << "" << 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" << noteNames[noteType] << "" << std::endl; for (int i = 0; i < note.getDots(); ++i) { str << "\t\t\t\t" << std::endl; } if (!e->isa(Note::EventRestType)) { if (processedDisplayAcc == Accidentals::DoubleFlat) { str << "\t\t\t\tflat-flat" << std::endl; } else if (processedDisplayAcc == Accidentals::Flat) { str << "\t\t\t\tflat" << std::endl; } else if (processedDisplayAcc == Accidentals::Natural) { str << "\t\t\t\tnatural" << std::endl; } else if (processedDisplayAcc == Accidentals::Sharp) { str << "\t\t\t\tsharp" << std::endl; } else if (processedDisplayAcc == Accidentals::DoubleSharp) { str << "\t\t\t\tdouble-sharp" << std::endl; } bool haveNotations = false; if (e->has(TIED_BACKWARD) && e->get (TIED_BACKWARD)) { if (!haveNotations) { str << "\t\t\t\t" << std::endl; haveNotations = true; } str << "\t\t\t\t\t" << std::endl; } if (e->has(TIED_FORWARD) && e->get (TIED_FORWARD)) { if (!haveNotations) { str << "\t\t\t\t" << std::endl; haveNotations = true; } str << "\t\t\t\t\t" << std::endl; } if (haveNotations) { str << "\t\t\t\t" << std::endl; } } // could also do down if you wanted str << "\t\t\t" << std::endl; } void MusicXmlExporter::writeKey(Rosegarden::Key whichKey, std::ofstream &str) { str << "\t\t\t\t" << std::endl; str << "\t\t\t\t" << (whichKey.isSharp() ? "" : "-") << (whichKey.getAccidentalCount()) << "" << std::endl; str << "\t\t\t\t"; if (whichKey.isMinor()) { str << "minor"; } else { str << "major"; } str << "" << std::endl; str << "\t\t\t\t" << std::endl; } void MusicXmlExporter::writeTime(TimeSignature timeSignature, std::ofstream &str) { str << "\t\t\t\t" << std::endl; } void MusicXmlExporter::writeClef(Clef whichClef, std::ofstream &str) { str << "\t\t\t\t" << std::endl; if (whichClef == Clef::Treble) { str << "\t\t\t\tG" << std::endl; str << "\t\t\t\t2" << std::endl; } else if (whichClef == Clef::Alto) { str << "\t\t\t\tC" << std::endl; str << "\t\t\t\t3" << std::endl; } else if (whichClef == Clef::Tenor) { str << "\t\t\t\tC" << std::endl; str << "\t\t\t\t4" << std::endl; } else if (whichClef == Clef::Bass) { str << "\t\t\t\tF" << std::endl; str << "\t\t\t\t4" << std::endl; } str << "\t\t\t\t" << 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 << "" << std::endl; str << "" << std::endl; // MusicXml header information str << "" << std::endl; str << "\t " << XmlExportable::encode(m_fileName) << " " << std::endl; // Movement, etc. info goes here str << "\t " << std::endl; if (composition->getCopyrightNote() != "") { str << "\t\t" << XmlExportable::encode(composition->getCopyrightNote()) << "" << std::endl; } str << "\t\t" << std::endl; // Incomplete: Insert date! // str << "\t\t\t" << << "" << std::endl; str << "\t\t\tRosegarden v" VERSION "" << std::endl; str << "\t\t" << std::endl; str << "\t " << std::endl; // MIDI information str << "\t" << 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" << std::endl; str << "\t\t\t" << XmlExportable::encode((*i).second->getLabel()) << "" << 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\tgetName() << "\">" << std::endl; str << "\t\t\t\t" << trackInstrument->getType() << "" << std::endl; str << "\t\t\t" << std::endl; str << "\t\t\tgetName() << "\">" << std::endl; str << "\t\t\t\t" << ((unsigned int)trackInstrument->getMidiChannel() + 1) << "" << std::endl; if (trackInstrument->sendsProgramChange()) { str << "\t\t\t\t" << ((unsigned int)trackInstrument->getProgramChange() + 1) << "" << std::endl; } str << "\t\t\t" << std::endl; */ } str << "\t\t" << std::endl; emit setProgress(int(double(trackNo++) / double(tracks.size()) * 20.0)); rgapp->refreshGUI(50); } // end track iterator str << "\t" << 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" << 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" << std::endl; startedAttributes = false; } while (measureNumber > oldMeasureNumber) { bool first = (oldMeasureNumber < 0); if (!first) { if (startedAttributes) { str << "\t\t\t" << std::endl; } str << "\t\t\n" << std::endl; } ++oldMeasureNumber; str << "\t\t" << std::endl; if (first) { str << "\t\t\t" << std::endl; // Divisions is divisions of crotchet (quarter-note) on which all // note-lengths are based str << "\t\t\t\t" << Note(Note::Crotchet).getDuration() << "" << 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" << std::endl; startedAttributes = true; } } // process event if (event->isa(Rosegarden::Key::EventType)) { if (!startedAttributes) { str << "\t\t\t" << 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" << 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" << 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" << std::endl; startedAttributes = false; } str << "\t\t" << std::endl; str << "\t" << std::endl; } emit setProgress(20 + int(double(trackNo++) / double(tracks.size()) * 80.0)); rgapp->refreshGUI(50); } str << "" << std::endl; str.close(); return true; } }