Logo Search packages:      
Sourcecode: canorus version File versions  Download package

lilypondimport.cpp

/*!
      Copyright (c) 2007, Matevž Jekovec, Canorus development team
      All Rights Reserved. See AUTHORS for a complete list of authors.
      
      Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE.GPL for details.
*/

#include <QTextStream>
#include <QRegExp>

#include <iostream> // DEBUG

#include "import/lilypondimport.h"
#include "core/note.h"
#include "core/playable.h"
#include "core/slur.h"

/*!
      Delimiters which separate various music elements in LilyPond syntax. These are new lines, tabs, blanks etc.
      
      \sa nextElement(), parseNextElement()
*/
const QRegExp CALilyPondImport::WHITESPACE_DELIMITERS = QRegExp("[\\s]");

/*!
      Delimiters which separate various music elements in LilyPond syntax, but are specific for LilyPond syntax.
      They are reported as its own element when parsing the next element.
      
      \sa nextElement(), parseNextElement()
*/ 
const QRegExp CALilyPondImport::SYNTAX_DELIMITERS = QRegExp("[<>{}]"); 

/*!
      Combined WHITESPACE_DELIMITERS and SYNTAX_DELIMITERS.
*/
const QRegExp CALilyPondImport::DELIMITERS =
      QRegExp(
            CALilyPondImport::WHITESPACE_DELIMITERS.pattern().left(CALilyPondImport::WHITESPACE_DELIMITERS.pattern().size()-1) +
            CALilyPondImport::SYNTAX_DELIMITERS.pattern().mid(1)
      );

CALilyPondImport::CALilyPondImport( const QString in )
 : CAImport(in) {
      initLilyPondImport();
}

CALilyPondImport::CALilyPondImport( QTextStream *in )
 : CAImport(in) {
      initLilyPondImport();   
}

CALilyPondImport::~CALilyPondImport() {
}

void CALilyPondImport::initLilyPondImport() {
      _curLine = _curChar = 0;
      _curSlur = 0; _curPhrasingSlur = 0;
      _templateVoice = 0;
}

void CALilyPondImport::addError(QString description, int curLine, int curChar) {
      _errors << QString(QObject::tr("<i>Fatal error, line %1, char %2:</i><br>"))
                 .arg(curLine?curLine:_curLine)
                 .arg(curChar?curChar:_curChar)
                 + description + "<br>";
}

