diff options
author | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-03-01 18:37:05 +0000 |
---|---|---|
committer | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-03-01 18:37:05 +0000 |
commit | 145364a8af6a1fec06556221e66d4b724a62fc9a (patch) | |
tree | 53bd71a544008c518034f208d64c932dc2883f50 /src/document/io | |
download | rosegarden-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.cpp | 154 | ||||
-rw-r--r-- | src/document/io/CsoundExporter.h | 63 | ||||
-rw-r--r-- | src/document/io/HydrogenLoader.cpp | 74 | ||||
-rw-r--r-- | src/document/io/HydrogenLoader.h | 83 | ||||
-rw-r--r-- | src/document/io/HydrogenXMLHandler.cpp | 403 | ||||
-rw-r--r-- | src/document/io/HydrogenXMLHandler.h | 132 | ||||
-rw-r--r-- | src/document/io/LilyPondExporter.cpp | 2419 | ||||
-rw-r--r-- | src/document/io/LilyPondExporter.h | 262 | ||||
-rw-r--r-- | src/document/io/MupExporter.cpp | 453 | ||||
-rw-r--r-- | src/document/io/MupExporter.h | 89 | ||||
-rw-r--r-- | src/document/io/MusicXmlExporter.cpp | 555 | ||||
-rw-r--r-- | src/document/io/MusicXmlExporter.h | 87 | ||||
-rw-r--r-- | src/document/io/RG21Loader.cpp | 797 | ||||
-rw-r--r-- | src/document/io/RG21Loader.h | 162 |
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.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.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 |