summaryrefslogtreecommitdiffstats
path: root/src/document/io/RG21Loader.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/document/io/RG21Loader.cpp')
-rw-r--r--src/document/io/RG21Loader.cpp797
1 files changed, 797 insertions, 0 deletions
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 <glaurent@telegraph-road.org>,
+ Chris Cannam <cannam@all-day-breakfast.com>,
+ Richard Bown <richard.bown@ferventsoftware.com>
+
+ The moral rights of Guillaume Laurent, Chris Cannam, and Richard
+ Bown to claim authorship of this work have been asserted.
+
+ Other copyrights also apply to some parts of this work. Please
+ see the AUTHORS file and individual file headers for details.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version. See the file
+ COPYING included with this distribution for more information.
+*/
+
+
+#include "RG21Loader.h"
+
+#include "misc/Debug.h"
+#include "misc/Strings.h"
+#include "base/Composition.h"
+#include "base/Event.h"
+#include "base/BaseProperties.h"
+#include "base/Instrument.h"
+#include "base/MidiProgram.h"
+#include "base/NotationTypes.h"
+#include "base/Segment.h"
+#include "base/Studio.h"
+#include "base/Track.h"
+#include "gui/editors/notation/NotationStrings.h"
+#include "gui/general/ProgressReporter.h"
+#include <qfile.h>
+#include <qobject.h>
+#include <qstring.h>
+#include <qstringlist.h>
+#include <qtextstream.h>
+#include <string>
+#include <vector>
+
+using std::vector;
+using std::string;
+
+
+namespace Rosegarden
+{
+
+using namespace BaseProperties;
+using namespace Accidentals;
+using namespace Marks;
+
+RG21Loader::RG21Loader(Studio *studio,
+ QObject *parent, const char* name)
+ : ProgressReporter(parent, name),
+ m_stream(0),
+ m_studio(studio),
+ m_composition(0),
+ m_currentSegment(0),
+ m_currentSegmentTime(0),
+ m_currentSegmentNb(0),
+ m_currentClef(Clef::Treble),
+ m_currentInstrumentId(MidiInstrumentBase),
+ m_inGroup(false),
+ m_tieStatus(0),
+ m_nbStaves(0)
+{}
+
+RG21Loader::~RG21Loader()
+{}
+
+bool RG21Loader::parseClef()
+{
+ if (m_tokens.count() != 3 || !m_currentSegment)
+ return false;
+
+ std::string clefName = qstrtostr(m_tokens[2].lower());
+
+ m_currentClef = Clef(clefName);
+ Event *clefEvent = m_currentClef.getAsEvent(m_currentSegmentTime);
+ m_currentSegment->insert(clefEvent);
+
+ return true;
+}
+
+bool RG21Loader::parseKey()
+{
+ if (m_tokens.count() < 3 || !m_currentSegment)
+ return false;
+
+ QString keyBase = m_tokens[2];
+ if (keyBase.length() > 1) {
+ // Deal correctly with e.g. Bb major
+ keyBase =
+ keyBase.left(1).upper() +
+ keyBase.right(keyBase.length() - 1).lower();
+ } else {
+ keyBase = keyBase.upper();
+ }
+
+ QString keyName = QString("%1 %2or")
+ .arg(keyBase)
+ .arg(m_tokens[3].lower());
+
+ m_currentKey = Rosegarden::Key(qstrtostr(keyName));
+ Event *keyEvent = m_currentKey.getAsEvent(m_currentSegmentTime);
+ m_currentSegment->insert(keyEvent);
+
+ return true;
+}
+
+bool RG21Loader::parseMetronome()
+{
+ if (m_tokens.count() < 2)
+ return false;
+ if (!m_composition)
+ return false;
+
+ QStringList::Iterator i = m_tokens.begin();
+ timeT duration = convertRG21Duration(i);
+
+ bool isNumeric = false;
+ int count = (*i).toInt(&isNumeric);
+ if (!count || !isNumeric)
+ return false;
+
+ // we need to take into account the fact that "duration" might not
+ // be a crotchet
+
+ double qpm = (count * duration) / Note(Note::Crotchet).getDuration();
+ m_composition->addTempoAtTime(m_currentSegmentTime,
+ m_composition->getTempoForQpm(qpm));
+ return true;
+}
+
+bool RG21Loader::parseChordItem()
+{
+ if (m_tokens.count() < 4)
+ return false;
+
+ QStringList::Iterator i = m_tokens.begin();
+ timeT duration = convertRG21Duration(i);
+
+ // get chord mod flags and nb of notes. chord mod is hex
+ int chordMods = (*i).toInt(0, 16);
+ ++i;
+ /*int nbNotes = (*i).toInt();*/
+ ++i;
+
+ vector<string> marks = convertRG21ChordMods(chordMods);
+
+ // now get notes
+ for (;i != m_tokens.end(); ++i) {
+
+ long pitch = (*i).toInt();
+ ++i;
+
+ // The noteMods field is nominally a hex integer. As it
+ // happens its value can never exceed 7, but I guess we
+ // should do the right thing anyway
+ int noteMods = (*i).toInt(0, 16);
+ pitch = convertRG21Pitch(pitch, noteMods);
+
+ Event *noteEvent = new Event(Note::EventType,
+ m_currentSegmentTime, duration);
+ noteEvent->set
+ <Int>(PITCH, pitch);
+
+ if (m_tieStatus == 1) {
+ noteEvent->set
+ <Bool>(TIED_FORWARD, true);
+ } else if (m_tieStatus == 2) {
+ noteEvent->set
+ <Bool>(TIED_BACKWARD, true);
+ }
+
+ if (marks.size() > 0) {
+ noteEvent->set
+ <Int>(MARK_COUNT, marks.size());
+ for (unsigned int j = 0; j < marks.size(); ++j) {
+ noteEvent->set
+ <String>(getMarkPropertyName(j), marks[j]);
+ }
+ }
+
+ // RG_DEBUG << "RG21Loader::parseChordItem() : insert note pitch " << pitch
+ // << " at time " << m_currentSegmentTime << endl;
+
+ setGroupProperties(noteEvent);
+
+ m_currentSegment->insert(noteEvent);
+ }
+
+ m_currentSegmentTime += duration;
+ if (m_tieStatus == 2)
+ m_tieStatus = 0;
+ else if (m_tieStatus == 1)
+ m_tieStatus = 2;
+
+ return true;
+}
+
+bool RG21Loader::parseRest()
+{
+ if (m_tokens.count() < 2)
+ return false;
+
+ QStringList::Iterator i = m_tokens.begin();
+ timeT duration = convertRG21Duration(i);
+
+ Event *restEvent = new Event(Note::EventRestType,
+ m_currentSegmentTime, duration,
+ Note::EventRestSubOrdering);
+
+ setGroupProperties(restEvent);
+
+ m_currentSegment->insert(restEvent);
+ m_currentSegmentTime += duration;
+
+ return true;
+}
+
+bool RG21Loader::parseText()
+{
+ if (!m_currentSegment)
+ return false;
+
+ std::string s;
+ for (unsigned int i = 1; i < m_tokens.count(); ++i) {
+ if (i > 1)
+ s += " ";
+ s += qstrtostr(m_tokens[i]);
+ }
+
+ if (!readNextLine() ||
+ m_tokens.count() != 2 || m_tokens[0].lower() != "position") {
+ return false;
+ }
+
+ int rg21posn = m_tokens[1].toInt();
+ std::string type = Text::UnspecifiedType;
+
+ switch (rg21posn) {
+
+ case TextAboveStave:
+ type = Text::LocalTempo;
+ break;
+
+ case TextAboveStaveLarge:
+ type = Text::Tempo;
+ break;
+
+ case TextAboveBarLine:
+ type = Text::Direction;
+ break;
+
+ case TextBelowStave:
+ type = Text::Lyric; // perhaps
+ break;
+
+ case TextBelowStaveItalic:
+ type = Text::LocalDirection;
+ break;
+
+ case TextChordName:
+ type = Text::ChordName;
+ break;
+
+ case TextDynamic:
+ type = Text::Dynamic;
+ break;
+ }
+
+ Text text(s, type);
+ Event *textEvent = text.getAsEvent(m_currentSegmentTime);
+ m_currentSegment->insert(textEvent);
+
+ return true;
+}
+
+void RG21Loader::setGroupProperties(Event *e)
+{
+ if (m_inGroup) {
+
+ e->set
+ <Int>(BEAMED_GROUP_ID, m_groupId);
+ e->set
+ <String>(BEAMED_GROUP_TYPE, m_groupType);
+
+ m_groupUntupledLength += e->getDuration();
+ }
+}
+
+bool RG21Loader::parseGroupStart()
+{
+ m_groupType = qstrtostr(m_tokens[0].lower());
+ m_inGroup = true;
+ m_groupId = m_currentSegment->getNextId();
+ m_groupStartTime = m_currentSegmentTime;
+
+ if (m_groupType == GROUP_TYPE_BEAMED) {
+
+ // no more to do
+
+ } else if (m_groupType == GROUP_TYPE_TUPLED) {
+
+ // RG2.1 records two figures A and B, of which A is a time
+ // value indicating the total duration of the group _after_
+ // tupling (which we would call the tupled length), and B is
+ // the count that appears above the group (which we call the
+ // untupled count). We need to know C, the total duration of
+ // the group _before_ tupling; then we can calculate the
+ // tuplet base (C / B) and tupled count (A * B / C).
+
+ m_groupTupledLength = m_tokens[1].toUInt() *
+ Note(Note::Hemidemisemiquaver).getDuration();
+
+ m_groupUntupledCount = m_tokens[2].toUInt();
+ m_groupUntupledLength = 0;
+
+ } else {
+
+ RG_DEBUG
+ << "RG21Loader::parseGroupStart: WARNING: Unknown group type "
+ << m_groupType << ", ignoring" << endl;
+ m_inGroup = false;
+ }
+
+ return true;
+}
+
+bool RG21Loader::parseIndicationStart()
+{
+ if (m_tokens.count() < 4)
+ return false;
+
+ unsigned int indicationId = m_tokens[2].toUInt();
+ std::string indicationType = qstrtostr(m_tokens[3].lower());
+
+ // RG_DEBUG << "Indication start: type is \"" << indicationType << "\"" << endl;
+
+ if (indicationType == "tie") {
+
+ if (m_tieStatus != 0) {
+ RG_DEBUG
+ << "RG21Loader:: parseIndicationStart: WARNING: Found tie within "
+ << "tie, ignoring" << endl;
+ return true;
+ }
+ // m_tieStatus = 1;
+
+ Segment::iterator i = m_currentSegment->end();
+ if (i != m_currentSegment->begin()) {
+ --i;
+ timeT t = (*i)->getAbsoluteTime();
+ while ((*i)->getAbsoluteTime() == t) {
+ (*i)->set
+ <Bool>(TIED_FORWARD, true);
+ if (i == m_currentSegment->begin())
+ break;
+ --i;
+ }
+ }
+ m_tieStatus = 2;
+
+ RG_DEBUG << "rg21io: Indication start: it's a tie" << endl;
+
+ } else {
+
+ // Jeez. Whose great idea was it to place marks _after_ the
+ // events they're marking in the RG2.1 file format?
+
+ timeT indicationTime = m_currentSegmentTime;
+ Segment::iterator i = m_currentSegment->end();
+ if (i != m_currentSegment->begin()) {
+ --i;
+ indicationTime = (*i)->getAbsoluteTime();
+ }
+
+ Indication indication(indicationType, 0);
+ Event *e = indication.getAsEvent(indicationTime);
+ e->setMaybe<Int>("indicationId", indicationId);
+ setGroupProperties(e);
+ m_indicationsExtant[indicationId] = e;
+
+ // place the indication in the segment now; don't wait for the
+ // close-indication, because some things may need to know about it
+ // before then (e.g. close-group)
+
+ m_currentSegment->insert(e);
+
+ RG_DEBUG << "rg21io: Indication start: it's a real indication; id is " << indicationId << ", event is:" << endl;
+ e->dump(std::cerr);
+
+ }
+
+ // other indications not handled yet
+ return true;
+}
+
+void RG21Loader::closeIndication()
+{
+ if (m_tokens.count() < 3)
+ return ;
+
+ unsigned int indicationId = m_tokens[2].toUInt();
+ EventIdMap::iterator i = m_indicationsExtant.find(indicationId);
+
+ RG_DEBUG << "rg21io: Indication close: indication id is " << indicationId << endl;
+
+ // this is normal (for ties):
+ if (i == m_indicationsExtant.end())
+ return ;
+
+ Event *indicationEvent = i->second;
+ m_indicationsExtant.erase(i);
+
+ indicationEvent->set
+ <Int>
+ //!!! (Indication::IndicationDurationPropertyName,
+ ("indicationduration",
+ m_currentSegmentTime - indicationEvent->getAbsoluteTime());
+}
+
+void RG21Loader::closeGroup()
+{
+ if (m_groupType == GROUP_TYPE_TUPLED) {
+
+ Segment::iterator i = m_currentSegment->end();
+ vector<Event *> toInsert;
+ vector<Segment::iterator> toErase;
+
+ if (i != m_currentSegment->begin()) {
+
+ --i;
+ long groupId;
+ timeT prev = m_groupStartTime + m_groupTupledLength;
+
+ while ((*i)->get
+ <Int>(BEAMED_GROUP_ID, groupId) &&
+ groupId == m_groupId) {
+
+ timeT absoluteTime = (*i)->getAbsoluteTime();
+ timeT offset = absoluteTime - m_groupStartTime;
+ timeT intended =
+ (offset * m_groupTupledLength) / m_groupUntupledLength;
+
+ RG_DEBUG
+ << "RG21Loader::closeGroup:"
+ << " m_groupStartTime = " << m_groupStartTime
+ << ", m_groupTupledLength = " << m_groupTupledLength
+ << ", m_groupUntupledCount = " << m_groupUntupledCount
+ << ", m_groupUntupledLength = " << m_groupUntupledLength
+ << ", absoluteTime = " << (*i)->getAbsoluteTime()
+ << ", offset = " << offset
+ << ", intended = " << intended
+ << ", new absolute time = "
+ << (absoluteTime + intended - offset)
+ << ", new duration = "
+ << (prev - absoluteTime)
+ << endl;
+
+ absoluteTime = absoluteTime + intended - offset;
+ Event *e(new Event(**i, absoluteTime, prev - absoluteTime));
+ prev = absoluteTime;
+
+ // See comment in parseGroupStart
+ e->set
+ <Int>(BEAMED_GROUP_TUPLET_BASE,
+ m_groupUntupledLength / m_groupUntupledCount);
+ e->set
+ <Int>(BEAMED_GROUP_TUPLED_COUNT,
+ m_groupTupledLength * m_groupUntupledCount /
+ m_groupUntupledLength);
+ e->set
+ <Int>(BEAMED_GROUP_UNTUPLED_COUNT, m_groupUntupledCount);
+
+ // To change the time of an event, we need to erase &
+ // re-insert it. But erasure will delete the event, and
+ // if it's an indication event that will invalidate our
+ // indicationsExtant entry. Hence this unpleasantness:
+
+ if ((*i)->isa(Indication::EventType)) {
+ long indicationId = 0;
+ if ((*i)->get
+ <Int>("indicationId", indicationId)) {
+ EventIdMap::iterator ei =
+ m_indicationsExtant.find(indicationId);
+ if (ei != m_indicationsExtant.end()) {
+ m_indicationsExtant.erase(ei);
+ m_indicationsExtant[indicationId] = e;
+ }
+ }
+ }
+
+ toInsert.push_back(e);
+ toErase.push_back(i);
+
+ if (i == m_currentSegment->begin())
+ break;
+ --i;
+ }
+ }
+
+ for (unsigned int i = 0; i < toInsert.size(); ++i) {
+ m_currentSegment->insert(toInsert[i]);
+ }
+ for (unsigned int i = 0; i < toErase.size(); ++i) {
+ m_currentSegment->erase(toErase[i]);
+ }
+
+ m_currentSegmentTime = m_groupStartTime + m_groupTupledLength;
+ }
+
+ m_inGroup = false;
+}
+
+bool RG21Loader::parseBarType()
+{
+ if (m_tokens.count() < 5)
+ return false;
+ if (!m_composition)
+ return false;
+
+ int staffNo = m_tokens[1].toInt();
+ if (staffNo > 0) {
+ RG_DEBUG
+ << "RG21Loader::parseBarType: We don't support different time\n"
+ << "signatures on different staffs; disregarding time signature for staff " << staffNo << endl;
+ return true;
+ }
+
+ // barNo is a hex integer
+ int barNo = m_tokens[2].toInt(0, 16);
+
+ int numerator = m_tokens[4].toInt();
+ int denominator = m_tokens[5].toInt();
+
+ timeT sigTime = m_composition->getBarRange(barNo).first;
+ TimeSignature timeSig(numerator, denominator);
+ m_composition->addTimeSignature(sigTime, timeSig);
+
+ return true;
+}
+
+bool RG21Loader::parseStaveType()
+{
+ //!!! tags & connected are not yet implemented
+
+ if (m_tokens.count() < 9)
+ return false;
+ if (!m_composition)
+ return false;
+
+ bool isNumeric = false;
+
+ int staffNo = m_tokens[1].toInt(&isNumeric);
+ if (!isNumeric)
+ return false;
+
+ int programNo = m_tokens[8].toInt();
+
+ if (staffNo >= (int)m_composition->getMinTrackId() &&
+ staffNo <= (int)m_composition->getMaxTrackId()) {
+
+ Track *track = m_composition->getTrackById(staffNo);
+
+ if (track) {
+ Instrument *instr =
+ m_studio->assignMidiProgramToInstrument(programNo, false);
+ if (instr)
+ track->setInstrument(instr->getId());
+ }
+ }
+
+ return true;
+}
+
+timeT RG21Loader::convertRG21Duration(QStringList::Iterator& i)
+{
+ QString durationString = (*i).lower();
+ ++i;
+
+ if (durationString == "dotted") {
+ durationString += ' ';
+ durationString += (*i).lower();
+ ++i;
+ }
+
+ try {
+
+ Note n(NotationStrings::getNoteForName(durationString));
+ return n.getDuration();
+
+ } catch (NotationStrings::MalformedNoteName m) {
+
+ RG_DEBUG << "RG21Loader::convertRG21Duration: Bad duration: "
+ << durationString << endl;
+ return 0;
+ }
+
+}
+
+void RG21Loader::closeSegment()
+{
+ if (m_currentSegment) {
+
+ TrackId trackId = m_currentSegmentNb - 1;
+
+ m_currentSegment->setTrack(trackId);
+
+ Track *track = new Track
+ (trackId, m_currentInstrumentId, trackId,
+ qstrtostr(m_currentStaffName), false);
+ m_currentInstrumentId = (++m_currentInstrumentId) % 16;
+
+ m_composition->addTrack(track);
+ m_composition->addSegment(m_currentSegment);
+ m_currentSegment = 0;
+ m_currentSegmentTime = 0;
+ m_currentClef = Clef(Clef::Treble);
+
+ } else {
+ // ??
+ }
+}
+
+long RG21Loader::convertRG21Pitch(long pitch, int noteModifier)
+{
+ Accidental accidental =
+ (noteModifier & ModSharp) ? Sharp :
+ (noteModifier & ModFlat) ? Flat :
+ (noteModifier & ModNatural) ? Natural : NoAccidental;
+
+ long rtn = Pitch::getPerformancePitchFromRG21Pitch
+ (pitch, accidental, m_currentClef, m_currentKey);
+
+ return rtn;
+}
+
+bool RG21Loader::readNextLine()
+{
+ bool inComment = false;
+
+ do {
+ inComment = false;
+
+ m_currentLine = m_stream->readLine();
+
+ if (m_stream->eof())
+ return false;
+
+ m_currentLine = m_currentLine.simplifyWhiteSpace();
+
+ if (m_currentLine[0] == '#' ||
+ m_currentLine.length() == 0) {
+ inComment = true;
+ continue; // skip comments
+ }
+
+ m_tokens = QStringList::split(' ', m_currentLine);
+
+ } while (inComment);
+
+ return true;
+}
+
+bool RG21Loader::load(const QString &fileName, Composition &comp)
+{
+ m_composition = &comp;
+ comp.clear();
+
+ QFile file(fileName);
+ if (file.open(IO_ReadOnly)) {
+ m_stream = new QTextStream(&file);
+ } else {
+ return false;
+ }
+
+ m_studio->unassignAllInstruments();
+
+ while (!m_stream->eof()) {
+
+ if (!readNextLine())
+ break;
+
+ QString firstToken = m_tokens.first();
+
+ if (firstToken == "Staves" || firstToken == "Staffs") { // nb staves
+
+ m_nbStaves = m_tokens[1].toUInt();
+
+ } else if (firstToken == "Name") { // Staff name
+
+ m_currentStaffName = m_tokens[1]; // we don't do anything with it yet
+ m_currentSegment = new Segment;
+ ++m_currentSegmentNb;
+
+ } else if (firstToken == "Clef") {
+
+ parseClef();
+
+ } else if (firstToken == "Key") {
+
+ parseKey();
+
+ } else if (firstToken == "Metronome") {
+
+ if (!readNextLine())
+ break;
+ parseMetronome();
+
+ } else if (firstToken == ":") { // chord
+
+ m_tokens.remove(m_tokens.begin()); // get rid of 1st token ':'
+ parseChordItem();
+
+ } else if (firstToken == "Rest") { // rest
+
+ if (!readNextLine())
+ break;
+
+ parseRest();
+
+ } else if (firstToken == "Text") {
+
+ if (!readNextLine())
+ break;
+
+ parseText();
+
+ } else if (firstToken == "Group") {
+
+ if (!readNextLine())
+ break;
+
+ parseGroupStart();
+
+ } else if (firstToken == "Mark") {
+
+ if (m_tokens[1] == "start")
+ parseIndicationStart();
+ else if (m_tokens[1] == "end")
+ closeIndication();
+
+ } else if (firstToken == "Bar") {
+
+ parseBarType();
+
+ } else if (firstToken == "Stave") {
+
+ parseStaveType();
+
+ } else if (firstToken == "End") {
+
+ if (m_inGroup)
+ closeGroup();
+ else
+ closeSegment();
+
+ } else {
+
+ RG_DEBUG << "RG21Loader::parse: Unsupported element type \"" << firstToken << "\", ignoring" << endl;
+ }
+ }
+
+ delete m_stream;
+ m_stream = 0;
+
+ return true;
+}
+
+vector<string> RG21Loader::convertRG21ChordMods(int chordMods)
+{
+ vector<string> marks;
+
+ // bit laborious!
+ if (chordMods & ModDot) marks.push_back(Staccato);
+ if (chordMods & ModLegato) marks.push_back(Tenuto);
+ if (chordMods & ModAccent) marks.push_back(Accent);
+ if (chordMods & ModSfz) marks.push_back(Sforzando);
+ if (chordMods & ModRfz) marks.push_back(Rinforzando);
+ if (chordMods & ModTrill) marks.push_back(Trill);
+ if (chordMods & ModTurn) marks.push_back(Turn);
+ if (chordMods & ModPause) marks.push_back(Pause);
+
+ return marks;
+}
+
+}