CAVoice *CALilyPondImport::importVoiceImpl() {
      CAVoice *voice = new CAVoice( "", 0 );
      
      if (templateVoice())
            voice->cloneVoiceProperties( templateVoice() );
      
      setCurVoice(voice);
      CADiatonicPitch prevPitch( 21, 0 );
      CAPlayableLength prevLength( CAPlayableLength::Quarter, 0 );
      bool chordCreated=false;
      bool changed=false;
      
      for (QString curElt = parseNextElement();
           (!in().isEmpty());
           curElt = ((curElt.size() && changed)?curElt:parseNextElement())) { // go to next element, if current one is empty or not changed
          changed=true; // changed is default to true and false, if none of if clauses were found
            if (curElt.startsWith("\\relative")) {
                  // initial \relative notePitch
                  QString notePitch = parseNextElement();
                  if (!isNote(notePitch)) {
                        addError("\\relative doesn't include pitch.");
                        continue;
                  }
                  
                  prevPitch = relativePitchFromLilyPond(notePitch, 21);
                  curElt.remove(0,9);
            } else
            if (curElt.startsWith("{")) {
                  // start of the voice
                  pushDepth(Voice);
                  curElt.remove(0,1);                 
            } else
            if (curElt.startsWith("}")) {
                  // end of the voice
                  popDepth();
                  curElt.remove(0,1);
            } else
            if (curElt.startsWith("<")) {
                  // start of the chord
                  pushDepth(Chord);
                  curElt.remove(0,1);
            } else
            if (curElt.startsWith(">")) {
                  // end of the chord
                  popDepth();
                  chordCreated=false;
                  if ( curVoice()->lastMusElement()->musElementType()==CAMusElement::Note ) {
                        prevPitch = static_cast<CANote*>(curVoice()->lastMusElement())->getChord().at(0)->diatonicPitch();
                  } else {
                        addError(QString("Chord should be finished with a note."));
                  }
                  curElt.remove(0,1);
            } else
            if (curElt.startsWith("~")) {
                  if ( curVoice()->lastMusElement()->musElementType()==CAMusElement::Note ) {
                        CANote *note = static_cast<CANote*>(curVoice()->lastMusElement());
                        note->setTieStart(
                              new CASlur( CASlur::TieType, CASlur::SlurPreferred, note->staff(), note, 0 )
                        );
                  } else {
                        addError(QString("Tie symbol must be right after the note and not %1. Tie ignored.").arg(CAMusElement::musElementTypeToString(curVoice()->lastMusElement()->musElementType())));
                  }
                  curElt.remove(0,1);
            } else
            if (curElt.startsWith("(")) {
                  if ( curVoice()->lastMusElement()->musElementType()==CAMusElement::Note ) {
                        CANote *note = static_cast<CANote*>(curVoice()->lastMusElement())->getChord().at(0);
                        _curSlur = new CASlur( CASlur::SlurType, CASlur::SlurPreferred, note->staff(), note, 0 );
                        note->setSlurStart(_curSlur);
                  } else {
                        addError(QString("Slur symbol must be right after the note and not %1. Slur ignored.").arg(CAMusElement::musElementTypeToString(curVoice()->lastMusElement()->musElementType())));
                  }
                  curElt.remove(0,1);
            } else
            if ( curElt.startsWith(")") && _curSlur ) {
                  if ( curVoice()->lastMusElement()->musElementType()==CAMusElement::Note ) {
                        CANote *note = static_cast<CANote*>(curVoice()->lastMusElement())->getChord().at(0);
                        note->setSlurEnd(_curSlur);
                        _curSlur->setNoteEnd(note);
                        _curSlur=0;
                  } else {
                        addError(QString("Slur symbol must be right after the note and not %1. Slur ignored.").arg(CAMusElement::musElementTypeToString(curVoice()->lastMusElement()->musElementType())));
                  }
                  curElt.remove(0,1);
            } else
            if (curElt.startsWith("\\(")) {
                  if ( curVoice()->lastMusElement()->musElementType()==CAMusElement::Note ) {
                        CANote *note = static_cast<CANote*>(curVoice()->lastMusElement())->getChord().at(0);
                        _curPhrasingSlur = new CASlur( CASlur::PhrasingSlurType, CASlur::SlurPreferred, note->staff(), note, 0 );
                        note->setPhrasingSlurStart(_curPhrasingSlur);
                  } else {
                        addError(QString("Phrasing slur symbol must be right after the note and not %1. Phrasing slur ignored.").arg(CAMusElement::musElementTypeToString(curVoice()->lastMusElement()->musElementType())));
                  }
                  curElt.remove(0,2);
            } else
            if ( curElt.startsWith("\\)") && _curPhrasingSlur ) {
                  if ( curVoice()->lastMusElement()->musElementType()==CAMusElement::Note ) {
                        CANote *note = static_cast<CANote*>(curVoice()->lastMusElement())->getChord().at(0);
                        note->setPhrasingSlurEnd(_curPhrasingSlur);
                        _curPhrasingSlur->setNoteEnd(note);
                        _curPhrasingSlur=0;
                  } else {
                        addError(QString("Phrasing slur symbol must be right after the note and not %1. Phrasing slur ignored.").arg(CAMusElement::musElementTypeToString(curVoice()->lastMusElement()->musElementType())));
                  }
                  curElt.remove(0,2);
            } else
            if (isNote(curElt)) {
                  // CANote
                  prevPitch = relativePitchFromLilyPond(curElt, prevPitch, true);
                  CAPlayableLength length = playableLengthFromLilyPond(curElt, true);
                  if (length.musicLength()!=CAPlayableLength::Undefined) // length may not be set
                        prevLength = length;
                  
                  CANote *note;
                  if (curDepth()!=Chord || !chordCreated) {
                        // the note is not part of the chord or is the first note in the chord
                        note = new CANote( prevPitch, prevLength, curVoice(), curVoice()->lastTimeEnd() );
                        if (curDepth()==Chord)
                              chordCreated = true;
                        curVoice()->append( note, false );
                  } else {
                        // the note is part of the already built chord
                        note = new CANote( prevPitch, prevLength, curVoice(), curVoice()->lastTimeStart() );
                        curVoice()->append( note, true );
                  }
                  
                  note->updateTies(); // close any opened ties if present
            } else
            if (isRest(curElt)) {
                  // CARest
                  CARest::CARestType type = restTypeFromLilyPond(curElt, true);
                  CAPlayableLength length = playableLengthFromLilyPond(curElt, true);
                  if (length.musicLength()!=CAPlayableLength::Undefined) // length may not be set
                        prevLength = length;
                                    
                  curVoice()->append( new CARest( type, prevLength, curVoice(), curVoice()->lastTimeEnd() ) );
            } else
            if (curElt.startsWith("|")) {
                  // CABarline::Single
                  // lookup an element with the same type at the same time
                  CABarline *bar = new CABarline(CABarline::Single, curVoice()->staff(), curVoice()->lastTimeEnd());
                  CABarline *sharedBar = static_cast<CABarline*>(findSharedElement(bar));

                  if (!sharedBar) {
                        curVoice()->append( bar );
                  } else {
                        curVoice()->append( sharedBar );
                        delete bar;
                  }
                  curElt.remove(0,1);
            } else
            if (curElt.startsWith("\\bar")) {
                  // CABarline
                  QString typeString = peekNextElement();
                  CABarline::CABarlineType type = barlineTypeFromLilyPond(peekNextElement());
                  
                  if (type==CABarline::Undefined) {
                        addError(QString("Error while parsing barline type. Barline type %1 unknown.").arg(typeString));
                  }
                  
                  // remove clef type from the input
                  parseNextElement();
                  
                  // lookup an element with the same type at the same time
                  CABarline *bar = new CABarline(type, curVoice()->staff(), curVoice()->lastTimeEnd());
                  CABarline *sharedBar = static_cast<CABarline*>(findSharedElement(bar));

                  if (!sharedBar) {
                        curVoice()->append( bar );
                  } else {
                        curVoice()->append( sharedBar );
                        delete bar;
                  }
                  curElt.remove(0,4);
            } else
            if (curElt.startsWith("\\clef")) {
                  // CAClef
                  QString typeString = peekNextElement();
                  CAClef::CAPredefinedClefType type = predefinedClefTypeFromLilyPond( peekNextElement() );
                  int offset = clefOffsetFromLilyPond( peekNextElement() );
                  
                  if (type==CAClef::Undefined) {
                        addError(QString("Error while parsing clef type. Clef type %1 unknown.").arg(typeString));
                  }
                  
                  // remove clef type from the input
                  parseNextElement();
                  
                  CAClef *clef = new CAClef( type, curVoice()->staff(), curVoice()->lastTimeEnd(), offset );
                  CAClef *sharedClef = static_cast<CAClef*>(findSharedElement(clef));
                  
                  if (!sharedClef) {
                        curVoice()->append( clef );
                  } else {
                        curVoice()->append( sharedClef );
                        delete clef;
                  }
                  curElt.remove(0,5);
            } else
            if (curElt=="\\key") {
                  // CAKeySignature
                  // pitch
                  QString keyString = peekNextElement();
                  if (!isNote(keyString)) {
                        addError(QString("Error while parsing key signature. Key pitch %1 unknown.").arg(keyString));
                        continue;
                  }
                  parseNextElement(); // remove pitch
                  
                  // gender
                  QString genderString = peekNextElement();
                  CADiatonicKey::CAGender gender = diatonicKeyGenderFromLilyPond(genderString);
                  parseNextElement();
                  
                  CAKeySignature *keySig = new CAKeySignature( CADiatonicKey(relativePitchFromLilyPond(keyString, CADiatonicPitch(3) ), gender), curVoice()->staff(), curVoice()->lastTimeEnd() );
                  CAKeySignature *sharedKeySig = static_cast<CAKeySignature*>(findSharedElement(keySig));
                  
                  if (!sharedKeySig) {
                        curVoice()->append( keySig );
                  } else {
                        curVoice()->append( sharedKeySig );
                        delete keySig;
                  }
                  curElt.remove(0,4);
            } else
            if (curElt.startsWith("\\time")) {
                  // CATimeSignature
                  QString timeString = peekNextElement();
                  // time signature should have beats/beat format
                  if (timeString.indexOf(QRegExp("\\d+/\\d+")) == -1) {
                        addError(QString("Invalid time signature beats format %1. Beat and number of beats should be written <beats>/<beat>.").arg(timeString));
                        continue;
                  }
                  CATime time = timeSigFromLilyPond(timeString);
                  parseNextElement();
                  
                  CATimeSignature *timeSig = new CATimeSignature(time.beats, time.beat, curVoice()->staff(), curVoice()->lastTimeEnd());
                  CATimeSignature *sharedTimeSig = static_cast<CATimeSignature*>(findSharedElement(timeSig));
                  
                  if (!sharedTimeSig) {
                        curVoice()->append( timeSig );
                  } else {
                        curVoice()->append( sharedTimeSig );
                        delete timeSig;
                  }
                  curElt.remove(0,5);
            } else
                  changed=false;
      }
      
      return voice;
}

