summaryrefslogtreecommitdiffstats
path: root/src/document/io
diff options
context:
space:
mode:
authortpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2010-03-01 18:37:05 +0000
committertpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2010-03-01 18:37:05 +0000
commit145364a8af6a1fec06556221e66d4b724a62fc9a (patch)
tree53bd71a544008c518034f208d64c932dc2883f50 /src/document/io
downloadrosegarden-145364a8af6a1fec06556221e66d4b724a62fc9a.tar.gz
rosegarden-145364a8af6a1fec06556221e66d4b724a62fc9a.zip
Added old abandoned KDE3 version of the RoseGarden MIDI tool
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/rosegarden@1097595 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'src/document/io')
-rw-r--r--src/document/io/CsoundExporter.cpp154
-rw-r--r--src/document/io/CsoundExporter.h63
-rw-r--r--src/document/io/HydrogenLoader.cpp74
-rw-r--r--src/document/io/HydrogenLoader.h83
-rw-r--r--src/document/io/HydrogenXMLHandler.cpp403
-rw-r--r--src/document/io/HydrogenXMLHandler.h132
-rw-r--r--src/document/io/LilyPondExporter.cpp2419
-rw-r--r--src/document/io/LilyPondExporter.h262
-rw-r--r--src/document/io/MupExporter.cpp453
-rw-r--r--src/document/io/MupExporter.h89
-rw-r--r--src/document/io/MusicXmlExporter.cpp555
-rw-r--r--src/document/io/MusicXmlExporter.h87
-rw-r--r--src/document/io/RG21Loader.cpp797
-rw-r--r--src/document/io/RG21Loader.h162
14 files changed, 5733 insertions, 0 deletions
diff --git a/src/document/io/CsoundExporter.cpp b/src/document/io/CsoundExporter.cpp
new file mode 100644
index 0000000..9b61372
--- /dev/null
+++ b/src/document/io/CsoundExporter.cpp
@@ -0,0 +1,154 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <[email protected]>,
+ Chris Cannam <[email protected]>,
+ Richard Bown <[email protected]>
+
+ 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 "CsoundExporter.h"
+
+#include "base/Event.h"
+#include "base/BaseProperties.h"
+#include "base/Composition.h"
+#include "base/NotationTypes.h"
+#include "base/Segment.h"
+#include "base/Track.h"
+#include "gui/general/ProgressReporter.h"
+#include <qobject.h>
+#include <fstream>
+#include "gui/application/RosegardenApplication.h"
+
+
+namespace Rosegarden
+{
+
+CsoundExporter::CsoundExporter(QObject *parent,
+ Composition *composition,
+ std::string fileName) :
+ ProgressReporter(parent, "csoundExporter"),
+ m_composition(composition),
+ m_fileName(fileName)
+{
+ // nothing else
+}
+
+CsoundExporter::~CsoundExporter()
+{
+ // nothing
+}
+
+static double
+convertTime(Rosegarden::timeT t)
+{
+ return double(t) / double(Note(Note::Crotchet).getDuration());
+}
+
+bool
+CsoundExporter::write()
+{
+ std::ofstream str(m_fileName.c_str(), std::ios::out);
+ if (!str) {
+ //std::cerr << "CsoundExporter::write() - can't write file" << std::endl;
+ return false;
+ }
+
+ str << ";; Csound score file written by Rosegarden\n\n";
+ if (m_composition->getCopyrightNote() != "") {
+ str << ";; Copyright note:\n;; "
+ //!!! really need to remove newlines from copyright note
+ << m_composition->getCopyrightNote() << "\n";
+ }
+
+ int trackNo = 0;
+ for (Composition::iterator i = m_composition->begin();
+ i != m_composition->end(); ++i) {
+
+ emit setProgress(int(double(trackNo++) / double(m_composition->getNbTracks()) * 100.0));
+ rgapp->refreshGUI(50);
+
+ str << "\n;; Segment: \"" << (*i)->getLabel() << "\"\n";
+ str << ";; on Track: \""
+ << m_composition->getTrackById((*i)->getTrack())->getLabel()
+ << "\"\n";
+ str << ";;\n;; Inst\tTime\tDur\tPitch\tVely\n"
+ << ";; ----\t----\t---\t-----\t----\n";
+
+ for (Segment::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
+
+ if ((*j)->isa(Note::EventType)) {
+
+ long pitch = 0;
+ (*j)->get
+ <Int>(BaseProperties::PITCH, pitch);
+
+ long velocity = 127;
+ (*j)->get
+ <Int>(BaseProperties::VELOCITY, velocity);
+
+ str << " i"
+ << (*i)->getTrack() << "\t"
+ << convertTime((*j)->getAbsoluteTime()) << "\t"
+ << convertTime((*j)->getDuration()) << "\t"
+ << 3 + (pitch / 12) << ((pitch % 12) < 10 ? ".0" : ".")
+ << pitch % 12 << "\t"
+ << velocity << "\t\n";
+
+ } else {
+ str << ";; Event type: " << (*j)->getType() << std::endl;
+ }
+ }
+ }
+
+ int tempoCount = m_composition->getTempoChangeCount();
+
+ if (tempoCount > 0) {
+
+ str << "\nt ";
+
+ for (int i = 0; i < tempoCount - 1; ++i) {
+
+ std::pair<timeT, tempoT> tempoChange =
+ m_composition->getTempoChange(i);
+
+ timeT myTime = tempoChange.first;
+ timeT nextTime = myTime;
+ if (i < m_composition->getTempoChangeCount() - 1) {
+ nextTime = m_composition->getTempoChange(i + 1).first;
+ }
+
+ int tempo = int(Composition::getTempoQpm(tempoChange.second));
+
+ str << convertTime( myTime) << " " << tempo << " "
+ << convertTime(nextTime) << " " << tempo << " ";
+ }
+
+ str << convertTime(m_composition->getTempoChange(tempoCount - 1).first)
+ << " "
+ << int(Composition::getTempoQpm(m_composition->getTempoChange(tempoCount - 1).second))
+ << std::endl;
+ }
+
+ str << "\ne" << std::endl;
+ str.close();
+ return true;
+}
+
+}
diff --git a/src/document/io/CsoundExporter.h b/src/document/io/CsoundExporter.h
new file mode 100644
index 0000000..0e8c2ac
--- /dev/null
+++ b/src/document/io/CsoundExporter.h
@@ -0,0 +1,63 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <[email protected]>,
+ Chris Cannam <[email protected]>,
+ Richard Bown <[email protected]>
+
+ 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.
+*/
+
+#ifndef _RG_CSOUNDEXPORTER_H_
+#define _RG_CSOUNDEXPORTER_H_
+
+#include "gui/general/ProgressReporter.h"
+#include <string>
+
+
+class QObject;
+
+
+namespace Rosegarden
+{
+
+class Composition;
+
+
+/**
+ * Csound scorefile export
+ */
+
+class CsoundExporter : public ProgressReporter
+{
+public:
+ CsoundExporter(QObject *parent, Composition *, std::string fileName);
+ ~CsoundExporter();
+
+ bool write();
+
+protected:
+ Composition *m_composition;
+ std::string m_fileName;
+};
+
+
+
+}
+
+#endif
diff --git a/src/document/io/HydrogenLoader.cpp b/src/document/io/HydrogenLoader.cpp
new file mode 100644
index 0000000..38f85fe
--- /dev/null
+++ b/src/document/io/HydrogenLoader.cpp
@@ -0,0 +1,74 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <[email protected]>,
+ Chris Cannam <[email protected]>,
+ Richard Bown <[email protected]>
+
+ 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 "HydrogenLoader.h"
+
+#include <qxml.h>
+#include "base/Composition.h"
+#include "base/PropertyName.h"
+#include "base/Segment.h"
+#include "base/Studio.h"
+#include "gui/general/ProgressReporter.h"
+#include "HydrogenXMLHandler.h"
+#include <qfile.h>
+#include <qobject.h>
+#include <qstring.h>
+
+
+namespace Rosegarden
+{
+
+HydrogenLoader::HydrogenLoader(Studio *studio,
+ QObject *parent, const char *name):
+ ProgressReporter(parent, name),
+ m_studio(studio)
+{}
+
+bool
+HydrogenLoader::load(const QString& fileName, Composition &comp)
+{
+ m_composition = &comp;
+ comp.clear();
+
+ QFile file(fileName);
+ if (!file.open(IO_ReadOnly)) {
+ return false;
+ }
+
+ m_studio->unassignAllInstruments();
+
+ HydrogenXMLHandler handler(m_composition);
+
+ QXmlInputSource source(file);
+ QXmlSimpleReader reader;
+ reader.setContentHandler(&handler);
+ reader.setErrorHandler(&handler);
+
+ bool ok = reader.parse(source);
+
+ return ok;
+}
+
+}
diff --git a/src/document/io/HydrogenLoader.h b/src/document/io/HydrogenLoader.h
new file mode 100644
index 0000000..f0cd724
--- /dev/null
+++ b/src/document/io/HydrogenLoader.h
@@ -0,0 +1,83 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <[email protected]>,
+ Chris Cannam <[email protected]>,
+ Richard Bown <[email protected]>
+
+ 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.
+*/
+
+#ifndef _RG_HYDROGENLOADER_H_
+#define _RG_HYDROGENLOADER_H_
+
+#include "base/PropertyName.h"
+#include "gui/general/ProgressReporter.h"
+#include <string>
+#include <vector>
+
+
+class QString;
+class QObject;
+
+
+namespace Rosegarden
+{
+
+class Studio;
+class Segment;
+class Composition;
+
+
+/**
+ * Hydrogen drum machine file importer - should work for 0.8.1 and above
+ * assuming they don't change the file spec without telling us.
+ *
+ */
+
+class HydrogenLoader : public ProgressReporter
+{
+public:
+ HydrogenLoader(Studio *,
+ QObject *parent = 0, const char *name = 0);
+
+ /**
+ * Load and parse the Hydrogen file \a fileName, and write it into the
+ * given Composition (clearing the existing segment data first).
+ * Return true for success.
+ */
+ bool load(const QString& fileName, Composition &);
+
+protected:
+ Composition *m_composition;
+ Studio *m_studio;
+ std::string m_fileName;
+
+private:
+ static const int MAX_DOTS = 4;
+ static const PropertyName SKIP_PROPERTY;
+};
+
+typedef std::vector<std::pair<std::string, Segment*> > SegmentMap;
+typedef std::vector<std::pair<std::string, Segment*> >::iterator SegmentMapIterator;
+typedef std::vector<std::pair<std::string, Segment*> >::const_iterator SegmentMapConstIterator;
+
+
+}
+
+#endif
diff --git a/src/document/io/HydrogenXMLHandler.cpp b/src/document/io/HydrogenXMLHandler.cpp
new file mode 100644
index 0000000..68e1b20
--- /dev/null
+++ b/src/document/io/HydrogenXMLHandler.cpp
@@ -0,0 +1,403 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <[email protected]>,
+ Chris Cannam <[email protected]>,
+ Richard Bown <[email protected]>
+
+ 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 "HydrogenXMLHandler.h"
+
+#include "base/Event.h"
+#include "base/BaseProperties.h"
+#include <klocale.h>
+#include "misc/Debug.h"
+#include "misc/Strings.h"
+#include "base/Composition.h"
+#include "base/Instrument.h"
+#include "base/MidiProgram.h"
+#include "base/NotationTypes.h"
+#include "base/Segment.h"
+#include "base/Track.h"
+#include <qstring.h>
+
+
+namespace Rosegarden
+{
+
+HydrogenXMLHandler::HydrogenXMLHandler(Composition *composition,
+ InstrumentId drumIns):
+ m_composition(composition),
+ m_drumInstrument(drumIns),
+ m_inNote(false),
+ m_inInstrument(false),
+ m_inPattern(false),
+ m_inSequence(false),
+ m_patternName(""),
+ m_patternSize(0),
+ m_sequenceName(""),
+ m_position(0),
+ m_velocity(0.0),
+ m_panL(0.0),
+ m_panR(0.0),
+ m_pitch(0.0),
+ m_instrument(0),
+ m_id(0),
+ m_muted(false),
+ m_fileName(""),
+ m_bpm(0),
+ m_volume(0.0),
+ m_name(""),
+ m_author(""),
+ m_notes(""),
+ m_songMode(false),
+ m_version(""),
+ m_currentProperty(""),
+ m_segment(0),
+ m_currentTrackNb(0),
+ m_segmentAdded(false),
+ m_currentBar(0),
+ m_newSegment(false)
+{}
+
+bool
+HydrogenXMLHandler::startDocument()
+{
+ RG_DEBUG << "HydrogenXMLHandler::startDocument" << endl;
+
+ m_inNote = false;
+ m_inInstrument = false;
+ m_inPattern = false;
+ m_inSequence = false;
+
+ // Pattern attributes
+ //
+ m_patternName = "";
+ m_patternSize = 0;
+
+ // Sequence attributes
+ //
+ m_sequenceName = "";
+
+ // Note attributes
+ //
+ m_position = 0;
+ m_velocity = 0.0;
+ m_panL = 0.0;
+ m_panR = 0.0;
+ m_pitch = 0.0;
+ m_instrument = 0;
+
+ // Instrument attributes
+ //
+ m_id = 0;
+ m_muted = false;
+ m_instrumentVolumes.clear();
+ m_fileName = "";
+
+ // Global attributes
+ //
+ m_bpm = 0;
+ m_volume = 0.0;
+ m_name = "";
+ m_author = "";
+ m_notes = "";
+ m_songMode = false;
+ m_version = "";
+
+ m_currentProperty = "";
+
+ m_segment = 0;
+ m_currentTrackNb = 0;
+ m_segmentAdded = 0;
+ m_currentBar = 0;
+ m_newSegment = false;
+
+ return true;
+}
+
+bool
+HydrogenXMLHandler::startElement(const QString& /*namespaceURI*/,
+ const QString& /*localName*/,
+ const QString& qName,
+ const QXmlAttributes& /*atts*/)
+{
+ QString lcName = qName.lower();
+
+ if (lcName == "note") {
+
+ if (m_inInstrument)
+ return false;
+
+ m_inNote = true;
+
+ } else if (lcName == "instrument") {
+
+ // Beware instrument attributes inside Notes
+ if (!m_inNote)
+ m_inInstrument = true;
+ } else if (lcName == "pattern") {
+ m_inPattern = true;
+ m_segmentAdded = false; // flag the segments being added
+ } else if (lcName == "sequence") {
+
+ // Create a new segment and set some flags
+ //
+ m_segment = new Segment();
+ m_newSegment = true;
+ m_inSequence = true;
+ }
+
+ m_currentProperty = lcName;
+
+ return true;
+}
+
+bool
+HydrogenXMLHandler::endElement(const QString& /*namespaceURI*/,
+ const QString& /*localName*/,
+ const QString& qName)
+{
+ QString lcName = qName.lower();
+
+ if (lcName == "note") {
+
+ RG_DEBUG << "HydrogenXMLHandler::endElement - Hydrogen Note : position = " << m_position
+ << ", velocity = " << m_velocity
+ << ", panL = " << m_panL
+ << ", panR = " << m_panR
+ << ", pitch = " << m_pitch
+ << ", instrument = " << m_instrument
+ << endl;
+
+ timeT barLength = m_composition->getBarEnd(m_currentBar) -
+ m_composition->getBarStart(m_currentBar);
+
+ timeT pos = m_composition->getBarStart(m_currentBar) +
+ timeT(
+ double(m_position) / double(m_patternSize) * double(barLength));
+
+ // Insert a rest if we've got a new segment
+ //
+ if (m_newSegment) {
+ Event *restEvent = new Event(Note::EventRestType,
+ m_composition->getBarStart(m_currentBar),
+ pos - m_composition->getBarStart(m_currentBar),
+ Note::EventRestSubOrdering);
+ m_segment->insert(restEvent);
+ m_newSegment = false;
+ }
+
+ // Create and insert this event
+ //
+ Event *noteEvent = new Event(Note::EventType,
+ pos, Note(Note::Semiquaver).getDuration());
+
+ // get drum mapping from instrument and calculate velocity
+ noteEvent->set
+ <Int>(
+ BaseProperties::PITCH, 36 + m_instrument);
+ noteEvent->set
+ <Int>(BaseProperties::VELOCITY,
+ int(127.0 * m_velocity * m_volume *
+ m_instrumentVolumes[m_instrument]));
+ m_segment->insert(noteEvent);
+
+ m_inNote = false;
+
+ } else if (lcName == "instrument" && m_inInstrument) {
+
+ RG_DEBUG << "HydrogenXMLHandler::endElement - Hydrogen Instrument : id = " << m_id
+ << ", muted = " << m_muted
+ << ", volume = " << m_instrumentVolumes[m_instrument]
+ << ", filename = \"" << m_fileName << "\""
+ << endl;
+
+ m_inInstrument = false;
+
+ } else if (lcName == "pattern") {
+ m_inPattern = false;
+
+ if (m_segmentAdded) {
+
+ // Add a blank track to demarcate patterns
+ //
+ Track *track = new Track
+ (m_currentTrackNb, m_drumInstrument, m_currentTrackNb,
+ "<blank spacer>", false);
+ m_currentTrackNb++;
+ m_composition->addTrack(track);
+
+ m_segmentAdded = false;
+
+ // Each pattern has it's own bar so that the imported
+ // song shows off each pattern a bar at a time.
+ //
+ m_currentBar++;
+ }
+
+ } else if (lcName == "sequence") {
+
+ // If we're closing out a sequencer tab and we have a m_segment then
+ // we should close up and add that segment. Only create if we have
+ // some Events in it
+ //
+ if (m_segment->size() > 0) {
+
+ m_segment->setTrack(m_currentTrackNb);
+
+ Track *track = new Track
+ (m_currentTrackNb, m_drumInstrument, m_currentTrackNb,
+ m_patternName, false);
+ m_currentTrackNb++;
+
+ // Enforce start and end markers for this bar so that we have a
+ // whole bar unit segment.
+ //
+ m_segment->setEndMarkerTime(m_composition->getBarEnd(m_currentBar));
+ QString label = QString("%1 - %2 %3 %4").arg(strtoqstr(m_patternName))
+ .arg(strtoqstr(m_sequenceName))
+ .arg(i18n(" imported from Hydrogen ")).arg(strtoqstr(m_version));
+ m_segment->setLabel(qstrtostr(label));
+
+ m_composition->addTrack(track);
+ m_composition->addSegment(m_segment);
+ m_segment = 0;
+
+ m_segmentAdded = true;
+ }
+
+ m_inSequence = false;
+
+ }
+
+ return true;
+}
+
+bool
+HydrogenXMLHandler::characters(const QString& chars)
+{
+ QString ch = chars.stripWhiteSpace();
+ if (ch == "")
+ return true;
+
+ if (m_inNote) {
+ if (m_currentProperty == "position") {
+ m_position = ch.toInt();
+ } else if (m_currentProperty == "velocity") {
+ m_velocity = qstrtodouble(ch);
+ } else if (m_currentProperty == "pan_L") {
+ m_panL = qstrtodouble(ch);
+ } else if (m_currentProperty == "pan_R") {
+ m_panR = qstrtodouble(ch);
+ } else if (m_currentProperty == "pitch") {
+ m_pitch = qstrtodouble(ch);
+ } else if (m_currentProperty == "instrument") {
+ m_instrument = ch.toInt();
+
+ // Standard kit conversion - hardcoded conversion for Hyrdogen's default
+ // drum kit. The m_instrument mapping for low values maps well onto the
+ // kick drum GM kit starting point (MIDI pitch = 36).
+ //
+ switch (m_instrument) {
+ case 11: // Cowbell
+ m_instrument = 20;
+ break;
+ case 12: // Ride Jazz
+ m_instrument = 15;
+ break;
+ case 14: // Ride Rock
+ m_instrument = 17;
+ break;
+ case 15: // Crash Jazz
+ m_instrument = 16;
+ break;
+
+ default:
+ break;
+ }
+
+ }
+ } else if (m_inInstrument) {
+ if (m_currentProperty == "id") {
+ m_id = ch.toInt();
+ } else if (m_currentProperty == "ismuted") {
+ if (ch.lower() == "true")
+ m_muted = true;
+ else
+ m_muted = false;
+ } else if (m_currentProperty == "filename") {
+ m_fileName = qstrtostr(chars); // don't strip whitespace from the filename
+ } else if (m_currentProperty == "volume") {
+ m_instrumentVolumes.push_back(qstrtodouble(ch));
+ }
+
+
+ } else if (m_inPattern) {
+
+ // Pattern attributes
+
+ if (m_currentProperty == "name") {
+ if (m_inSequence)
+ m_sequenceName = qstrtostr(chars);
+ else
+ m_patternName = qstrtostr(chars);
+ } else if (m_currentProperty == "size") {
+ m_patternSize = ch.toInt();
+ }
+
+ } else {
+
+ // Global attributes
+ if (m_currentProperty == "version") {
+ m_version = qstrtostr(chars);
+ } else if (m_currentProperty == "bpm") {
+
+ m_bpm = qstrtodouble(ch);
+ m_composition->addTempoAtTime
+ (0, Composition::getTempoForQpm(m_bpm));
+
+ } else if (m_currentProperty == "volume") {
+ m_volume = qstrtodouble(ch);
+ } else if (m_currentProperty == "name") {
+ m_name = qstrtostr(chars);
+ } else if (m_currentProperty == "author") {
+ m_author = qstrtostr(chars);
+ } else if (m_currentProperty == "notes") {
+ m_notes = qstrtostr(chars);
+ } else if (m_currentProperty == "mode") {
+ if (ch.lower() == "song")
+ m_songMode = true;
+ else
+ m_songMode = false;
+ }
+ }
+
+ return true;
+}
+
+bool
+HydrogenXMLHandler::endDocument()
+{
+ RG_DEBUG << "HydrogenXMLHandler::endDocument" << endl;
+ return true;
+}
+
+}
diff --git a/src/document/io/HydrogenXMLHandler.h b/src/document/io/HydrogenXMLHandler.h
new file mode 100644
index 0000000..0bce68b
--- /dev/null
+++ b/src/document/io/HydrogenXMLHandler.h
@@ -0,0 +1,132 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <[email protected]>,
+ Chris Cannam <[email protected]>,
+ Richard Bown <[email protected]>
+
+ 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.
+*/
+
+#ifndef _RG_HYDROGENXMLHANDLER_H_
+#define _RG_HYDROGENXMLHANDLER_H_
+
+#include "HydrogenLoader.h"
+#include "base/MidiProgram.h"
+#include "base/Track.h"
+#include <string>
+#include <qstring.h>
+#include <vector>
+#include <qxml.h>
+
+
+class QXmlAttributes;
+
+
+namespace Rosegarden
+{
+
+class Segment;
+class Composition;
+
+
+class HydrogenXMLHandler : public QXmlDefaultHandler
+{
+public:
+ HydrogenXMLHandler(Composition *comp,
+ InstrumentId drumInstrument = MidiInstrumentBase + 9);
+
+ /**
+ * Overloaded handler functions
+ */
+ virtual bool startDocument();
+ virtual bool startElement(const QString& namespaceURI,
+ const QString& localName,
+ const QString& qName,
+ const QXmlAttributes& atts);
+
+ virtual bool endElement(const QString& namespaceURI,
+ const QString& localName,
+ const QString& qName);
+
+ virtual bool characters(const QString& ch);
+
+ virtual bool endDocument ();
+
+protected:
+ Composition *m_composition;
+ InstrumentId m_drumInstrument;
+
+ bool m_inNote;
+ bool m_inInstrument;
+ bool m_inPattern;
+ bool m_inSequence;
+
+ // Pattern attributes
+ //
+ std::string m_patternName;
+ int m_patternSize;
+
+ // Sequence attributes
+ //
+ std::string m_sequenceName;
+
+ // Note attributes
+ //
+ int m_position;
+ double m_velocity;
+ double m_panL;
+ double m_panR;
+ double m_pitch;
+ int m_instrument;
+
+ // Instrument attributes
+ //
+ int m_id;
+ bool m_muted;
+ std::vector<double> m_instrumentVolumes;
+ std::string m_fileName;
+
+ // Global attributes
+ //
+ double m_bpm;
+ double m_volume;
+ std::string m_name;
+ std::string m_author;
+ std::string m_notes;
+ bool m_songMode; // Song mode or pattern mode?
+ std::string m_version;
+
+ //
+ QString m_currentProperty;
+
+ Segment *m_segment;
+ TrackId m_currentTrackNb;
+ bool m_segmentAdded;
+ int m_currentBar;
+ bool m_newSegment;
+
+ SegmentMap m_segmentMap;
+
+};
+
+
+
+}
+
+#endif
diff --git a/src/document/io/LilyPondExporter.cpp b/src/document/io/LilyPondExporter.cpp
new file mode 100644
index 0000000..68731f8
--- /dev/null
+++ b/src/document/io/LilyPondExporter.cpp
@@ -0,0 +1,2419 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <[email protected]>,
+ Chris Cannam <[email protected]>,
+ Richard Bown <[email protected]>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ This file is Copyright 2002
+ Hans Kieserman <[email protected]>
+ with heavy lifting from csoundio as it was on 13/5/2002.
+
+ Numerous additions and bug fixes by
+ Michael McIntyre <[email protected]>
+
+ Some restructuring by Chris Cannam.
+
+ Massive brain surgery, fixes, improvements, and additions by
+ Heikki Junes
+
+ 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 "LilyPondExporter.h"
+
+#include <klocale.h>
+#include "misc/Debug.h"
+#include "misc/Strings.h"
+#include "document/ConfigGroups.h"
+#include "base/BaseProperties.h"
+#include "base/Composition.h"
+#include "base/Configuration.h"
+#include "base/Event.h"
+#include "base/Exception.h"
+#include "base/Instrument.h"
+#include "base/NotationTypes.h"
+#include "base/PropertyName.h"
+#include "base/Segment.h"
+#include "base/SegmentNotationHelper.h"
+#include "base/Sets.h"
+#include "base/Staff.h"
+#include "base/Studio.h"
+#include "base/Track.h"
+#include "base/NotationQuantizer.h"
+#include "base/Marker.h"
+#include "base/StaffExportTypes.h"
+#include "document/RosegardenGUIDoc.h"
+#include "gui/application/RosegardenApplication.h"
+#include "gui/application/RosegardenGUIView.h"
+#include "gui/editors/notation/NotationProperties.h"
+#include "gui/editors/notation/NotationView.h"
+#include "gui/editors/guitar/Chord.h"
+#include "gui/general/ProgressReporter.h"
+#include "gui/widgets/CurrentProgressDialog.h"
+#include <kconfig.h>
+#include <kmessagebox.h>
+#include <qfileinfo.h>
+#include <qobject.h>
+#include <qregexp.h>
+#include <qstring.h>
+#include <qtextcodec.h>
+#include <kapplication.h>
+#include <sstream>
+#include <algorithm>
+
+namespace Rosegarden
+{
+
+using namespace BaseProperties;
+
+const PropertyName LilyPondExporter::SKIP_PROPERTY
+ = "LilyPondExportSkipThisEvent";
+
+LilyPondExporter::LilyPondExporter(RosegardenGUIApp *parent,
+ RosegardenGUIDoc *doc,
+ std::string fileName) :
+ ProgressReporter((QObject *)parent, "lilypondExporter"),
+ m_doc(doc),
+ m_fileName(fileName),
+ m_lastClefFound(Clef::Treble)
+{
+ m_composition = &m_doc->getComposition();
+ m_studio = &m_doc->getStudio();
+ m_view = ((RosegardenGUIApp *)parent)->getView();
+ m_notationView = NULL;
+
+ readConfigVariables();
+}
+
+LilyPondExporter::LilyPondExporter(NotationView *parent,
+ RosegardenGUIDoc *doc,
+ std::string fileName) :
+ ProgressReporter((QObject *)parent, "lilypondExporter"),
+ m_doc(doc),
+ m_fileName(fileName),
+ m_lastClefFound(Clef::Treble)
+
+{
+ m_composition = &m_doc->getComposition();
+ m_studio = &m_doc->getStudio();
+ m_view = NULL;
+ m_notationView = ((NotationView *)parent);
+
+ readConfigVariables();
+}
+
+void
+LilyPondExporter::readConfigVariables(void)
+{
+ // grab config info
+ KConfig *cfg = kapp->config();
+ cfg->setGroup(NotationViewConfigGroup);
+
+ m_paperSize = cfg->readUnsignedNumEntry("lilypapersize", PAPER_A4);
+ m_paperLandscape = cfg->readBoolEntry("lilypaperlandscape", false);
+ m_fontSize = cfg->readUnsignedNumEntry("lilyfontsize", FONT_20);
+ m_raggedBottom = cfg->readBoolEntry("lilyraggedbottom", false);
+ m_exportSelection = cfg->readUnsignedNumEntry("lilyexportselection", EXPORT_NONMUTED_TRACKS);
+ m_exportLyrics = cfg->readBoolEntry("lilyexportlyrics", true);
+ m_exportMidi = cfg->readBoolEntry("lilyexportmidi", false);
+ m_exportTempoMarks = cfg->readUnsignedNumEntry("lilyexporttempomarks", EXPORT_NONE_TEMPO_MARKS);
+ m_exportPointAndClick = cfg->readBoolEntry("lilyexportpointandclick", false);
+ m_exportBeams = cfg->readBoolEntry("lilyexportbeamings", false);
+ m_exportStaffMerge = cfg->readBoolEntry("lilyexportstaffmerge", false);
+ m_exportStaffGroup = cfg->readBoolEntry("lilyexportstaffbrackets", true);
+ m_lyricsHAlignment = cfg->readBoolEntry("lilylyricshalignment", LEFT_ALIGN);
+
+ m_languageLevel = cfg->readUnsignedNumEntry("lilylanguage", LILYPOND_VERSION_2_6);
+ m_exportMarkerMode = cfg->readUnsignedNumEntry("lilyexportmarkermode", EXPORT_NO_MARKERS );
+}
+
+LilyPondExporter::~LilyPondExporter()
+{
+ // nothing
+}
+
+void
+LilyPondExporter::handleStartingEvents(eventstartlist &eventsToStart,
+ std::ofstream &str)
+{
+ eventstartlist::iterator m = eventsToStart.begin();
+
+ while (m != eventsToStart.end()) {
+
+ try {
+ Indication i(**m);
+
+ if (i.getIndicationType() == Indication::Slur) {
+ if ((*m)->get
+ <Bool>(NotationProperties::SLUR_ABOVE))
+ str << "^( ";
+ else
+ str << "_( ";
+ } else if (i.getIndicationType() == Indication::PhrasingSlur) {
+ if ((*m)->get
+ <Bool>(NotationProperties::SLUR_ABOVE))
+ str << "^\\( ";
+ else
+ str << "_\\( ";
+ } else if (i.getIndicationType() == Indication::Crescendo) {
+ str << "\\< ";
+ } else if (i.getIndicationType() == Indication::Decrescendo) {
+ str << "\\> ";
+ }
+
+ } catch (Event::BadType) {
+ // Not an indication
+ } catch (Event::NoData e) {
+ std::cerr << "Bad indication: " << e.getMessage() << std::endl;
+ }
+
+ eventstartlist::iterator n(m);
+ ++n;
+ eventsToStart.erase(m);
+ m = n;
+ }
+}
+
+void
+LilyPondExporter::handleEndingEvents(eventendlist &eventsInProgress,
+ const Segment::iterator &j,
+ std::ofstream &str)
+{
+ eventendlist::iterator k = eventsInProgress.begin();
+
+ while (k != eventsInProgress.end()) {
+
+ eventendlist::iterator l(k);
+ ++l;
+
+ // Handle and remove all the relevant events in progress
+ // This assumes all deferred events are indications
+
+ try {
+ Indication i(**k);
+
+ timeT indicationEnd =
+ (*k)->getNotationAbsoluteTime() + i.getIndicationDuration();
+ timeT eventEnd =
+ (*j)->getNotationAbsoluteTime() + (*j)->getNotationDuration();
+
+ if (indicationEnd < eventEnd ||
+ ((i.getIndicationType() == Indication::Slur ||
+ i.getIndicationType() == Indication::PhrasingSlur) &&
+ indicationEnd == eventEnd)) {
+
+ if (i.getIndicationType() == Indication::Slur) {
+ str << ") ";
+ } else if (i.getIndicationType() == Indication::PhrasingSlur) {
+ str << "\\) ";
+ } else if (i.getIndicationType() == Indication::Crescendo ||
+ i.getIndicationType() == Indication::Decrescendo) {
+ str << "\\! ";
+ }
+
+ eventsInProgress.erase(k);
+ }
+
+ } catch (Event::BadType) {
+ // not an indication
+
+ } catch (Event::NoData e) {
+ std::cerr << "Bad indication: " << e.getMessage() << std::endl;
+ }
+
+ k = l;
+ }
+}
+
+std::string
+LilyPondExporter::convertPitchToLilyNote(int pitch, Accidental accidental,
+ const Rosegarden::Key &key)
+{
+ Pitch p(pitch, accidental);
+ std::string lilyNote = "";
+
+ lilyNote += (char)tolower(p.getNoteName(key));
+ // std::cout << "lilyNote: " << lilyNote << std::endl;
+ Accidental acc = p.getAccidental(key);
+ if (acc == Accidentals::DoubleFlat)
+ lilyNote += "eses";
+ else if (acc == Accidentals::Flat)
+ lilyNote += "es";
+ else if (acc == Accidentals::Sharp)
+ lilyNote += "is";
+ else if (acc == Accidentals::DoubleSharp)
+ lilyNote += "isis";
+
+ return lilyNote;
+}
+
+std::string
+LilyPondExporter::composeLilyMark(std::string eventMark, bool stemUp)
+{
+
+ std::string inStr = "", outStr = "";
+ std::string prefix = (stemUp) ? "_" : "^";
+
+ // shoot text mark straight through unless it's sf or rf
+ if (Marks::isTextMark(eventMark)) {
+ inStr = protectIllegalChars(Marks::getTextFromMark(eventMark));
+
+ if (inStr == "sf") {
+ inStr = "\\sf";
+ } else if (inStr == "rf") {
+ inStr = "\\rfz";
+ } else {
+ inStr = "\\markup { \\italic " + inStr + " } ";
+ }
+
+ outStr = prefix + inStr;
+
+ } else if (Marks::isFingeringMark(eventMark)) {
+
+ // fingering marks: use markup syntax only for non-trivial fingerings
+
+ inStr = protectIllegalChars(Marks::getFingeringFromMark(eventMark));
+
+ if (inStr != "0" && inStr != "1" && inStr != "2" && inStr != "3" && inStr != "4" && inStr != "5" && inStr != "+" ) {
+ inStr = "\\markup { \\finger \"" + inStr + "\" } ";
+ }
+
+ outStr = prefix + inStr;
+
+ } else {
+ outStr = "-";
+
+ // use full \accent format for everything, even though some shortcuts
+ // exist, for the sake of consistency
+ if (eventMark == Marks::Accent) {
+ outStr += "\\accent";
+ } else if (eventMark == Marks::Tenuto) {
+ outStr += "\\tenuto";
+ } else if (eventMark == Marks::Staccato) {
+ outStr += "\\staccato";
+ } else if (eventMark == Marks::Staccatissimo) {
+ outStr += "\\staccatissimo";
+ } else if (eventMark == Marks::Marcato) {
+ outStr += "\\marcato";
+ } else if (eventMark == Marks::Trill) {
+ outStr += "\\trill";
+ } else if (eventMark == Marks::LongTrill) {
+ // span trill up to the next note:
+ // tweak the beginning of the next note using an invisible rest having zero length
+ outStr += "\\startTrillSpan s4*0 \\stopTrillSpan";
+ } else if (eventMark == Marks::Turn) {
+ outStr += "\\turn";
+ } else if (eventMark == Marks::Pause) {
+ outStr += "\\fermata";
+ } else if (eventMark == Marks::UpBow) {
+ outStr += "\\upbow";
+ } else if (eventMark == Marks::DownBow) {
+ outStr += "\\downbow";
+ } else {
+ outStr = "";
+ std::cerr << "LilyPondExporter::composeLilyMark() - unhandled mark: "
+ << eventMark << std::endl;
+ }
+ }
+
+ return outStr;
+}
+
+std::string
+LilyPondExporter::indent(const int &column)
+{
+ std::string outStr = "";
+ for (int c = 1; c <= column; c++) {
+ outStr += " ";
+ }
+ return outStr;
+}
+
+std::string
+LilyPondExporter::protectIllegalChars(std::string inStr)
+{
+
+ QString tmpStr = strtoqstr(inStr);
+
+ tmpStr.replace(QRegExp("&"), "\\&");
+ tmpStr.replace(QRegExp("\\^"), "\\^");
+ tmpStr.replace(QRegExp("%"), "\\%");
+ tmpStr.replace(QRegExp("<"), "\\<");
+ tmpStr.replace(QRegExp(">"), "\\>");
+ tmpStr.replace(QRegExp("\\["), "");
+ tmpStr.replace(QRegExp("\\]"), "");
+ tmpStr.replace(QRegExp("\\{"), "");
+ tmpStr.replace(QRegExp("\\}"), "");
+
+ //
+ // LilyPond uses utf8 encoding.
+ //
+ return tmpStr.utf8().data();
+}
+
+struct MarkerComp {
+ // Sort Markers by time
+ // Perhaps this should be made generic with a template?
+ bool operator()( Marker *a, Marker *b ) {
+ return a->getTime() < b->getTime();
+ }
+};
+
+bool
+LilyPondExporter::write()
+{
+ QString tmpName = strtoqstr(m_fileName);
+
+ // dmm - modified to act upon the filename itself, rather than the whole
+ // path; fixes bug #855349
+
+ // split name into parts:
+ QFileInfo nfo(tmpName);
+ QString dirName = nfo.dirPath();
+ QString baseName = nfo.fileName();
+
+ // sed LilyPond-choking chars out of the filename proper
+ bool illegalFilename = (baseName.contains(' ') || baseName.contains("\\"));
+ baseName.replace(QRegExp(" "), "");
+ baseName.replace(QRegExp("\\\\"), "");
+ baseName.replace(QRegExp("'"), "");
+ baseName.replace(QRegExp("\""), "");
+
+ // cat back together
+ tmpName = dirName + '/' + baseName;
+
+ if (illegalFilename) {
+ CurrentProgressDialog::freeze();
+ int reply = KMessageBox::warningContinueCancel(
+ 0, i18n("LilyPond does not allow spaces or backslashes in filenames.\n\n"
+ "Would you like to use\n\n %1\n\n instead?").arg(baseName));
+ if (reply != KMessageBox::Continue)
+ return false;
+ }
+
+ std::ofstream str(qstrtostr(tmpName).c_str(), std::ios::out);
+ if (!str) {
+ std::cerr << "LilyPondExporter::write() - can't write file " << tmpName << std::endl;
+ return false;
+ }
+
+ str << "% This LilyPond file was generated by Rosegarden " << protectIllegalChars(VERSION) << std::endl;
+
+ switch (m_languageLevel) {
+
+ case LILYPOND_VERSION_2_6:
+ str << "\\version \"2.6.0\"" << std::endl;
+ break;
+
+ case LILYPOND_VERSION_2_8:
+ str << "\\version \"2.8.0\"" << std::endl;
+ break;
+
+ case LILYPOND_VERSION_2_10:
+ str << "\\version \"2.10.0\"" << std::endl;
+ break;
+
+ case LILYPOND_VERSION_2_12:
+ str << "\\version \"2.12.0\"" << std::endl;
+ break;
+
+ default:
+ // force the default version if there was an error
+ std::cerr << "ERROR: Unknown language level " << m_languageLevel
+ << ", using \\version \"2.6.0\" instead" << std::endl;
+ str << "\\version \"2.6.0\"" << std::endl;
+ m_languageLevel = LILYPOND_VERSION_2_6;
+ }
+
+ // enable "point and click" debugging via pdf to make finding the
+ // unfortunately inevitable errors easier
+ if (m_exportPointAndClick) {
+ str << "% point and click debugging is enabled" << std::endl;
+ } else {
+ str << "% point and click debugging is disabled" << std::endl;
+ str << "#(ly:set-option 'point-and-click #f)" << std::endl;
+ }
+
+ // LilyPond \header block
+
+ // set indention level to make future changes to horizontal layout less
+ // tedious, ++col to indent a new level, --col to de-indent
+ int col = 0;
+
+ // grab user headers from metadata
+ Configuration metadata = m_composition->getMetadata();
+ std::vector<std::string> propertyNames = metadata.getPropertyNames();
+
+ // open \header section if there's metadata to grab, and if the user
+ // wishes it
+ if (!propertyNames.empty()) {
+ str << "\\header {" << std::endl;
+ col++; // indent+
+
+ bool userTagline = false;
+
+ for (unsigned int index = 0; index < propertyNames.size(); ++index) {
+ std::string property = propertyNames [index];
+ if (property == headerDedication || property == headerTitle ||
+ property == headerSubtitle || property == headerSubsubtitle ||
+ property == headerPoet || property == headerComposer ||
+ property == headerMeter || property == headerOpus ||
+ property == headerArranger || property == headerInstrument ||
+ property == headerPiece || property == headerCopyright ||
+ property == headerTagline) {
+ std::string header = protectIllegalChars(metadata.get<String>(property));
+ if (header != "") {
+ str << indent(col) << property << " = \"" << header << "\"" << std::endl;
+ // let users override defaults, but allow for providing
+ // defaults if they don't:
+ if (property == headerTagline)
+ userTagline = true;
+ }
+ }
+ }
+
+ // default tagline
+ if (!userTagline) {
+ str << indent(col) << "tagline = \""
+ << "Created using Rosegarden " << protectIllegalChars(VERSION) << " and LilyPond"
+ << "\"" << std::endl;
+ }
+
+ // close \header
+ str << indent(--col) << "}" << std::endl;
+ }
+
+ // LilyPond \paper block (optional)
+ if (m_raggedBottom) {
+ str << indent(col) << "\\paper {" << std::endl;
+ str << indent(++col) << "ragged-bottom=##t" << std::endl;
+ str << indent(--col) << "}" << std::endl;
+ }
+
+ // LilyPond music data! Mapping:
+ // LilyPond Voice = Rosegarden Segment
+ // LilyPond Staff = Rosegarden Track
+ // (not the cleanest output but maybe the most reliable)
+
+ // paper/font sizes
+ int font;
+ switch (m_fontSize) {
+ case 0 :
+ font = 11;
+ break;
+ case 1 :
+ font = 13;
+ break;
+ case 2 :
+ font = 16;
+ break;
+ case 3 :
+ font = 19;
+ break;
+ case 4 :
+ font = 20;
+ break;
+ case 5 :
+ font = 23;
+ break;
+ case 6 :
+ font = 26;
+ break;
+ default :
+ font = 20; // if config problem
+ }
+
+ str << indent(col) << "#(set-global-staff-size " << font << ")" << std::endl;
+
+ // write user-specified paper type as default paper size
+ std::string paper = "";
+ switch (m_paperSize) {
+ case PAPER_A3 :
+ paper += "a3";
+ break;
+ case PAPER_A4 :
+ paper += "a4";
+ break;
+ case PAPER_A5 :
+ paper += "a5";
+ break;
+ case PAPER_A6 :
+ paper += "a6";
+ break;
+ case PAPER_LEGAL :
+ paper += "legal";
+ break;
+ case PAPER_LETTER :
+ paper += "letter";
+ break;
+ case PAPER_TABLOID :
+ paper += "tabloid";
+ break;
+ case PAPER_NONE :
+ paper = "";
+ break; // "do not specify"
+ }
+ if (paper != "") {
+ str << indent(col) << "#(set-default-paper-size \"" << paper << "\""
+ << (m_paperLandscape ? " 'landscape" : "") << ")"
+ << std::endl;
+ }
+
+ // Find out the printed length of the composition
+ Composition::iterator i = m_composition->begin();
+ if ((*i) == NULL) {
+ str << indent(col) << "\\score {" << std::endl;
+ str << indent(++col) << "% no segments found" << std::endl;
+ // bind staffs with or without staff group bracket
+ str << indent(col) // indent
+ << "<<" << " s4 " << ">>" << std::endl;
+ str << indent(col) << "\\layout { }" << std::endl;
+ str << indent(--col) << "}" << std::endl;
+ return true;
+ }
+ timeT compositionStartTime = (*i)->getStartTime();
+ timeT compositionEndTime = (*i)->getEndMarkerTime();
+ for (; i != m_composition->end(); ++i) {
+ if (compositionStartTime > (*i)->getStartTime() && (*i)->getTrack() >= 0) {
+ compositionStartTime = (*i)->getStartTime();
+ }
+ if (compositionEndTime < (*i)->getEndMarkerTime()) {
+ compositionEndTime = (*i)->getEndMarkerTime();
+ }
+ }
+
+ // define global context which is common for all staffs
+ str << indent(col++) << "global = { " << std::endl;
+ TimeSignature timeSignature = m_composition->
+ getTimeSignatureAt(m_composition->getStartMarker());
+ if (m_composition->getBarStart(m_composition->getBarNumber(compositionStartTime)) < compositionStartTime) {
+ str << indent(col) << "\\partial ";
+ // Arbitrary partial durations are handled by the following way:
+ // split the partial duration to 64th notes: instead of "4" write "64*16". (hjj)
+ Note partialNote = Note::getNearestNote(1, MAX_DOTS);
+ int partialDuration = m_composition->getBarStart(m_composition->getBarNumber(compositionStartTime) + 1) - compositionStartTime;
+ writeDuration(1, str);
+ str << "*" << ((int)(partialDuration / partialNote.getDuration()))
+ << std::endl;
+ }
+ int leftBar = 0;
+ int rightBar = leftBar;
+ do {
+ bool isNew = false;
+ m_composition->getTimeSignatureInBar(rightBar + 1, isNew);
+
+ if (isNew || (m_composition->getBarStart(rightBar + 1) >= compositionEndTime)) {
+ // - set initial time signature; further time signature changes
+ // are defined within the segments, because they may be hidden
+ str << indent(col) << (leftBar == 0 ? "" : "% ") << "\\time "
+ << timeSignature.getNumerator() << "/"
+ << timeSignature.getDenominator() << std::endl;
+ // - place skips upto the end of the composition;
+ // this justifies the printed staffs
+ str << indent(col);
+ timeT leftTime = m_composition->getBarStart(leftBar);
+ timeT rightTime = m_composition->getBarStart(rightBar + 1);
+ if (leftTime < compositionStartTime) {
+ leftTime = compositionStartTime;
+ }
+ writeSkip(timeSignature, leftTime, rightTime - leftTime, false, str);
+ str << " %% " << (leftBar + 1) << "-" << (rightBar + 1) << std::endl;
+
+ timeSignature = m_composition->getTimeSignatureInBar(rightBar + 1, isNew);
+ leftBar = rightBar + 1;
+ }
+ } while (m_composition->getBarStart(++rightBar) < compositionEndTime);
+ str << indent(--col) << "}" << std::endl;
+
+ // time signatures changes are in segments, reset initial value
+ timeSignature = m_composition->
+ getTimeSignatureAt(m_composition->getStartMarker());
+
+ // All the tempo changes are included in "globalTempo" context.
+ // This context contains only skip notes between the tempo changes.
+ // First tempo marking should still be include in \midi{ } block.
+ // If tempo marks are printed in future, they should probably be
+ // included in this context and the note duration in the tempo
+ // mark should be according to the time signature. (hjj)
+ int tempoCount = m_composition->getTempoChangeCount();
+
+ if (tempoCount > 0) {
+
+ timeT prevTempoChangeTime = m_composition->getStartMarker();
+ int tempo = int(Composition::getTempoQpm(m_composition->getTempoAtTime(prevTempoChangeTime)));
+ bool tempoMarksInvisible = false;
+
+ str << indent(col++) << "globalTempo = {" << std::endl;
+ if (m_exportTempoMarks == EXPORT_NONE_TEMPO_MARKS && tempoMarksInvisible == false) {
+ str << indent(col) << "\\override Score.MetronomeMark #'transparent = ##t" << std::endl;
+ tempoMarksInvisible = true;
+ }
+ str << indent(col) << "\\tempo 4 = " << tempo << " ";
+ int prevTempo = tempo;
+
+ for (int i = 0; i < tempoCount; ++i) {
+
+ std::pair<timeT, long> tempoChange =
+ m_composition->getTempoChange(i);
+
+ timeT tempoChangeTime = tempoChange.first;
+
+ tempo = int(Composition::getTempoQpm(tempoChange.second));
+
+ // First tempo change may be before the first segment.
+ // Do not apply it before the first segment appears.
+ if (tempoChangeTime < compositionStartTime) {
+ tempoChangeTime = compositionStartTime;
+ } else if (tempoChangeTime >= compositionEndTime) {
+ tempoChangeTime = compositionEndTime;
+ }
+ if (prevTempoChangeTime < compositionStartTime) {
+ prevTempoChangeTime = compositionStartTime;
+ } else if (prevTempoChangeTime >= compositionEndTime) {
+ prevTempoChangeTime = compositionEndTime;
+ }
+ writeSkip(m_composition->getTimeSignatureAt(tempoChangeTime),
+ tempoChangeTime, tempoChangeTime - prevTempoChangeTime, false, str);
+ // add new \tempo only if tempo was changed
+ if (tempo != prevTempo) {
+ if (m_exportTempoMarks == EXPORT_FIRST_TEMPO_MARK && tempoMarksInvisible == false) {
+ str << std::endl << indent(col) << "\\override Score.MetronomeMark #'transparent = ##t";
+ tempoMarksInvisible = true;
+ }
+ str << std::endl << indent(col) << "\\tempo 4 = " << tempo << " ";
+ }
+
+ prevTempo = tempo;
+ prevTempoChangeTime = tempoChangeTime;
+ if (prevTempoChangeTime == compositionEndTime)
+ break;
+ }
+ // First tempo change may be before the first segment.
+ // Do not apply it before the first segment appears.
+ if (prevTempoChangeTime < compositionStartTime) {
+ prevTempoChangeTime = compositionStartTime;
+ }
+ writeSkip(m_composition->getTimeSignatureAt(prevTempoChangeTime),
+ prevTempoChangeTime, compositionEndTime - prevTempoChangeTime, false, str);
+ str << std::endl;
+ str << indent(--col) << "}" << std::endl;
+ }
+ // Markers
+ // Skip until marker, make sure there's only one marker per measure
+ if ( m_exportMarkerMode != EXPORT_NO_MARKERS ) {
+ str << indent(col++) << "markers = {" << std::endl;
+ timeT prevMarkerTime = 0;
+
+ // Need the markers sorted by time
+ Composition::markercontainer markers( m_composition->getMarkers() ); // copy
+ std::sort( markers.begin(), markers.end(), MarkerComp() );
+ Composition::markerconstiterator i_marker = markers.begin();
+
+ while ( i_marker != markers.end() ) {
+ timeT markerTime = m_composition->getBarStartForTime((*i_marker)->getTime());
+ RG_DEBUG << "Marker: " << (*i_marker)->getTime() << " previous: " << prevMarkerTime << endl;
+ // how to cope with time signature changes?
+ if ( markerTime > prevMarkerTime ) {
+ str << indent(col);
+ writeSkip(m_composition->getTimeSignatureAt(markerTime),
+ markerTime, markerTime - prevMarkerTime, false, str);
+ str << "\\mark ";
+ switch (m_exportMarkerMode) {
+ case EXPORT_DEFAULT_MARKERS:
+ // Use the marker name for text
+ str << "\\default %% " << (*i_marker)->getName() << std::endl;
+ break;
+ case EXPORT_TEXT_MARKERS:
+ // Raise the text above the staff as not to clash with the other stuff
+ str << "\\markup { \\hspace #0 \\raise #1.5 \"" << (*i_marker)->getName() << "\" }" << std::endl;
+ break;
+ default:
+ break;
+ }
+ prevMarkerTime = markerTime;
+ }
+ ++i_marker;
+ }
+ str << indent(--col) << "}" << std::endl;
+ }
+
+ // open \score section
+ str << "\\score {" << std::endl;
+
+ int lastTrackIndex = -1;
+ int voiceCounter = 0;
+ bool firstTrack = true;
+ int staffGroupCounter = 0;
+ int pianoStaffCounter = 0;
+ int bracket = 0;
+ int prevBracket = -1;
+
+ // Write out all segments for each Track, in track order.
+ // This involves a hell of a lot of loops through all tracks
+ // and segments, but the time spent doing that should still
+ // be relatively small in the greater scheme.
+
+ Track *track = 0;
+
+ for (int trackPos = 0;
+ (track = m_composition->getTrackByPosition(trackPos)) != 0; ++trackPos) {
+
+ for (Composition::iterator i = m_composition->begin();
+ i != m_composition->end(); ++i) {
+
+ if ((*i)->getTrack() != track->getId())
+ continue;
+
+ // handle the bracket(s) for the first track, and if no brackets
+ // present, open with a <<
+ prevBracket = bracket;
+ bracket = track->getStaffBracket();
+
+ //!!! how will all these indentions work out? Probably not well,
+ // but maybe if users always provide sensible input, this will work
+ // out sensibly. Maybe. If not, we'll need some tracking gizmos to
+ // figure out the indention, or just skip the indention for these or
+ // something. TBA.
+ if (firstTrack) {
+ // seems to be common to every case now
+ str << indent(col++) << "<< % common" << std::endl;
+ }
+
+ if (firstTrack && m_exportStaffGroup) {
+
+ if (bracket == Brackets::SquareOn) {
+ str << indent(col++) << "\\context StaffGroup = \"" << staffGroupCounter++
+ << "\" << " << std::endl; //indent+
+ } else if (bracket == Brackets::CurlyOn) {
+ str << indent(col++) << "\\context PianoStaff = \"" << pianoStaffCounter++
+ << "\" << " << std::endl; //indent+
+ } else if (bracket == Brackets::CurlySquareOn) {
+ str << indent(col++) << "\\context StaffGroup = \"" << staffGroupCounter++
+ << "\" << " << std::endl; //indent+
+ str << indent(col++) << "\\context PianoStaff = \"" << pianoStaffCounter++
+ << "\" << " << std::endl; //indent+
+ }
+
+ // Make chords offset colliding notes by default (only write for
+ // first track)
+ str << indent(++col) << "% force offset of colliding notes in chords:"
+ << std::endl;
+ str << indent(col) << "\\override Score.NoteColumn #\'force-hshift = #1.0"
+ << std::endl;
+ }
+
+ emit setProgress(int(double(trackPos) /
+ double(m_composition->getNbTracks()) * 100.0));
+ rgapp->refreshGUI(50);
+
+ bool currentSegmentSelected = false;
+ if ((m_exportSelection == EXPORT_SELECTED_SEGMENTS) &&
+ (m_view != NULL) && (m_view->haveSelection())) {
+ //
+ // Check whether the current segment is in the list of selected segments.
+ //
+ SegmentSelection selection = m_view->getSelection();
+ for (SegmentSelection::iterator it = selection.begin(); it != selection.end(); it++) {
+ if ((*it) == (*i)) currentSegmentSelected = true;
+ }
+ } else if ((m_exportSelection == EXPORT_SELECTED_SEGMENTS) && (m_notationView != NULL)) {
+ currentSegmentSelected = m_notationView->hasSegment(*i);
+ }
+
+ // Check whether the track is a non-midi track.
+ InstrumentId instrumentId = track->getInstrument();
+ bool isMidiTrack = instrumentId >= MidiInstrumentBase;
+
+ if (isMidiTrack && ( // Skip non-midi tracks.
+ (m_exportSelection == EXPORT_ALL_TRACKS) ||
+ ((m_exportSelection == EXPORT_NONMUTED_TRACKS) && (!track->isMuted())) ||
+ ((m_exportSelection == EXPORT_SELECTED_TRACK) && (m_view != NULL) &&
+ (track->getId() == m_composition->getSelectedTrack())) ||
+ ((m_exportSelection == EXPORT_SELECTED_TRACK) && (m_notationView != NULL) &&
+ (track->getId() == m_notationView->getCurrentSegment()->getTrack())) ||
+ ((m_exportSelection == EXPORT_SELECTED_SEGMENTS) && (currentSegmentSelected)))) {
+ if ((int) (*i)->getTrack() != lastTrackIndex) {
+ if (lastTrackIndex != -1) {
+ // close the old track (Staff context)
+ str << indent(--col) << ">> % Staff ends" << std::endl; //indent-
+ }
+ lastTrackIndex = (*i)->getTrack();
+
+
+ // handle any necessary bracket closures with a rude
+ // hack, because bracket closures need to be handled
+ // right under staff closures, but at this point in the
+ // loop we are one track too early for closing, so we use
+ // the bracket setting for the previous track for closing
+ // purposes (I'm not quite sure why this works, but it does)
+ if (m_exportStaffGroup) {
+ if (prevBracket == Brackets::SquareOff ||
+ prevBracket == Brackets::SquareOnOff) {
+ str << indent(--col) << ">> % StaffGroup " << staffGroupCounter
+ << std::endl; //indent-
+ } else if (prevBracket == Brackets::CurlyOff) {
+ str << indent(--col) << ">> % PianoStaff " << pianoStaffCounter
+ << std::endl; //indent-
+ } else if (prevBracket == Brackets::CurlySquareOff) {
+ str << indent(--col) << ">> % PianoStaff " << pianoStaffCounter
+ << std::endl; //indent-
+ str << indent(--col) << ">> % StaffGroup " << staffGroupCounter
+ << std::endl; //indent-
+ }
+ }
+
+ // handle any bracket start events (unless track staff
+ // brackets are being ignored, as when printing single parts
+ // out of a bigger score one by one)
+ if (!firstTrack && m_exportStaffGroup) {
+ if (bracket == Brackets::SquareOn ||
+ bracket == Brackets::SquareOnOff) {
+ str << indent(col++) << "\\context StaffGroup = \""
+ << ++staffGroupCounter << "\" <<" << std::endl;
+ } else if (bracket == Brackets::CurlyOn) {
+ str << indent(col++) << "\\context PianoStaff = \""
+ << ++pianoStaffCounter << "\" <<" << std::endl;
+ } else if (bracket == Brackets::CurlySquareOn) {
+ str << indent(col++) << "\\context StaffGroup = \""
+ << ++staffGroupCounter << "\" <<" << std::endl;
+ str << indent(col++) << "\\context PianoStaff = \""
+ << ++pianoStaffCounter << "\" <<" << std::endl;
+ }
+ }
+
+ // avoid problem with <untitled> tracks yielding a
+ // .ly file that jumbles all notes together on a
+ // single staff... every Staff context has to
+ // have a unique name, even if the
+ // Staff.instrument property is the same for
+ // multiple staffs...
+ // Added an option to merge staffs with the same, non-empty
+ // name. This option makes it possible to produce staffs
+ // with polyphonic, and polyrhytmic, music. Polyrhytmic
+ // music in a single staff is typical in piano, or
+ // guitar music. (hjj)
+ // In the case of colliding note heads, user may define
+ // - DISPLACED_X -- for a note/chord
+ // - INVISIBLE -- for a rest
+ std::ostringstream staffName;
+ staffName << protectIllegalChars(m_composition->
+ getTrackById(lastTrackIndex)->getLabel());
+
+ if (!m_exportStaffMerge || staffName.str() == "") {
+ str << std::endl << indent(col)
+ << "\\context Staff = \"track "
+ << (trackPos + 1) << "\" ";
+ } else {
+ str << std::endl << indent(col)
+ << "\\context Staff = \"" << staffName.str()
+ << "\" ";
+ }
+
+ str << "<< " << std::endl;
+
+ // The octavation is omitted in the instrument name.
+ // HJJ: Should it be automatically added to the clef: G^8 ?
+ // What if two segments have different transpose in a track?
+ std::ostringstream staffNameWithTranspose;
+ staffNameWithTranspose << "\\markup { \\column { \"" << staffName.str() << " \"";
+ if (((*i)->getTranspose() % 12) != 0) {
+ staffNameWithTranspose << " \\line { ";
+ switch ((*i)->getTranspose() % 12) {
+ case 1 : staffNameWithTranspose << "\"in D\" \\smaller \\flat"; break;
+ case 2 : staffNameWithTranspose << "\"in D\""; break;
+ case 3 : staffNameWithTranspose << "\"in E\" \\smaller \\flat"; break;
+ case 4 : staffNameWithTranspose << "\"in E\""; break;
+ case 5 : staffNameWithTranspose << "\"in F\""; break;
+ case 6 : staffNameWithTranspose << "\"in G\" \\smaller \\flat"; break;
+ case 7 : staffNameWithTranspose << "\"in G\""; break;
+ case 8 : staffNameWithTranspose << "\"in A\" \\smaller \\flat"; break;
+ case 9 : staffNameWithTranspose << "\"in A\""; break;
+ case 10 : staffNameWithTranspose << "\"in B\" \\smaller \\flat"; break;
+ case 11 : staffNameWithTranspose << "\"in B\""; break;
+ }
+ staffNameWithTranspose << " }";
+ }
+ staffNameWithTranspose << " } }";
+ if (m_languageLevel < LILYPOND_VERSION_2_10) {
+ str << indent(++col) << "\\set Staff.instrument = " << staffNameWithTranspose.str()
+ << std::endl;
+ } else {
+ str << indent(++col) << "\\set Staff.instrumentName = "
+ << staffNameWithTranspose.str() << std::endl;
+ }
+
+ if (m_exportMidi) {
+ // Set midi instrument for the Staff
+ std::ostringstream staffMidiName;
+ Instrument *instr = m_studio->getInstrumentById(
+ m_composition->getTrackById(lastTrackIndex)->getInstrument());
+ staffMidiName << instr->getProgramName();
+
+ str << indent(col) << "\\set Staff.midiInstrument = \"" << staffMidiName.str()
+ << "\"" << std::endl;
+ }
+
+ // multi measure rests are used by default
+ str << indent(col) << "\\set Score.skipBars = ##t" << std::endl;
+
+ // turn off the stupid accidental cancelling business,
+ // because we don't do that ourselves, and because my 11
+ // year old son pointed out to me that it "Looks really
+ // stupid. Why is it cancelling out four flats and then
+ // adding five flats back? That's brain damaged."
+ str << indent(col) << "\\set Staff.printKeyCancellation = ##f" << std::endl;
+ str << indent(col) << "\\new Voice \\global" << std::endl;
+ if (tempoCount > 0) {
+ str << indent(col) << "\\new Voice \\globalTempo" << std::endl;
+ }
+ if ( m_exportMarkerMode != EXPORT_NO_MARKERS ) {
+ str << indent(col) << "\\new Voice \\markers" << std::endl;
+ }
+
+ }
+
+ // Temporary storage for non-atomic events (!BOOM)
+ // ex. LilyPond expects signals when a decrescendo starts
+ // as well as when it ends
+ eventendlist eventsInProgress;
+ eventstartlist eventsToStart;
+
+ // If the segment doesn't start at 0, add a "skip" to the start
+ // No worries about overlapping segments, because Voices can overlap
+ // voiceCounter is a hack because LilyPond does not by default make
+ // them unique
+ std::ostringstream voiceNumber;
+ voiceNumber << "voice " << ++voiceCounter;
+
+ str << std::endl << indent(col++) << "\\context Voice = \"" << voiceNumber.str()
+ << "\" {"; // indent+
+
+ str << std::endl << indent(col) << "\\override Voice.TextScript #'padding = #2.0";
+ str << std::endl << indent(col) << "\\override MultiMeasureRest #'expand-limit = 1" << std::endl;
+
+ // staff notation size
+ int staffSize = track->getStaffSize();
+ if (staffSize == StaffTypes::Small) str << indent(col) << "\\small" << std::endl;
+ else if (staffSize == StaffTypes::Tiny) str << indent(col) << "\\tiny" << std::endl;
+
+ SegmentNotationHelper helper(**i);
+ helper.setNotationProperties();
+
+ int firstBar = m_composition->getBarNumber((*i)->getStartTime());
+
+ if (firstBar > 0) {
+ // Add a skip for the duration until the start of the first
+ // bar in the segment. If the segment doesn't start on a bar
+ // line, an additional skip will be written (in the form of
+ // a series of rests) at the start of writeBar, below.
+ //!!! This doesn't cope correctly yet with time signature changes
+ // during this skipped section.
+ str << std::endl << indent(col);
+ writeSkip(timeSignature, compositionStartTime,
+ m_composition->getBarStart(firstBar) - compositionStartTime,
+ false, str);
+ }
+
+ std::string lilyText = ""; // text events
+ std::string prevStyle = ""; // track note styles
+
+ Rosegarden::Key key;
+
+ bool haveRepeating = false;
+ bool haveAlternates = false;
+
+ bool nextBarIsAlt1 = false;
+ bool nextBarIsAlt2 = false;
+ bool prevBarWasAlt2 = false;
+
+ int MultiMeasureRestCount = 0;
+
+ bool nextBarIsDouble = false;
+ bool nextBarIsEnd = false;
+ bool nextBarIsDot = false;
+
+ for (int barNo = m_composition->getBarNumber((*i)->getStartTime());
+ barNo <= m_composition->getBarNumber((*i)->getEndMarkerTime());
+ ++barNo) {
+
+ timeT barStart = m_composition->getBarStart(barNo);
+ timeT barEnd = m_composition->getBarEnd(barNo);
+ if (barStart < compositionStartTime) {
+ barStart = compositionStartTime;
+ }
+
+ // open \repeat section if this is the first bar in the
+ // repeat
+ if ((*i)->isRepeating() && !haveRepeating) {
+
+ haveRepeating = true;
+
+ //!!! calculate the number of times this segment
+ //repeats and make the following variable meaningful
+ int numRepeats = 2;
+
+ str << std::endl << indent(col++) << "\\repeat volta " << numRepeats << " {";
+ }
+
+ // open the \alternative section if this bar is alternative ending 1
+ // ending (because there was an "Alt1" flag in the
+ // previous bar to the left of where we are right now)
+ //
+ // Alt1 remains in effect until we run into Alt2, which
+ // runs to the end of the segment
+ if (nextBarIsAlt1 && haveRepeating) {
+ str << std::endl << indent(--col) << "} \% repeat close (before alternatives) ";
+ str << std::endl << indent(col++) << "\\alternative {";
+ str << std::endl << indent(col++) << "{ \% open alternative 1 ";
+ nextBarIsAlt1 = false;
+ haveAlternates = true;
+ } else if (nextBarIsAlt2 && haveRepeating) {
+ if (!prevBarWasAlt2) {
+ col--;
+ // add an extra str to the following to shut up
+ // compiler warning from --ing and ++ing it in the
+ // same statement
+ str << std::endl << indent(--col) << "} \% close alternative 1 ";
+ str << std::endl << indent(col++) << "{ \% open alternative 2";
+ col++;
+ }
+ prevBarWasAlt2 = true;
+ }
+
+ // write out a bar's worth of events
+ writeBar(*i, barNo, barStart, barEnd, col, key,
+ lilyText,
+ prevStyle, eventsInProgress, str,
+ MultiMeasureRestCount,
+ nextBarIsAlt1, nextBarIsAlt2, nextBarIsDouble, nextBarIsEnd, nextBarIsDot);
+
+ }
+
+ // close \repeat
+ if (haveRepeating) {
+
+ // close \alternative section if present
+ if (haveAlternates) {
+ str << std::endl << indent(--col) << " } \% close alternative 2 ";
+ }
+
+ // close \repeat section in either case
+ str << std::endl << indent(--col) << " } \% close "
+ << (haveAlternates ? "alternatives" : "repeat");
+ }
+
+ // closing bar
+ if (((*i)->getEndMarkerTime() == compositionEndTime) && !haveRepeating) {
+ str << std::endl << indent(col) << "\\bar \"|.\"";
+ }
+
+ // close Voice context
+ str << std::endl << indent(--col) << "} % Voice" << std::endl; // indent-
+
+ //
+ // Write accumulated lyric events to the Lyric context, if desired.
+ //
+ // Sync the code below with LyricEditDialog::unparse() !!
+ //
+ if (m_exportLyrics) {
+ for (long currentVerse = 0, lastVerse = 0;
+ currentVerse <= lastVerse;
+ currentVerse++) {
+ bool haveLyric = false;
+ bool firstNote = true;
+ QString text = "";
+
+ timeT lastTime = (*i)->getStartTime();
+ for (Segment::iterator j = (*i)->begin();
+ (*i)->isBeforeEndMarker(j); ++j) {
+
+ bool isNote = (*j)->isa(Note::EventType);
+ bool isLyric = false;
+
+ if (!isNote) {
+ if ((*j)->isa(Text::EventType)) {
+ std::string textType;
+ if ((*j)->get
+ <String>(Text::TextTypePropertyName, textType) &&
+ textType == Text::Lyric) {
+ isLyric = true;
+ }
+ }
+ }
+
+ if (!isNote && !isLyric) continue;
+
+ timeT myTime = (*j)->getNotationAbsoluteTime();
+
+ if (isNote) {
+ if ((myTime > lastTime) || firstNote) {
+ if (!haveLyric)
+ text += " _";
+ lastTime = myTime;
+ haveLyric = false;
+ firstNote = false;
+ }
+ }
+
+ if (isLyric) {
+ long verse;
+ (*j)->get<Int>(Text::LyricVersePropertyName, verse);
+
+ if (verse == currentVerse) {
+ std::string ssyllable;
+ (*j)->get<String>(Text::TextPropertyName, ssyllable);
+ text += " ";
+
+ QString syllable(strtoqstr(ssyllable));
+ syllable.replace(QRegExp("\\s+"), "");
+ text += "\"" + syllable + "\"";
+ haveLyric = true;
+ } else if (verse > lastVerse) {
+ lastVerse = verse;
+ }
+ }
+ }
+
+ text.replace( QRegExp(" _+([^ ])") , " \\1" );
+ text.replace( "\"_\"" , " " );
+
+ // Do not create empty context for lyrics.
+ // Does this save some vertical space, as was written
+ // in earlier comment?
+ QRegExp rx( "\"" );
+ if ( rx.search( text ) != -1 ) {
+
+ str << indent(col) << "\\lyricsto \"" << voiceNumber.str() << "\""
+ << " \\new Lyrics \\lyricmode {" << std::endl;
+ if (m_lyricsHAlignment == RIGHT_ALIGN) {
+ str << indent(++col) << "\\override LyricText #'self-alignment-X = #RIGHT"
+ << std::endl;
+ } else if (m_lyricsHAlignment == CENTER_ALIGN) {
+ str << indent(++col) << "\\override LyricText #'self-alignment-X = #CENTER"
+ << std::endl;
+ } else {
+ str << indent(++col) << "\\override LyricText #'self-alignment-X = #LEFT"
+ << std::endl;
+ }
+ str << indent(col) << "\\set ignoreMelismata = ##t" << std::endl;
+ str << indent(col) << text.utf8() << " " << std::endl;
+ str << indent(col) << "\\unset ignoreMelismata" << std::endl;
+ str << indent(--col) << "} % Lyrics " << (currentVerse+1) << std::endl;
+ // close the Lyrics context
+ } // if ( rx.search( text....
+ } // for (long currentVerse = 0....
+ } // if (m_exportLyrics....
+ } // if (isMidiTrack....
+ firstTrack = false;
+ } // for (Composition::iterator i = m_composition->begin()....
+ } // for (int trackPos = 0....
+
+ // close the last track (Staff context)
+ if (voiceCounter > 0) {
+ str << indent(--col) << ">> % Staff (final) ends" << std::endl; // indent-
+
+ // handle any necessary final bracket closures (if brackets are being
+ // exported)
+ if (m_exportStaffGroup) {
+ if (bracket == Brackets::SquareOff ||
+ bracket == Brackets::SquareOnOff) {
+ str << indent(--col) << ">> % StaffGroup " << staffGroupCounter
+ << std::endl; //indent-
+ } else if (bracket == Brackets::CurlyOff) {
+ str << indent(--col) << ">> % PianoStaff (final) " << pianoStaffCounter
+ << std::endl; //indent-
+ } else if (bracket == Brackets::CurlySquareOff) {
+ str << indent(--col) << ">> % PianoStaff (final) " << pianoStaffCounter
+ << std::endl; //indent-
+ str << indent(--col) << ">> % StaffGroup (final) " << staffGroupCounter
+ << std::endl; //indent-
+ }
+ }
+ } else {
+ str << indent(--col) << "% (All staffs were muted.)" << std::endl;
+ }
+
+ // close \notes section
+ str << std::endl << indent(--col) << ">> % notes" << std::endl << std::endl; // indent-
+// str << std::endl << indent(col) << ">> % global wrapper" << std::endl;
+
+ // write \layout block
+ str << indent(col) << "\\layout { }" << std::endl;
+
+ // write initial tempo in Midi block, if user wishes (added per user request...
+ // makes debugging the .ly file easier because fewer "noisy" errors are
+ // produced during the process of rendering MIDI...)
+ if (m_exportMidi) {
+ int tempo = int(Composition::getTempoQpm(m_composition->getTempoAtTime(m_composition->getStartMarker())));
+ // Incomplete? Can I get away without converting tempo relative to the time
+ // signature for this purpose? we'll see...
+ str << indent(col++) << "\\midi {" << std::endl;
+ str << indent(col) << "\\tempo 4 = " << tempo << std::endl;
+ str << indent(--col) << "} " << std::endl;
+ }
+
+ // close \score section and close out the file
+ str << "} % score" << std::endl;
+ str.close();
+ return true;
+}
+
+timeT
+LilyPondExporter::calculateDuration(Segment *s,
+ const Segment::iterator &i,
+ timeT barEnd,
+ timeT &soundingDuration,
+ const std::pair<int, int> &tupletRatio,
+ bool &overlong)
+{
+ timeT duration = (*i)->getNotationDuration();
+ timeT absTime = (*i)->getNotationAbsoluteTime();
+
+ RG_DEBUG << "LilyPondExporter::calculateDuration: first duration, absTime: "
+ << duration << ", " << absTime << endl;
+
+ timeT durationCorrection = 0;
+
+ if ((*i)->isa(Note::EventType) || (*i)->isa(Note::EventRestType)) {
+ try {
+ // tuplet compensation, etc
+ Note::Type type = (*i)->get<Int>(NOTE_TYPE);
+ int dots = (*i)->get<Int>(NOTE_DOTS);
+ durationCorrection = Note(type, dots).getDuration() - duration;
+ } catch (Exception e) { // no properties
+ }
+ }
+
+ duration += durationCorrection;
+
+ RG_DEBUG << "LilyPondExporter::calculateDuration: now duration is "
+ << duration << " after correction of " << durationCorrection << endl;
+
+ soundingDuration = duration * tupletRatio.first/ tupletRatio.second;
+
+ timeT toNext = barEnd - absTime;
+ if (soundingDuration > toNext) {
+ soundingDuration = toNext;
+ duration = soundingDuration * tupletRatio.second/ tupletRatio.first;
+ overlong = true;
+ }
+
+ RG_DEBUG << "LilyPondExporter::calculateDuration: time to barEnd is "
+ << toNext << endl;
+
+ // Examine the following event, and truncate our duration
+ // if we overlap it.
+ Segment::iterator nextElt = s->end();
+ toNext = soundingDuration;
+
+ if ((*i)->isa(Note::EventType)) {
+
+ Chord chord(*s, i, m_composition->getNotationQuantizer());
+ Segment::iterator nextElt = chord.getFinalElement();
+ ++nextElt;
+
+ if (s->isBeforeEndMarker(nextElt)) {
+ // The quantizer sometimes sticks a rest at the same time
+ // as this note -- don't use that one here, and mark it as
+ // not to be exported -- it's just a heavy-handed way of
+ // rendering counterpoint in RG
+ if ((*nextElt)->isa(Note::EventRestType) &&
+ (*nextElt)->getNotationAbsoluteTime() == absTime) {
+ (*nextElt)->set<Bool>(SKIP_PROPERTY, true);
+ ++nextElt;
+ }
+ }
+
+ } else {
+ nextElt = i;
+ ++nextElt;
+ while (s->isBeforeEndMarker(nextElt)) {
+ if ((*nextElt)->isa(Controller::EventType) ||
+ (*nextElt)->isa(ProgramChange::EventType) ||
+ (*nextElt)->isa(SystemExclusive::EventType) ||
+ (*nextElt)->isa(ChannelPressure::EventType) ||
+ (*nextElt)->isa(KeyPressure::EventType) ||
+ (*nextElt)->isa(PitchBend::EventType))
+ ++nextElt;
+ else
+ break;
+ }
+ }
+
+ if (s->isBeforeEndMarker(nextElt)) {
+ RG_DEBUG << "LilyPondExporter::calculateDuration: inside conditional " << endl;
+ toNext = (*nextElt)->getNotationAbsoluteTime() - absTime;
+ // if the note was lengthened, assume it was lengthened to the left
+ // when truncating to the beginning of the next note
+ if (durationCorrection > 0) {
+ toNext += durationCorrection;
+ }
+ if (soundingDuration > toNext) {
+ soundingDuration = toNext;
+ duration = soundingDuration * tupletRatio.second/ tupletRatio.first;
+ }
+ }
+
+ RG_DEBUG << "LilyPondExporter::calculateDuration: second toNext is "
+ << toNext << endl;
+
+ RG_DEBUG << "LilyPondExporter::calculateDuration: final duration, soundingDuration: " << duration << ", " << soundingDuration << endl;
+
+ return duration;
+}
+
+void
+LilyPondExporter::writeBar(Segment *s,
+ int barNo, int barStart, int barEnd, int col,
+ Rosegarden::Key &key,
+ std::string &lilyText,
+ std::string &prevStyle,
+ eventendlist &eventsInProgress,
+ std::ofstream &str,
+ int &MultiMeasureRestCount,
+ bool &nextBarIsAlt1, bool &nextBarIsAlt2,
+ bool &nextBarIsDouble, bool &nextBarIsEnd, bool &nextBarIsDot)
+{
+ int lastStem = 0; // 0 => unset, -1 => down, 1 => up
+ int isGrace = 0;
+
+ Segment::iterator i = s->findTime(barStart);
+ if (!s->isBeforeEndMarker(i))
+ return ;
+
+ if (MultiMeasureRestCount == 0) {
+ str << std::endl;
+
+ if ((barNo + 1) % 5 == 0) {
+ str << "%% " << barNo + 1 << std::endl << indent(col);
+ } else {
+ str << indent(col);
+ }
+ }
+
+ bool isNew = false;
+ TimeSignature timeSignature = m_composition->getTimeSignatureInBar(barNo, isNew);
+ if (isNew) {
+ if (timeSignature.isHidden()) {
+ str << "\\once \\override Staff.TimeSignature #'break-visibility = #(vector #f #f #f) ";
+ }
+ str << "\\time "
+ << timeSignature.getNumerator() << "/"
+ << timeSignature.getDenominator()
+ << std::endl << indent(col);
+ }
+
+ timeT absTime = (*i)->getNotationAbsoluteTime();
+ timeT writtenDuration = 0;
+ std::pair<int,int> barDurationRatio(timeSignature.getNumerator(),timeSignature.getDenominator());
+ std::pair<int,int> durationRatioSum(0,1);
+ static std::pair<int,int> durationRatio(0,1);
+
+ if (absTime > barStart) {
+ Note note(Note::getNearestNote(absTime - barStart, MAX_DOTS));
+ writtenDuration += note.getDuration();
+ durationRatio = writeSkip(timeSignature, 0, note.getDuration(), true, str);
+ durationRatioSum = fractionSum(durationRatioSum,durationRatio);
+ // str << qstrtostr(QString(" %{ %1/%2 %} ").arg(durationRatio.first).arg(durationRatio.second)); // DEBUG
+ }
+
+ timeT prevDuration = -1;
+ eventstartlist eventsToStart;
+
+ long groupId = -1;
+ std::string groupType = "";
+ std::pair<int, int> tupletRatio(1, 1);
+
+ bool overlong = false;
+ bool newBeamedGroup = false;
+ int notesInBeamedGroup = 0;
+
+ while (s->isBeforeEndMarker(i)) {
+
+ if ((*i)->getNotationAbsoluteTime() >= barEnd)
+ break;
+
+ // First test whether we're entering or leaving a group,
+ // before we consider how to write the event itself (at least
+ // for pre-2.0 LilyPond output)
+ QString startGroupBeamingsStr = "";
+ QString endGroupBeamingsStr = "";
+
+ if ((*i)->isa(Note::EventType) || (*i)->isa(Note::EventRestType) ||
+ (*i)->isa(Clef::EventType) || (*i)->isa(Rosegarden::Key::EventType)) {
+
+ long newGroupId = -1;
+ if ((*i)->get
+ <Int>(BEAMED_GROUP_ID, newGroupId)) {
+
+ if (newGroupId != groupId) {
+ // entering a new beamed group
+
+ if (groupId != -1) {
+ // and leaving an old one
+ if (groupType == GROUP_TYPE_TUPLED) {
+ if (m_exportBeams && notesInBeamedGroup > 0)
+ endGroupBeamingsStr += "] ";
+ endGroupBeamingsStr += "} ";
+ } else if (groupType == GROUP_TYPE_BEAMED) {
+ if (m_exportBeams && notesInBeamedGroup > 0)
+ endGroupBeamingsStr += "] ";
+ }
+ }
+
+ groupId = newGroupId;
+ groupType = "";
+ (void)(*i)->get
+ <String>(BEAMED_GROUP_TYPE, groupType);
+
+ if (groupType == GROUP_TYPE_TUPLED) {
+ long numerator = 0;
+ long denominator = 0;
+ (*i)->get
+ <Int>(BEAMED_GROUP_TUPLED_COUNT, numerator);
+ (*i)->get
+ <Int>(BEAMED_GROUP_UNTUPLED_COUNT, denominator);
+ if (numerator == 0 || denominator == 0) {
+ std::cerr << "WARNING: LilyPondExporter::writeBar: "
+ << "tupled event without tupled/untupled counts"
+ << std::endl;
+ groupId = -1;
+ groupType = "";
+ } else {
+ startGroupBeamingsStr += QString("\\times %1/%2 { ").arg(numerator).arg(denominator);
+ tupletRatio = std::pair<int, int>(numerator, denominator);
+ // Require explicit beamed groups,
+ // fixes bug #1683205.
+ // HJJ: Why line below was originally present?
+ // newBeamedGroup = true;
+ notesInBeamedGroup = 0;
+ }
+ } else if (groupType == GROUP_TYPE_BEAMED) {
+ newBeamedGroup = true;
+ notesInBeamedGroup = 0;
+ // there can currently be only on group type, reset tuplet ratio
+ tupletRatio = std::pair<int, int>(1,1);
+ }
+ }
+
+ }
+ else {
+
+ if (groupId != -1) {
+ // leaving a beamed group
+ if (groupType == GROUP_TYPE_TUPLED) {
+ if (m_exportBeams && notesInBeamedGroup > 0)
+ endGroupBeamingsStr += "] ";
+ endGroupBeamingsStr += "} ";
+ tupletRatio = std::pair<int, int>(1, 1);
+ } else if (groupType == GROUP_TYPE_BEAMED) {
+ if (m_exportBeams && notesInBeamedGroup > 0)
+ endGroupBeamingsStr += "] ";
+ }
+ groupId = -1;
+ groupType = "";
+ }
+ }
+ }
+
+ // Test whether the next note is grace note or not.
+ // The start or end of beamed grouping should be put in proper places.
+ str << endGroupBeamingsStr.utf8();
+ if ((*i)->has(IS_GRACE_NOTE) && (*i)->get<Bool>(IS_GRACE_NOTE)) {
+ if (isGrace == 0) {
+ isGrace = 1;
+ str << "\\grace { ";
+ // str << "%{ grace starts %} "; // DEBUG
+ }
+ } else if (isGrace == 1) {
+ isGrace = 0;
+ // str << "%{ grace ends %} "; // DEBUG
+ str << "} ";
+ }
+ str << startGroupBeamingsStr.utf8();
+
+ timeT soundingDuration = -1;
+ timeT duration = calculateDuration
+ (s, i, barEnd, soundingDuration, tupletRatio, overlong);
+
+ if (soundingDuration == -1) {
+ soundingDuration = duration * tupletRatio.first / tupletRatio.second;
+ }
+
+ if ((*i)->has(SKIP_PROPERTY)) {
+ (*i)->unset(SKIP_PROPERTY);
+ ++i;
+ continue;
+ }
+
+ bool needsSlashRest = false;
+
+ if ((*i)->isa(Note::EventType)) {
+
+ Chord chord(*s, i, m_composition->getNotationQuantizer());
+ Event *e = *chord.getInitialNote();
+ bool tiedForward = false;
+ bool tiedUp = false;
+
+ // Examine the following event, and truncate our duration
+ // if we overlap it.
+
+ if (e->has(DISPLACED_X)) {
+ double xDisplacement = 1 + ((double) e->get
+ <Int>(DISPLACED_X)) / 1000;
+ str << "\\once \\override NoteColumn #'force-hshift = #"
+ << xDisplacement << " ";
+ }
+
+ bool hiddenNote = false;
+ if (e->has(INVISIBLE)) {
+ if (e->get
+ <Bool>(INVISIBLE)) {
+ hiddenNote = true;
+ }
+ }
+
+ if ( hiddenNote ) {
+ str << "\\hideNotes ";
+ }
+
+ if (e->has(NotationProperties::STEM_UP)) {
+ if (e->get
+ <Bool>(NotationProperties::STEM_UP)) {
+ if (lastStem != 1) {
+ str << "\\stemUp ";
+ lastStem = 1;
+ }
+ }
+ else {
+ if (lastStem != -1) {
+ str << "\\stemDown ";
+ lastStem = -1;
+ }
+ }
+ } else {
+ if (lastStem != 0) {
+ str << "\\stemNeutral ";
+ lastStem = 0;
+ }
+ }
+
+ if (chord.size() > 1)
+ str << "< ";
+
+ Segment::iterator stylei = s->end();
+
+ for (i = chord.getInitialElement(); s->isBeforeEndMarker(i); ++i) {
+
+ if ((*i)->isa(Text::EventType)) {
+ if (!handleDirective(*i, lilyText, nextBarIsAlt1, nextBarIsAlt2,
+ nextBarIsDouble, nextBarIsEnd, nextBarIsDot)) {
+
+ handleText(*i, lilyText);
+ }
+
+ } else if ((*i)->isa(Note::EventType)) {
+
+ if (m_languageLevel >= LILYPOND_VERSION_2_8) {
+ // one \tweak per each chord note
+ if (chord.size() > 1)
+ writeStyle(*i, prevStyle, col, str, true);
+ else
+ writeStyle(*i, prevStyle, col, str, false);
+ } else {
+ // only one override per chord, and that outside the <>
+ stylei = i;
+ }
+ writePitch(*i, key, str);
+
+ bool noteHasCautionaryAccidental = false;
+ (*i)->get
+ <Bool>(NotationProperties::USE_CAUTIONARY_ACCIDENTAL, noteHasCautionaryAccidental);
+ if (noteHasCautionaryAccidental)
+ str << "?";
+
+ // get TIED_FORWARD and TIE_IS_ABOVE for later
+ (*i)->get<Bool>(TIED_FORWARD, tiedForward);
+ (*i)->get<Bool>(TIE_IS_ABOVE, tiedUp);
+
+ str << " ";
+ } else if ((*i)->isa(Indication::EventType)) {
+ eventsToStart.insert(*i);
+ eventsInProgress.insert(*i);
+ }
+
+ if (i == chord.getFinalElement())
+ break;
+ }
+
+ if (chord.size() > 1)
+ str << "> ";
+
+ if (duration != prevDuration) {
+ durationRatio = writeDuration(duration, str);
+ str << " ";
+ prevDuration = duration;
+ }
+
+ if (m_languageLevel == LILYPOND_VERSION_2_6) {
+ // only one override per chord, and that outside the <>
+ if (stylei != s->end()) {
+ writeStyle(*stylei, prevStyle, col, str, false);
+ stylei = s->end();
+ }
+ }
+
+ if (lilyText != "") {
+ str << lilyText;
+ lilyText = "";
+ }
+ writeSlashes(*i, str);
+
+ writtenDuration += soundingDuration;
+ std::pair<int,int> ratio = fractionProduct(durationRatio,tupletRatio);
+ durationRatioSum = fractionSum(durationRatioSum, ratio);
+ // str << qstrtostr(QString(" %{ %1/%2 * %3/%4 = %5/%6 %} ").arg(durationRatio.first).arg(durationRatio.second).arg(tupletRatio.first).arg(tupletRatio.second).arg(ratio.first).arg(ratio.second)); // DEBUG
+
+ std::vector<Mark> marks(chord.getMarksForChord());
+ // problem here: stem direction unavailable (it's a view-local property)
+ bool stemUp = true;
+ e->get
+ <Bool>(NotationProperties::STEM_UP, stemUp);
+ for (std::vector<Mark>::iterator j = marks.begin(); j != marks.end(); ++j) {
+ str << composeLilyMark(*j, stemUp);
+ }
+ if (marks.size() > 0)
+ str << " ";
+
+ handleEndingEvents(eventsInProgress, i, str);
+ handleStartingEvents(eventsToStart, str);
+
+ if (tiedForward)
+ if (tiedUp)
+ str << "^~ ";
+ else
+ str << "_~ ";
+
+ if ( hiddenNote ) {
+ str << "\\unHideNotes ";
+ }
+
+ if (newBeamedGroup) {
+ // This is a workaround for bug #1705430:
+ // Beaming groups erroneous after merging notes
+ // There will be fewer "e4. [ ]" errors in LilyPond-compiling.
+ // HJJ: This should be fixed in notation engine,
+ // after which the workaround below should be removed.
+ Note note(Note::getNearestNote(duration, MAX_DOTS));
+
+ switch (note.getNoteType()) {
+ case Note::SixtyFourthNote:
+ case Note::ThirtySecondNote:
+ case Note::SixteenthNote:
+ case Note::EighthNote:
+ notesInBeamedGroup++;
+ break;
+ }
+ }
+ // // Old version before the workaround for bug #1705430:
+ // if (newBeamedGroup)
+ // notesInBeamedGroup++;
+ } else if ((*i)->isa(Note::EventRestType)) {
+
+ bool hiddenRest = false;
+ if ((*i)->has(INVISIBLE)) {
+ if ((*i)->get
+ <Bool>(INVISIBLE)) {
+ hiddenRest = true;
+ }
+ }
+
+ bool offsetRest = false;
+ int restOffset = 0;
+ if ((*i)->has(DISPLACED_Y)) {
+ restOffset = (*i)->get<Int>(DISPLACED_Y);
+ offsetRest = true;
+ }
+
+ if (offsetRest) {
+ std::cout << "REST OFFSET: " << restOffset << std::endl;
+ } else {
+ std::cout << "NO REST OFFSET" << std::endl;
+ }
+
+ if (MultiMeasureRestCount == 0) {
+ if (hiddenRest) {
+ str << "s";
+ } else if (duration == timeSignature.getBarDuration()) {
+ // Look ahead the segment in order to detect
+ // the number of measures in the multi measure rest.
+ Segment::iterator mm_i = i;
+ while (s->isBeforeEndMarker(++mm_i)) {
+ if ((*mm_i)->isa(Note::EventRestType) &&
+ (*mm_i)->getNotationDuration() == (*i)->getNotationDuration() &&
+ timeSignature == m_composition->getTimeSignatureAt((*mm_i)->getNotationAbsoluteTime())) {
+ MultiMeasureRestCount++;
+ } else {
+ break;
+ }
+ }
+ str << "R";
+ } else {
+ if (offsetRest) {
+ // use offset height to get an approximate corresponding
+ // height on staff
+ restOffset = restOffset / 1000;
+ restOffset -= restOffset * 2;
+
+ // use height on staff to get a MIDI pitch
+ // get clef from whatever the last clef event was
+ Rosegarden::Key k;
+ Accidental a;
+ Pitch helper(restOffset, m_lastClefFound, k, a);
+
+ // port some code from writePitch() here, rather than
+ // rewriting writePitch() to do both jobs, which
+ // somebody could conceivably clean up one day if anyone
+ // is bored
+
+ // use MIDI pitch to get a named note
+ int p = helper.getPerformancePitch();
+ std::string n = convertPitchToLilyNote(p, a, k);
+
+ // write named note
+ str << n;
+
+ // generate and write octave marks
+ std::string m = "";
+ int o = (int)(p / 12);
+
+ // mystery hack (it was always aiming too low)
+ o++;
+
+ if (o < 4) {
+ for (; o < 4; o++)
+ m += ",";
+ } else {
+ for (; o > 4; o--)
+ m += "\'";
+ }
+
+ str << m;
+
+ // defer the \rest until after any duration, because it
+ // can't come before a duration if a duration change is
+ // necessary, which is all determined a bit further on
+ needsSlashRest = true;
+
+
+ std::cout << "using pitch letter:"
+ << n << m
+ << " for offset: "
+ << restOffset
+ << " for calculated octave: "
+ << o
+ << " in clef: "
+ << m_lastClefFound.getClefType()
+ << std::endl;
+ } else {
+ str << "r";
+ }
+ }
+
+ if (duration != prevDuration) {
+ durationRatio = writeDuration(duration, str);
+ if (MultiMeasureRestCount > 0) {
+ str << "*" << (1 + MultiMeasureRestCount);
+ }
+ prevDuration = duration;
+ }
+
+ // have to add \rest to a fake rest note after any required
+ // duration change
+ if (needsSlashRest) {
+ str << "\\rest";
+ needsSlashRest = false;
+ }
+
+ if (lilyText != "") {
+ str << lilyText;
+ lilyText = "";
+ }
+
+ str << " ";
+
+ handleEndingEvents(eventsInProgress, i, str);
+ handleStartingEvents(eventsToStart, str);
+
+ if (newBeamedGroup)
+ notesInBeamedGroup++;
+ } else {
+ MultiMeasureRestCount--;
+ }
+ writtenDuration += soundingDuration;
+ std::pair<int,int> ratio = fractionProduct(durationRatio,tupletRatio);
+ durationRatioSum = fractionSum(durationRatioSum, ratio);
+ // str << qstrtostr(QString(" %{ %1/%2 * %3/%4 = %5/%6 %} ").arg(durationRatio.first).arg(durationRatio.second).arg(tupletRatio.first).arg(tupletRatio.second).arg(ratio.first).arg(ratio.second)); // DEBUG
+ } else if ((*i)->isa(Clef::EventType)) {
+
+ try {
+ // Incomplete: Set which note the clef should center on (DMM - why?)
+ // To allow octavation of the clef, enclose the clefname always with quotes.
+ str << "\\clef \"";
+
+ Clef clef(**i);
+
+ if (clef.getClefType() == Clef::Treble) {
+ str << "treble";
+ } else if (clef.getClefType() == Clef::French) {
+ str << "french";
+ } else if (clef.getClefType() == Clef::Soprano) {
+ str << "soprano";
+ } else if (clef.getClefType() == Clef::Mezzosoprano) {
+ str << "mezzosoprano";
+ } else if (clef.getClefType() == Clef::Alto) {
+ str << "alto";
+ } else if (clef.getClefType() == Clef::Tenor) {
+ str << "tenor";
+ } else if (clef.getClefType() == Clef::Baritone) {
+ str << "baritone";
+ } else if (clef.getClefType() == Clef::Varbaritone) {
+ str << "varbaritone";
+ } else if (clef.getClefType() == Clef::Bass) {
+ str << "bass";
+ } else if (clef.getClefType() == Clef::Subbass) {
+ str << "subbass";
+ }
+
+ // save clef for later use by rests that need repositioned
+ m_lastClefFound = clef;
+ std::cout << "getting clef"
+ << std::endl
+ << "clef: "
+ << clef.getClefType()
+ << " lastClefFound: "
+ << m_lastClefFound.getClefType()
+ << std::endl;
+
+ // Transpose the clef one or two octaves up or down, if specified.
+ int octaveOffset = clef.getOctaveOffset();
+ if (octaveOffset > 0) {
+ str << "^" << 8*octaveOffset;
+ } else if (octaveOffset < 0) {
+ str << "_" << -8*octaveOffset;
+ }
+
+ str << "\"" << std::endl << indent(col);
+
+ } catch (Exception e) {
+ std::cerr << "Bad clef: " << e.getMessage() << std::endl;
+ }
+
+ } else if ((*i)->isa(Rosegarden::Key::EventType)) {
+ // ignore hidden key signatures
+ bool hiddenKey = false;
+ if ((*i)->has(INVISIBLE)) {
+ (*i)->get <Bool>(INVISIBLE, hiddenKey);
+ }
+
+ if (!hiddenKey) {
+ try {
+ str << "\\key ";
+ key = Rosegarden::Key(**i);
+
+ Accidental accidental = Accidentals::NoAccidental;
+
+ str << convertPitchToLilyNote(key.getTonicPitch(), accidental, key);
+
+ if (key.isMinor()) {
+ str << " \\minor";
+ } else {
+ str << " \\major";
+ }
+ str << std::endl << indent(col);
+
+ } catch (Exception e) {
+ std::cerr << "Bad key: " << e.getMessage() << std::endl;
+ }
+ }
+
+ } else if ((*i)->isa(Text::EventType)) {
+
+ if (!handleDirective(*i, lilyText, nextBarIsAlt1, nextBarIsAlt2,
+ nextBarIsDouble, nextBarIsEnd, nextBarIsDot)) {
+ handleText(*i, lilyText);
+ }
+
+ } else if ((*i)->isa(Guitar::Chord::EventType)) {
+
+ try {
+ Guitar::Chord chord = Guitar::Chord(**i);
+ const Guitar::Fingering& fingering = chord.getFingering();
+
+ int barreStart = 0, barreEnd = 0, barreFret = 0;
+
+ //
+ // Check if there is a barre.
+ //
+ if (fingering.hasBarre()) {
+ Guitar::Fingering::Barre barre = fingering.getBarre();
+ barreStart = barre.start;
+ barreEnd = barre.end;
+ barreFret = barre.fret;
+ }
+
+ if (barreStart == 0) {
+ str << " s4*0^\\markup \\fret-diagram #\"";
+ } else {
+ str << " s4*0^\\markup \\override #'(barre-type . straight) \\fret-diagram #\"";
+ }
+ //
+ // Check each string individually.
+ // Note: LilyPond numbers strings differently.
+ //
+ for (int stringNum = 6; stringNum >= 1; --stringNum) {
+ if (barreStart == stringNum) {
+ str << "c:" << barreStart << "-" << barreEnd << "-" << barreFret << ";";
+ }
+
+ if (fingering.getStringStatus( 6-stringNum ) == Guitar::Fingering::MUTED) {
+ str << stringNum << "-x;";
+ } else if (fingering.getStringStatus( 6-stringNum ) == Guitar::Fingering::OPEN) {
+ str << stringNum << "-o;";
+ } else {
+ int stringStatus = fingering.getStringStatus(6-stringNum);
+ if ((stringNum <= barreStart) && (stringNum >= barreEnd)) {
+ str << stringNum << "-" << barreFret << ";";
+ } else {
+ str << stringNum << "-" << stringStatus << ";";
+ }
+ }
+ }
+ str << "\" ";
+
+ } catch (Exception e) { // GuitarChord ctor failed
+ RG_DEBUG << "Bad GuitarChord event in LilyPond export" << endl;
+ }
+ }
+
+ // LilyPond 2.0 introduces required postfix syntax for beaming
+ if (m_exportBeams && newBeamedGroup && notesInBeamedGroup > 0) {
+ str << "[ ";
+ newBeamedGroup = false;
+ }
+
+ if ((*i)->isa(Indication::EventType)) {
+ eventsToStart.insert(*i);
+ eventsInProgress.insert(*i);
+ }
+
+ ++i;
+ }
+
+ if (groupId != -1) {
+ if (groupType == GROUP_TYPE_TUPLED) {
+ if (m_exportBeams && notesInBeamedGroup > 0)
+ str << "] ";
+ str << "} ";
+ tupletRatio = std::pair<int, int>(1, 1);
+ } else if (groupType == GROUP_TYPE_BEAMED) {
+ if (m_exportBeams && notesInBeamedGroup > 0)
+ str << "] ";
+ }
+ }
+
+ if (isGrace == 1) {
+ isGrace = 0;
+ // str << "%{ grace ends %} "; // DEBUG
+ str << "} ";
+ }
+
+ if (lastStem != 0) {
+ str << "\\stemNeutral ";
+ }
+
+ if (overlong) {
+ str << std::endl << indent(col) <<
+ qstrtostr(QString("% %1").
+ arg(i18n("warning: overlong bar truncated here")));
+ }
+
+ if (fractionSmaller(durationRatioSum, barDurationRatio)) {
+ str << std::endl << indent(col) <<
+ qstrtostr(QString("% %1").
+ arg(i18n("warning: bar too short, padding with rests")));
+ str << std::endl << indent(col) <<
+ qstrtostr(QString("% %1/%2 < %3/%4").
+ arg(durationRatioSum.first).
+ arg(durationRatioSum.second).
+ arg(barDurationRatio.first).
+ arg(barDurationRatio.second))
+ << std::endl << indent(col);
+ durationRatio = writeSkip(timeSignature, writtenDuration,
+ (barEnd - barStart) - writtenDuration, true, str);
+ durationRatioSum = fractionSum(durationRatioSum,durationRatio);
+ }
+ //
+ // Export bar and bar checks.
+ //
+ if (nextBarIsDouble) {
+ str << "\\bar \"||\" ";
+ nextBarIsDouble = false;
+ } else if (nextBarIsEnd) {
+ str << "\\bar \"|.\" ";
+ nextBarIsEnd = false;
+ } else if (nextBarIsDot) {
+ str << "\\bar \":\" ";
+ nextBarIsDot = false;
+ } else if (MultiMeasureRestCount == 0) {
+ str << " |";
+ }
+}
+
+std::pair<int,int>
+LilyPondExporter::writeSkip(const TimeSignature &timeSig,
+ timeT offset,
+ timeT duration,
+ bool useRests,
+ std::ofstream &str)
+{
+ DurationList dlist;
+ timeSig.getDurationListForInterval(dlist, duration, offset);
+ std::pair<int,int> durationRatioSum(0,1);
+ std::pair<int,int> durationRatio(0,1);
+
+ int t = 0, count = 0;
+
+ for (DurationList::iterator i = dlist.begin(); ; ++i) {
+
+ if (i == dlist.end() || (*i) != t) {
+
+ if (count > 0) {
+
+ if (!useRests)
+ str << "\\skip ";
+ else if (t == timeSig.getBarDuration())
+ str << "R";
+ else
+ str << "r";
+
+ durationRatio = writeDuration(t, str);
+
+ if (count > 1) {
+ str << "*" << count;
+ durationRatio = fractionProduct(durationRatio,count);
+ }
+ str << " ";
+
+ durationRatioSum = fractionSum(durationRatioSum,durationRatio);
+ }
+
+ if (i != dlist.end()) {
+ t = *i;
+ count = 1;
+ }
+
+ } else {
+ ++count;
+ }
+
+ if (i == dlist.end())
+ break;
+ }
+ return durationRatioSum;
+}
+
+bool
+LilyPondExporter::handleDirective(const Event *textEvent,
+ std::string &lilyText,
+ bool &nextBarIsAlt1, bool &nextBarIsAlt2,
+ bool &nextBarIsDouble, bool &nextBarIsEnd, bool &nextBarIsDot)
+{
+ Text text(*textEvent);
+
+ if (text.getTextType() == Text::LilyPondDirective) {
+ std::string directive = text.getText();
+ if (directive == Text::Segno) {
+ lilyText += "^\\markup { \\musicglyph #\"scripts.segno\" } ";
+ } else if (directive == Text::Coda) {
+ lilyText += "^\\markup { \\musicglyph #\"scripts.coda\" } ";
+ } else if (directive == Text::Alternate1) {
+ nextBarIsAlt1 = true;
+ } else if (directive == Text::Alternate2) {
+ nextBarIsAlt1 = false;
+ nextBarIsAlt2 = true;
+ } else if (directive == Text::BarDouble) {
+ nextBarIsDouble = true;
+ } else if (directive == Text::BarEnd) {
+ nextBarIsEnd = true;
+ } else if (directive == Text::BarDot) {
+ nextBarIsDot = true;
+ } else {
+ // pass along less special directives for handling as plain text,
+ // so they can be attached to chords and whatlike without
+ // redundancy
+ return false;
+ }
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void
+LilyPondExporter::handleText(const Event *textEvent,
+ std::string &lilyText)
+{
+ try {
+
+ Text text(*textEvent);
+ std::string s = text.getText();
+
+ // only protect illegal chars if this is Text, rather than
+ // LilyPondDirective
+ if ((*textEvent).isa(Text::EventType))
+ s = protectIllegalChars(s);
+
+ if (text.getTextType() == Text::Tempo) {
+
+ // print above staff, bold, large
+ lilyText += "^\\markup { \\bold \\large \"" + s + "\" } ";
+
+ } else if (text.getTextType() == Text::LocalTempo ||
+ text.getTextType() == Text::Chord) {
+
+ // print above staff, bold, small
+ lilyText += "^\\markup { \\bold \"" + s + "\" } ";
+
+ } else if (text.getTextType() == Text::Dynamic) {
+
+ // supported dynamics first
+ if (s == "ppppp" || s == "pppp" || s == "ppp" ||
+ s == "pp" || s == "p" || s == "mp" ||
+ s == "mf" || s == "f" || s == "ff" ||
+ s == "fff" || s == "ffff" || s == "rfz" ||
+ s == "sf") {
+
+ lilyText += "-\\" + s + " ";
+
+ } else {
+ // export as a plain markup:
+ // print below staff, bold italics, small
+ lilyText += "_\\markup { \\bold \\italic \"" + s + "\" } ";
+ }
+
+ } else if (text.getTextType() == Text::Direction) {
+
+ // print above staff, large
+ lilyText += "^\\markup { \\large \"" + s + "\" } ";
+
+ } else if (text.getTextType() == Text::LocalDirection) {
+
+ // print below staff, bold italics, small
+ lilyText += "_\\markup { \\bold \\italic \"" + s + "\" } ";
+
+ // LilyPond directives that don't require special handling across
+ // barlines are handled here along with ordinary text types. These
+ // can be injected wherever they happen to occur, and should get
+ // attached to the right bits in due course without extra effort.
+ //
+ } else if (text.getText() == Text::Gliss) {
+ lilyText += "\\glissando ";
+ } else if (text.getText() == Text::Arpeggio) {
+ lilyText += "\\arpeggio ";
+ } else if (text.getText() == Text::Tiny) {
+ lilyText += "\\tiny ";
+ } else if (text.getText() == Text::Small) {
+ lilyText += "\\small ";
+ } else if (text.getText() == Text::NormalSize) {
+ lilyText += "\\normalsize ";
+ } else {
+ textEvent->get
+ <String>(Text::TextTypePropertyName, s);
+ std::cerr << "LilyPondExporter::write() - unhandled text type: "
+ << s << std::endl;
+ }
+ } catch (Exception e) {
+ std::cerr << "Bad text: " << e.getMessage() << std::endl;
+ }
+}
+
+void
+LilyPondExporter::writePitch(const Event *note,
+ const Rosegarden::Key &key,
+ std::ofstream &str)
+{
+ // Note pitch (need name as well as octave)
+ // It is also possible to have "relative" pitches,
+ // but for simplicity we always use absolute pitch
+ // 60 is middle C, one unit is a half-step
+
+ long pitch = 60;
+ note->get
+ <Int>(PITCH, pitch);
+
+ Accidental accidental = Accidentals::NoAccidental;
+ note->get
+ <String>(ACCIDENTAL, accidental);
+
+ // format of LilyPond note is:
+ // name + octave + (duration) + text markup
+
+ // calculate note name and write note
+ std::string lilyNote;
+
+ lilyNote = convertPitchToLilyNote(pitch, accidental, key);
+
+ str << lilyNote;
+
+ // generate and write octave marks
+ std::string octaveMarks = "";
+ int octave = (int)(pitch / 12);
+
+ // tweak the octave break for B# / Cb
+ if ((lilyNote == "bisis") || (lilyNote == "bis")) {
+ octave--;
+ } else if ((lilyNote == "ceses") || (lilyNote == "ces")) {
+ octave++;
+ }
+
+ if (octave < 4) {
+ for (; octave < 4; octave++)
+ octaveMarks += ",";
+ } else {
+ for (; octave > 4; octave--)
+ octaveMarks += "\'";
+ }
+
+ str << octaveMarks;
+}
+
+void
+LilyPondExporter::writeStyle(const Event *note, std::string &prevStyle,
+ int col, std::ofstream &str, bool isInChord)
+{
+ // some hard-coded styles in order to provide rudimentary style export support
+ // note that this is technically bad practice, as style names are not supposed
+ // to be fixed but deduced from the style files actually present on the system
+ const std::string styleMensural = "Mensural";
+ const std::string styleTriangle = "Triangle";
+ const std::string styleCross = "Cross";
+ const std::string styleClassical = "Classical";
+
+ // handle various note styles before opening any chord
+ // brackets
+ std::string style = "";
+ note->get
+ <String>(NotationProperties::NOTE_STYLE, style);
+
+ if (style != prevStyle) {
+
+ if (style == styleClassical && prevStyle == "")
+ return ;
+
+ if (!isInChord)
+ prevStyle = style;
+
+ if (style == styleMensural) {
+ style = "mensural";
+ } else if (style == styleTriangle) {
+ style = "triangle";
+ } else if (style == styleCross) {
+ style = "cross";
+ } else {
+ style = "default"; // failsafe default or explicit
+ }
+
+ if (!isInChord) {
+ str << std::endl << indent(col) << "\\override Voice.NoteHead #'style = #'" << style << std::endl << indent(col);
+ } else {
+ str << "\\tweak #'style #'" << style << " ";
+ }
+ }
+}
+
+std::pair<int,int>
+LilyPondExporter::writeDuration(timeT duration,
+ std::ofstream &str)
+{
+ Note note(Note::getNearestNote(duration, MAX_DOTS));
+ std::pair<int,int> durationRatio(0,1);
+
+ switch (note.getNoteType()) {
+
+ case Note::SixtyFourthNote:
+ str << "64"; durationRatio = std::pair<int,int>(1,64);
+ break;
+
+ case Note::ThirtySecondNote:
+ str << "32"; durationRatio = std::pair<int,int>(1,32);
+ break;
+
+ case Note::SixteenthNote:
+ str << "16"; durationRatio = std::pair<int,int>(1,16);
+ break;
+
+ case Note::EighthNote:
+ str << "8"; durationRatio = std::pair<int,int>(1,8);
+ break;
+
+ case Note::QuarterNote:
+ str << "4"; durationRatio = std::pair<int,int>(1,4);
+ break;
+
+ case Note::HalfNote:
+ str << "2"; durationRatio = std::pair<int,int>(1,2);
+ break;
+
+ case Note::WholeNote:
+ str << "1"; durationRatio = std::pair<int,int>(1,1);
+ break;
+
+ case Note::DoubleWholeNote:
+ str << "\\breve"; durationRatio = std::pair<int,int>(2,1);
+ break;
+ }
+
+ for (int numDots = 0; numDots < note.getDots(); numDots++) {
+ str << ".";
+ }
+ durationRatio = fractionProduct(durationRatio,
+ std::pair<int,int>((1<<(note.getDots()+1))-1,1<<note.getDots()));
+ return durationRatio;
+}
+
+void
+LilyPondExporter::writeSlashes(const Event *note, std::ofstream &str)
+{
+ // write slashes after text
+ // / = 8 // = 16 /// = 32, etc.
+ long slashes = 0;
+ note->get
+ <Int>(NotationProperties::SLASHES, slashes);
+ if (slashes > 0) {
+ str << ":";
+ int length = 4;
+ for (int c = 1; c <= slashes; c++) {
+ length *= 2;
+ }
+ str << length;
+ }
+}
+
+}
diff --git a/src/document/io/LilyPondExporter.h b/src/document/io/LilyPondExporter.h
new file mode 100644
index 0000000..ffb831d
--- /dev/null
+++ b/src/document/io/LilyPondExporter.h
@@ -0,0 +1,262 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <[email protected]>,
+ Chris Cannam <[email protected]>,
+ Richard Bown <[email protected]>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ This file is Copyright 2002
+ Hans Kieserman <[email protected]>
+ with heavy lifting from csoundio as it was on 13/5/2002.
+
+ Numerous additions and bug fixes by
+ Michael McIntyre <[email protected]>
+
+ Some restructuring by Chris Cannam.
+
+ Brain surgery to support LilyPond 2.x export by Heikki Junes.
+
+ 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.
+*/
+
+#ifndef _RG_LILYPONDEXPORTER_H_
+#define _RG_LILYPONDEXPORTER_H_
+
+#include "base/Event.h"
+#include "base/PropertyName.h"
+#include "base/Segment.h"
+#include "gui/general/ProgressReporter.h"
+#include <fstream>
+#include <set>
+#include <string>
+#include <utility>
+
+
+class QObject;
+
+
+namespace Rosegarden
+{
+
+class TimeSignature;
+class Studio;
+class RosegardenGUIApp;
+class RosegardenGUIView;
+class RosegardenGUIDoc;
+class NotationView;
+class Key;
+class Composition;
+
+const std::string headerDedication = "dedication";
+const std::string headerTitle = "title";
+const std::string headerSubtitle = "subtitle";
+const std::string headerSubsubtitle = "subsubtitle";
+const std::string headerPoet = "poet";
+const std::string headerComposer = "composer";
+const std::string headerMeter = "meter";
+const std::string headerOpus = "opus";
+const std::string headerArranger = "arranger";
+const std::string headerInstrument = "instrument";
+const std::string headerPiece = "piece";
+const std::string headerCopyright = "copyright";
+const std::string headerTagline = "tagline";
+
+/**
+ * LilyPond scorefile export
+ */
+
+class LilyPondExporter : public ProgressReporter
+{
+public:
+ typedef std::multiset<Event*, Event::EventCmp> eventstartlist;
+ typedef std::multiset<Event*, Event::EventEndCmp> eventendlist;
+
+public:
+ LilyPondExporter(RosegardenGUIApp *parent, RosegardenGUIDoc *, std::string fileName);
+ LilyPondExporter(NotationView *parent, RosegardenGUIDoc *, std::string fileName);
+ ~LilyPondExporter();
+
+ bool write();
+
+protected:
+ RosegardenGUIView *m_view;
+ NotationView *m_notationView;
+ RosegardenGUIDoc *m_doc;
+ Composition *m_composition;
+ Studio *m_studio;
+ std::string m_fileName;
+ Clef m_lastClefFound;
+
+ void readConfigVariables(void);
+ void writeBar(Segment *, int barNo, int barStart, int barEnd, int col,
+ Rosegarden::Key &key, std::string &lilyText,
+ std::string &prevStyle, eventendlist &eventsInProgress,
+ std::ofstream &str, int &MultiMeasureRestCount,
+ bool &nextBarIsAlt1, bool &nextBarIsAlt2,
+ bool &nextBarIsDouble, bool &nextBarIsEnd, bool &nextBarIsDot);
+
+ timeT calculateDuration(Segment *s,
+ const Segment::iterator &i,
+ timeT barEnd,
+ timeT &soundingDuration,
+ const std::pair<int, int> &tupletRatio,
+ bool &overlong);
+
+ void handleStartingEvents(eventstartlist &eventsToStart, std::ofstream &str);
+ void handleEndingEvents(eventendlist &eventsInProgress,
+ const Segment::iterator &j, std::ofstream &str);
+
+ // convert note pitch into LilyPond format note string
+ std::string convertPitchToLilyNote(int pitch,
+ Accidental accidental,
+ const Rosegarden::Key &key);
+
+ // compose an appropriate LilyPond representation for various Marks
+ std::string composeLilyMark(std::string eventMark, bool stemUp);
+
+ // find/protect illegal characters in user-supplied strings
+ std::string protectIllegalChars(std::string inStr);
+
+ // return a string full of column tabs
+ std::string indent(const int &column);
+
+ std::pair<int,int> writeSkip(const TimeSignature &timeSig,
+ timeT offset,
+ timeT duration,
+ bool useRests,
+ std::ofstream &);
+
+ /*
+ * Handle LilyPond directive. Returns true if the event was a directive,
+ * so subsequent code does not bother to process the event twice
+ */
+ bool handleDirective(const Event *textEvent,
+ std::string &lilyText,
+ bool &nextBarIsAlt1, bool &nextBarIsAlt2,
+ bool &nextBarIsDouble, bool &nextBarIsEnd, bool &nextBarIsDot);
+
+ void handleText(const Event *, std::string &lilyText);
+ void writePitch(const Event *note, const Rosegarden::Key &key, std::ofstream &);
+ void writeStyle(const Event *note, std::string &prevStyle, int col, std::ofstream &, bool isInChord);
+ std::pair<int,int> writeDuration(timeT duration, std::ofstream &);
+ void writeSlashes(const Event *note, std::ofstream &);
+
+private:
+ static const int MAX_DOTS = 4;
+ static const PropertyName SKIP_PROPERTY;
+
+ unsigned int m_paperSize;
+ static const unsigned int PAPER_A3 = 0;
+ static const unsigned int PAPER_A4 = 1;
+ static const unsigned int PAPER_A5 = 2;
+ static const unsigned int PAPER_A6 = 3;
+ static const unsigned int PAPER_LEGAL = 4;
+ static const unsigned int PAPER_LETTER = 5;
+ static const unsigned int PAPER_TABLOID = 6;
+ static const unsigned int PAPER_NONE = 7;
+
+ bool m_paperLandscape;
+ unsigned int m_fontSize;
+ static const unsigned int FONT_11 = 0;
+ static const unsigned int FONT_13 = 1;
+ static const unsigned int FONT_16 = 2;
+ static const unsigned int FONT_19 = 3;
+ static const unsigned int FONT_20 = 4;
+ static const unsigned int FONT_23 = 5;
+ static const unsigned int FONT_26 = 6;
+
+ bool m_exportLyrics;
+ bool m_exportMidi;
+
+ unsigned int m_lyricsHAlignment;
+ static const unsigned int LEFT_ALIGN = 0;
+ static const unsigned int CENTER_ALIGN = 1;
+ static const unsigned int RIGHT_ALIGN = 2;
+
+ unsigned int m_exportTempoMarks;
+ static const unsigned int EXPORT_NONE_TEMPO_MARKS = 0;
+ static const unsigned int EXPORT_FIRST_TEMPO_MARK = 1;
+ static const unsigned int EXPORT_ALL_TEMPO_MARKS = 2;
+
+ unsigned int m_exportSelection;
+ static const unsigned int EXPORT_ALL_TRACKS = 0;
+ static const unsigned int EXPORT_NONMUTED_TRACKS = 1;
+ static const unsigned int EXPORT_SELECTED_TRACK = 2;
+ static const unsigned int EXPORT_SELECTED_SEGMENTS = 3;
+
+ bool m_exportPointAndClick;
+ bool m_exportBeams;
+ bool m_exportStaffGroup;
+ bool m_exportStaffMerge;
+ bool m_raggedBottom;
+
+ unsigned int m_exportMarkerMode;
+
+ static const unsigned int EXPORT_NO_MARKERS = 0;
+ static const unsigned int EXPORT_DEFAULT_MARKERS = 1;
+ static const unsigned int EXPORT_TEXT_MARKERS = 2;
+
+ int m_languageLevel;
+ static const int LILYPOND_VERSION_2_6 = 0;
+ static const int LILYPOND_VERSION_2_8 = 1;
+ static const int LILYPOND_VERSION_2_10 = 2;
+ static const int LILYPOND_VERSION_2_12 = 3;
+
+ std::pair<int,int> fractionSum(std::pair<int,int> x,std::pair<int,int> y) {
+ std::pair<int,int> z(
+ x.first * y.second + x.second * y.first,
+ x.second * y.second);
+ return fractionSimplify(z);
+ }
+ std::pair<int,int> fractionProduct(std::pair<int,int> x,std::pair<int,int> y) {
+ std::pair<int,int> z(
+ x.first * y.first,
+ x.second * y.second);
+ return fractionSimplify(z);
+ }
+ std::pair<int,int> fractionProduct(std::pair<int,int> x,int y) {
+ std::pair<int,int> z(
+ x.first * y,
+ x.second);
+ return fractionSimplify(z);
+ }
+ bool fractionSmaller(std::pair<int,int> x,std::pair<int,int> y) {
+ return (x.first * y.second < x.second * y.first);
+ }
+ std::pair<int,int> fractionSimplify(std::pair<int,int> x) {
+ return std::pair<int,int>(x.first/gcd(x.first,x.second),
+ x.second/gcd(x.first,x.second));
+ }
+ int gcd(int a, int b) {
+ // Euclid's algorithm to find the greatest common divisor
+ while ( 1 ) {
+ int r = a % b;
+ if ( r == 0 )
+ return (b == 0 ? 1 : b);
+ a = b;
+ b = r;
+ }
+ }
+};
+
+
+
+}
+
+#endif
+
diff --git a/src/document/io/MupExporter.cpp b/src/document/io/MupExporter.cpp
new file mode 100644
index 0000000..067c909
--- /dev/null
+++ b/src/document/io/MupExporter.cpp
@@ -0,0 +1,453 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <[email protected]>,
+ Chris Cannam <[email protected]>,
+ Richard Bown <[email protected]>
+
+ 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 "MupExporter.h"
+
+#include "misc/Debug.h"
+#include "base/BaseProperties.h"
+#include "base/Composition.h"
+#include "base/Event.h"
+#include "base/Exception.h"
+#include "base/NotationQuantizer.h"
+#include "base/NotationTypes.h"
+#include "base/Segment.h"
+#include "base/SegmentNotationHelper.h"
+#include "base/Sets.h"
+#include "base/Track.h"
+#include "gui/general/ProgressReporter.h"
+#include <qobject.h>
+
+using std::string;
+
+namespace Rosegarden
+{
+using namespace BaseProperties;
+
+MupExporter::MupExporter(QObject *parent,
+ Composition *composition,
+ string fileName) :
+ ProgressReporter(parent, "mupExporter"),
+ m_composition(composition),
+ m_fileName(fileName)
+{
+ // nothing else
+}
+
+MupExporter::~MupExporter()
+{
+ // nothing
+}
+
+bool
+MupExporter::write()
+{
+ Composition *c = m_composition;
+
+ std::ofstream str(m_fileName.c_str(), std::ios::out);
+ if (!str) {
+ std::cerr << "MupExporter::write() - can't write file " << m_fileName
+ << std::endl;
+ return false;
+ }
+
+ str << "score\n";
+ str << "\tstaffs=" << c->getNbTracks() << "\n";
+
+ int ts = c->getTimeSignatureCount();
+ std::pair<timeT, TimeSignature> tspair;
+ if (ts > 0)
+ tspair = c->getTimeSignatureChange(0);
+ str << "\ttime="
+ << tspair.second.getNumerator() << "/"
+ << tspair.second.getDenominator() << "\n";
+
+ for (int barNo = -1; barNo < c->getNbBars(); ++barNo) {
+
+ for (TrackId trackNo = c->getMinTrackId();
+ trackNo <= c->getMaxTrackId(); ++trackNo) {
+
+ if (barNo < 0) {
+ writeClefAndKey(str, trackNo);
+ continue;
+ }
+
+ if (barNo == 0 && trackNo == 0) {
+ str << "\nmusic\n";
+ }
+
+ str << "\t" << trackNo + 1 << ":";
+
+ Segment *s = 0;
+ timeT barStart = c->getBarStart(barNo);
+ timeT barEnd = c->getBarEnd(barNo);
+
+ for (Composition::iterator ci = c->begin(); ci != c->end(); ++ci) {
+ if ((*ci)->getTrack() == trackNo &&
+ (*ci)->getStartTime() < barEnd &&
+ (*ci)->getEndMarkerTime() > barStart) {
+ s = *ci;
+ break;
+ }
+ }
+
+ TimeSignature timeSig(c->getTimeSignatureAt(barStart));
+
+ if (!s) {
+ // write empty bar
+ writeInventedRests(str, timeSig, 0, barEnd - barStart);
+ continue;
+ }
+
+ if (s->getStartTime() > barStart) {
+ writeInventedRests(str, timeSig,
+ 0, s->getStartTime() - barStart);
+ }
+
+ // Mup insists that every bar has the correct duration, and won't
+ // recover if one goes wrong. Keep careful tabs on this: it means
+ // that for example we have to round chord durations down where
+ // the next chord starts too soon
+ //!!! we _really_ can't cope with time sig changes yet!
+
+ timeT writtenDuration = writeBar(str, c, s, barStart, barEnd,
+ timeSig, trackNo);
+
+ if (writtenDuration < timeSig.getBarDuration()) {
+ RG_DEBUG << "writtenDuration: " << writtenDuration
+ << ", bar duration " << timeSig.getBarDuration()
+ << endl;
+ writeInventedRests(str, timeSig, writtenDuration,
+ timeSig.getBarDuration() - writtenDuration);
+
+ } else if (writtenDuration > timeSig.getBarDuration()) {
+ std::cerr << "WARNING: overfull bar in Mup export: duration " << writtenDuration
+ << " into bar of duration " << timeSig.getBarDuration()
+ << std::endl;
+ //!!! warn user
+ }
+
+ str << "\n";
+ }
+
+ if (barNo >= 0)
+ str << "bar" << std::endl;
+ }
+
+ str << "\n" << std::endl;
+ str.close();
+ return true;
+}
+
+timeT
+MupExporter::writeBar(std::ofstream &str,
+ Composition *c,
+ Segment *s,
+ timeT barStart, timeT barEnd,
+ TimeSignature &timeSig,
+ TrackId trackNo)
+{
+ timeT writtenDuration = 0;
+ SegmentNotationHelper helper(*s);
+ helper.setNotationProperties();
+
+ long currentGroupId = -1;
+ string currentGroupType = "";
+ long currentTupletCount = 3;
+ bool first = true;
+ bool openBeamWaiting = false;
+
+ for (Segment::iterator si = s->findTime(barStart);
+ s->isBeforeEndMarker(si) &&
+ (*si)->getNotationAbsoluteTime() < barEnd; ++si) {
+
+ if ((*si)->isa(Note::EventType)) {
+
+ Chord chord(*s, si, c->getNotationQuantizer());
+ Event *e = *chord.getInitialNote();
+
+ timeT absTime = e->getNotationAbsoluteTime();
+ timeT duration = e->getNotationDuration();
+ try {
+ // tuplet compensation, etc
+ Note::Type type = e->get<Int>(NOTE_TYPE);
+ int dots = e->get
+ <Int>(NOTE_DOTS);
+ duration = Note(type, dots).getDuration();
+ } catch (Exception e) { // no properties
+ std::cerr << "WARNING: MupExporter::writeBar: incomplete note properties: " << e.getMessage() << std::endl;
+ }
+
+ timeT toNext = duration;
+ Segment::iterator nextElt = chord.getFinalElement();
+ if (s->isBeforeEndMarker(++nextElt)) {
+ toNext = (*nextElt)->getNotationAbsoluteTime() - absTime;
+ if (toNext < duration)
+ duration = toNext;
+ }
+
+ bool enteringGroup = false;
+
+ if (e->has(BEAMED_GROUP_ID) && e->has(BEAMED_GROUP_TYPE)) {
+
+ long id = e->get
+ <Int>(BEAMED_GROUP_ID);
+ string type = e->get
+ <String>(BEAMED_GROUP_TYPE);
+
+ if (id != currentGroupId) {
+
+ // leave previous group first
+ if (currentGroupId >= 0) {
+ if (!openBeamWaiting)
+ str << " ebm";
+ openBeamWaiting = false;
+
+ if (currentGroupType == GROUP_TYPE_TUPLED) {
+ str << "; }" << currentTupletCount;
+ }
+ }
+
+ currentGroupId = id;
+ currentGroupType = type;
+ enteringGroup = true;
+ }
+ } else {
+
+ if (currentGroupId >= 0) {
+ if (!openBeamWaiting)
+ str << " ebm";
+ openBeamWaiting = false;
+
+ if (currentGroupType == GROUP_TYPE_TUPLED) {
+ str << "; }" << currentTupletCount;
+ }
+
+ currentGroupId = -1;
+ currentGroupType = "";
+ }
+ }
+
+ if (openBeamWaiting)
+ str << " bm";
+ if (!first)
+ str << ";";
+ str << " ";
+
+ if (currentGroupType == GROUP_TYPE_TUPLED) {
+ e->get
+ <Int>(BEAMED_GROUP_UNTUPLED_COUNT, currentTupletCount);
+ if (enteringGroup)
+ str << "{ ";
+ //!!! duration = helper.getCompensatedNotationDuration(e);
+
+ }
+
+ writeDuration(str, duration);
+
+ if (toNext > duration && currentGroupType != GROUP_TYPE_TUPLED) {
+ writeInventedRests
+ (str, timeSig,
+ absTime + duration - barStart, toNext - duration);
+ }
+
+ writtenDuration += toNext;
+
+ for (Chord::iterator chi = chord.begin();
+ chi != chord.end(); ++chi) {
+ writePitch(str, trackNo, **chi);
+ }
+
+ openBeamWaiting = false;
+ if (currentGroupType == GROUP_TYPE_BEAMED ||
+ currentGroupType == GROUP_TYPE_TUPLED) {
+ if (enteringGroup)
+ openBeamWaiting = true;
+ }
+
+ si = chord.getFinalElement();
+
+ first = false;
+
+ } else if ((*si)->isa(Note::EventRestType)) {
+
+ if (currentGroupId >= 0) {
+
+ if (!openBeamWaiting)
+ str << " ebm";
+ openBeamWaiting = false;
+
+ if (currentGroupType == GROUP_TYPE_TUPLED) {
+ str << "; }" << currentTupletCount;
+ }
+
+ currentGroupId = -1;
+ currentGroupType = "";
+ }
+
+ if (openBeamWaiting)
+ str << " bm";
+ if (!first)
+ str << ";";
+ str << " ";
+
+ writeDuration(str, (*si)->getNotationDuration());
+ writtenDuration += (*si)->getNotationDuration();
+ str << "r";
+
+ first = false;
+ openBeamWaiting = false;
+
+ } // ignore all other sorts of events for now
+ }
+
+ if (currentGroupId >= 0) {
+ if (!openBeamWaiting)
+ str << " ebm";
+ openBeamWaiting = false;
+
+ if (currentGroupType == GROUP_TYPE_TUPLED) {
+ str << "; }" << currentTupletCount;
+ }
+ }
+
+ if (openBeamWaiting)
+ str << " bm";
+ if (!first)
+ str << ";";
+
+ return writtenDuration;
+}
+
+void
+MupExporter::writeClefAndKey(std::ofstream &str, TrackId trackNo)
+{
+ Composition *c = m_composition;
+
+ for (Composition::iterator i = c->begin(); i != c->end(); ++i) {
+ if ((*i)->getTrack() == trackNo) {
+
+ Clef clef((*i)->getClefAtTime((*i)->getStartTime()));
+ Rosegarden::Key key((*i)->getKeyAtTime((*i)->getStartTime()));
+
+
+ str << "staff " << trackNo + 1 << "\n";
+
+ if (clef.getClefType() == Clef::Treble) {
+ str << "\tclef=treble\n";
+ } else if (clef.getClefType() == Clef::Alto) {
+ str << "\tclef=alto\n";
+ } else if (clef.getClefType() == Clef::Tenor) {
+ str << "\tclef=tenor\n";
+ } else if (clef.getClefType() == Clef::Bass) {
+ str << "\tclef=bass\n";
+ }
+
+ str << "\tkey=" << key.getAccidentalCount()
+ << (key.isSharp() ? "#" : "&")
+ << (key.isMinor() ? "minor" : "major") << std::endl;
+
+ m_clefKeyMap[trackNo] = ClefKeyPair(clef, key);
+
+ return ;
+ }
+ }
+}
+
+void
+MupExporter::writeInventedRests(std::ofstream &str,
+ TimeSignature &timeSig,
+ timeT offset,
+ timeT duration)
+{
+ str << " ";
+ DurationList dlist;
+ timeSig.getDurationListForInterval(dlist, duration, offset);
+ for (DurationList::iterator i = dlist.begin();
+ i != dlist.end(); ++i) {
+ writeDuration(str, *i);
+ str << "r;";
+ }
+}
+
+void
+MupExporter::writePitch(std::ofstream &str, TrackId trackNo,
+ Event *event)
+{
+ long pitch = 0;
+ if (!event->get
+ <Int>(PITCH, pitch)) {
+ str << "c"; // have to write something, or it won't parse
+ return ;
+ }
+
+ Accidental accidental = Accidentals::NoAccidental;
+ (void)event->get
+ <String>(ACCIDENTAL, accidental);
+
+ // mup octave: treble clef is in octave 4?
+
+ ClefKeyPair ck;
+ ClefKeyMap::iterator ckmi = m_clefKeyMap.find(trackNo);
+ if (ckmi != m_clefKeyMap.end())
+ ck = ckmi->second;
+
+ Pitch p(pitch, accidental);
+ Accidental acc(p.getDisplayAccidental(ck.second));
+ char note(p.getNoteName(ck.second));
+ int octave(p.getOctave());
+
+ // just to avoid assuming that the note names returned by Pitch are in
+ // the same set as those expected by Mup -- in practice they are the same
+ // letters but this changes the case
+ str << "cdefgab"[Pitch::getIndexForNote(note)];
+
+ if (acc == Accidentals::DoubleFlat)
+ str << "&&";
+ else if (acc == Accidentals::Flat)
+ str << "&";
+ else if (acc == Accidentals::Sharp)
+ str << "#";
+ else if (acc == Accidentals::DoubleSharp)
+ str << "##";
+ else if (acc == Accidentals::Natural)
+ str << "n";
+
+ str << octave + 1;
+}
+
+void
+MupExporter::writeDuration(std::ofstream &str, timeT duration)
+{
+ Note note(Note::getNearestNote(duration, 2));
+ int n = Note::Semibreve - note.getNoteType();
+ if (n < 0)
+ str << "1/" << (1 << ( -n));
+ else
+ str << (1 << n);
+ for (int d = 0; d < note.getDots(); ++d)
+ str << ".";
+}
+
+}
diff --git a/src/document/io/MupExporter.h b/src/document/io/MupExporter.h
new file mode 100644
index 0000000..3740252
--- /dev/null
+++ b/src/document/io/MupExporter.h
@@ -0,0 +1,89 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <[email protected]>,
+ Chris Cannam <[email protected]>,
+ Richard Bown <[email protected]>
+
+ 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.
+*/
+
+#ifndef _RG_MUPEXPORTER_H_
+#define _RG_MUPEXPORTER_H_
+
+#include "base/Track.h"
+#include "gui/general/ProgressReporter.h"
+#include <map>
+#include <string>
+#include <utility>
+#include "base/Event.h"
+#include "base/NotationTypes.h"
+#include <fstream>
+
+
+class QObject;
+
+
+namespace Rosegarden
+{
+
+class TimeSignature;
+class Segment;
+class Event;
+class Composition;
+
+
+/**
+ * Mup file export
+ */
+
+class MupExporter : public ProgressReporter
+{
+public:
+ MupExporter(QObject *parent, Composition *, std::string fileName);
+ ~MupExporter();
+
+ bool write();
+
+protected:
+ timeT writeBar(std::ofstream &,
+ Composition *,
+ Segment *,
+ timeT, timeT,
+ TimeSignature &,
+ TrackId);
+ void writeClefAndKey(std::ofstream &, TrackId trackNo);
+ void writeInventedRests(std::ofstream &,
+ TimeSignature &timeSig,
+ timeT offset,
+ timeT duration);
+ void writePitch(std::ofstream &, TrackId, Event *event);
+ void writeDuration(std::ofstream &, timeT duration);
+
+ typedef std::pair<Clef, Rosegarden::Key> ClefKeyPair;
+ typedef std::map<TrackId, ClefKeyPair> ClefKeyMap;
+ ClefKeyMap m_clefKeyMap;
+
+ Composition *m_composition;
+ std::string m_fileName;
+};
+
+
+}
+
+#endif
diff --git a/src/document/io/MusicXmlExporter.cpp b/src/document/io/MusicXmlExporter.cpp
new file mode 100644
index 0000000..e1384c6
--- /dev/null
+++ b/src/document/io/MusicXmlExporter.cpp
@@ -0,0 +1,555 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <[email protected]>,
+ Chris Cannam <[email protected]>,
+ Richard Bown <[email protected]>
+
+ This file is Copyright 2002
+ Hans Kieserman <[email protected]>
+ 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 <qobject.h>
+
+namespace Rosegarden
+{
+
+using namespace BaseProperties;
+
+MusicXmlExporter::MusicXmlExporter(QObject *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;
+}
+
+}
diff --git a/src/document/io/MusicXmlExporter.h b/src/document/io/MusicXmlExporter.h
new file mode 100644
index 0000000..f730de8
--- /dev/null
+++ b/src/document/io/MusicXmlExporter.h
@@ -0,0 +1,87 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <[email protected]>,
+ Chris Cannam <[email protected]>,
+ Richard Bown <[email protected]>
+
+ This file is Copyright 2002
+ Hans Kieserman <[email protected]>
+ 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.
+*/
+
+#ifndef _RG_MUSICXMLEXPORTER_H_
+#define _RG_MUSICXMLEXPORTER_H_
+
+#include "base/Event.h"
+#include "base/NotationTypes.h"
+#include "gui/general/ProgressReporter.h"
+#include <fstream>
+#include <set>
+#include <string>
+
+
+class QObject;
+
+
+namespace Rosegarden
+{
+
+class RosegardenGUIDoc;
+class Key;
+class Clef;
+class AccidentalTable;
+
+
+/**
+ * MusicXml scorefile export
+ */
+
+class MusicXmlExporter : public ProgressReporter
+{
+public:
+ typedef std::multiset<Event*, Event::EventCmp> eventstartlist;
+ typedef std::multiset<Event*, Event::EventEndCmp> eventendlist;
+public:
+ MusicXmlExporter(QObject *parent, RosegardenGUIDoc *, std::string fileName);
+ ~MusicXmlExporter();
+
+ bool write();
+
+protected:
+ RosegardenGUIDoc *m_doc;
+ std::string m_fileName;
+ void writeClef(Rosegarden::Clef, std::ofstream &str);
+ void writeKey(Rosegarden::Key, std::ofstream &str);
+ void writeTime(TimeSignature timeSignature, std::ofstream &str);
+ void writeNote(Event *e, timeT lastNoteTime,
+ AccidentalTable &table,
+ const Clef &clef,
+ const Rosegarden::Key &key,
+ std::ofstream &str);
+
+ std::string numToId(int);
+};
+
+
+
+}
+
+#endif
diff --git a/src/document/io/RG21Loader.cpp b/src/document/io/RG21Loader.cpp
new file mode 100644
index 0000000..84f3d03
--- /dev/null
+++ b/src/document/io/RG21Loader.cpp
@@ -0,0 +1,797 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <[email protected]>,
+ Chris Cannam <[email protected]>,
+ Richard Bown <[email protected]>
+
+ 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 "RG21Loader.h"
+
+#include "misc/Debug.h"
+#include "misc/Strings.h"
+#include "base/Composition.h"
+#include "base/Event.h"
+#include "base/BaseProperties.h"
+#include "base/Instrument.h"
+#include "base/MidiProgram.h"
+#include "base/NotationTypes.h"
+#include "base/Segment.h"
+#include "base/Studio.h"
+#include "base/Track.h"
+#include "gui/editors/notation/NotationStrings.h"
+#include "gui/general/ProgressReporter.h"
+#include <qfile.h>
+#include <qobject.h>
+#include <qstring.h>
+#include <qstringlist.h>
+#include <qtextstream.h>
+#include <string>
+#include <vector>
+
+using std::vector;
+using std::string;
+
+
+namespace Rosegarden
+{
+
+using namespace BaseProperties;
+using namespace Accidentals;
+using namespace Marks;
+
+RG21Loader::RG21Loader(Studio *studio,
+ QObject *parent, const char* name)
+ : ProgressReporter(parent, name),
+ m_stream(0),
+ m_studio(studio),
+ m_composition(0),
+ m_currentSegment(0),
+ m_currentSegmentTime(0),
+ m_currentSegmentNb(0),
+ m_currentClef(Clef::Treble),
+ m_currentInstrumentId(MidiInstrumentBase),
+ m_inGroup(false),
+ m_tieStatus(0),
+ m_nbStaves(0)
+{}
+
+RG21Loader::~RG21Loader()
+{}
+
+bool RG21Loader::parseClef()
+{
+ if (m_tokens.count() != 3 || !m_currentSegment)
+ return false;
+
+ std::string clefName = qstrtostr(m_tokens[2].lower());
+
+ m_currentClef = Clef(clefName);
+ Event *clefEvent = m_currentClef.getAsEvent(m_currentSegmentTime);
+ m_currentSegment->insert(clefEvent);
+
+ return true;
+}
+
+bool RG21Loader::parseKey()
+{
+ if (m_tokens.count() < 3 || !m_currentSegment)
+ return false;
+
+ QString keyBase = m_tokens[2];
+ if (keyBase.length() > 1) {
+ // Deal correctly with e.g. Bb major
+ keyBase =
+ keyBase.left(1).upper() +
+ keyBase.right(keyBase.length() - 1).lower();
+ } else {
+ keyBase = keyBase.upper();
+ }
+
+ QString keyName = QString("%1 %2or")
+ .arg(keyBase)
+ .arg(m_tokens[3].lower());
+
+ m_currentKey = Rosegarden::Key(qstrtostr(keyName));
+ Event *keyEvent = m_currentKey.getAsEvent(m_currentSegmentTime);
+ m_currentSegment->insert(keyEvent);
+
+ return true;
+}
+
+bool RG21Loader::parseMetronome()
+{
+ if (m_tokens.count() < 2)
+ return false;
+ if (!m_composition)
+ return false;
+
+ QStringList::Iterator i = m_tokens.begin();
+ timeT duration = convertRG21Duration(i);
+
+ bool isNumeric = false;
+ int count = (*i).toInt(&isNumeric);
+ if (!count || !isNumeric)
+ return false;
+
+ // we need to take into account the fact that "duration" might not
+ // be a crotchet
+
+ double qpm = (count * duration) / Note(Note::Crotchet).getDuration();
+ m_composition->addTempoAtTime(m_currentSegmentTime,
+ m_composition->getTempoForQpm(qpm));
+ return true;
+}
+
+bool RG21Loader::parseChordItem()
+{
+ if (m_tokens.count() < 4)
+ return false;
+
+ QStringList::Iterator i = m_tokens.begin();
+ timeT duration = convertRG21Duration(i);
+
+ // get chord mod flags and nb of notes. chord mod is hex
+ int chordMods = (*i).toInt(0, 16);
+ ++i;
+ /*int nbNotes = (*i).toInt();*/
+ ++i;
+
+ vector<string> marks = convertRG21ChordMods(chordMods);
+
+ // now get notes
+ for (;i != m_tokens.end(); ++i) {
+
+ long pitch = (*i).toInt();
+ ++i;
+
+ // The noteMods field is nominally a hex integer. As it
+ // happens its value can never exceed 7, but I guess we
+ // should do the right thing anyway
+ int noteMods = (*i).toInt(0, 16);
+ pitch = convertRG21Pitch(pitch, noteMods);
+
+ Event *noteEvent = new Event(Note::EventType,
+ m_currentSegmentTime, duration);
+ noteEvent->set
+ <Int>(PITCH, pitch);
+
+ if (m_tieStatus == 1) {
+ noteEvent->set
+ <Bool>(TIED_FORWARD, true);
+ } else if (m_tieStatus == 2) {
+ noteEvent->set
+ <Bool>(TIED_BACKWARD, true);
+ }
+
+ if (marks.size() > 0) {
+ noteEvent->set
+ <Int>(MARK_COUNT, marks.size());
+ for (unsigned int j = 0; j < marks.size(); ++j) {
+ noteEvent->set
+ <String>(getMarkPropertyName(j), marks[j]);
+ }
+ }
+
+ // RG_DEBUG << "RG21Loader::parseChordItem() : insert note pitch " << pitch
+ // << " at time " << m_currentSegmentTime << endl;
+
+ setGroupProperties(noteEvent);
+
+ m_currentSegment->insert(noteEvent);
+ }
+
+ m_currentSegmentTime += duration;
+ if (m_tieStatus == 2)
+ m_tieStatus = 0;
+ else if (m_tieStatus == 1)
+ m_tieStatus = 2;
+
+ return true;
+}
+
+bool RG21Loader::parseRest()
+{
+ if (m_tokens.count() < 2)
+ return false;
+
+ QStringList::Iterator i = m_tokens.begin();
+ timeT duration = convertRG21Duration(i);
+
+ Event *restEvent = new Event(Note::EventRestType,
+ m_currentSegmentTime, duration,
+ Note::EventRestSubOrdering);
+
+ setGroupProperties(restEvent);
+
+ m_currentSegment->insert(restEvent);
+ m_currentSegmentTime += duration;
+
+ return true;
+}
+
+bool RG21Loader::parseText()
+{
+ if (!m_currentSegment)
+ return false;
+
+ std::string s;
+ for (unsigned int i = 1; i < m_tokens.count(); ++i) {
+ if (i > 1)
+ s += " ";
+ s += qstrtostr(m_tokens[i]);
+ }
+
+ if (!readNextLine() ||
+ m_tokens.count() != 2 || m_tokens[0].lower() != "position") {
+ return false;
+ }
+
+ int rg21posn = m_tokens[1].toInt();
+ std::string type = Text::UnspecifiedType;
+
+ switch (rg21posn) {
+
+ case TextAboveStave:
+ type = Text::LocalTempo;
+ break;
+
+ case TextAboveStaveLarge:
+ type = Text::Tempo;
+ break;
+
+ case TextAboveBarLine:
+ type = Text::Direction;
+ break;
+
+ case TextBelowStave:
+ type = Text::Lyric; // perhaps
+ break;
+
+ case TextBelowStaveItalic:
+ type = Text::LocalDirection;
+ break;
+
+ case TextChordName:
+ type = Text::ChordName;
+ break;
+
+ case TextDynamic:
+ type = Text::Dynamic;
+ break;
+ }
+
+ Text text(s, type);
+ Event *textEvent = text.getAsEvent(m_currentSegmentTime);
+ m_currentSegment->insert(textEvent);
+
+ return true;
+}
+
+void RG21Loader::setGroupProperties(Event *e)
+{
+ if (m_inGroup) {
+
+ e->set
+ <Int>(BEAMED_GROUP_ID, m_groupId);
+ e->set
+ <String>(BEAMED_GROUP_TYPE, m_groupType);
+
+ m_groupUntupledLength += e->getDuration();
+ }
+}
+
+bool RG21Loader::parseGroupStart()
+{
+ m_groupType = qstrtostr(m_tokens[0].lower());
+ m_inGroup = true;
+ m_groupId = m_currentSegment->getNextId();
+ m_groupStartTime = m_currentSegmentTime;
+
+ if (m_groupType == GROUP_TYPE_BEAMED) {
+
+ // no more to do
+
+ } else if (m_groupType == GROUP_TYPE_TUPLED) {
+
+ // RG2.1 records two figures A and B, of which A is a time
+ // value indicating the total duration of the group _after_
+ // tupling (which we would call the tupled length), and B is
+ // the count that appears above the group (which we call the
+ // untupled count). We need to know C, the total duration of
+ // the group _before_ tupling; then we can calculate the
+ // tuplet base (C / B) and tupled count (A * B / C).
+
+ m_groupTupledLength = m_tokens[1].toUInt() *
+ Note(Note::Hemidemisemiquaver).getDuration();
+
+ m_groupUntupledCount = m_tokens[2].toUInt();
+ m_groupUntupledLength = 0;
+
+ } else {
+
+ RG_DEBUG
+ << "RG21Loader::parseGroupStart: WARNING: Unknown group type "
+ << m_groupType << ", ignoring" << endl;
+ m_inGroup = false;
+ }
+
+ return true;
+}
+
+bool RG21Loader::parseIndicationStart()
+{
+ if (m_tokens.count() < 4)
+ return false;
+
+ unsigned int indicationId = m_tokens[2].toUInt();
+ std::string indicationType = qstrtostr(m_tokens[3].lower());
+
+ // RG_DEBUG << "Indication start: type is \"" << indicationType << "\"" << endl;
+
+ if (indicationType == "tie") {
+
+ if (m_tieStatus != 0) {
+ RG_DEBUG
+ << "RG21Loader:: parseIndicationStart: WARNING: Found tie within "
+ << "tie, ignoring" << endl;
+ return true;
+ }
+ // m_tieStatus = 1;
+
+ Segment::iterator i = m_currentSegment->end();
+ if (i != m_currentSegment->begin()) {
+ --i;
+ timeT t = (*i)->getAbsoluteTime();
+ while ((*i)->getAbsoluteTime() == t) {
+ (*i)->set
+ <Bool>(TIED_FORWARD, true);
+ if (i == m_currentSegment->begin())
+ break;
+ --i;
+ }
+ }
+ m_tieStatus = 2;
+
+ RG_DEBUG << "rg21io: Indication start: it's a tie" << endl;
+
+ } else {
+
+ // Jeez. Whose great idea was it to place marks _after_ the
+ // events they're marking in the RG2.1 file format?
+
+ timeT indicationTime = m_currentSegmentTime;
+ Segment::iterator i = m_currentSegment->end();
+ if (i != m_currentSegment->begin()) {
+ --i;
+ indicationTime = (*i)->getAbsoluteTime();
+ }
+
+ Indication indication(indicationType, 0);
+ Event *e = indication.getAsEvent(indicationTime);
+ e->setMaybe<Int>("indicationId", indicationId);
+ setGroupProperties(e);
+ m_indicationsExtant[indicationId] = e;
+
+ // place the indication in the segment now; don't wait for the
+ // close-indication, because some things may need to know about it
+ // before then (e.g. close-group)
+
+ m_currentSegment->insert(e);
+
+ RG_DEBUG << "rg21io: Indication start: it's a real indication; id is " << indicationId << ", event is:" << endl;
+ e->dump(std::cerr);
+
+ }
+
+ // other indications not handled yet
+ return true;
+}
+
+void RG21Loader::closeIndication()
+{
+ if (m_tokens.count() < 3)
+ return ;
+
+ unsigned int indicationId = m_tokens[2].toUInt();
+ EventIdMap::iterator i = m_indicationsExtant.find(indicationId);
+
+ RG_DEBUG << "rg21io: Indication close: indication id is " << indicationId << endl;
+
+ // this is normal (for ties):
+ if (i == m_indicationsExtant.end())
+ return ;
+
+ Event *indicationEvent = i->second;
+ m_indicationsExtant.erase(i);
+
+ indicationEvent->set
+ <Int>
+ //!!! (Indication::IndicationDurationPropertyName,
+ ("indicationduration",
+ m_currentSegmentTime - indicationEvent->getAbsoluteTime());
+}
+
+void RG21Loader::closeGroup()
+{
+ if (m_groupType == GROUP_TYPE_TUPLED) {
+
+ Segment::iterator i = m_currentSegment->end();
+ vector<Event *> toInsert;
+ vector<Segment::iterator> toErase;
+
+ if (i != m_currentSegment->begin()) {
+
+ --i;
+ long groupId;
+ timeT prev = m_groupStartTime + m_groupTupledLength;
+
+ while ((*i)->get
+ <Int>(BEAMED_GROUP_ID, groupId) &&
+ groupId == m_groupId) {
+
+ timeT absoluteTime = (*i)->getAbsoluteTime();
+ timeT offset = absoluteTime - m_groupStartTime;
+ timeT intended =
+ (offset * m_groupTupledLength) / m_groupUntupledLength;
+
+ RG_DEBUG
+ << "RG21Loader::closeGroup:"
+ << " m_groupStartTime = " << m_groupStartTime
+ << ", m_groupTupledLength = " << m_groupTupledLength
+ << ", m_groupUntupledCount = " << m_groupUntupledCount
+ << ", m_groupUntupledLength = " << m_groupUntupledLength
+ << ", absoluteTime = " << (*i)->getAbsoluteTime()
+ << ", offset = " << offset
+ << ", intended = " << intended
+ << ", new absolute time = "
+ << (absoluteTime + intended - offset)
+ << ", new duration = "
+ << (prev - absoluteTime)
+ << endl;
+
+ absoluteTime = absoluteTime + intended - offset;
+ Event *e(new Event(**i, absoluteTime, prev - absoluteTime));
+ prev = absoluteTime;
+
+ // See comment in parseGroupStart
+ e->set
+ <Int>(BEAMED_GROUP_TUPLET_BASE,
+ m_groupUntupledLength / m_groupUntupledCount);
+ e->set
+ <Int>(BEAMED_GROUP_TUPLED_COUNT,
+ m_groupTupledLength * m_groupUntupledCount /
+ m_groupUntupledLength);
+ e->set
+ <Int>(BEAMED_GROUP_UNTUPLED_COUNT, m_groupUntupledCount);
+
+ // To change the time of an event, we need to erase &
+ // re-insert it. But erasure will delete the event, and
+ // if it's an indication event that will invalidate our
+ // indicationsExtant entry. Hence this unpleasantness:
+
+ if ((*i)->isa(Indication::EventType)) {
+ long indicationId = 0;
+ if ((*i)->get
+ <Int>("indicationId", indicationId)) {
+ EventIdMap::iterator ei =
+ m_indicationsExtant.find(indicationId);
+ if (ei != m_indicationsExtant.end()) {
+ m_indicationsExtant.erase(ei);
+ m_indicationsExtant[indicationId] = e;
+ }
+ }
+ }
+
+ toInsert.push_back(e);
+ toErase.push_back(i);
+
+ if (i == m_currentSegment->begin())
+ break;
+ --i;
+ }
+ }
+
+ for (unsigned int i = 0; i < toInsert.size(); ++i) {
+ m_currentSegment->insert(toInsert[i]);
+ }
+ for (unsigned int i = 0; i < toErase.size(); ++i) {
+ m_currentSegment->erase(toErase[i]);
+ }
+
+ m_currentSegmentTime = m_groupStartTime + m_groupTupledLength;
+ }
+
+ m_inGroup = false;
+}
+
+bool RG21Loader::parseBarType()
+{
+ if (m_tokens.count() < 5)
+ return false;
+ if (!m_composition)
+ return false;
+
+ int staffNo = m_tokens[1].toInt();
+ if (staffNo > 0) {
+ RG_DEBUG
+ << "RG21Loader::parseBarType: We don't support different time\n"
+ << "signatures on different staffs; disregarding time signature for staff " << staffNo << endl;
+ return true;
+ }
+
+ // barNo is a hex integer
+ int barNo = m_tokens[2].toInt(0, 16);
+
+ int numerator = m_tokens[4].toInt();
+ int denominator = m_tokens[5].toInt();
+
+ timeT sigTime = m_composition->getBarRange(barNo).first;
+ TimeSignature timeSig(numerator, denominator);
+ m_composition->addTimeSignature(sigTime, timeSig);
+
+ return true;
+}
+
+bool RG21Loader::parseStaveType()
+{
+ //!!! tags & connected are not yet implemented
+
+ if (m_tokens.count() < 9)
+ return false;
+ if (!m_composition)
+ return false;
+
+ bool isNumeric = false;
+
+ int staffNo = m_tokens[1].toInt(&isNumeric);
+ if (!isNumeric)
+ return false;
+
+ int programNo = m_tokens[8].toInt();
+
+ if (staffNo >= (int)m_composition->getMinTrackId() &&
+ staffNo <= (int)m_composition->getMaxTrackId()) {
+
+ Track *track = m_composition->getTrackById(staffNo);
+
+ if (track) {
+ Instrument *instr =
+ m_studio->assignMidiProgramToInstrument(programNo, false);
+ if (instr)
+ track->setInstrument(instr->getId());
+ }
+ }
+
+ return true;
+}
+
+timeT RG21Loader::convertRG21Duration(QStringList::Iterator& i)
+{
+ QString durationString = (*i).lower();
+ ++i;
+
+ if (durationString == "dotted") {
+ durationString += ' ';
+ durationString += (*i).lower();
+ ++i;
+ }
+
+ try {
+
+ Note n(NotationStrings::getNoteForName(durationString));
+ return n.getDuration();
+
+ } catch (NotationStrings::MalformedNoteName m) {
+
+ RG_DEBUG << "RG21Loader::convertRG21Duration: Bad duration: "
+ << durationString << endl;
+ return 0;
+ }
+
+}
+
+void RG21Loader::closeSegment()
+{
+ if (m_currentSegment) {
+
+ TrackId trackId = m_currentSegmentNb - 1;
+
+ m_currentSegment->setTrack(trackId);
+
+ Track *track = new Track
+ (trackId, m_currentInstrumentId, trackId,
+ qstrtostr(m_currentStaffName), false);
+ m_currentInstrumentId = (++m_currentInstrumentId) % 16;
+
+ m_composition->addTrack(track);
+ m_composition->addSegment(m_currentSegment);
+ m_currentSegment = 0;
+ m_currentSegmentTime = 0;
+ m_currentClef = Clef(Clef::Treble);
+
+ } else {
+ // ??
+ }
+}
+
+long RG21Loader::convertRG21Pitch(long pitch, int noteModifier)
+{
+ Accidental accidental =
+ (noteModifier & ModSharp) ? Sharp :
+ (noteModifier & ModFlat) ? Flat :
+ (noteModifier & ModNatural) ? Natural : NoAccidental;
+
+ long rtn = Pitch::getPerformancePitchFromRG21Pitch
+ (pitch, accidental, m_currentClef, m_currentKey);
+
+ return rtn;
+}
+
+bool RG21Loader::readNextLine()
+{
+ bool inComment = false;
+
+ do {
+ inComment = false;
+
+ m_currentLine = m_stream->readLine();
+
+ if (m_stream->eof())
+ return false;
+
+ m_currentLine = m_currentLine.simplifyWhiteSpace();
+
+ if (m_currentLine[0] == '#' ||
+ m_currentLine.length() == 0) {
+ inComment = true;
+ continue; // skip comments
+ }
+
+ m_tokens = QStringList::split(' ', m_currentLine);
+
+ } while (inComment);
+
+ return true;
+}
+
+bool RG21Loader::load(const QString &fileName, Composition &comp)
+{
+ m_composition = &comp;
+ comp.clear();
+
+ QFile file(fileName);
+ if (file.open(IO_ReadOnly)) {
+ m_stream = new QTextStream(&file);
+ } else {
+ return false;
+ }
+
+ m_studio->unassignAllInstruments();
+
+ while (!m_stream->eof()) {
+
+ if (!readNextLine())
+ break;
+
+ QString firstToken = m_tokens.first();
+
+ if (firstToken == "Staves" || firstToken == "Staffs") { // nb staves
+
+ m_nbStaves = m_tokens[1].toUInt();
+
+ } else if (firstToken == "Name") { // Staff name
+
+ m_currentStaffName = m_tokens[1]; // we don't do anything with it yet
+ m_currentSegment = new Segment;
+ ++m_currentSegmentNb;
+
+ } else if (firstToken == "Clef") {
+
+ parseClef();
+
+ } else if (firstToken == "Key") {
+
+ parseKey();
+
+ } else if (firstToken == "Metronome") {
+
+ if (!readNextLine())
+ break;
+ parseMetronome();
+
+ } else if (firstToken == ":") { // chord
+
+ m_tokens.remove(m_tokens.begin()); // get rid of 1st token ':'
+ parseChordItem();
+
+ } else if (firstToken == "Rest") { // rest
+
+ if (!readNextLine())
+ break;
+
+ parseRest();
+
+ } else if (firstToken == "Text") {
+
+ if (!readNextLine())
+ break;
+
+ parseText();
+
+ } else if (firstToken == "Group") {
+
+ if (!readNextLine())
+ break;
+
+ parseGroupStart();
+
+ } else if (firstToken == "Mark") {
+
+ if (m_tokens[1] == "start")
+ parseIndicationStart();
+ else if (m_tokens[1] == "end")
+ closeIndication();
+
+ } else if (firstToken == "Bar") {
+
+ parseBarType();
+
+ } else if (firstToken == "Stave") {
+
+ parseStaveType();
+
+ } else if (firstToken == "End") {
+
+ if (m_inGroup)
+ closeGroup();
+ else
+ closeSegment();
+
+ } else {
+
+ RG_DEBUG << "RG21Loader::parse: Unsupported element type \"" << firstToken << "\", ignoring" << endl;
+ }
+ }
+
+ delete m_stream;
+ m_stream = 0;
+
+ return true;
+}
+
+vector<string> RG21Loader::convertRG21ChordMods(int chordMods)
+{
+ vector<string> marks;
+
+ // bit laborious!
+ if (chordMods & ModDot) marks.push_back(Staccato);
+ if (chordMods & ModLegato) marks.push_back(Tenuto);
+ if (chordMods & ModAccent) marks.push_back(Accent);
+ if (chordMods & ModSfz) marks.push_back(Sforzando);
+ if (chordMods & ModRfz) marks.push_back(Rinforzando);
+ if (chordMods & ModTrill) marks.push_back(Trill);
+ if (chordMods & ModTurn) marks.push_back(Turn);
+ if (chordMods & ModPause) marks.push_back(Pause);
+
+ return marks;
+}
+
+}
diff --git a/src/document/io/RG21Loader.h b/src/document/io/RG21Loader.h
new file mode 100644
index 0000000..1e944af
--- /dev/null
+++ b/src/document/io/RG21Loader.h
@@ -0,0 +1,162 @@
+
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ Rosegarden
+ A MIDI and audio sequencer and musical notation editor.
+
+ This program is Copyright 2000-2008
+ Guillaume Laurent <[email protected]>,
+ Chris Cannam <[email protected]>,
+ Richard Bown <[email protected]>
+
+ 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.
+*/
+
+#ifndef _RG_RG21LOADER_H_
+#define _RG_RG21LOADER_H_
+
+#include "base/MidiProgram.h"
+#include "base/NotationTypes.h"
+#include "gui/general/ProgressReporter.h"
+#include <map>
+#include <string>
+#include <qstring.h>
+#include <qstringlist.h>
+#include <vector>
+#include "base/Event.h"
+
+
+class QTextStream;
+class QObject;
+class Iterator;
+
+
+namespace Rosegarden
+{
+
+class Studio;
+class Segment;
+class Event;
+class Composition;
+
+
+/**
+ * Rosegarden 2.1 file import
+ */
+class RG21Loader : public ProgressReporter
+{
+public:
+ RG21Loader(Studio *,
+ QObject *parent = 0, const char *name = 0);
+ ~RG21Loader();
+
+ /**
+ * Load and parse the RG2.1 file \a fileName, and write it into the
+ * given Composition (clearing the existing segment data first).
+ * Return true for success.
+ */
+ bool load(const QString& fileName, Composition &);
+
+protected:
+
+ // RG21 note mods
+ enum { ModSharp = (1<<0),
+ ModFlat = (1<<1),
+ ModNatural = (1<<2)
+ };
+
+ // RG21 chord mods
+ enum { ModDot = (1<<0),
+ ModLegato = (1<<1),
+ ModAccent = (1<<2),
+ ModSfz = (1<<3),
+ ModRfz = (1<<4),
+ ModTrill = (1<<5),
+ ModTurn = (1<<6),
+ ModPause = (1<<7)
+ };
+
+ // RG21 text positions
+ enum { TextAboveStave = 0,
+ TextAboveStaveLarge,
+ TextAboveBarLine,
+ TextBelowStave,
+ TextBelowStaveItalic,
+ TextChordName,
+ TextDynamic
+ };
+
+ bool parseClef();
+ bool parseKey();
+ bool parseMetronome();
+ bool parseChordItem();
+ bool parseRest();
+ bool parseText();
+ bool parseGroupStart();
+ bool parseIndicationStart();
+ bool parseBarType();
+ bool parseStaveType();
+
+ void closeGroup();
+ void closeIndication();
+ void closeSegment();
+
+ void setGroupProperties(Event *);
+
+ long convertRG21Pitch(long rg21pitch, int noteModifier);
+ timeT convertRG21Duration(QStringList::Iterator&);
+ std::vector<std::string> convertRG21ChordMods(int chordMod);
+
+ bool readNextLine();
+
+ //--------------- Data members ---------------------------------
+
+ QTextStream *m_stream;
+
+ Studio *m_studio;
+ Composition* m_composition;
+ Segment* m_currentSegment;
+ unsigned int m_currentSegmentTime;
+ unsigned int m_currentSegmentNb;
+ Clef m_currentClef;
+ Rosegarden::Key m_currentKey;
+ InstrumentId m_currentInstrumentId;
+
+ typedef std::map<int, Event *> EventIdMap;
+ EventIdMap m_indicationsExtant;
+
+ bool m_inGroup;
+ long m_groupId;
+ std::string m_groupType;
+ timeT m_groupStartTime;
+ int m_groupTupledLength;
+ int m_groupTupledCount;
+ int m_groupUntupledLength;
+ int m_groupUntupledCount;
+
+ int m_tieStatus; // 0 -> none, 1 -> tie started, 2 -> seen one note
+
+ QString m_currentLine;
+ QString m_currentStaffName;
+
+ QStringList m_tokens;
+
+ unsigned int m_nbStaves;
+};
+
+
+
+}
+
+#endif