CALyricsContext *CALilyPondImport::importLyricsContextImpl() {
      CALyricsContext *lc = new CALyricsContext( "", 1, static_cast<CASheet*>(0) );
      
      CASyllable *lastSyllable = 0;
      int timeSDummy=0; // dummy timestart to keep the order of inserted syllables. Real timeStarts are sets when repositSyllables() is called
      for (QString curElt = parseNextElement(); (!in().isEmpty() || !curElt.isEmpty() ); curElt = parseNextElement(), timeSDummy++) {
            QString text = curElt;
            if (curElt == "_")
                  text = "";
            
            if (lastSyllable && text=="--") {
                  lastSyllable->setHyphenStart(true);
            } else
            if (lastSyllable && text=="__") {
                  lastSyllable->setMelismaStart(true);
            } else {
                  lc->addSyllable( lastSyllable = new CASyllable( text, false, false, lc, timeSDummy, 0 ) );
            }
      }
      lc->repositSyllables(); // sets syllables timeStarts and timeLengths
      
      return lc;
}

/*!
      Returns the first element in input stream ended with one of the delimiters and shorten input stream for the element.
      
      \todo Only one-character syntax delimiters are supported so far. 
      \sa peekNextElement()
*/
const QString CALilyPondImport::parseNextElement() {
      // find the first non-whitespace character
      int start = in().indexOf(QRegExp("\\S"));
      if (start==-1)
            start = 0;
      
      int i = in().indexOf(DELIMITERS, start);
      if (i==-1)
            i=in().size();
      
      QString ret;
      if (i==start) {
            // syntax delimiter only
            ret = in().mid(start,1); // \todo Support for syntax delimiters longer than 1 character
            in().remove(0, start+1);
      } else {
            // ordinary whitespace/syntax delimiter
            ret = in().mid(start, i-start);
            in().remove(0, i);
      }
      
      return ret;
}

/*!
      Returns the first element in input stream ended with one of the delimiters but don't shorten the stream.
      
      \sa parseNextElement()
*/
const QString CALilyPondImport::peekNextElement() {
      // find the first non-whitespace character
      int start = in().indexOf(QRegExp("\\S"));
      if (start==-1)
            start = 0;
      
      int i = in().indexOf(DELIMITERS, start);
      if (i==-1)
            i=in().size();
      
      QString ret;
      if (i==start) {
            // syntax delimiter only
            ret = in().left(1); // \todo
      } else {
            // ordinary whitespace/syntax delimiter
            ret = in().mid(start, i-start);
      }
      
      return ret;
}

/*!
      Gathers a list of music elements with the given element's start time and returns the first music element in the
      gathered list with the same attributes.
      
      This method is usually called when voices have "shared" music elements (barlines, clefs etc.). However, in LilyPond
      syntax the music element can/should be present in all the voices. This function finds this shared music element, if
      it already exists.
      
      If the music element with the same properties exists, user should delete its own instance and add an already
      existing instance of the returned shared music element to the voice.
      
      \sa CAMusElement::compare()
*/
CAMusElement* CALilyPondImport::findSharedElement(CAMusElement *elt) {
      if ( !curVoice() || !curVoice()->staff() )
            return 0;
      
      // gather a list of all the music elements of that type in the staff at that time
      QList<CAMusElement*> foundElts = curVoice()->staff()->getEltByType( elt->musElementType(), elt->timeStart() );
      
      // compare gathered music elements properties
      for (int i=0; i<foundElts.size(); i++)
            if (!foundElts[i]->compare(elt))             // element has exactly the same properties
                  if (!curVoice()->contains(foundElts[i])) // element isn't present in the voice yet
                        return foundElts[i];
      
      return 0;
}

/*!
      Returns true, if the given LilyPond element is a note.
      
      \sa isRest()
*/
bool CALilyPondImport::isNote(const QString elt) {
      return QString(elt[0]).contains(QRegExp("[a-g]"));
}

/*!
      Returns true, if the given LilyPond element is a rest.
      
      \sa isNote()
*/
bool CALilyPondImport::isRest(const QString elt) {
      return (elt[0]=='r' || elt[0]=='s' || elt[0]=='R');
}

/*!
      Generates the note pitch and number of accidentals from the note written in LilyPond syntax.
      
      \sa playableLengthFromLilyPond()
*/
CADiatonicPitch CALilyPondImport::relativePitchFromLilyPond(QString& constNName, CADiatonicPitch prevPitch, bool parse) {
      QString noteName = constNName;
      
      // determine pitch
      int curPitch = noteName[0].toLatin1() - 'a' + 5 // determine the 0-6 pitch from note name
                     - (prevPitch.noteName() % 7);    
      while (curPitch<-3)     //normalize pitch - the max +/- interval is fourth
            curPitch+=7;
      while (curPitch>3)
            curPitch-=7;
      curPitch += prevPitch.noteName();
      
      // determine accidentals
      signed char curAccs = 0;
      while (noteName.indexOf("is") != -1) {
            curAccs++;
            noteName.remove(0, noteName.indexOf("is") + 2);
            if (parse)
                  constNName.remove(0, constNName.indexOf("is") + 2);
      }
      while ((noteName.indexOf("es") != -1) || (noteName.indexOf("as") != -1)) {
            curAccs--;
            noteName.remove(0, ((noteName.indexOf("es")==-1) ? (noteName.indexOf("as")+2) : (noteName.indexOf("es")+2)) );
            if (parse)
                  constNName.remove(0, ((constNName.indexOf("es")==-1) ? (constNName.indexOf("as")+2) : (constNName.indexOf("es")+2)) );
      }
      if (!curAccs && parse)
            constNName.remove(0, 1);
      
      // add octave up/down
      for (int i=0; i<noteName.size(); i++) {
            if (noteName[i]=='\'') {
                  curPitch+=7;
                  if (parse)
                        constNName.remove(0,1);
            } else if (noteName[i]==',') {
                  curPitch-=7;
                  if (parse)
                        constNName.remove(0,1);
            }
      }
      
      return CADiatonicPitch( curPitch, curAccs );
}

/*!
      Generates playable lentgth and number of dots from the note/rest string in LilyPond syntax.
      If the playable element doesn't include length, { CAPlayable::CAPlayableLength::Undefined, 0 } is returned.
      This function also shortens the given string for the playable length, if \a parse is True.
      
      \sa relativePitchFromString()
*/
CAPlayableLength CALilyPondImport::playableLengthFromLilyPond(QString& elt, bool parse) {
      CAPlayableLength ret;
      
      // index of the first number
      int start = elt.indexOf(QRegExp("[\\d]"));
      if (start == -1)  // no length written
            return ret;
      else {            // length written
            // count dots
            int d=0;
            int dStart;
            for (int i = dStart = elt.indexOf(".",start);
                 i!=-1 && i<elt.size() && elt[i]=='.';
                 i++, ret.setDotted( ret.dotted()+1 ));
            
            if (dStart == -1)
                  dStart = elt.indexOf(QRegExp("[\\D]"), start);
            if (dStart == -1)
                  dStart = elt.size();
            
            ret.setMusicLength( static_cast<CAPlayableLength::CAMusicLength>(elt.mid(start, dStart-start).toInt()) );
            if (parse)
                  elt.remove(start, dStart-start+ret.dotted());
      }
      
      return ret;
}

/*!
      Genarates rest type from the LilyPond syntax for the given rest.
      This function also shortens the given string for the rest type, if \a parse is True.
*/
CARest::CARestType CALilyPondImport::restTypeFromLilyPond( QString& elt, bool parse ) {
      CARest::CARestType t = CARest::Normal;
      
      if (elt[0]=='r' || elt[0]=='R')
            t = CARest::Normal;
      else
            t = CARest::Hidden;
      
      if (parse)
            elt.remove( 0, 1 );
      
      return t;
}

/*!
      Genarates clef type from the LilyPond syntax for the given clef from format "clefType".
*/
CAClef::CAPredefinedClefType CALilyPondImport::predefinedClefTypeFromLilyPond( const QString constClef ) {
      // remove any quotes/double quotes
      QString clef(constClef);
      clef.remove(QRegExp("[\"']"));
      
      if ( clef.contains("treble") || clef.contains("violin") || clef.contains("G") ) return CAClef::Treble;
      if ( clef.contains("french") ) return CAClef::French;
      if ( clef.contains("bass") || clef.contains("F") ) return CAClef::Bass;
      if ( clef.contains("varbaritone") ) return CAClef::Varbaritone;
      if ( clef.contains("subbass") ) return CAClef::Subbass;
      if ( clef.contains("mezzosoprano") ) return CAClef::Mezzosoprano;
      if ( clef.contains("soprano") ) return CAClef::Soprano;
      if ( clef.contains("alto") ) return CAClef::Alto;
      if ( clef.contains("tenor") ) return CAClef::Tenor;
      if ( clef.contains("baritone") ) return CAClef::Baritone;
      if ( clef=="percussion" ) return CAClef::Percussion;
      if ( clef=="tab" ) return CAClef::Tablature;
      
      return CAClef::Treble;
}

/*!
      Returns the Canorus octava or whichever interval above or below the clef.
*/
int CALilyPondImport::clefOffsetFromLilyPond( const QString constClef ) {
      // remove any quotes/double quotes
      QString clef(constClef);
      clef.remove(QRegExp("[\"']"));
      
      if ( !clef.contains("_") && !clef.contains("^") )
            return 0;
      
      int m;
      int idx = clef.indexOf("^");
      if (idx==-1) {
            idx = clef.indexOf("_");
            m=-1;
      } else
            m=1;
      
      return clef.right( clef.size()-(idx+1) ).toInt()*m;
}

/*!
      Returns the key signature gender from format \\genderType.
*/
CADiatonicKey::CAGender CALilyPondImport::diatonicKeyGenderFromLilyPond(QString gender) {
      if (gender=="\\major")
            return CADiatonicKey::Major;
      else
            return CADiatonicKey::Minor;
}

/*!
      Returns the time signature beat and beats in beats/beat format.
*/
CALilyPondImport::CATime CALilyPondImport::timeSigFromLilyPond(QString timeSig) {
      int beats=0, beat=0;
      
      beats = timeSig.mid(0, timeSig.indexOf("/")).toInt();
      beat = timeSig.mid(timeSig.indexOf("/")+1).toInt();
      
      CATime time = { beats, beat };
      return time;
}

/*!
      Genarates barline type from the LilyPond syntax for the given barline from format "barlineType".
*/
CABarline::CABarlineType CALilyPondImport::barlineTypeFromLilyPond(QString constBarline) {
      // remove any quotes/double quotes
      QString barline(constBarline);
      barline.remove(QRegExp("[\"']"));
      
      if (barline=="|") return CABarline::Single; else
      if (barline=="||") return CABarline::Double; else
      if (barline=="|.") return CABarline::End; else
      if (barline=="|:") return CABarline::RepeatOpen; else
      if (barline==":|") return CABarline::RepeatClose; else
      if (barline==":|:") return CABarline::RepeatCloseOpen; else
      if (barline==":") return CABarline::Dotted; else
      return CABarline::Undefined;
}

const QString CALilyPondImport::readableStatus() {
      switch (status()) {
      case 0:
            return tr("Ready");
      case 1:
            return tr("Importing...");
      case -1:
            return tr("Error while importing!\nLine %1:%2.").arg(curLine()).arg(curChar());
      }
}

Generated by  Doxygen 1.6.0   Back to index