MuseScore/mscore/importxml.cpp
2014-01-29 22:55:49 +01:00

5319 lines
219 KiB
C++

//=============================================================================
// MusE Score
// Linux Music Score Editor
// $Id: importxml.cpp 5653 2012-05-19 20:19:58Z lvinken $
//
// Copyright (C) 2002-2014 Werner Schweer and others
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//=============================================================================
// TODO LVI 2011-01-18: it seems brackets ending after the last note in a measure
// import OK but stop is not exported OK. Find out what causes this (import, export,
// structural issue).
// TODO LVI 2011-10-30: determine how to report import errors.
// Currently all output (both debug and error reports) are done using qDebug.
// TODO LVI 2012-04-26: read drumset definition from MusicXML
// Mapping Musescore DrumInstrument to MusicXML elements
// pitch part-list/score-part/midi-instrument/midi-unpitched
// name part-list/score-part/score-instrument/instrument-name
// notehead part/measure/note/notehead (once for each individual note)
// line part/measure/note/unpitched (once for each individual note) + current clef
// stemdirection part/measure/note/stem (once for each individual note)
// voice N/A (leave empty ?)
// shortcut N/A (leave empty ?)
/**
MusicXML import.
*/
#include "config.h"
// #include "musescore.h"
#include "musicxml.h"
#include "file.h"
#include "libmscore/score.h"
#include "libmscore/rest.h"
#include "libmscore/chord.h"
#include "libmscore/sig.h"
#include "libmscore/key.h"
#include "libmscore/clef.h"
#include "libmscore/note.h"
#include "libmscore/element.h"
#include "libmscore/sym.h"
#include "libmscore/slur.h"
#include "libmscore/tie.h"
#include "libmscore/hairpin.h"
#include "libmscore/tuplet.h"
#include "libmscore/segment.h"
#include "libmscore/dynamic.h"
#include "libmscore/page.h"
#include "libmscore/staff.h"
#include "libmscore/part.h"
#include "libmscore/measure.h"
#include "libmscore/style.h"
#include "libmscore/bracket.h"
#include "libmscore/timesig.h"
#include "libmscore/xml.h"
#include "libmscore/barline.h"
#include "libmscore/lyrics.h"
#include "libmscore/volta.h"
#include "libmscore/textline.h"
#include "libmscore/keysig.h"
#include "libmscore/pitchspelling.h"
#include "libmscore/utils.h"
#include "libmscore/layoutbreak.h"
#include "libmscore/tremolo.h"
#include "libmscore/box.h"
#include "libmscore/repeat.h"
#include "libmscore/ottava.h"
#include "libmscore/trill.h"
#include "libmscore/pedal.h"
#include "libmscore/harmony.h"
#include "libmscore/tempotext.h"
#include "libmscore/articulation.h"
#include "libmscore/arpeggio.h"
#include "libmscore/glissando.h"
#include "libmscore/breath.h"
#include "libmscore/tempo.h"
#include "libmscore/chordlist.h"
#include "libmscore/mscore.h"
#include "libmscore/accidental.h"
#include "libmscore/rehearsalmark.h"
#include "libmscore/fingering.h"
#include "preferences.h"
#include "musicxmlsupport.h"
#include "libmscore/chordline.h"
#include "libmscore/figuredbass.h"
#include "libmscore/fret.h"
#include "libmscore/qzipreader_p.h"
#include "libmscore/stafftype.h"
#include "libmscore/stringdata.h"
#include "libmscore/drumset.h"
#include "libmscore/beam.h"
#include "libmscore/jump.h"
#include "libmscore/marker.h"
#include "importxmlfirstpass.h"
namespace Ms {
//---------------------------------------------------------
// local defines for debug output
//---------------------------------------------------------
// #define DEBUG_VOICE_MAPPER true
// #define DEBUG_TICK true
//---------------------------------------------------------
// MusicXMLStepAltOct2Pitch
//---------------------------------------------------------
/**
Convert MusicXML \a step / \a alter / \a octave to midi pitch.
Note: same code is also in libmscore/tablature.cpp.
TODO: combine ?
*/
static int MusicXMLStepAltOct2Pitch(char step, int alter, int octave)
{
int istep = step - 'A';
// a b c d e f g
static int table[7] = { 9, 11, 0, 2, 4, 5, 7 };
if (istep < 0 || istep > 6) {
qDebug("MusicXMLStepAltOct2Pitch: illegal step %d, <%c>", istep, step);
return -1;
}
int pitch = table[istep] + alter + (octave+1) * 12;
if (pitch < 0)
pitch = -1;
if (pitch > 127)
pitch = -1;
return pitch;
}
//---------------------------------------------------------
// xmlSetPitch
//---------------------------------------------------------
/**
Convert MusicXML \a step / \a alter / \a octave to midi pitch,
set pitch and tpc.
*/
static void xmlSetPitch(Note* n, char step, int alter, int octave, Ottava* ottava, int track)
{
// qDebug("xmlSetPitch(n=%p, st=%c, alter=%d, octave=%d)",
// n, step, alter, octave);
int pitch = MusicXMLStepAltOct2Pitch(step, alter, octave);
if (pitch < 0)
pitch = 0;
if (pitch > 127)
pitch = 127;
if (ottava != 0 && ottava->track() == track)
pitch -= ottava->pitchShift();
n->setPitch(pitch);
int istep = step - 'A';
// a b c d e f g
static int table1[7] = { 5, 6, 0, 1, 2, 3, 4 };
int tpc = step2tpc(table1[istep], AccidentalVal(alter));
// alternativ: tpc = step2tpc((istep + 5) % 7, alter); // rotate istep 5 steps
n->setTpc(tpc);
}
//---------------------------------------------------------
// calcTicks
//---------------------------------------------------------
static int calcTicks(QString text, int divisions)
{
bool ok;
int val = MxmlSupport::stringToInt(text, &ok);
if (!ok) {
qDebug("MusicXml-Import: bad duration value: <%s>",
qPrintable(text));
}
if (val == 0) // neuratron scanner produces sometimes 0 !?
val = 1;
val *= MScore::division;
val /= divisions;
return val;
}
//---------------------------------------------------------
// noteDurationAsFraction
//---------------------------------------------------------
/**
Determine note duration as fraction. Prefer note type over duration.
Input e is the note element.
If chord or grace, duration is 0.
*/
static Fraction noteDurationAsFraction(const int divisions, const QDomElement e)
{
int actualNotes = 0;
bool chord = false;
int dots = 0;
int duration = 0;
bool grace = false;
int normalNotes = 0;
bool rest = false;
QString type;
for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
if (ee.tagName() == "chord")
chord = true;
else if (ee.tagName() == "dot")
dots++;
else if (ee.tagName() == "duration") {
bool ok;
duration = MxmlSupport::stringToInt(ee.text(), &ok);
if (!ok)
qDebug("MusicXml-Import: bad duration value: <%s>",
qPrintable(ee.text()));
}
else if (ee.tagName() == "grace")
grace = true;
else if (ee.tagName() == "rest")
rest = true;
else if (ee.tagName() == "time-modification") {
for (QDomElement eee = ee.firstChildElement(); !eee.isNull(); eee = eee.nextSiblingElement()) {
if (eee.tagName() == "actual-notes") {
bool ok;
actualNotes = MxmlSupport::stringToInt(eee.text(), &ok);
if (!ok || divisions <= 0)
qDebug("MusicXml-Import: bad actual-notes value: <%s>",
qPrintable(eee.text()));
}
if (eee.tagName() == "normal-notes") {
bool ok;
normalNotes = MxmlSupport::stringToInt(eee.text(), &ok);
if (!ok || divisions <= 0)
qDebug("MusicXml-Import: bad normal-notes value: <%s>",
qPrintable(eee.text()));
}
}
}
else if (ee.tagName() == "type")
type = ee.text();
}
// if chord or grace, duration is 0
if (chord || grace)
return Fraction(0, 1);
// calculate note duration as fraction based on type, dots, normal and actual notes
// if that does not succeed, fallback to using the <duration> element
// note divisions = ticks / quarter note
Fraction f = MxmlSupport::calculateFraction(type, dots, normalNotes, actualNotes);
if (!f.isValid()) {
f = Fraction(duration, 4 * divisions);
}
// bug fix for rests in triplet
if (f.isValid() && rest && normalNotes == 0 && actualNotes == 0) {
if ((Fraction(duration, 4 * divisions) / f) == Fraction(2, 3)) {
// duration is exactly 2/3 of what is expected based on type
f = Fraction(duration, 4 * divisions);
}
}
#ifdef DEBUG_TICK
qDebug("time-in-fraction: note type %s dots %d norm %d act %d"
" dur %d chord %d grace %d-> dt frac %s (ticks %d)",
qPrintable(type), dots, normalNotes, actualNotes, duration,
chord, grace, qPrintable(f.print()), f.ticks());
#endif
return f;
}
//---------------------------------------------------------
// moveTick
//---------------------------------------------------------
/**
Move tick and typFr by amount specified in the element e, which must be
a forward, backup or note.
*/
static void moveTick(const int mtick, int& tick, int& maxtick, Fraction& typFr, const int divisions, const QDomElement e)
{
if (e.tagName() == "forward") {
for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
if (ee.tagName() == "duration") {
int val = calcTicks(ee.text(), divisions);
#ifdef DEBUG_TICK
qDebug("forward %d", val);
#endif
bool ok;
val = MxmlSupport::stringToInt(ee.text(), &ok);
if (!ok || divisions <= 0)
qDebug("MusicXml-Import: bad divisions value: <%s>",
qPrintable(ee.text()));
Fraction f(val, 4 * divisions); // note divisions = ticks / quarter note
typFr += f;
typFr.reduce();
tick = mtick + typFr.ticks();
if (tick > maxtick)
maxtick = tick;
}
else if (ee.tagName() == "voice")
;
else if (ee.tagName() == "staff")
;
else
domError(ee);
}
}
else if (e.tagName() == "backup") {
for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
if (ee.tagName() == "duration") {
int val = calcTicks(ee.text(), divisions);
#ifdef DEBUG_TICK
qDebug("backup %d", val);
#endif
bool ok;
val = MxmlSupport::stringToInt(ee.text(), &ok);
if (!ok || divisions <= 0)
qDebug("MusicXml-Import: bad divisions value: <%s>",
qPrintable(ee.text()));
Fraction f(val, 4 * divisions); // note divisions = ticks / quarter note
if (f < typFr) {
typFr -= f;
typFr.reduce();
}
else
typFr = Fraction(0, 1);
tick = mtick + typFr.ticks();
}
else
domError(ee);
}
}
else if (e.tagName() == "note") {
#ifdef DEBUG_TICK
int ticks = 0;
for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
QString tag(ee.tagName());
if (tag == "duration") {
ticks = calcTicks(ee.text(), divisions);
}
}
qDebug("note %d", ticks);
#endif
Fraction f = noteDurationAsFraction(divisions, e);
typFr += f;
typFr.reduce();
tick = mtick + typFr.ticks();
if (tick > maxtick)
maxtick = tick;
}
}
//---------------------------------------------------------
// determineMeasureStart
//---------------------------------------------------------
/**
Determine the start ticks of each measure
i.e. the sum of all previous measures length
or start tick measure equals start tick previous measure plus length previous measure
*/
static void determineMeasureStart(const QVector<int>& ml, QVector<int>& ms)
{
ms.resize(ml.size());
// first measure starts at tick = 0
ms[0] = 0;
// all others start at start tick previous measure plus length previous measure
for (int i = 1; i < ml.size(); i++)
ms[i] = ms.at(i - 1) + ml.at(i - 1);
#ifdef DEBUG_TICK
for (int i = 0; i < ms.size(); i++)
qDebug("measurelength ms[%d] %d", i + 1, ms.at(i));
#endif
}
//---------------------------------------------------------
// MusicXml
//---------------------------------------------------------
/**
MusicXml constructor.
*/
MusicXml::MusicXml(QDomDocument* d, MxmlReaderFirstPass const& p1)
:
lastVolta(0),
doc(d),
pass1(p1),
beamMode(BeamMode::NONE),
pageWidth(0),
pageHeight(0)
{
// Determine the start tick of each measure in the part
pass1.determineMeasureLength(measureLength);
determineMeasureStart(measureLength, measureStart);
}
//---------------------------------------------------------
// initMusicXmlSchema
// return false on error
//---------------------------------------------------------
static bool initMusicXmlSchema(QXmlSchema& schema)
{
// read the MusicXML schema from the application resources
QFile schemaFile(":/schema/musicxml.xsd");
if (!schemaFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
qDebug("initMusicXmlSchema() could not open resource musicxml.xsd");
MScore::lastError = QT_TRANSLATE_NOOP("file", "internal error: could not open resource musicxml.xsd\n");
return false;
}
// copy the schema into a QByteArray and fixup xs:imports,
// using a path to the application resources instead of to www.musicxml.org
// to prevent downloading from the net
QByteArray schemaBa;
QTextStream schemaStream(&schemaFile);
while (!schemaStream.atEnd()) {
QString line = schemaStream.readLine();
if (line.contains("xs:import"))
line.replace("http://www.musicxml.org/xsd", "qrc:///schema");
schemaBa += line.toUtf8();
schemaBa += "\n";
}
// load and validate the schema
schema.load(schemaBa);
if (!schema.isValid()) {
qDebug("initMusicXmlSchema() internal error: MusicXML schema is invalid");
MScore::lastError = QT_TRANSLATE_NOOP("file", "internal error: MusicXML schema is invalid\n");
return false;
}
return true;
}
//---------------------------------------------------------
// musicXMLValidationErrorDialog
//---------------------------------------------------------
/**
Show a dialog displaying the MusicXML validation error(s)
and asks the user if he wants to try to load the file anyway.
Return QMessageBox::Yes (try anyway) or QMessageBox::No (don't)
*/
static int musicXMLValidationErrorDialog(QString text, QString detailedText)
{
QMessageBox errorDialog;
errorDialog.setIcon(QMessageBox::Question);
errorDialog.setText(text);
errorDialog.setInformativeText("Do you want to try to load this file anyway ?");
errorDialog.setDetailedText(detailedText);
errorDialog.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
errorDialog.setDefaultButton(QMessageBox::No);
return errorDialog.exec();
}
//---------------------------------------------------------
// extractRootfile
//---------------------------------------------------------
/**
Extract rootfile from compressed MusicXML file \a qf, return true if OK and false on error.
*/
static bool extractRootfile(QFile* qf, QByteArray& data)
{
MQZipReader f(qf->fileName());
data = f.fileData("META-INF/container.xml");
QDomDocument container;
int line, column;
QString err;
if (!container.setContent(data, false, &err, &line, &column)) {
QString s = QT_TRANSLATE_NOOP("file", "error reading container.xml at line %1 column %2: %3\n");
MScore::lastError = s.arg(line).arg(column).arg(err);
return false;
}
// extract first rootfile
QString rootfile = "";
for (QDomElement e = container.documentElement(); !e.isNull(); e = e.nextSiblingElement()) {
if (e.tagName() == "container") {
for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
if (ee.tagName() == "rootfiles") {
for (QDomElement eee = ee.firstChildElement(); !eee.isNull(); eee = eee.nextSiblingElement()) {
if (eee.tagName() == "rootfile") {
if (rootfile == "")
rootfile = eee.attribute(QString("full-path"));
}
else
domError(eee);
}
}
else
domError(ee);
}
}
else
domError(e);
}
if (rootfile == "") {
qDebug("can't find rootfile in: %s", qPrintable(qf->fileName()));
MScore::lastError = QT_TRANSLATE_NOOP("file", "can't find rootfile\n");
return false;
}
// read the rootfile
data = f.fileData(rootfile);
return true;
}
//---------------------------------------------------------
// doValidate
//---------------------------------------------------------
/**
Validate MusicXML data from file \a name contained in QIODevice \a dev.
*/
static Score::FileError doValidate(const QString& name, QIODevice* dev)
{
QTime t;
t.start();
// initialize the schema
ValidatorMessageHandler messageHandler;
QXmlSchema schema;
schema.setMessageHandler(&messageHandler);
if (!initMusicXmlSchema(schema))
return Score::FILE_BAD_FORMAT; // appropriate error message has been printed by initMusicXmlSchema
// validate the data
QXmlSchemaValidator validator(schema);
bool valid = validator.validate(dev, QUrl::fromLocalFile(name));
qDebug("Validation time elapsed: %d ms", t.elapsed());
if (valid)
qDebug("importMusicXml() file '%s' is a valid MusicXML file", qPrintable(name));
else {
qDebug("importMusicXml() file '%s' is not a valid MusicXML file", qPrintable(name));
MScore::lastError = QT_TRANSLATE_NOOP("file", "this is not a valid MusicXML file\n");
QString text = QString("File '%1' is not a valid MusicXML file").arg(name);
if (musicXMLValidationErrorDialog(text, messageHandler.getErrors()) != QMessageBox::Yes)
return Score::FILE_USER_ABORT;
}
// return OK
return Score::FILE_NO_ERROR;
}
//---------------------------------------------------------
// doImport
//---------------------------------------------------------
/**
Import MusicXML data from file \a name contained in QIODevice \a dev into score \a score.
*/
static Score::FileError doImport(Score* score, const QString& name, QIODevice* dev, MxmlReaderFirstPass const& pass1)
{
QTime t;
t.start();
QDomDocument doc;
int line;
int column;
QString err;
if (!doc.setContent(dev, false, &err, &line, &column)) {
QString s = QT_TRANSLATE_NOOP("file", "error at line %1 column %2: %3\n");
MScore::lastError = s.arg(line).arg(column).arg(err);
return Score::FILE_BAD_FORMAT;
}
docName = name; // set filename for domError
MusicXml musicxml(&doc, pass1);
musicxml.import(score);
qDebug("Parsing time elapsed: %d ms", t.elapsed());
return Score::FILE_NO_ERROR;
}
//---------------------------------------------------------
// doValidateAndImport
//---------------------------------------------------------
/**
Validate and import MusicXML data from file \a name contained in QIODevice \a dev into score \a score.
*/
static Score::FileError doValidateAndImport(Score* score, const QString& name, QIODevice* dev)
{
// validate the file
Score::FileError res;
res = doValidate(name, dev);
if (res != Score::FILE_NO_ERROR)
return res;
// pass 1
dev->seek(0);
MxmlReaderFirstPass pass1;
res = pass1.setContent(dev);
if (res != Score::FILE_NO_ERROR)
return res;
pass1.parseFile();
// import the file
dev->seek(0);
res = doImport(score, name, dev, pass1);
qDebug("importMusicXml() return %d", res);
return res;
}
//---------------------------------------------------------
// importMusicXml
// return Score::FILE_* errors
//---------------------------------------------------------
/**
Import MusicXML file \a name into the Score.
*/
Score::FileError importMusicXml(Score* score, const QString& name)
{
qDebug("importMusicXml(%p, %s)", score, qPrintable(name));
// open the MusicXML file
QFile xmlFile(name);
if (!xmlFile.exists())
return Score::FILE_NOT_FOUND;
if (!xmlFile.open(QIODevice::ReadOnly)) {
qDebug("importMusicXml() could not open MusicXML file '%s'", qPrintable(name));
MScore::lastError = QT_TRANSLATE_NOOP("file", "could not open MusicXML file\n");
return Score::FILE_OPEN_ERROR;
}
// and import it
return doValidateAndImport(score, name, &xmlFile);
}
//---------------------------------------------------------
// importCompressedMusicXml
// return false on error
//---------------------------------------------------------
/**
Import compressed MusicXML file \a name into the Score.
*/
Score::FileError importCompressedMusicXml(Score* score, const QString& name)
{
qDebug("importCompressedMusicXml(%p, %s)", score, qPrintable(name));
// open the compressed MusicXML file
QFile mxlFile(name);
if (!mxlFile.exists())
return Score::FILE_NOT_FOUND;
if (!mxlFile.open(QIODevice::ReadOnly)) {
qDebug("importCompressedMusicXml() could not open compressed MusicXML file '%s'", qPrintable(name));
MScore::lastError = QT_TRANSLATE_NOOP("file", "could not open compressed MusicXML file\n");
return Score::FILE_OPEN_ERROR;
}
// extract the root file
QByteArray data;
if (!extractRootfile(&mxlFile, data))
return Score::FILE_BAD_FORMAT; // appropriate error message has been printed by extractRootfile
QBuffer buffer(&data);
buffer.open(QIODevice::ReadOnly);
// and import it
return doValidateAndImport(score, name, &buffer);
}
//---------------------------------------------------------
// import
// scorePartwise
// part-list
// part
// work
// identification
//---------------------------------------------------------
static void tupletAssert();
/**
Parse the MusicXML file, which must be in score-partwise format.
*/
void MusicXml::import(Score* s)
{
tupletAssert();
score = s;
tie = 0;
for (int i = 0; i < MAX_NUMBER_LEVEL; ++i)
slur[i] = 0;
for (int i = 0; i < MAX_BRACKETS; ++i)
bracket[i] = 0;
for (int i = 0; i < MAX_DASHES; ++i)
dashes[i] = 0;
ottava = 0;
trill = 0;
pedal = 0;
harmony = 0;
tremStart = 0;
hairpin = 0;
figBass = 0;
figBassExtend = false;
// TODO only if multi-measure rests used ???
// score->style()->set(ST_createMultiMeasureRests, true);
for (QDomElement e = doc->documentElement(); !e.isNull(); e = e.nextSiblingElement()) {
if (e.tagName() == "score-partwise")
scorePartwise(e.firstChildElement());
else
domError(e);
}
}
//---------------------------------------------------------
// addText
//---------------------------------------------------------
static void addText(VBox*& vbx, Score* s, QString strTxt, int stl)
{
if (!strTxt.isEmpty()) {
Text* text = new Text(s);
text->setTextStyleType(stl);
text->setText(strTxt);
if (vbx == 0)
vbx = new VBox(s);
vbx->add(text);
}
}
//---------------------------------------------------------
// doCredits
//---------------------------------------------------------
/**
Create Text elements for the credits read from MusicXML credit-words elements.
Apply simple heuristics using only default x and y to recognize the meaning of credit words
If no credits are found, create credits from meta data.
*/
void MusicXml::doCredits()
{
// IMPORT_LAYOUT
// qDebug("MusicXml::doCredits()");
/*
const PageFormat* pf = score->pageFormat();
qDebug("page format w=%g h=%g spatium=%g DPMM=%g DPI=%g",
pf->width(), pf->height(), score->spatium(), MScore::DPMM, DPI);
// page width and height in tenths
const double pw = pf->width() * 10 * DPI / score->spatium();
const double ph = pf->height() * 10 * DPI / score->spatium();
*/
const int pw1 = pageWidth / 3;
const int pw2 = pageWidth * 2 / 3;
const int ph2 = pageHeight / 2;
// qDebug("page format w=%g h=%g", pw, ph);
// qDebug("page format w=%d h=%d", pageWidth, pageHeight);
// qDebug("page format pw1=%d pw2=%d ph2=%d", pw1, pw2, ph2);
// dump the credits
/*
for (ciCreditWords ci = credits.begin(); ci != credits.end(); ++ci) {
CreditWords* w = *ci;
qDebug("credit-words defx=%g defy=%g just=%s hal=%s val=%s words=%s",
w->defaultX,
w->defaultY,
w->justify.toUtf8().data(),
w->hAlign.toUtf8().data(),
w->vAlign.toUtf8().data(),
w->words.toUtf8().data());
}
*/
CreditWords* crwTitle = 0;
CreditWords* crwSubTitle = 0;
CreditWords* crwComposer = 0;
CreditWords* crwPoet = 0;
CreditWords* crwCopyRight = 0;
QMap<int, CreditWords*> creditMap; // store credit-words sorted on y pos
int nWordsHeader = 0; // number of credit-words in the header
int nWordsFooter = 0; // number of credit-words in the footer
for (ciCreditWords ci = credits.begin(); ci != credits.end(); ++ci) {
CreditWords* w = *ci;
double defx = w->defaultX;
double defy = w->defaultY;
// composer is in the right column
if (pw2 < defx) {
// found composer
if (!crwComposer) crwComposer = w;
}
// poet is in the left column
else if (defx < pw1) {
// found poet
if (!crwPoet) crwPoet = w;
}
// save others (in the middle column) to be handled later
else {
creditMap.insert(defy, w);
// and count #words in header and footer
if (defy > ph2)
nWordsHeader++;
else
nWordsFooter++;
}
} // end for (ciCreditWords ...
/*
qDebug("header %d footer %d", nWordsHeader, nWordsFooter);
QMap<int, CreditWords*>::const_iterator ci = creditMap.constBegin();
while (ci != creditMap.constEnd()) {
CreditWords* w = ci.value();
qDebug("creditMap %d credit-words defx=%g defy=%g just=%s hal=%s val=%s words=%s",
ci.key(),
w->defaultX,
w->defaultY,
w->justify.toUtf8().data(),
w->hAlign.toUtf8().data(),
w->vAlign.toUtf8().data(),
w->words.toUtf8().data());
++ci;
}
*/
// assign title, subtitle and copyright
QList<int> keys = creditMap.uniqueKeys(); // note: ignoring credit-words at the same y pos
// if any credit-words present, the highest is the title
// note that the keys are sorted in ascending order
// -> use the last key
if (keys.size() >= 1)
crwTitle = creditMap.value(keys.at(keys.size() - 1));
// if two credit-words present and both are in the header or the footer,
// the lowest one is the subtitle, else it is the copyright
if (keys.size() == 2) {
if (nWordsHeader == 2 || nWordsFooter == 2)
crwSubTitle = creditMap.value(keys.at(0));
else
crwCopyRight = creditMap.value(keys.at(0));
}
// if three or more credit-words present
// the second-highest is the subtitle
// the lowest one is the copyright
if (keys.size() >= 3) {
crwSubTitle = creditMap.value(keys.at(keys.size() - 2));
crwCopyRight = creditMap.value(keys.at(0));
}
/*
if (crwTitle) qDebug("title='%s'", crwTitle->words.toUtf8().data());
if (crwSubTitle) qDebug("subtitle='%s'", crwSubTitle->words.toUtf8().data());
if (crwComposer) qDebug("composer='%s'", crwComposer->words.toUtf8().data());
if (crwPoet) qDebug("poet='%s'", crwPoet->words.toUtf8().data());
if (crwCopyRight) qDebug("copyright='%s'", crwCopyRight->words.toUtf8().data());
*/
QString strTitle;
QString strSubTitle;
QString strComposer;
QString strPoet;
QString strTranslator;
if (crwTitle || crwSubTitle || crwComposer || crwPoet || crwCopyRight) {
// use credits
if (crwTitle) strTitle = crwTitle->words;
if (crwSubTitle) strSubTitle = crwSubTitle->words;
if (crwComposer) strComposer = crwComposer->words;
if (crwPoet) strPoet = crwPoet->words;
}
else {
// use metadata
if (!(score->metaTag("movementTitle").isEmpty() && score->metaTag("workTitle").isEmpty())) {
strTitle = score->metaTag("movementTitle");
if (strTitle.isEmpty())
strTitle = score->metaTag("workTitle");
}
if (!(score->metaTag("movementNumber").isEmpty() && score->metaTag("workNumber").isEmpty())) {
strSubTitle = score->metaTag("movementNumber");
if (strSubTitle.isEmpty())
strSubTitle = score->metaTag("workNumber");
}
QString metaComposer = score->metaTag("composer");
QString metaPoet = score->metaTag("poet");
QString metaTranslator = score->metaTag("translator");
if (!metaComposer.isEmpty()) strComposer = metaComposer;
if (!metaPoet.isEmpty()) strPoet = metaPoet;
if (!metaTranslator.isEmpty()) strTranslator = metaTranslator;
}
VBox* vbox = 0;
addText(vbox, score, strTitle, TEXT_STYLE_TITLE);
addText(vbox, score, strSubTitle, TEXT_STYLE_SUBTITLE);
addText(vbox, score, strComposer, TEXT_STYLE_COMPOSER);
addText(vbox, score, strPoet, TEXT_STYLE_POET);
addText(vbox, score, strTranslator, TEXT_STYLE_TRANSLATOR);
if (vbox) {
vbox->setTick(0);
score->measures()->add(vbox);
}
// if no <rights> element was read and a copyright was found in the credit-words
// set the rights metadata to the value found
// note that MusicXML files can contain at least two different copyright statements:
// - in the <rights> element (metadata)
// - in the <credit-words> (the printed version)
// while MuseScore supports only the first one
if (score->metaTag("copyright") == "" && crwCopyRight)
score->setMetaTag("copyright", crwCopyRight->words);
}
//---------------------------------------------------------
// determineTimeSig
//---------------------------------------------------------
/**
Determine the time signature based on \a beats, \a beatType and \a timeSymbol.
Sets return parameters \a st, \a bts, \a btp.
Return true if OK, false on error.
*/
static bool determineTimeSig(const QString beats, const QString beatType, const QString timeSymbol,
TimeSigType& st, int& bts, int& btp)
{
// initialize
st = TSIG_NORMAL;
bts = 0; // the beats (max 4 separated by "+") as integer
btp = 0; // beat-type as integer
// determine if timesig is valid
if (beats == "2" && beatType == "2" && timeSymbol == "cut") {
st = TSIG_ALLA_BREVE;
bts = 2;
btp = 2;
return true;
}
else if (beats == "4" && beatType == "4" && timeSymbol == "common") {
st = TSIG_FOUR_FOUR;
bts = 4;
btp = 4;
return true;
}
else {
if (!timeSymbol.isEmpty()) {
qDebug("ImportMusicXml: time symbol <%s> not recognized with beats=%s and beat-type=%s",
qPrintable(timeSymbol), qPrintable(beats), qPrintable(beatType));
return false;
}
btp = beatType.toInt();
QStringList list = beats.split("+");
for (int i = 0; i < list.size(); i++)
bts += list.at(i).toInt();
}
return true;
}
//---------------------------------------------------------
// readPageFormat
//---------------------------------------------------------
void MusicXml::readPageFormat(PageFormat* pf, QDomElement de, qreal conversion)
{
qreal _oddRightMargin = 0.0;
qreal _evenRightMargin = 0.0;
QSizeF size;
for (QDomElement e = de.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
const QString& tag(e.tagName());
const QString& val(e.text());
if (tag == "page-margins") {
QString type = e.attribute("type","both");
qreal lm = 0.0, rm = 0.0, tm = 0.0, bm = 0.0;
for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
const QString& tag(ee.tagName());
qreal val = ee.text().toDouble() * conversion;
if (tag == "left-margin")
lm = val;
else if (tag == "right-margin")
rm = val;
else if (tag == "top-margin")
tm = val;
else if (tag == "bottom-margin")
bm = val;
else
domError(ee);
}
pf->setTwosided(type == "odd" || type == "even");
if (type == "odd" || type == "both") {
pf->setOddLeftMargin(lm);
_oddRightMargin = rm;
pf->setOddTopMargin(tm);
pf->setOddBottomMargin(bm);
}
if (type == "even" || type == "both") {
pf->setEvenLeftMargin(lm);
_evenRightMargin = rm;
pf->setEvenTopMargin(tm);
pf->setEvenBottomMargin(bm);
}
}
else if (tag == "page-height") {
size.rheight() = val.toDouble() * conversion;
// set pageHeight and pageWidth for use by doCredits()
pageHeight = static_cast<int>(val.toDouble() + 0.5);
}
else if (tag == "page-width") {
size.rwidth() = val.toDouble() * conversion;
// set pageHeight and pageWidth for use by doCredits()
pageWidth = static_cast<int>(val.toDouble() + 0.5);
}
else
domError(e);
}
pf->setSize(size);
qreal w1 = size.width() - pf->oddLeftMargin() - _oddRightMargin;
qreal w2 = size.width() - pf->evenLeftMargin() - _evenRightMargin;
pf->setPrintableWidth(qMax(w1, w2)); // silently adjust right margins
}
//---------------------------------------------------------
// scorePartwise
//---------------------------------------------------------
/**
Read the MusicXML score-partwise element.
*/
void MusicXml::scorePartwise(QDomElement ee)
{
// In a first pass collect all parts in case the part-list does not
// list them all. Incomplete part-list's are generated by some versions
// of Finale.
// Furthermore, determine the length in ticks of each measure in the part
for (QDomElement e = ee; !e.isNull(); e = e.nextSiblingElement()) {
if (e.tagName() == "part") {
QString id = e.attribute(QString("id"));
if (id == "")
qDebug("MusicXML import: part without id");
else {
Part* part = new Part(score);
part->setId(id);
score->appendPart(part);
Staff* staff = new Staff(score, part, 0);
part->staves()->push_back(staff);
score->staves().push_back(staff);
tuplets.resize(VOICES); // part now contains one staff, thus VOICES voices
}
}
}
// Read the score
for (QDomElement e = ee; !e.isNull(); e = e.nextSiblingElement()) {
QString tag(e.tagName());
if (tag == "part-list")
xmlPartList(e.firstChildElement());
else if (tag == "part")
xmlPart(e.firstChildElement(), e.attribute(QString("id")));
else if (tag == "work") {
for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
if (ee.tagName() == "work-number")
score->setMetaTag("workNumber", ee.text());
else if (ee.tagName() == "work-title")
score->setMetaTag("workTitle", ee.text());
else
domError(ee);
}
}
else if (tag == "identification") {
// read the metadata
for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
if (ee.tagName() == "creator") {
// type is an arbitrary label
score->setMetaTag(ee.attribute(QString("type")), ee.text());
}
else if (ee.tagName() == "rights")
score->setMetaTag("copyright", ee.text());
else if (ee.tagName() == "encoding")
score->setMetaTag("encoding", ee.text());
else if (ee.tagName() == "source")
score->setMetaTag("source", ee.text());
else if (ee.tagName() == "miscellaneous")
; // ignore
else
domError(ee);
}
}
else if (tag == "defaults") {
// IMPORT_LAYOUT
double millimeter = score->spatium()/10.0;
double tenths = 1.0;
for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
QString tag(ee.tagName());
if (tag == "scaling") {
for (QDomElement eee = ee.firstChildElement(); !eee.isNull(); eee = eee.nextSiblingElement()) {
QString tag(eee.tagName());
if (tag == "millimeters")
millimeter = eee.text().toDouble();
else if (tag == "tenths")
tenths = eee.text().toDouble();
else
domError(eee);
}
double _spatium = MScore::DPMM * (millimeter * 10.0 / tenths);
if (preferences.musicxmlImportLayout)
score->setSpatium(_spatium);
}
else if (tag == "page-layout") {
PageFormat pf;
readPageFormat(&pf, ee, millimeter / (tenths * INCH));
if (preferences.musicxmlImportLayout)
score->setPageFormat(pf);
}
else if (tag == "system-layout") {
for (QDomElement eee = ee.firstChildElement(); !eee.isNull(); eee = eee.nextSiblingElement()) {
QString tag(eee.tagName());
Spatium val(eee.text().toDouble() / 10.0);
if (tag == "system-margins")
;
else if (tag == "system-distance") {
if (preferences.musicxmlImportLayout) {
score->style()->set(ST_minSystemDistance, val);
qDebug("system distance %f", val.val());
}
}
else if (tag == "top-system-distance")
;
else
domError(eee);
}
}
else if (tag == "staff-layout") {
for (QDomElement eee = ee.firstChildElement(); !eee.isNull(); eee = eee.nextSiblingElement()) {
QString tag(eee.tagName());
Spatium val(eee.text().toDouble() / 10.0);
if (tag == "staff-distance") {
if (preferences.musicxmlImportLayout)
score->style()->set(ST_staffDistance, val);
}
else
domError(eee);
}
}
else if (tag == "music-font")
domNotImplemented(ee);
else if (tag == "word-font")
domNotImplemented(ee);
else if (tag == "lyric-font")
domNotImplemented(ee);
else if (tag == "appearance")
domNotImplemented(ee);
else if (tag == "lyric-language")
domNotImplemented(ee);
else
domError(ee);
}
score->setDefaultsRead(true); // TODO only if actually succeeded ?
// IMPORT_LAYOUT END
}
else if (tag == "movement-number")
score->setMetaTag("movementNumber", e.text());
else if (tag == "movement-title")
score->setMetaTag("movementTitle", e.text());
else if (tag == "credit") {
QString page = e.attribute(QString("page"), "1");
// handle only page 1 credits (to extract title etc.)
if (page == "1") {
// multiple credit-words elements may be present,
// which are appended
// use the position info from the first one
// font information is ignored, credits will be styled
bool creditWordsRead = false;
double defaultx = 0;
double defaulty = 0;
QString justify;
QString halign;
QString valign;
QString crwords;
for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
QString tag(ee.tagName());
if (tag == "credit-words") {
// IMPORT_LAYOUT
if (!creditWordsRead) {
defaultx = ee.attribute(QString("default-x")).toDouble();
defaulty = ee.attribute(QString("default-y")).toDouble();
justify = ee.attribute(QString("justify"));
halign = ee.attribute(QString("halign"));
valign = ee.attribute(QString("valign"));
creditWordsRead = true;
}
crwords += ee.text();
}
else
domError(ee);
}
if (crwords != "") {
CreditWords* cw = new CreditWords(defaultx, defaulty, justify, halign, valign, crwords);
credits.append(cw);
}
}
}
else
domError(e);
}
// add brackets where required
/*
qDebug("partGroupList");
for (int i = 0; i < (int) partGroupList.size(); i++) {
MusicXmlPartGroup* pg = partGroupList[i];
qDebug("part-group span %d start %d type %d barlinespan %d",
pg->span, pg->start, pg->type, pg->barlineSpan);
}
*/
// set of (typically multi-staff) parts containing one or more explicit brackets
// spanning only that part: these won't get an implicit brace later
// e.g. a two-staff piano part with an explicit brace
QSet<Part const* const> partSet;
// handle the explicit brackets
const QList<Part*>& il = score->parts();
for (int i = 0; i < (int) partGroupList.size(); i++) {
MusicXmlPartGroup* pg = partGroupList[i];
// add part to set
if (pg->span == 1)
partSet << il.at(pg->start);
// determine span in staves
int stavesSpan = 0;
for (int j = 0; j < pg->span; j++)
stavesSpan += il.at(pg->start + j)->nstaves();
// add bracket and set the span
// TODO: use group-symbol default-x to determine horizontal order of brackets
if (pg->type == NO_BRACKET)
il.at(pg->start)->staff(0)->setBracket(0, NO_BRACKET);
else
il.at(pg->start)->staff(0)->addBracket(BracketItem(pg->type, stavesSpan));
if (pg->barlineSpan)
il.at(pg->start)->staff(0)->setBarLineSpan(pg->span);
}
// handle the implicit brackets:
// multi-staff parts w/o explicit brackets get a brace
foreach(Part const* const p, il) {
if (p->nstaves() > 1 && !partSet.contains(p)) {
p->staff(0)->addBracket(BracketItem(BRACKET_BRACE, p->nstaves()));
p->staff(0)->setBarLineSpan(p->nstaves());
}
}
// having read all parts (meaning all segments have been created),
// now attach all jumps and markers to segments
// simply use the first SegChordRest in the measure
for (int i = 0; i < jumpsMarkers.size(); i++) {
Segment* seg = jumpsMarkers.at(i).meas()->first(Segment::SegChordRest);
qDebug("jumpsMarkers jm %p meas %p ",
jumpsMarkers.at(i).el(), jumpsMarkers.at(i).meas());
if (seg) {
qDebug("attach to seg %p", seg);
seg->add(jumpsMarkers.at(i).el());
}
else {
qDebug("no segment found");
}
}
}
//---------------------------------------------------------
// partGroupStart
//---------------------------------------------------------
typedef std::map<int,MusicXmlPartGroup*> MusicXmlPartGroupMap;
/**
Store part-group start with number \a n, first part \a p and symbol / \a s in the partGroups
map \a pgs for later reference, as at this time insufficient information is available to be able
to generate the brackets.
*/
static void partGroupStart(MusicXmlPartGroupMap& pgs, int n, int p, QString s, bool barlineSpan)
{
// qDebug("partGroupStart number=%d part=%d symbol=%s\n", n, p, s.toLatin1().data());
if (pgs.count(n) > 0) {
qDebug("part-group number=%d already active", n);
return;
}
BracketType bracketType = NO_BRACKET;
if (s == "")
; // ignore (handle as NO_BRACKET)
else if (s == "none")
; // already set to NO_BRACKET
else if (s == "brace")
bracketType = BRACKET_BRACE;
else if (s == "bracket")
bracketType = BRACKET_NORMAL;
else if (s == "line")
bracketType = BRACKET_LINE;
else if (s == "square")
bracketType = BRACKET_SQUARE;
else {
qDebug("part-group symbol=%s not supported", s.toLatin1().data());
return;
}
MusicXmlPartGroup* pg = new MusicXmlPartGroup;
pg->span = 0;
pg->start = p;
pg->barlineSpan = barlineSpan,
pg->type = bracketType;
pgs[n] = pg;
}
//---------------------------------------------------------
// partGroupStop
//---------------------------------------------------------
/**
Handle part-group stop with number \a n and part \a p.
For part group n, the start part, span (in parts) and type are now known.
To generate brackets, the span in staves must also be known.
*/
static void partGroupStop(MusicXmlPartGroupMap& pgs, int n, int p,
MusicXmlPartGroupList& pgl)
{
if (pgs.count(n) == 0) {
qDebug("part-group number=%d not active", n);
return;
}
pgs[n]->span = p - pgs[n]->start;
// qDebug("part-group number=%d start=%d span=%d type=%d",
// n, pgs[n]->start, pgs[n]->span, pgs[n]->type);
pgl.push_back(pgs[n]);
pgs.erase(n);
}
//---------------------------------------------------------
// xmlPartList
//---------------------------------------------------------
/**
Read the MusicXML part-list element.
*/
void MusicXml::xmlPartList(QDomElement e)
{
int scoreParts = 0;
MusicXmlPartGroupMap partGroups;
for (; !e.isNull(); e = e.nextSiblingElement()) {
if (e.tagName() == "score-part")
xmlScorePart(e.firstChildElement(), e.attribute(QString("id")), scoreParts);
else if (e.tagName() == "part-group") {
bool barlineSpan = true;
int number = e.attribute(QString("number")).toInt() - 1;
QString symbol = "";
QString type = e.attribute(QString("type"));
for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
if (ee.tagName() == "group-symbol")
symbol = ee.text();
else if (ee.tagName() == "group-barline") {
if (ee.text() == "no")
barlineSpan = false;
}
else
domError(ee);
}
if (type == "start")
partGroupStart(partGroups, number, scoreParts, symbol, barlineSpan);
else if (type == "stop")
partGroupStop(partGroups, number, scoreParts, partGroupList);
else
qDebug("Import MusicXml:xmlPartList: part-group type '%s' not supported",
qPrintable(type));
}
else
domError(e);
}
}
//---------------------------------------------------------
// xmlScorePart
//---------------------------------------------------------
/**
Read the MusicXML score-part element.
*/
void MusicXml::xmlScorePart(QDomElement e, QString id, int& parts)
{
Part* part = 0;
foreach(Part* p, score->parts()) {
if (p->id() == id) {
part = p;
parts++;
break;
}
}
if (part == 0) {
// Some versions of Rosegarden (at least v11.02) mention parts
// in the <part-list>, but don't contain the corresponding <part>s.
// (i.e. the part-list is overcomplete).
// These parts are reported but can safely be ignored.
qDebug("Import MusicXml::xmlScorePart: cannot find part %s", qPrintable(id));
return;
}
qDebug("MusicXml::xmlScorePart: instruments part %s", qPrintable(id));
drumsets.insert(id, MusicXMLDrumset());
for (; !e.isNull(); e = e.nextSiblingElement()) {
if (e.tagName() == "part-name") {
// OK? (ws) Yes it should be ok.part-name is display in front of staff in finale. (la)
part->setLongName(e.text());
// part->setTrackName(e.text());
}
else if (e.tagName() == "part-name-display") {
// TODO
domNotImplemented(e);
}
else if (e.tagName() == "part-abbreviation") {
part->setShortName(e.text());
}
else if (e.tagName() == "part-abbreviation-display") {
// TODO
domNotImplemented(e);
}
else if (e.tagName() == "score-instrument") {
QString instrId = e.attribute("id");
for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
if (ee.tagName() == "ensemble")
domNotImplemented(e);
else if (ee.tagName() == "instrument-name") {
qDebug("MusicXml::xmlScorePart: instrument id %s name %s",
qPrintable(instrId), qPrintable(ee.text()));
drumsets[id].insert(instrId, MusicXMLDrumInstrument(ee.text()));
// part-name or instrument-name?
if (part->longName().isEmpty())
part->setLongName(ee.text());
}
else if (ee.tagName() == "instrument-sound")
domNotImplemented(e);
else if (ee.tagName() == "solo")
domNotImplemented(e);
else if (ee.tagName() == "virtual-instrument")
domNotImplemented(e);
else
domError(ee);
}
}
else if (e.tagName() == "midi-instrument") {
QString instrId = e.attribute("id");
for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
if (ee.tagName() == "midi-bank")
domNotImplemented(e);
else if (ee.tagName() == "midi-channel")
part->setMidiChannel(ee.text().toInt() - 1);
else if (ee.tagName() == "midi-program")
part->setMidiProgram(ee.text().toInt() - 1);
else if (ee.tagName() == "midi-unpitched") {
qDebug("MusicXml::xmlScorePart: instrument id %s midi-unpitched %s",
qPrintable(instrId), qPrintable(ee.text()));
if (drumsets[id].contains(instrId))
drumsets[id][instrId].pitch = ee.text().toInt() - 1;
}
else if (ee.tagName() == "volume") {
double vol = ee.text().toDouble();
if (vol >= 0 && vol <= 100)
part->setVolume(( vol / 100) * 127);
}
else if (ee.tagName() == "pan") {
double pan = ee.text().toDouble();
if (pan >= -90 && pan <= 90)
part->setPan( ((pan + 90) / 180) * 127 );
}
else
domError(ee);
}
}
else
domError(e);
}
/*
score->parts().push_back(part);
Staff* staff = new Staff(score, part, 0);
part->staves()->push_back(staff);
score->staves().push_back(staff);
*/
// dump drumset for this part
/*
MusicXMLDrumsetIterator i(drumsets[id]);
while (i.hasNext()) {
i.next();
qDebug("%s %s", qPrintable(i.key()), qPrintable(i.value().toString()));
}
*/
}
//---------------------------------------------------------
// VoiceDesc
//---------------------------------------------------------
VoiceDesc::VoiceDesc() : _staff(-1), _voice(-1), _overlaps(false)
{
for (int i = 0; i < MAX_STAVES; ++i) {
_chordRests[i] = 0;
_staffAlloc[i] = -1;
_voices[i] = -1;
}
}
void VoiceDesc::incrChordRests(int s)
{
if (0 <= s && s < MAX_STAVES)
_chordRests[s]++;
}
int VoiceDesc::numberChordRests() const
{
int res = 0;
for (int i = 0; i < MAX_STAVES; ++i)
res += _chordRests[i];
return res;
}
int VoiceDesc::preferredStaff() const
{
int max = 0;
int res = 0;
for (int i = 0; i < MAX_STAVES; ++i)
if (_chordRests[i] > max) {
max = _chordRests[i];
res = i;
}
return res;
}
QString VoiceDesc::toString() const
{
QString res = "[";
for (int i = 0; i < MAX_STAVES; ++i)
res += QString(" %1").arg(_chordRests[i]);
res += QString(" ] overlaps %1").arg(_overlaps);
if (_overlaps) {
res += " staffAlloc [";
for (int i = 0; i < MAX_STAVES; ++i)
res += QString(" %1").arg(_staffAlloc[i]);
res += " ] voices [";
for (int i = 0; i < MAX_STAVES; ++i)
res += QString(" %1").arg(_voices[i]);
res += " ]";
}
else
res += QString(" staff %1 voice %2").arg(_staff + 1).arg(_voice + 1);
return res;
}
//---------------------------------------------------------
// fillGap -- fill one gap (tstart - tend) in this track in this measure with rest(s)
//---------------------------------------------------------
static void fillGap(Measure* measure, int track, int tstart, int tend)
{
int ctick = tstart;
int restLen = tend - tstart;
// qDebug("\nfillGIFV fillGap(measure %p track %d tstart %d tend %d) restLen %d len",
// measure, track, tstart, tend, restLen);
// note: as MScore::division (#ticks in a quarter note) equals 480
// MScore::division / 64 (#ticks in a 256th note) uequals 7.5 but is rounded down to 7
while (restLen > MScore::division / 64) {
int len = restLen;
TDuration d(TDuration::V_INVALID);
if (measure->ticks() == restLen)
d.setType(TDuration::V_MEASURE);
else
d.setVal(len);
Rest* rest = new Rest(measure->score(), d);
rest->setDuration(Fraction::fromTicks(len));
rest->setTrack(track);
rest->setVisible(false);
Segment* s = measure->getSegment(rest, tstart);
s->add(rest);
len = rest->globalDuration().ticks();
// qDebug(" %d", len);
ctick += len;
restLen -= len;
}
}
//---------------------------------------------------------
// fillGapsInFirstVoices -- fill gaps in first voice of every staff in this measure for this part with rest(s)
//---------------------------------------------------------
static void fillGapsInFirstVoices(Measure* measure, Part* part)
{
int measTick = measure->tick();
int measLen = measure->ticks();
int nextMeasTick = measTick + measLen;
int staffIdx = part->score()->staffIdx(part);
/*
qDebug("fillGIFV measure %p part %p idx %d nstaves %d tick %d - %d (len %d)",
measure, part, staffIdx, part->nstaves(),
measTick, nextMeasTick, measLen);
*/
for (int st = 0; st < part->nstaves(); ++st) {
int track = (staffIdx + st) * VOICES;
int endOfLastCR = measTick;
for (Segment* s = measure->first(); s; s = s->next()) {
// qDebug("fillGIFV segment %p tp %s", s, s->subTypeName());
Element* el = s->element(track);
if (el) {
// qDebug(" el[%d] %p", track, el);
if (s->isChordRest()) {
ChordRest* cr = static_cast<ChordRest*>(el);
int crTick = cr->tick();
int crLen = cr->globalDuration().ticks();
int nextCrTick = crTick + crLen;
/*
qDebug(" chord/rest tick %d - %d (len %d)",
crTick, nextCrTick, crLen);
*/
if (crTick > endOfLastCR) {
/*
qDebug(" GAP: track %d tick %d - %d",
track, endOfLastCR, crTick);
*/
fillGap(measure, track, endOfLastCR, crTick);
}
endOfLastCR = nextCrTick;
}
}
}
if (nextMeasTick > endOfLastCR) {
/*
qDebug("fillGIFV measure end GAP: track %d tick %d - %d",
track, endOfLastCR, nextMeasTick);
*/
fillGap(measure, track, endOfLastCR, nextMeasTick);
}
}
}
//---------------------------------------------------------
// xmlPart
//---------------------------------------------------------
/**
Read the MusicXML part element.
*/
void MusicXml::xmlPart(QDomElement e, QString id)
{
// qDebug("xmlPart(id='%s')", qPrintable(id));
if (id == "") {
qDebug("MusicXML import: part without id");
return;
}
Part* part = 0;
foreach(Part* p, score->parts()) {
if (p->id() == id) {
part = p;
break;
}
}
if (part == 0) {
qDebug("Import MusicXml:xmlPart: cannot find part %s", id.toLatin1().data());
return;
}
fractionTSig = Fraction(0, 1);
tick = 0;
maxtick = 0;
prevtick = 0;
lastMeasureLen = 0;
multiMeasureRestCount = -1;
startMultiMeasureRest = false;
// initVoiceMapperAndMapVoices(e);
voicelist = pass1.getVoiceList(id);
#ifdef DEBUG_VOICE_MAPPER
// debug: print voice mapper contents
qDebug("voiceMapperStats: new staff");
for (QMap<int, VoiceDesc>::const_iterator i = voicelist.constBegin(); i != voicelist.constEnd(); ++i) {
qDebug("voiceMapperStats: voice %d staff data %s",
i.key() + 1, qPrintable(i.value().toString()));
}
#endif
if (!score->measures()->first()) {
doCredits();
}
for (int measureNr = 0; !e.isNull(); e = e.nextSiblingElement(), measureNr++) {
if (e.tagName() == "measure") {
// set the correct start tick for the measure
tick = measureStart.at(measureNr);
Measure* measure = xmlMeasure(part, e, e.attribute(QString("number")).toInt()-1, measureLength.at(measureNr));
if (measure)
fillGapsInFirstVoices(measure, part);
}
else
domError(e);
}
// qDebug("wedge list:");
auto i = spanners.constBegin();
while (i != spanners.constEnd()) {
Spanner* sp = i.key();
int tick1 = i.value().first;
int tick2 = i.value().second;
// qDebug("wedge %p tick1 %d tick2 %d", sp, tick1, tick2);
sp->setTick(tick1);
sp->setTick2(tick2);
sp->score()->addElement(sp);
++i;
}
spanners.clear();
// determine if part contains a drumset
// this is the case if any instrument has a midi-unpitched element,
// (which stored in the MusicXMLDrumInstrument pitch field)
// debug: also dump the drumset for this part
Drumset* drumset = new Drumset;
bool hasDrumset = false;
drumset->clear();
MusicXMLDrumsetIterator ii(drumsets[id]);
while (ii.hasNext()) {
ii.next();
// qDebug("%s %s", qPrintable(ii.key()), qPrintable(ii.value().toString()));
int pitch = ii.value().pitch;
if (0 <= pitch && pitch <= 127) {
hasDrumset = true;
drumset->drum(ii.value().pitch)
= DrumInstrument(ii.value().name.toLatin1().constData(),
ii.value().notehead, ii.value().line, ii.value().stemDirection);
}
}
// qDebug("hasDrumset %d", hasDrumset);
if (hasDrumset) {
// set staff type to percussion if incorrectly imported as pitched staff
// Note: part has been read, staff type already set based on clef type and staff-details
// but may be incorrect for a percussion staff that does not use a percussion clef
for (int j = 0; j < part->nstaves(); ++j)
if (part->staff(j)->lines() == 5 && !part->staff(j)->isDrumStaff())
part->staff(j)->setStaffType(score->staffType(PERC_DEFAULT_STAFF_TYPE));
// set drumset for instrument
part->instr()->setUseDrumset(true);
part->instr()->setDrumset(drumset);
part->instr()->channel(0).bank = 128;
part->instr()->channel(0).updateInitList();
}
else
delete drumset;
}
//---------------------------------------------------------
// readFiguredBassItem
//---------------------------------------------------------
static void readFiguredBassItem(FiguredBassItem* fgi, const QDomElement& de,
bool paren, bool& extend)
{
// read the <figure> node de
for (QDomElement e = de.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
const QString& tag(e.tagName());
const QString& val(e.text());
int iVal = val.toInt();
if (tag == "extend")
extend = true;
else if (tag == "figure-number") {
// MusicXML spec states figure-number is a number
// MuseScore can only handle single digit
if (1 <= iVal && iVal <= 9)
fgi->setDigit(iVal);
}
else if (tag == "prefix")
fgi->setPrefix(fgi->MusicXML2Modifier(val));
else if (tag == "suffix")
fgi->setSuffix(fgi->MusicXML2Modifier(val));
else
domError(e);
}
// set parentheses
if (paren) {
// parenthesis open
if (fgi->prefix() != FiguredBassItem::ModifierNone)
fgi->setParenth1(FiguredBassItem::ParenthesisRoundOpen); // before prefix
else if (fgi->digit() != FBIDigitNone)
fgi->setParenth2(FiguredBassItem::ParenthesisRoundOpen); // before digit
else if (fgi->suffix() != FiguredBassItem::ModifierNone)
fgi->setParenth3(FiguredBassItem::ParenthesisRoundOpen); // before suffix
// parenthesis close
if (fgi->suffix() != FiguredBassItem::ModifierNone)
fgi->setParenth4(FiguredBassItem::ParenthesisRoundClosed); // after suffix
else if (fgi->digit() != FBIDigitNone)
fgi->setParenth3(FiguredBassItem::ParenthesisRoundClosed); // after digit
else if (fgi->prefix() != FiguredBassItem::ModifierNone)
fgi->setParenth2(FiguredBassItem::ParenthesisRoundClosed); // after prefix
}
}
//---------------------------------------------------------
// Read MusicXML
//
// Set the FiguredBass state based on the MusicXML <figured-bass> node de.
// Note that onNote and ticks must be set by the MusicXML importer,
// as the required context is not present in the items DOM tree.
// Exception: if a <duration> element is present, tick can be set.
// Return true if valid, non-empty figure(s) are found
// Set extend to true if extend elements were found
//---------------------------------------------------------
static bool readFigBass(FiguredBass* fb, const QDomElement& de, int divisions, bool& extend)
{
extend = false;
bool parentheses = (de.attribute("parentheses") == "yes");
QString normalizedText;
int idx = 0;
for (QDomElement e = de.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
const QString& tag(e.tagName());
const QString& val(e.text());
if (tag == "duration") {
bool ok = true;
int duration = val.toInt(&ok);
if (ok) {
duration *= MScore::division;
duration /= divisions;
fb->setTicks(duration);
}
else
qDebug("MusicXml-Import: bad duration value: <%s>",
qPrintable(val));
}
else if (tag == "figure") {
bool figureExtend = false;
FiguredBassItem* pItem = new FiguredBassItem(fb->score(), idx++);
pItem->setTrack(fb->track());
pItem->setParent(fb);
readFiguredBassItem(pItem, e, parentheses, figureExtend);
if (figureExtend)
extend = true;
fb->appendItem(*pItem);
// add item normalized text
if (!normalizedText.isEmpty())
normalizedText.append('\n');
normalizedText.append(pItem->normalizedText());
}
else {
domError(e);
return false;
}
}
fb->setText(normalizedText); // this is the text to show while editing
bool res = !normalizedText.isEmpty();
return res;
}
//---------------------------------------------------------
// removeBeam -- // beam mode for all elements and remove the beam
//---------------------------------------------------------
static void removeBeam(Beam*& beam)
{
for (int i = 0; i < beam->elements().size(); ++i)
beam->elements().at(i)->setBeamMode(BeamMode::NONE);
delete beam;
beam = 0;
}
//---------------------------------------------------------
// handleBeamAndStemDir
//---------------------------------------------------------
static void handleBeamAndStemDir(ChordRest* cr, const BeamMode bm, const MScore::Direction sd, Beam*& beam)
{
if (!cr) return;
// create a new beam
if (bm == BeamMode::BEGIN) {
// if currently in a beam, delete it
if (beam) {
qDebug("handleBeamAndStemDir() new beam, removing previous incomplete beam %p", beam);
removeBeam(beam);
}
// create a new beam
beam = new Beam(cr->score());
beam->setTrack(cr->track());
beam->setBeamDirection(sd);
}
// add ChordRest to beam
if (beam) {
// verify still in the same track (switching voices in the middle of a beam is not supported)
// and in a beam ...
// (note no check is done on correct order of beam begin/continue/end)
if (cr->track() == beam->track()
&& (bm == BeamMode::BEGIN || bm == BeamMode::MID || bm == BeamMode::END)) {
// ... and actually add cr to the beam
beam->add(cr);
}
else {
qDebug("handleBeamAndStemDir() from track %d to track %d bm %d -> abort beam",
beam->track(), cr->track(), bm);
// ... or reset beam mode for all elements and remove the beam
removeBeam(beam);
}
}
// if no beam, set stem direction on chord itself
if (!beam) {
static_cast<Chord*>(cr)->setStemDirection(sd);
cr->setBeamMode(BeamMode::NONE);
}
// terminate the currect beam and add to the score
if (beam && bm == BeamMode::END)
beam = 0;
}
//---------------------------------------------------------
// xmlMeasure
//---------------------------------------------------------
/**
Read the MusicXML measure element.
*/
Measure* MusicXml::xmlMeasure(Part* part, QDomElement e, int number, int measureLen)
{
#ifdef DEBUG_TICK
qDebug("xmlMeasure %d begin", number);
#endif
int staves = score->nstaves();
int staff = score->staffIdx(part);
// current "tick" within this measure as fraction
// calculated using note type, backup and forward
Fraction noteTypeTickFr;
// maximum "tick" within this measure as fraction
Fraction maxNoteTypeTickFr;
// current beam
Beam* beam = 0;
if (staves == 0) {
qDebug("no staves!");
return 0;
}
// search measure for tick
Measure* measure = 0;
Measure* lastMeasure = 0;
for (MeasureBase* mb = score->measures()->first(); mb; mb = mb->next()) {
if (mb->type() != Element::MEASURE)
continue;
Measure* m = (Measure*)mb;
lastMeasure = m;
if (m->tick() == tick) {
measure = m;
break;
}
}
if (!measure) {
//
// DEBUG:
if (lastMeasure && lastMeasure->tick() > tick) {
qDebug("Measure at position %d not found!", tick);
}
measure = new Measure(score);
measure->setTick(tick);
measure->setLen(Fraction::fromTicks(measureLen));
measure->setNo(number);
score->measures()->add(measure);
} else {
// ws:
// int pstaves = part->nstaves();
// for (int i = 0; i < pstaves; ++i) {
// Staff* reals = score->staff(staff+i);
// measure->mstaff(staff+i)->lines->setLines(reals->lines());
// }
}
QString implicit = e.attribute("implicit", "no");
if (implicit == "yes")
measure->setIrregular(true);
QString cv = "1"; // current voice for chords, default is 1
QList<GraceNoteInfo> graceNotesInfos;
for (e = e.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
if (e.tagName() == "attributes")
xmlAttributes(measure, staff, e.firstChildElement());
else if (e.tagName() == "note") {
xmlNote(measure, staff, part->id(), beam, cv, e, graceNotesInfos);
moveTick(measure->tick(), tick, maxtick, noteTypeTickFr, divisions, e);
#ifdef DEBUG_TICK
qDebug(" after inserting note tick=%d", tick);
#endif
}
else if (e.tagName() == "backup") {
// moveTick(tick, maxtick, divisions, e);
moveTick(measure->tick(), tick, maxtick, noteTypeTickFr, divisions, e);
}
else if (e.tagName() == "direction") {
direction(measure, staff, e);
}
else if (e.tagName() == "print") {
// IMPORT_LAYOUT
QString newSystem = e.attribute("new-system", "no");
QString newPage = e.attribute("new-page", "no");
//
// in MScore the break happens _after_ the marked measure:
//
Measure* pm = (Measure*)(measure->prev()); // TODO: MeasureBase
if (pm == 0)
qDebug("ImportXml: warning: break on first measure");
else {
if (preferences.musicxmlImportBreaks
&& (newSystem == "yes" || newPage == "yes")) {
LayoutBreak* lb = new LayoutBreak(score);
lb->setTrack(staff * VOICES);
lb->setLayoutBreakType(
newSystem == "yes" ? LayoutBreak::LINE : LayoutBreak::PAGE
);
pm->add(lb);
}
}
for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
if (ee.tagName() == "system-layout") {
}
else if (ee.tagName() == "staff-layout") {
}
else if (ee.tagName() == "measure-numbering") {
}
else
domError(ee);
}
// IMPORT_LAYOUT END
}
else if (e.tagName() == "forward") {
// moveTick(tick, maxtick, divisions, e);
moveTick(measure->tick(), tick, maxtick, noteTypeTickFr, divisions, e);
}
else if (e.tagName() == "barline") {
QString loc = e.attribute("location", "right");
QString barStyle;
QString endingNumber;
QString endingType;
QString endingText;
QString repeat;
for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
if (ee.tagName() == "bar-style")
barStyle = ee.text();
else if (ee.tagName() == "ending") {
endingNumber = ee.attribute("number");
endingType = ee.attribute("type");
endingText = ee.text();
}
else if (ee.tagName() == "repeat")
repeat = ee.attribute("direction");
else
domError(ee);
}
if ((barStyle != "") || (repeat != "")) {
BarLine* barLine = new BarLine(score);
bool visible = true;
if (barStyle == "light-heavy" && repeat == "backward") {
barLine->setBarLineType(END_REPEAT);
}
else if (barStyle == "heavy-light" && repeat == "forward") {
barLine->setBarLineType(START_REPEAT);
}
else if (barStyle == "light-heavy" && repeat.isEmpty())
barLine->setBarLineType(END_BAR);
else if (barStyle == "regular")
barLine->setBarLineType(NORMAL_BAR);
else if (barStyle == "dashed")
barLine->setBarLineType(BROKEN_BAR);
else if (barStyle == "dotted")
barLine->setBarLineType(DOTTED_BAR);
else if (barStyle == "light-light")
barLine->setBarLineType(DOUBLE_BAR);
/*
else if (barStyle == "heavy-light")
;
else if (barStyle == "heavy-heavy")
;
*/
else if (barStyle == "none") {
barLine->setBarLineType(NORMAL_BAR);
visible = false;
}
else if (barStyle == "") {
if (repeat == "backward")
barLine->setBarLineType(END_REPEAT);
else if (repeat == "forward")
barLine->setBarLineType(START_REPEAT);
else
qDebug("ImportXml: warning: empty bar type");
}
else
qDebug("unsupported bar type <%s>", barStyle.toLatin1().data());
barLine->setTrack(staff * VOICES);
if (barLine->barLineType() == START_REPEAT) {
measure->setRepeatFlags(RepeatStart);
}
else if (barLine->barLineType() == END_REPEAT) {
measure->setRepeatFlags(RepeatEnd);
}
else {
if (loc == "right")
measure->setEndBarLineType(barLine->barLineType(), false, visible);
else if (measure->prevMeasure())
measure->prevMeasure()->setEndBarLineType(barLine->barLineType(), false, visible);
}
}
if (!(endingNumber.isEmpty() && endingType.isEmpty())) {
if (endingNumber.isEmpty())
qDebug("ImportXml: warning: empty ending number");
else if (endingType.isEmpty())
qDebug("ImportXml: warning: empty ending type");
else {
QStringList sl = endingNumber.split(",", QString::SkipEmptyParts);
QList<int> iEndingNumbers;
bool unsupported = false;
foreach(const QString &s, sl) {
int iEndingNumber = s.toInt();
if (iEndingNumber <= 0) {
unsupported = true;
break;
}
iEndingNumbers.append(iEndingNumber);
}
if (unsupported)
qDebug("ImportXml: warning: unsupported ending number <%s>",
endingNumber.toLatin1().data());
else {
if (endingType == "start") {
Volta* volta = new Volta(score);
volta->setTrack(staff * VOICES);
volta->setText(endingText.isEmpty() ? endingNumber : endingText);
// LVIFIX TODO also support endings "1 - 3"
volta->endings().clear();
volta->endings().append(iEndingNumbers);
volta->setTick(measure->tick());
score->addElement(volta);
lastVolta = volta;
}
else if (endingType == "stop") {
if (lastVolta) {
lastVolta->setVoltaType(VoltaType::CLOSED);
lastVolta->setTick2(measure->tick() + measure->ticks());
lastVolta = 0;
}
else {
qDebug("lastVolta == 0 on stop");
}
}
else if (endingType == "discontinue") {
if (lastVolta) {
lastVolta->setVoltaType(VoltaType::OPEN);
lastVolta->setTick2(measure->tick() + measure->ticks());
lastVolta = 0;
}
else {
qDebug("lastVolta == 0 on discontinue");
}
}
else
qDebug("ImportXml: warning: unsupported ending type <%s>",
endingType.toLatin1().data());
}
}
}
}
else if (e.tagName() == "sound")
domNotImplemented(e);
else if (e.tagName() == "harmony")
xmlHarmony(e, tick, measure, staff);
else if (e.tagName() == "figured-bass") {
if (figBass) {
qDebug("more than one figured bass element on note not supported");
}
else {
// read figured bass element to attach to next note
figBassExtend = false;
bool mustkeep = false;
figBass = new FiguredBass(score);
// mustkeep = figBass->readMusicXML(e, divisions, figBassExtend);
mustkeep = readFigBass(figBass, e, divisions, figBassExtend);
// qDebug("xmlMeaure: fb mustkeep %d extend %d", mustkeep, figBassExtend);
if (!mustkeep) {
delete figBass;
figBass = 0;
}
}
}
else
domError(e);
}
#ifdef DEBUG_TICK
qDebug("end_of_measure measure->tick()=%d maxtick=%d lastMeasureLen=%d measureLen=%d tsig=%d(%s)",
measure->tick(), maxtick, lastMeasureLen, measureLen,
fractionTSig.ticks(), qPrintable(fractionTSig.print()));
#endif
// can't have beams extending into the next measure
if (beam)
removeBeam(beam);
// TODO:
// - how to handle fractionTSig.isZero (shouldn't happen ?)
// - how to handle unmetered music
if (fractionTSig.isValid() && !fractionTSig.isZero())
measure->setTimesig(fractionTSig);
#if 1 // previous code
// measure->setLen(Fraction::fromTicks(measureLen));
lastMeasureLen = measureLen;
tick = maxtick;
#endif
// multi-measure rest handling:
// the first measure in a multi-measure rest gets setBreakMultiMeasureRest(true)
// and count down the remaining number of measures
// the first measure after a multi-measure rest gets setBreakMultiMeasureRest(true)
// for all other measures breakMultiMeasureRest is unchanged (stays default (false))
if (startMultiMeasureRest) {
measure->setBreakMultiMeasureRest(true);
startMultiMeasureRest = false;
}
else {
if (multiMeasureRestCount > 0) {
// measure is continuation of a multi-measure rest
--multiMeasureRestCount;
}
else if (multiMeasureRestCount == 0) {
// measure is first measure after a multi-measure rest
measure->setBreakMultiMeasureRest(true);
--multiMeasureRestCount;
}
}
return measure;
}
//---------------------------------------------------------
// setSLinePlacement -- helper for direction
//---------------------------------------------------------
static void setSLinePlacement(SLine* sli, float /*s*/, const QString pl, bool hasYoff, qreal yoff)
{
/*
qDebug("setSLinePlacement s=%g pl='%s' hasy=%d yoff=%g",
s, qPrintable(pl), hasYoff, yoff
);
*/
float offs = 0.0;
if (hasYoff) offs = yoff;
else {
// MuseScore 1.x compatible offsets
if (pl == "above")
sli->setPlacement(Element::ABOVE);
else if (pl == "below")
sli->setPlacement(Element::BELOW);
else
qDebug("setSLinePlacement invalid placement '%s'", qPrintable(pl));
}
sli->setYoff(offs);
//qDebug(" -> offs*s=%g", offs * s);
}
//---------------------------------------------------------
// metronome
//---------------------------------------------------------
static QString ucs4ToString(int uc)
{
QString s;
if (uc & 0xffff0000) {
s = QChar(QChar::highSurrogate(uc));
s += QChar(QChar::lowSurrogate(uc));
}
else
s = QChar(uc);
return s;
}
/**
Read the MusicXML metronome element.
*/
/*
<metronome parentheses="yes">
<beat-unit>quarter</beat-unit>
<beat-unit-dot/>
<per-minute>50</per-minute>
</metronome>
<metronome parentheses="yes">
<beat-unit>quarter</beat-unit>
<beat-unit-dot/>
<beat-unit>half</beat-unit>
<beat-unit-dot/>
</metronome>
*/
static void metronome(QDomElement e, Text* t)
{
if (!t) return;
bool textAdded = false;
QString tempoText = t->text();
QString parenth = e.attribute("parentheses");
if (parenth == "yes")
tempoText += "(";
for (e = e.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
QString txt = e.text();
if (e.tagName() == "beat-unit") {
if (textAdded) tempoText += " = ";
if (txt == "breve") tempoText += ucs4ToString(0x1d15c);
else if (txt == "whole") tempoText += ucs4ToString(0x1d15d);
else if (txt == "half") tempoText += ucs4ToString(0x1d15e);
else if (txt == "quarter") tempoText += ucs4ToString(0x1d15f);
else if (txt == "eighth") tempoText += ucs4ToString(0x1d160);
else if (txt == "16th") tempoText += ucs4ToString(0x1d161);
else if (txt == "32nd") tempoText += ucs4ToString(0x1d162);
else if (txt == "64th") tempoText += ucs4ToString(0x1d163);
else tempoText += txt;
textAdded = true;
}
else if (e.tagName() == "beat-unit-dot") {
tempoText += ucs4ToString(0x1d16d);
textAdded = true;
}
else if (e.tagName() == "per-minute") {
if (textAdded) tempoText += " = ";
tempoText += txt;
textAdded = true;
}
else
domError(e);
} // for (e = e.firstChildElement(); ...
if (parenth == "yes")
tempoText += ")";
t->setText(tempoText);
}
//---------------------------------------------------------
// addElement
//---------------------------------------------------------
static void addElement(Element* el, bool hasYoffset, int staff, int rstaff, Score* /* score */, QString& placement,
qreal rx, qreal ry, int /* offset */, Measure* measure, int tick)
{
if (hasYoffset) /* el->setYoff(yoffset) */; // TODO is this still necessary ? Some element types do ot support this
else {
el->setPlacement(placement == "above" ? Element::ABOVE : Element::BELOW);
// double y = (staff + rstaff) * (score->styleD(ST_staffDistance) + 4); // TODO 4 = #lines/staff - 1
// y += (placement == "above" ? -3 : 5);
// y *= score->spatium();
// el->setReadPos(QPoint(0, y));
}
el->setUserOff(QPointF(rx, ry));
// el->setMxmlOff(offset);
el->setTrack((staff + rstaff) * VOICES);
Segment* s = measure->getSegment(Segment::SegChordRest, tick);
s->add(el);
}
//---------------------------------------------------------
// direction
//---------------------------------------------------------
/**
Read the MusicXML direction element.
*/
// LVI FIXME: introduce offset concept to mscore.
// offset changes only the print position (not the tick), but unlike relative-x
// it is expressed in terms of divisions (MusicXML direction.dtd)
// even though the DTD does not mention it, practically speaking
// offset and relative-x are mutually exclusive
// Typical example:
// <direction placement="above">
// <direction-type>
// <words>Fine</words>
// </direction-type>
// <sound fine="yes"/>
// </direction>
// Note: multiple direction-type (and multiple words) elements may be present,
// and at least one direction-type must be present but sound is optional and
// at most one can be present.
void MusicXml::direction(Measure* measure, int staff, QDomElement e)
{
QString placement = e.attribute("placement");
QString dirType;
QString type;
QString txt;
QString lang;
QString fontWeight = "";
QString fontStyle = "";
QString fontSize = "";
int offset = 0; // not supported yet
int rstaff = 0;
QStringList dynamics;
// int spread;
qreal rx = 0.0;
qreal ry = 0.0;
qreal yoffset = 0.0; // actually this is default-y
// qreal xoffset;
bool hasYoffset = false;
QString dynaVelocity = "";
QString tempo = "";
QString rehearsal = "";
QString sndCapo = "";
QString sndCoda = "";
QString sndDacapo = "";
QString sndDalsegno = "";
QString sndSegno = "";
QString sndFine = "";
bool coda = false;
bool segno = false;
int ottavasize = 0;
bool pedalLine = false;
int number = 1;
QString lineEnd;
// qreal endLength;
QString lineType;
QDomElement metrEl;
for (e = e.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
if (e.tagName() == "direction-type") {
for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
// IMPORT_LAYOUT
dirType = ee.tagName();
if (preferences.musicxmlImportLayout) {
ry = ee.attribute(QString("relative-y"), "0").toDouble() * -.1;
rx = ee.attribute(QString("relative-x"), "0").toDouble() * .1;
yoffset = ee.attribute("default-y").toDouble(&hasYoffset) * -0.1;
// xoffset = ee.attribute("default-x", "0.0").toDouble() * 0.1;
}
if (dirType == "words") {
txt = ee.text();
lang = ee.attribute(QString("xml:lang"), "it");
fontWeight = ee.attribute(QString("font-weight"));
fontSize = ee.attribute(QString("font-size"));
fontStyle = ee.attribute(QString("font-style"));
}
else if (dirType == "rehearsal") {
rehearsal = ee.text();
}
else if (dirType == "pedal") {
type = ee.attribute(QString("type"));
pedalLine = ee.attribute(QString("line")) == "yes";
}
else if (dirType == "dynamics") {
QDomElement eee = ee.firstChildElement();
if (!eee.isNull()) {
if (eee.tagName() == "other-dynamics")
dynamics.push_back(eee.text());
else
dynamics.push_back(eee.tagName());
}
}
else if (dirType == "wedge") {
type = ee.attribute(QString("type"));
// spread = ee.attribute(QString("spread"), "0").toInt();
}
else if (dirType == "dashes") {
type = ee.attribute(QString("type"));
number = ee.attribute(QString("number"), "1").toInt();
}
else if (dirType == "bracket") {
type = ee.attribute(QString("type"));
number = ee.attribute(QString("number"), "1").toInt();
lineEnd = ee.attribute(QString("line-end"), "none");
// endLength = ee.attribute(QString("end-length"), "0").toDouble() * 0.1;
lineType = ee.attribute(QString("line-type"), "solid");
}
else if (dirType == "metronome")
metrEl = ee;
else if (dirType == "octave-shift") {
type = ee.attribute(QString("type"));
ottavasize = ee.attribute(QString("size"), "0").toInt();
}
else if (dirType == "coda")
coda = true;
else if (dirType == "segno")
segno = true;
else if (dirType == "other-direction")
; // application-specific, non-standard direction: ignore
else
domError(ee);
}
}
else if (e.tagName() == "sound") {
// attr: dynamics, tempo
// LVIFIX: TODO coda and segno should be numbered uniquely
sndCapo = e.attribute("capo");
sndCoda = e.attribute("coda");
sndDacapo = e.attribute("dacapo");
sndDalsegno = e.attribute("dalsegno");
sndFine = e.attribute("fine");
sndSegno = e.attribute("segno");
tempo = e.attribute("tempo");
dynaVelocity = e.attribute("dynamics");
}
else if (e.tagName() == "offset")
offset = (e.text().toInt() * MScore::division)/divisions;
else if (e.tagName() == "staff") {
// DEBUG: <staff>0</staff>
rstaff = e.text().toInt() - 1;
if (rstaff < 0) // ???
rstaff = 0;
}
else if (e.tagName() == "voice")
domNotImplemented(e);
else
domError(e);
} // for (e = e.firstChildElement(); ...
/*
qDebug(" tempo=%s txt=%s metrEl=%s coda=%d segno=%d sndCapo=%s sndCoda=%s"
" sndDacapo=%s sndDalsegno=%s sndFine=%s sndSegno=%s",
tempo.toLatin1().data(),
txt.toLatin1().data(),
qPrintable(metrEl.tagName()),
coda,
segno,
sndCapo.toLatin1().data(),
sndCoda.toLatin1().data(),
sndDacapo.toLatin1().data(),
sndDalsegno.toLatin1().data(),
sndFine.toLatin1().data(),
sndSegno.toLatin1().data()
);
*/
// Try to recognize the various repeats
QString repeat = "";
// Easy cases first
if (coda) repeat = "coda";
if (segno) repeat = "segno";
// As sound may be missing, next do a wild-card match with known repeat texts
QString lowerTxt = txt.toLower();
QRegExp daCapo("d\\.? *c\\.?|da *capo");
QRegExp daCapoAlFine("d\\.? *c\\.? *al *fine|da *capo *al *fine");
QRegExp daCapoAlCoda("d\\.? *c\\.? *al *coda|da *capo *al *coda");
QRegExp dalSegno("d\\.? *s\\.?|d[ae]l *segno");
QRegExp dalSegnoAlFine("d\\.? *s\\.? *al *fine|d[ae]l *segno *al *fine");
QRegExp dalSegnoAlCoda("d\\.? *s\\.? *al *coda|d[ae]l *segno *al *coda");
QRegExp fine("fine");
QRegExp toCoda("to *coda");
if (daCapo.exactMatch(lowerTxt)) repeat = "daCapo";
if (daCapoAlFine.exactMatch(lowerTxt)) repeat = "daCapoAlFine";
if (daCapoAlCoda.exactMatch(lowerTxt)) repeat = "daCapoAlCoda";
if (dalSegno.exactMatch(lowerTxt)) repeat = "dalSegno";
if (dalSegnoAlFine.exactMatch(lowerTxt)) repeat = "dalSegnoAlFine";
if (dalSegnoAlCoda.exactMatch(lowerTxt)) repeat = "dalSegnoAlCoda";
if (fine.exactMatch(lowerTxt)) repeat = "fine";
if (toCoda.exactMatch(lowerTxt)) repeat = "toCoda";
// If that did not work, try to recognize a sound attribute
if (repeat == "" && sndCoda != "") repeat = "coda";
if (repeat == "" && sndDacapo != "") repeat = "daCapo";
if (repeat == "" && sndDalsegno != "") repeat = "dalSegno";
if (repeat == "" && sndFine != "") repeat = "fine";
if (repeat == "" && sndSegno != "") repeat = "segno";
// If a repeat was found, assume words is no longer needed
if (repeat != "") txt = "";
/*
qDebug(" txt=%s repeat=%s",
txt.toLatin1().data(),
repeat.toLatin1().data()
);
*/
if (repeat != "") {
Jump* jp = 0;
Marker* m = 0;
if (repeat == "segno") {
m = new Marker(score);
// note: Marker::read() also contains code to set text style based on type
// avoid duplicated code
m->setTextStyleType(TEXT_STYLE_REPEAT_LEFT);
// apparently this MUST be after setTextStyle
m->setMarkerType(MarkerType::SEGNO);
}
else if (repeat == "coda") {
m = new Marker(score);
m->setTextStyleType(TEXT_STYLE_REPEAT_LEFT);
m->setMarkerType(MarkerType::CODA);
}
else if (repeat == "fine") {
m = new Marker(score);
m->setTextStyleType(TEXT_STYLE_REPEAT_RIGHT);
m->setMarkerType(MarkerType::FINE);
}
else if (repeat == "toCoda") {
m = new Marker(score);
m->setTextStyleType(TEXT_STYLE_REPEAT_RIGHT);
m->setMarkerType(MarkerType::TOCODA);
}
else if (repeat == "daCapo") {
jp = new Jump(score);
jp->setTextStyleType(TEXT_STYLE_REPEAT_RIGHT);
jp->setJumpType(JumpType::DC);
}
else if (repeat == "daCapoAlCoda") {
jp = new Jump(score);
jp->setTextStyleType(TEXT_STYLE_REPEAT_RIGHT);
jp->setJumpType(JumpType::DC_AL_CODA);
}
else if (repeat == "daCapoAlFine") {
jp = new Jump(score);
jp->setTextStyleType(TEXT_STYLE_REPEAT_RIGHT);
jp->setJumpType(JumpType::DC_AL_FINE);
}
else if (repeat == "dalSegno") {
jp = new Jump(score);
jp->setTextStyleType(TEXT_STYLE_REPEAT_RIGHT);
jp->setJumpType(JumpType::DS);
}
else if (repeat == "dalSegnoAlCoda") {
jp = new Jump(score);
jp->setTextStyleType(TEXT_STYLE_REPEAT_RIGHT);
jp->setJumpType(JumpType::DS_AL_CODA);
}
else if (repeat == "dalSegnoAlFine") {
jp = new Jump(score);
jp->setTextStyleType(TEXT_STYLE_REPEAT_RIGHT);
jp->setJumpType(JumpType::DS_AL_FINE);
}
if (jp) {
jp->setTrack((staff + rstaff) * VOICES);
qDebug("jumpsMarkers adding jm %p meas %p",jp, measure);
jumpsMarkers.append(JumpMarkerDesc(jp, measure));
}
if (m) {
m->setTrack((staff + rstaff) * VOICES);
qDebug("jumpsMarkers adding jm %p meas %p",m, measure);
jumpsMarkers.append(JumpMarkerDesc(m, measure));
}
}
if ((dirType == "words" && repeat == "") || dirType == "metronome") {
/*
qDebug("words txt='%s' metrEl='%s' tempo='%s' pl='%s' hasyoffs=%d fsz='%s' fst='%s' fw='%s'",
txt.toUtf8().data(),
qPrintable(metrEl.tagName()),
tempo.toUtf8().data(),
placement.toUtf8().data(),
hasYoffset,
fontSize.toUtf8().data(),
fontStyle.toUtf8().data(),
fontWeight.toUtf8().data()
);
*/
Text* t;
if (tempo != "" && tempo.toDouble() > 0) {
t = new TempoText(score);
double tpo = tempo.toDouble()/60.0;
((TempoText*) t)->setTempo(tpo);
score->setTempo(tick, tpo);
}
else {
t = new Text(score);
t->setTextStyleType(TEXT_STYLE_TECHNIQUE);
}
if (!fontSize.isEmpty() || !fontStyle.isEmpty() || !fontWeight.isEmpty()) {
if (!fontSize.isEmpty()) {
bool ok = true;
float size = fontSize.toFloat(&ok);
if (ok)
t->setSize(size);
}
t->setItalic(fontStyle == "italic");
t->setBold(fontWeight == "bold");
}
t->setText(txt);
if (metrEl.tagName() != "") metronome(metrEl, t);
if (hasYoffset) t->setYoff(yoffset);
addElement(t, hasYoffset, staff, rstaff, score, placement,
rx, ry, offset, measure, tick);
/*
if (hasYoffset) t->setYoff(yoffset);
else t->setAbove(placement == "above");
t->setUserOff(QPointF(rx, ry));
t->setMxmlOff(offset);
t->setTrack((staff + rstaff) * VOICES);
Segment* s = measure->getSegment(Segment::SegChordRest, tick);
s->add(t);
*/
}
else if (dirType == "rehearsal") {
Text* t = new RehearsalMark(score);
t->setText(rehearsal);
if (hasYoffset) t->setYoff(yoffset);
else t->setPlacement(placement == "above" ? Element::ABOVE : Element::BELOW);
if (hasYoffset) t->setYoff(yoffset);
addElement(t, hasYoffset, staff, rstaff, score, placement,
rx, ry, offset, measure, tick);
/*
t->setUserOff(QPointF(rx, ry));
t->setMxmlOff(offset);
t->setTrack((staff + rstaff) * VOICES);
Segment* s = measure->getSegment(Segment::SegChordRest, tick);
s->add(t);
*/
}
else if (dirType == "pedal") {
if (pedalLine) {
if (type == "start") {
if (pedal) {
qDebug("overlapping pedal lines not supported");
}
else {
pedal = new Pedal(score);
pedal->setTrack((staff + rstaff) * VOICES);
if (placement == "") placement = "below";
setSLinePlacement(pedal,
score->spatium(), placement,
hasYoffset, yoffset);
spanners[pedal] = QPair<int, int>(tick, -1);
// qDebug("pedal=%p inserted at first tick %d", pedal, tick);
}
}
else if (type == "stop") {
if (!pedal) {
qDebug("pedal line stop without start");
}
else {
spanners[pedal].second = tick;
// qDebug("pedal=%p second tick %d", pedal, tick);
pedal = 0;
}
}
else
qDebug("unknown pedal %s", qPrintable(type));
}
else {
Symbol* s = new Symbol(score);
s->setAlign(ALIGN_LEFT | ALIGN_BASELINE);
s->setOffsetType(OFFSET_SPATIUM);
if (type == "start")
s->setSym(SymId::keyboardPedalPed);
else if (type == "stop")
s->setSym(SymId::keyboardPedalUp);
else
qDebug("unknown pedal %s", type.toLatin1().data());
if (hasYoffset) s->setYoff(yoffset);
else s->setPlacement(placement == "above" ? Element::ABOVE : Element::BELOW);
s->setUserOff(QPointF(rx, ry));
// s->setMxmlOff(offset);
s->setTrack((staff + rstaff) * VOICES);
Segment* seg = measure->getSegment(Segment::SegChordRest, tick);
seg->add(s);
}
}
else if (dirType == "dynamics") {
// more than one dynamic ???
// LVIFIX: check import/export of <other-dynamics>unknown_text</...>
for (QStringList::Iterator it = dynamics.begin(); it != dynamics.end(); ++it ) {
Dynamic* dyn = new Dynamic(score);
dyn->setDynamicType(*it);
if (!dynaVelocity.isEmpty()) {
int dynaValue = round(dynaVelocity.toDouble() * 0.9);
if (dynaValue > 127)
dynaValue = 127;
else if (dynaValue < 0)
dynaValue = 0;
dyn->setVelocity( dynaValue );
}
if (hasYoffset) dyn->setYoff(yoffset);
addElement(dyn, hasYoffset, staff, rstaff, score, placement,
rx, ry, offset, measure, tick);
}
}
else if (dirType == "wedge") {
// qDebug("wedge type='%s' hairpin=%p", qPrintable(type), hairpin);
// bool above = (placement == "above");
if (type == "crescendo" || type == "diminuendo") {
if (hairpin) {
qDebug("overlapping wedge not supported");
}
else {
hairpin = new Hairpin(score);
hairpin->setHairpinType(type == "crescendo"
? Hairpin::CRESCENDO : Hairpin::DECRESCENDO);
hairpin->setPlacement(placement == "above" ? Element::ABOVE : Element::BELOW);
if (hasYoffset)
hairpin->setYoff(yoffset);
// else
// hairpin->setYoff(above ? -3 : 8);
// hairpin->setUserOff(rx, ry));
hairpin->setTrack((staff + rstaff) * VOICES);
spanners[hairpin] = QPair<int, int>(tick, -1);
// qDebug("hairpin=%p inserted at first tick %d", hairpin, tick);
}
}
else if (type == "stop") {
if (!hairpin) {
qDebug("wedge stop without start");
}
else {
spanners[hairpin].second = tick;
// qDebug("hairpin=%p second tick %d", hairpin, tick);
hairpin = 0;
}
}
else
qDebug("unknown wedge type: %s", qPrintable(type));
}
else if (dirType == "bracket") {
int n = number-1;
TextLine* b = bracket[n];
if (type == "start") {
if (b) {
qDebug("overlapping bracket number %d", number);
}
else {
b = new TextLine(score);
// what does placement affect?
//yoffset += (placement == "above" ? 0.0 : 5.0);
// store for later to set in segment
// b->setUserOff(QPointF(rx + xoffset, ry + yoffset));
// b->setMxmlOff(offset);
if (placement == "") placement = "above"; // set default
setSLinePlacement(b,
score->spatium(), placement,
hasYoffset, yoffset);
b->setBeginHook(lineEnd != "none");
if (lineEnd == "up")
b->setBeginHookHeight(-1 * b->beginHookHeight());
// hack: assume there was a words element before the bracket
if (!txt.isEmpty()) {
b->setBeginText(txt, score->textStyle(TEXT_STYLE_TEXTLINE));
}
if (lineType == "solid")
b->setLineStyle(Qt::SolidLine);
else if (lineType == "dashed")
b->setLineStyle(Qt::DashLine);
else if (lineType == "dotted")
b->setLineStyle(Qt::DotLine);
else
qDebug("unsupported line-type: %s", lineType.toLatin1().data());
b->setTrack((staff + rstaff) * VOICES);
spanners[b] = QPair<int, int>(tick, -1);
bracket[n] = b;
//qDebug("bracket=%p inserted at first tick %d", b, tick);
}
}
else if (type == "stop") {
if (!b)
qDebug("bracket stop without start, number %d", number);
else {
// TODO: MuseScore doesn't support lines which start and end on different staves
/*
QPointF userOff = b->userOff();
b->add(b->createLineSegment());
b->setUserOff(QPointF()); // restore the offset
b->setMxmlOff2(offset);
LineSegment* ls1 = b->lineSegments().front();
LineSegment* ls2 = b->lineSegments().back();
// what does placement affect?
//yoffset += (placement == "above" ? 0.0 : 5.0);
ls1->setUserOff(userOff);
ls2->setUserOff2(QPointF(rx + xoffset, ry + yoffset));
*/
b->setEndHook(lineEnd != "none");
if (lineEnd == "up")
b->setEndHookHeight(-1 * b->endHookHeight());
spanners[b].second = tick;
bracket[n] = 0;
//qDebug("bracket=%p second tick %d", b, tick);
}
}
}
else if (dirType == "dashes") {
int n = number-1;
TextLine* b = dashes[n];
if (type == "start") {
if (b) {
qDebug("overlapping dashes, number %d", number);
}
else {
b = new TextLine(score);
// what does placement affect?
//yoffset += (placement == "above" ? 0.0 : 5.0);
// store for later to set in segment
// b->setUserOff(QPointF(rx + xoffset, ry + yoffset));
// b->setMxmlOff(offset);
if (placement == "") placement = "above"; // set default
setSLinePlacement(b,
score->spatium(), placement,
hasYoffset, yoffset);
// hack: assume there was a words element before the dashes
if (!txt.isEmpty()) {
b->setBeginText(txt, score->textStyle(TEXT_STYLE_TEXTLINE));
}
b->setBeginHook(false);
b->setLineStyle(Qt::DashLine);
b->setTrack((staff + rstaff) * VOICES);
spanners[b] = QPair<int, int>(tick, -1);
dashes[n] = b;
//qDebug("dashes=%p inserted at first tick %d", b, tick);
}
}
else if (type == "stop") {
if (!b) {
qDebug("dashes stop without start, number %d", number);
}
else {
// b->setTick2(tick);
// TODO: MuseScore doesn't support lines which start and end on different staves
/*
QPointF userOff = b->userOff();
b->add(b->createLineSegment());
b->setUserOff(QPointF()); // restore the offset
b->setMxmlOff2(offset);
LineSegment* ls1 = b->lineSegments().front();
LineSegment* ls2 = b->lineSegments().back();
// what does placement affect?
//yoffset += (placement == "above" ? 0.0 : 5.0);
ls1->setUserOff(userOff);
ls2->setUserOff2(QPointF(rx + xoffset, ry + yoffset));
*/
b->setEndHook(false);
spanners[b].second = tick;
dashes[n] = 0;
//qDebug("dashes=%p second tick %d", b, tick);
}
}
}
else if (dirType == "octave-shift") {
if (type == "up" || type == "down") {
if (ottava) {
qDebug("overlapping octave-shift not supported");
}
else {
if (!(ottavasize == 8 || ottavasize == 15)) {
qDebug("unknown octave-shift size %d", ottavasize);
}
else {
ottava = new Ottava(score);
ottava->setTrack((staff + rstaff) * VOICES);
if (type == "down" && ottavasize == 8) ottava->setOttavaType(OttavaType::OTTAVA_8VA);
if (type == "down" && ottavasize == 15) ottava->setOttavaType(OttavaType::OTTAVA_15MA);
if (type == "up" && ottavasize == 8) ottava->setOttavaType(OttavaType::OTTAVA_8VB);
if (type == "up" && ottavasize == 15) ottava->setOttavaType(OttavaType::OTTAVA_15MB);
if (placement == "") placement = "above"; // set default
setSLinePlacement(ottava,
score->spatium(), placement,
hasYoffset, yoffset);
spanners[ottava] = QPair<int, int>(tick, -1);
//qDebug("ottava=%p inserted at first tick %d", ottava, tick);
}
}
}
else if (type == "stop") {
if (!ottava) {
qDebug("octave-shift stop without start");
}
else {
spanners[ottava].second = tick;
//qDebug("ottava=%p second tick %d", ottava, tick);
ottava = 0;
}
}
}
}
//---------------------------------------------------------
// setStaffLines - set stafflines and barline span for a single staff
//---------------------------------------------------------
static void setStaffLines(Score* score, int staffIdx, int stafflines)
{
score->staff(staffIdx)->setLines(stafflines);
score->staff(staffIdx)->setBarLineTo((stafflines-1) * 2);
}
//---------------------------------------------------------
// readStringData
//---------------------------------------------------------
static void readStringData(StringData* t, QDomElement de)
{
t->setFrets(25);
for (QDomElement e = de.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
const QString& tag(e.tagName());
int val = e.text().toInt();
if (tag == "staff-lines") {
if (val > 0) {
// resize the string table and init with zeroes
t->stringList() = QVector<int>(val).toList();
}
else
qDebug("Tablature::readMusicXML: illegal staff-lines %d", val);
}
else if (tag == "staff-tuning") {
int line = e.attribute("line").toInt();
QString step;
int alter = 0;
int octave = 0;
for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
const QString& tag(ee.tagName());
int val = ee.text().toInt();
if (tag == "tuning-alter")
alter = val;
else if (tag == "tuning-octave")
octave = val;
else if (tag == "tuning-step")
step = ee.text();
else
domError(ee);
}
if (0 < line && line <= t->stringList().size()) {
int pitch = MusicXMLStepAltOct2Pitch(step[0].toLatin1(), alter, octave);
if (pitch >= 0)
t->stringList()[line - 1] = pitch;
else
qDebug("Tablature::readMusicXML invalid string %d tuning step/alter/oct %s/%d/%d",
line, qPrintable(step), alter, octave);
}
}
else if (tag == "capo") {
; // not supported: silently ignored
}
else {
; // others silently ignored
}
}
}
//---------------------------------------------------------
// xmlStaffDetails
//---------------------------------------------------------
static void xmlStaffDetails(Score* score, int staff, StringData* t, QDomElement e)
{
int number = e.attribute(QString("number"), "-1").toInt();
int staffIdx = staff;
if (number != -1)
staffIdx += number - 1;
bool hasStafflines = false;
int stafflines = 5;
for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
if (ee.tagName() == "staff-lines") {
hasStafflines = true;
stafflines = ee.text().toInt();
}
else if (ee.tagName() == "staff-tuning")
; // ignore here (but prevent error message), handled by Tablature::readMusicXML
else
domNotImplemented(ee);
}
if (hasStafflines) {
if (number == -1) {
int staves = score->staff(staff)->part()->nstaves();
for (int i = 0; i < staves; ++i)
setStaffLines(score, staffIdx+i, stafflines);
}
else
setStaffLines(score, staffIdx, stafflines);
}
if (t) {
readStringData(t, e);
Instrument* i = score->staff(staff)->part()->instr();
i->setStringData(t);
}
}
//---------------------------------------------------------
// xmlAttributes
//---------------------------------------------------------
/**
Read the MusicXML attributes element.
*/
// Standard order of attributes as written by Dolet for Finale is divisions,
// key, time, staves and clef(s). For the first measure this means number of
// staves must be read first, as it determines how many key and time signatures
// must be inserted.
void MusicXml::xmlAttributes(Measure* measure, int staff, QDomElement e)
{
QString beats = "";
QString beatType = "";
QString timeSymbol = "";
int staves = 1; // default is one staff
for (QDomElement e2 = e; !e2.isNull(); e2 = e2.nextSiblingElement()) {
if (e2.tagName() == "staves") {
staves = e2.text().toInt();
Part* part = score->staff(staff)->part();
part->setStaves(staves);
// grow tuplets size, do not shrink to prevent losing info
if (staves * VOICES > tuplets.size())
tuplets.resize(staves * VOICES);
}
}
for (; !e.isNull(); e = e.nextSiblingElement()) {
if (e.tagName() == "divisions") {
bool ok;
divisions = MxmlSupport::stringToInt(e.text(), &ok);
if (!ok) {
qDebug("MusicXml-Import: bad divisions value: <%s>",
qPrintable(e.text()));
divisions = 4;
}
}
else if (e.tagName() == "key") {
int number = e.attribute(QString("number"), "-1").toInt();
QString printObject(e.attribute("print-object", "yes"));
int staffIdx = staff;
if (number != -1)
staffIdx += number - 1;
KeySigEvent key;
for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
if (ee.tagName() == "fifths")
key.setAccidentalType(ee.text().toInt());
else if (ee.tagName() == "mode")
domNotImplemented(ee);
else
domError(ee);
}
if (number == -1) {
//
// apply key to all staves in the part
//
int staves = score->staff(staff)->part()->nstaves();
// apply to all staves in part
for (int i = 0; i < staves; ++i) {
KeySigEvent oldkey = score->staff(staffIdx+i)->keymap()->key(tick);
if (oldkey != key) {
// new key differs from key in effect at this tick
KeySig* keysig = new KeySig(score);
keysig->setTrack((staffIdx + i) * VOICES);
keysig->setKeySigEvent(key);
keysig->setVisible(printObject == "yes");
Segment* s = measure->getSegment(keysig, tick);
s->add(keysig);
}
}
}
else {
//
// apply key to staff(staffIdx) only
//
KeySigEvent oldkey = score->staff(staffIdx)->keymap()->key(tick);
if (oldkey != key) {
// new key differs from key in effect at this tick
KeySig* keysig = new KeySig(score);
keysig->setTrack(staffIdx * VOICES);
keysig->setKeySigEvent(key);
keysig->setVisible(printObject == "yes");
Segment* s = measure->getSegment(keysig, tick);
s->add(keysig);
}
}
}
else if (e.tagName() == "time") {
timeSymbol = e.attribute("symbol");
for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
if (ee.tagName() == "beats")
beats = ee.text();
else if (ee.tagName() == "beat-type") {
beatType = ee.text();
}
else if (ee.tagName() == "senza-misura")
;
else
domError(ee);
}
}
else if (e.tagName() == "clef") {
int st = xmlClef(e, staff, measure);
int number = e.attribute(QString("number"), "-1").toInt();
int staffIdx = staff;
if (number != -1)
staffIdx += number - 1;
// qDebug("xmlAttributes clef score->staff(0) %p staffIdx %d score->staff(%d) %p",
// score->staff(0), staffIdx, staffIdx, score->staff(staffIdx));
if (st != STANDARD_STAFF_TYPE)
score->staff(staffIdx)->setStaffType(score->staffType(st));
}
else if (e.tagName() == "staves")
; // ignore, already handled
else if (e.tagName() == "staff-details") {
int number = e.attribute(QString("number"), "-1").toInt();
int staffIdx = staff;
if (number != -1)
staffIdx += number - 1;
StringData* t = 0;
if (score->staff(staffIdx)->isTabStaff())
t = new StringData;
xmlStaffDetails(score, staff, t, e);
}
else if (e.tagName() == "instruments")
domNotImplemented(e);
else if (e.tagName() == "transpose") {
Interval interval;
for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
int i = ee.text().toInt();
if (ee.tagName() == "diatonic")
interval.diatonic = i;
else if (ee.tagName() == "chromatic")
interval.chromatic = i;
else if (ee.tagName() == "octave-change")
; // TODO
else
domError(ee);
}
score->staff(staff)->part()->instr()->setTranspose(interval);
}
else if (e.tagName() == "measure-style")
for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
if (ee.tagName() == "multiple-rest") {
int multipleRest = ee.text().toInt();
if (multipleRest > 1) {
multiMeasureRestCount = multipleRest - 1;
startMultiMeasureRest = true;
}
else
qDebug("ImportMusicXml: multiple-rest %d not supported",
multipleRest);
}
else
domError(ee);
}
else
domError(e);
}
if (beats != "" && beatType != "") {
// determine if timesig is valid
TimeSigType st = TSIG_NORMAL;
int bts = 0; // total beats as integer (beats may contain multiple numbers, separated by "+")
int btp = 0; // beat-type as integer
if (determineTimeSig(beats, beatType, timeSymbol, st, bts, btp)) {
fractionTSig = Fraction(bts, btp);
// add timesig to all staves
//ws score->sigmap()->add(tick, TimeSig::getSig(st));
Part* part = score->staff(staff)->part();
int staves = part->nstaves();
for (int i = 0; i < staves; ++i) {
TimeSig* timesig = new TimeSig(score);
timesig->setTrack((staff + i) * VOICES);
timesig->setSig(fractionTSig, st);
// handle simple compound time signature
if (beats.contains(QChar('+'))) {
timesig->setNumeratorString(beats);
timesig->setDenominatorString(beatType);
}
Segment* s = measure->getSegment(timesig, tick);
s->add(timesig);
}
}
}
}
//---------------------------------------------------------
// addLyrics -- add a single lyric to the score
// or delete it (if number too high)
//---------------------------------------------------------
static void addLyric(ChordRest* cr, Lyrics* l, int lyricNo)
{
if (lyricNo > MAX_LYRICS) {
qDebug("too much lyrics (>%d)", MAX_LYRICS);
delete l;
}
else {
l->setNo(lyricNo);
cr->add(l);
}
}
//---------------------------------------------------------
// addLyrics -- add a notes lyrics to the score
//---------------------------------------------------------
static void addLyrics(ChordRest* cr,
QMap<int, Lyrics*>& numbrdLyrics,
QMap<int, Lyrics*>& defyLyrics,
QList<Lyrics*>& unNumbrdLyrics)
{
// first the lyrics with valid number
int lyricNo = -1;
for (QMap<int, Lyrics*>::const_iterator i = numbrdLyrics.constBegin(); i != numbrdLyrics.constEnd(); ++i) {
lyricNo = i.key(); // use number obtained from MusicXML file
Lyrics* l = i.value();
addLyric(cr, l, lyricNo);
}
// then the lyrics without valid number but with valid default-y
for (QMap<int, Lyrics*>::const_iterator i = defyLyrics.constBegin(); i != defyLyrics.constEnd(); ++i) {
lyricNo++; // use sequence number
Lyrics* l = i.value();
addLyric(cr, l, lyricNo);
}
// finally the remaining lyrics, which are simply added in order they appear in the MusicXML file
for (QList<Lyrics*>::const_iterator i = unNumbrdLyrics.constBegin(); i != unNumbrdLyrics.constEnd(); ++i) {
lyricNo++; // use sequence number
Lyrics* l = *i;
addLyric(cr, l, lyricNo);
}
}
//---------------------------------------------------------
// xmlLyric -- parse a MusicXML lyric element
//---------------------------------------------------------
void MusicXml::xmlLyric(int trk, QDomElement e,
QMap<int, Lyrics*>& numbrdLyrics,
QMap<int, Lyrics*>& defyLyrics,
QList<Lyrics*>& unNumbrdLyrics)
{
Lyrics* l = new Lyrics(score);
//TODO-WS l->setTick(tick);
l->setTrack(trk);
bool ok = true;
int lyricNo = e.attribute(QString("number")).toInt(&ok) - 1;
if (ok) {
if (lyricNo < 0) {
qDebug("invalid lyrics number (<0)");
delete l;
return;
}
else if (lyricNo > MAX_LYRICS) {
qDebug("too much lyrics (>%d)", MAX_LYRICS);
delete l;
return;
}
else {
numbrdLyrics[lyricNo] = l;
}
}
else {
int defaultY = e.attribute(QString("default-y")).toInt(&ok);
if (ok)
// invert default-y as it decreases with increasing lyric number
defyLyrics[-defaultY] = l;
else
unNumbrdLyrics.append(l);
}
for (e = e.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
if (e.tagName() == "syllabic") {
if (e.text() == "single")
l->setSyllabic(Lyrics::SINGLE);
else if (e.text() == "begin")
l->setSyllabic(Lyrics::BEGIN);
else if (e.text() == "end")
l->setSyllabic(Lyrics::END);
else if (e.text() == "middle")
l->setSyllabic(Lyrics::MIDDLE);
else
qDebug("unknown syllabic %s", qPrintable(e.text()));
}
else if (e.tagName() == "text")
l->setText(l->text()+e.text());
else if (e.tagName() == "elision")
if (e.text().isEmpty()) {
l->setText(l->text()+" ");
}
else {
l->setText(l->text()+e.text());
}
else if (e.tagName() == "extend")
;
else if (e.tagName() == "end-line")
;
else if (e.tagName() == "end-paragraph")
;
else
domError(e);
}
}
#if 0
//---------------------------------------------------------
// hasElem
//---------------------------------------------------------
/**
Determine if \a e has a child \a tagname.
*/
static bool hasElem(const QDomElement e, const QString& tagname)
{
return !e.elementsByTagName(tagname).isEmpty();
}
#endif
//---------------------------------------------------------
// tupletAssert -- check assertions for tuplet handling
//---------------------------------------------------------
/**
Check assertions for tuplet handling. If this fails, MusicXML
import will almost certainly break in non-obvious ways.
Should never happen, thus it is OK to quit the application.
*/
static void tupletAssert()
{
if (!(TDuration::V_BREVE == TDuration::V_LONG + 1
&& TDuration::V_WHOLE == TDuration::V_BREVE + 1
&& TDuration::V_HALF == TDuration::V_WHOLE + 1
&& TDuration::V_QUARTER == TDuration::V_HALF + 1
&& TDuration::V_EIGHT == TDuration::V_QUARTER + 1
&& TDuration::V_16TH == TDuration::V_EIGHT + 1
&& TDuration::V_32ND == TDuration::V_16TH + 1
&& TDuration::V_64TH == TDuration::V_32ND + 1
&& TDuration::V_128TH == TDuration::V_64TH + 1
&& TDuration::V_256TH == TDuration::V_128TH + 1
)) {
qFatal("tupletAssert() failed");
}
}
//---------------------------------------------------------
// smallestTypeAndCount
//---------------------------------------------------------
/**
Determine the smallest note type and the number of those
present in a ChordRest.
For a note without dots the type equals the note type
and count is one.
For a single dotted note the type equals half the note type
and count is three.
A double dotted note is similar.
Note: code assumes when duration().type() is incremented,
the note length is divided by two, checked by tupletAssert().
*/
static void smallestTypeAndCount(ChordRest const* const cr, int& type, int& count)
{
type = cr->durationType().type();
count = 1;
switch (cr->durationType().dots()) {
case 0:
// nothing to do
break;
case 1:
type += 1; // next-smaller type
count = 3;
break;
case 2:
type += 2; // next-next-smaller type
count = 7;
break;
default:
qDebug("smallestTypeAndCount() does not support more than 2 dots");
}
}
//---------------------------------------------------------
// matchTypeAndCount
//---------------------------------------------------------
/**
Given two note types and counts, if the types are not equal,
make them equal by successively doubling the count of the
largest type.
*/
static void matchTypeAndCount(int& type1, int& count1, int& type2, int& count2)
{
while (type1 < type2) {
type1++;
count1 *= 2;
}
while (type2 < type1) {
type2++;
count2 *= 2;
}
}
//---------------------------------------------------------
// determineTupletTypeAndCount
//---------------------------------------------------------
/**
Determine type and number of smallest notes in the tuplet
*/
static void determineTupletTypeAndCount(Tuplet* t, int& tupletType, int& tupletCount)
{
int elemCount = 0; // number of tuplet elements handled
foreach (DurationElement* de, t->elements()) {
if (de->type() == Element::CHORD || de->type() == Element::REST) {
ChordRest* cr = static_cast<ChordRest*>(de);
if (elemCount == 0) {
// first note: init variables
smallestTypeAndCount(cr, tupletType, tupletCount);
}
else {
int noteType = 0;
int noteCount = 0;
smallestTypeAndCount(cr, noteType, noteCount);
// match the types
matchTypeAndCount(tupletType, tupletCount, noteType, noteCount);
tupletCount += noteCount;
}
}
elemCount++;
}
}
//---------------------------------------------------------
// determineTupletBaseLen
//---------------------------------------------------------
/**
Determine tuplet baseLen as determined by the tuplet ratio,
and type and number of smallest notes in the tuplet.
Example: baselen of a 3:2 tuplet with 1/16, 1/8, 1/8 and 1/16
is 1/8. For this tuplet smalles note is 1/16, count is 6.
*/
TDuration determineTupletBaseLen(Tuplet* t)
{
int tupletType = 0; // smallest note type in the tuplet
int tupletCount = 0; // number of smallest notes in the tuplet
// first determine type and number of smallest notes in the tuplet
determineTupletTypeAndCount(t, tupletType, tupletCount);
// sanity check:
// for a 3:2 tuplet, count must be a multiple of 3
if (tupletCount % t->ratio().numerator()) {
qDebug("determineTupletBaseLen(%p) cannot divide count %d by %d", t, tupletCount, t->ratio().numerator());
return TDuration();
}
// calculate baselen in smallest notes
tupletCount /= t->ratio().numerator();
// normalize
while (tupletCount > 1 && (tupletCount % 2) == 0) {
tupletCount /= 2;
tupletType -= 1;
}
return TDuration(TDuration::DurationType(tupletType));
}
//---------------------------------------------------------
// isTupletFilled
//---------------------------------------------------------
/**
Determine if the tuplet contains the required number of notes,
either (1) of the specified normal type
or (2) the amount of the smallest notes in the tuplet equals
actual notes.
Example (1): a 3:2 tuplet with a 1/4 and a 1/8 note is filled
if normal type is 1/8, it is not filled if normal
type is 1/4.
Example (2): a 3:2 tuplet with a 1/4 and a 1/8 note is filled.
Use note types instead of duration to prevent errors due to rounding.
*/
bool isTupletFilled(Tuplet* t, TDuration normalType)
{
if (!t) return false;
int tupletType = 0; // smallest note type in the tuplet
int tupletCount = 0; // number of smallest notes in the tuplet
// first determine type and number of smallest notes in the tuplet
determineTupletTypeAndCount(t, tupletType, tupletCount);
// then compare ...
if (normalType.isValid()) {
int matchedNormalType = normalType.type();
int matchedNormalCount = t->ratio().numerator();
// match the types
matchTypeAndCount(tupletType, tupletCount, matchedNormalType, matchedNormalCount);
// ... result scenario (1)
return tupletCount >= matchedNormalCount;
}
else {
// ... result scenario (2)
return tupletCount >= t->ratio().numerator();
}
}
//---------------------------------------------------------
// xmlTuplet
//---------------------------------------------------------
/**
Parse and handle tuplet(s)
Tuplets with <actual-notes> and <normal-notes> but without <tuplet>
are handled correctly.
TODO Nested tuplets are not (yet) supported.
Note that cr must be initialized: fields measure, score, tick
and track are used.
*/
void xmlTuplet(Tuplet*& tuplet, ChordRest* cr, int ticks, QDomElement e)
{
int actualNotes = 1;
int normalNotes = 1;
TDuration normalType;
bool rest = false;
QString tupletType;
QString tupletPlacement;
QString tupletBracket;
QString tupletShowNumber;
// parse the elements required for tuplet handling
for (e = e.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
QString tag(e.tagName());
QString s(e.text());
if (tag == "notations") {
for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
if (ee.tagName() == "tuplet") {
tupletType = ee.attribute("type");
tupletPlacement = ee.attribute("placement");
tupletBracket = ee.attribute("bracket");
tupletShowNumber = ee.attribute("show-number");
}
}
}
else if (tag == "rest") {
rest = true;
}
else if (tag == "time-modification") { // tuplets
for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
if (ee.tagName() == "actual-notes")
actualNotes = ee.text().toInt();
else if (ee.tagName() == "normal-notes")
normalNotes = ee.text().toInt();
else if (ee.tagName() == "normal-type") {
// "measure" is not a valid normal-type,
// but would be accepted by setType()
if (ee.text() != "measure")
normalType.setType(ee.text());
}
else
domError(ee);
}
}
}
// detect tremolo
if (actualNotes == 2 && normalNotes == 1)
return;
// Special case:
// Encore generates rests in tuplets w/o <tuplet> or <time-modification>.
// Detect this by comparing the actual duration with the expected duration
// based on note type. If actual is 2/3 of expected, the rest is part
// of a tuplet.
if (rest && actualNotes == 1 && normalNotes == 1 && cr->duration().ticks() != ticks) {
if (2 * cr->duration().ticks() == 3 * ticks) {
actualNotes = 3;
normalNotes = 2;
}
}
// check for obvious errors
if (tupletType == "start" && tuplet) {
qDebug("tuplet already started");
// TODO: how to recover ?
}
if (tupletType == "stop" && !tuplet) {
qDebug("tuplet stop but no tuplet started");
// TODO: how to recover ?
}
if (tupletType != "" && tupletType != "start" && tupletType != "stop") {
qDebug("unknown tuplet type %s", qPrintable(tupletType));
}
// Tuplet are either started by the tuplet start
// or when the time modification is first found.
if (!tuplet) {
if (tupletType == "start"
|| (!tuplet && (actualNotes != 1 || normalNotes != 1))) {
tuplet = new Tuplet(cr->score());
tuplet->setTrack(cr->track());
tuplet->setRatio(Fraction(actualNotes, normalNotes));
tuplet->setTick(cr->tick());
// set bracket, leave at default if unspecified
if (tupletBracket == "yes")
tuplet->setBracketType(Tuplet::SHOW_BRACKET);
else if (tupletBracket == "no")
tuplet->setBracketType(Tuplet::SHOW_NO_BRACKET);
// set number, default is "actual" (=SHOW_NUMBER)
if (tupletShowNumber == "both")
tuplet->setNumberType(Tuplet::SHOW_RELATION);
else if (tupletShowNumber == "none")
tuplet->setNumberType(Tuplet::NO_TEXT);
else
tuplet->setNumberType(Tuplet::SHOW_NUMBER);
// TODO type, placement, bracket
tuplet->setParent(cr->measure());
}
}
// Add chord to the current tuplet.
// Must also check for actual/normal notes to prevent
// adding one chord too much if tuplet stop is missing.
if (tuplet && !(actualNotes == 1 && normalNotes == 1)) {
cr->setTuplet(tuplet);
tuplet->add(cr);
}
// Tuplets are stopped by the tuplet stop
// or when the tuplet is filled completely
// (either with knowledge of the normal type
// or as a last resort calculated based on
// actual and normal notes plus total duration)
// or when the time-modification is not found.
if (tuplet) {
if (tupletType == "stop"
|| isTupletFilled(tuplet, normalType)
|| (actualNotes == 1 && normalNotes == 1)) {
// set baselen
TDuration td = determineTupletBaseLen(tuplet);
// qDebug("stop tuplet %p basetype %d", tuplet, tupletType);
tuplet->setBaseLen(td);
// TODO determine usefulness of following check
int totalDuration = 0;
foreach (DurationElement* de, tuplet->elements()) {
if (de->type() == Element::CHORD || de->type() == Element::REST) {
totalDuration+=de->globalDuration().ticks();
}
}
if (!(totalDuration && normalNotes)) {
qDebug("MusicXML::import: tuplet stop but bad duration");
}
tuplet = 0;
}
}
}
//---------------------------------------------------------
// addArticulationToChord
//---------------------------------------------------------
/**
Add Articulation to Chord.
*/
static void addArticulationToChord(ChordRest* cr, ArticulationType articSym, QString dir)
{
Articulation* na = new Articulation(cr->score());
na->setArticulationType(articSym);
if (dir == "up") {
na->setUp(true);
na->setAnchor(A_TOP_STAFF);
}
else if (dir == "down") {
na->setUp(false);
na->setAnchor(A_BOTTOM_STAFF);
}
cr->add(na);
}
//---------------------------------------------------------
// addMordentToChord
//---------------------------------------------------------
/**
Add Mordent to Chord.
*/
static void addMordentToChord(ChordRest* cr, QString name, QString attrLong, QString attrAppr, QString attrDep)
{
ArticulationType articSym = ARTICULATIONS; // legal but impossible ArticulationType value here indicating "not found"
if (name == "inverted-mordent") {
if ((attrLong == "" || attrLong == "no") && attrAppr == "" && attrDep == "") articSym = Articulation_Prall;
else if (attrLong == "yes" && attrAppr == "" && attrDep == "") articSym = Articulation_PrallPrall;
else if (attrLong == "yes" && attrAppr == "below" && attrDep == "") articSym = Articulation_UpPrall;
else if (attrLong == "yes" && attrAppr == "above" && attrDep == "") articSym = Articulation_DownPrall;
else if (attrLong == "yes" && attrAppr == "" && attrDep == "below") articSym = Articulation_PrallDown;
else if (attrLong == "yes" && attrAppr == "" && attrDep == "above") articSym = Articulation_PrallUp;
}
else if (name == "mordent") {
if ((attrLong == "" || attrLong == "no") && attrAppr == "" && attrDep == "") articSym = Articulation_Mordent;
else if (attrLong == "yes" && attrAppr == "" && attrDep == "") articSym = Articulation_PrallMordent;
else if (attrLong == "yes" && attrAppr == "below" && attrDep == "") articSym = Articulation_UpMordent;
else if (attrLong == "yes" && attrAppr == "above" && attrDep == "") articSym = Articulation_DownMordent;
}
if (articSym != ARTICULATIONS) {
Articulation* na = new Articulation(cr->score());
na->setArticulationType(articSym);
cr->add(na);
}
else
qDebug("unknown ornament: name '%s' long '%s' approach '%s' departure '%s'",
qPrintable(name), qPrintable(attrLong), qPrintable(attrAppr), qPrintable(attrDep));
}
//---------------------------------------------------------
// readArticulations
//---------------------------------------------------------
/**
Read a "simple" MuseScore articulation.
These are the articulations that can be
- represented by an enum ArticulationType
- added to a ChordRest
Return true (articulation recognized and handled)
or false (articulation not recognized).
Note simple implementation: MusicXML syntax is not strictly
checked, the articulations parent element does not matter.
*/
static bool readArticulations(ChordRest* cr, QString mxmlName)
{
QMap<QString, ArticulationType> map; // map MusicXML articulation name to MuseScore symbol
map["accent"] = Articulation_Sforzatoaccent;
map["staccatissimo"] = Articulation_Staccatissimo;
map["staccato"] = Articulation_Staccato;
map["tenuto"] = Articulation_Tenuto;
map["turn"] = Articulation_Turn;
map["inverted-turn"] = Articulation_Reverseturn;
map["stopped"] = Articulation_Plusstop;
map["up-bow"] = Articulation_Upbow;
map["down-bow"] = Articulation_Downbow;
map["detached-legato"] = Articulation_Portato;
map["spiccato"] = Articulation_Staccatissimo;
map["snap-pizzicato"] = Articulation_Snappizzicato;
map["schleifer"] = Articulation_Schleifer;
map["open-string"] = Articulation_Ouvert;
map["thumb-position"] = Articulation_Thumb;
if (map.contains(mxmlName)) {
addArticulationToChord(cr, map.value(mxmlName), "");
return true;
}
else
return false;
}
//---------------------------------------------------------
// convertAccidental
//---------------------------------------------------------
/**
Convert a MusicXML accidental name to a MuseScore enum Accidental::Type.
*/
static Accidental::AccidentalType convertAccidental(QString mxmlName)
{
QMap<QString, Accidental::AccidentalType> map; // map MusicXML accidental name to MuseScore enum Accidental::Type
map["natural"] = Accidental::ACC_NATURAL;
map["flat"] = Accidental::ACC_FLAT;
map["sharp"] = Accidental::ACC_SHARP;
map["double-sharp"] = Accidental::ACC_SHARP2;
map["sharp-sharp"] = Accidental::ACC_SHARP2;
map["flat-flat"] = Accidental::ACC_FLAT2;
map["double-flat"] = Accidental::ACC_FLAT2;
map["natural-flat"] = Accidental::ACC_NONE;
map["quarter-flat"] = Accidental::ACC_MIRRORED_FLAT;
map["quarter-sharp"] = Accidental::ACC_SHARP_SLASH;
map["three-quarters-flat"] = Accidental::ACC_MIRRORED_FLAT2;
map["three-quarters-sharp"] = Accidental::ACC_SHARP_SLASH4;
map["sharp-down"] = Accidental::ACC_SHARP_ARROW_DOWN;
map["sharp-up"] = Accidental::ACC_SHARP_ARROW_UP;
map["natural-down"] = Accidental::ACC_NATURAL_ARROW_DOWN;
map["natural-up"] = Accidental::ACC_NATURAL_ARROW_UP;
map["flat-down"] = Accidental::ACC_FLAT_ARROW_DOWN;
map["flat-up"] = Accidental::ACC_FLAT_ARROW_UP;
map["slash-quarter-sharp"] = Accidental::ACC_MIRRIRED_FLAT_SLASH;
map["slash-sharp"] = Accidental::ACC_SHARP_SLASH;
map["slash-flat"] = Accidental::ACC_FLAT_SLASH;
map["double-slash-flat"] = Accidental::ACC_FLAT_SLASH2;
map["sori"] = Accidental::ACC_SORI;
map["koron"] = Accidental::ACC_KORON;
map["natural-sharp"] = Accidental::ACC_NONE;
if (map.contains(mxmlName))
return map.value(mxmlName);
else
qDebug("unknown accidental %s", qPrintable(mxmlName));
// default: return Accidental::ACC_NONE
return Accidental::ACC_NONE;
}
//---------------------------------------------------------
// convertNotehead
//---------------------------------------------------------
/**
Convert a MusicXML notehead name to a MuseScore headgroup.
*/
static NoteHeadGroup convertNotehead(QString mxmlName)
{
QMap<QString, int> map; // map MusicXML notehead name to a MuseScore headgroup
map["slash"] = 5;
map["triangle"] = 3;
map["diamond"] = 2;
map["x"] = 1;
map["circle-x"] = 6;
map["do"] = 7;
map["re"] = 8;
map["mi"] = 4;
map["fa"] = 9;
map["so"] = 12;
map["la"] = 10;
map["ti"] = 11;
map["normal"] = 0;
if (map.contains(mxmlName))
return NoteHeadGroup(map.value(mxmlName));
else
qDebug("unknown notehead %s", qPrintable(mxmlName));
// default: return 0
return NoteHeadGroup::HEAD_NORMAL;
}
//---------------------------------------------------------
// addTextToNote
//---------------------------------------------------------
/**
Add Text to Note.
*/
static void addTextToNote(QString txt, int style, Score* score, Note* note)
{
if (!txt.isEmpty()) {
Text* t = new Fingering(score);
t->setTextStyleType(style);
t->setText(txt);
note->add(t);
}
}
//---------------------------------------------------------
// addFermata
//---------------------------------------------------------
/**
Add a MusicXML fermata.
Note: MusicXML common.mod: "The fermata type is upright if not specified."
*/
static void addFermata(ChordRest* cr, const QString type, const ArticulationType articSym)
{
if (type == "upright" || type == "")
addArticulationToChord(cr, articSym, "up");
else if (type == "inverted")
addArticulationToChord(cr, articSym, "down");
else
qDebug("unknown fermata type '%s'", qPrintable(type));
}
//---------------------------------------------------------
// xmlFermata
//---------------------------------------------------------
/**
Read a MusicXML fermata.
Note: MusicXML common.mod: "An empty fermata element represents a normal fermata."
*/
static void xmlFermata(ChordRest* cr, QDomElement e)
{
QString fermata = e.text();
QString fermataType = e.attribute(QString("type"));
if (fermata == "normal" || fermata == "")
addFermata(cr, fermataType, Articulation_Fermata);
else if (fermata == "angled")
addFermata(cr, fermataType, Articulation_Shortfermata);
else if (fermata == "square")
addFermata(cr, fermataType, Articulation_Longfermata);
else
qDebug("unknown fermata '%s'\n", qPrintable(fermata));
}
//---------------------------------------------------------
// xmlNotations
//---------------------------------------------------------
/**
Read MusicXML notations.
*/
void MusicXml::xmlNotations(Note* note, ChordRest* cr, int trk, int ticks, QDomElement e)
{
Measure* measure = cr->measure();
int track = cr->track();
QString wavyLineType;
QString arpeggioType;
QString glissandoType;
int breath = -1;
int tremolo = 0;
QString tremoloType;
QString placement;
QStringList dynamics;
qreal rx = 0.0;
qreal ry = 0.0;
qreal yoffset = 0.0; // actually this is default-y
// qreal xoffset = 0.0; // not used
bool hasYoffset = false;
QSet<QString> slurIds; // combination start/stop and number must be unique within a note
QString chordLineType;
for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
if (ee.tagName() == "slur") {
int slurNo = ee.attribute(QString("number"), "1").toInt() - 1;
QString slurType = ee.attribute(QString("type"));
QString lineType = ee.attribute(QString("line-type"), "solid");
// PriMus Music-Notation by Columbussoft (build 10093) generates overlapping
// slurs that do not have a number attribute to distinguish them.
// The duplicates must be ignored, to prevent memory allocation issues,
// which caused a MuseScore crash
QString slurId = QString("slur %1").arg(slurType) + QString(" %1").arg(slurNo);
bool unique = !slurIds.contains(slurId);
if (unique) {
slurIds.insert(slurId);
if (slurType == "start") {
bool endSlur = false;
if (slur[slurNo] == 0) {
slur[slurNo] = new Slur(score);
if (lineType == "dotted")
slur[slurNo]->setLineType(1);
else if (lineType == "dashed")
slur[slurNo]->setLineType(2);
slur[slurNo]->setTick(cr->tick());
slur[slurNo]->setStartElement(cr);
}
else
endSlur = true;
QString pl = ee.attribute(QString("placement"));
if (pl == "above")
slur[slurNo]->setSlurDirection(MScore::UP);
else if (pl == "below")
slur[slurNo]->setSlurDirection(MScore::DOWN);
//slur[slurNo]->setStart(tick, trk + voice);
//slur[slurNo]->setTrack((staff + relStaff) * VOICES);
slur[slurNo]->setTrack(track);
slur[slurNo]->setTrack2(track);
score->addElement(slur[slurNo]);
if (endSlur) {
slur[slurNo]->setTick(cr->tick());
slur[slurNo]->setStartElement(cr);
slur[slurNo] = 0;
}
}
else if (slurType == "stop") {
if (slur[slurNo] == 0) {
slur[slurNo] = new Slur(score);
slur[slurNo]->setTick2(cr->tick());
slur[slurNo]->setTrack2(track);
slur[slurNo]->setEndElement(cr);
}
else {
slur[slurNo]->setTick2(cr->tick());
slur[slurNo]->setTrack2(track);
slur[slurNo]->setEndElement(cr);
slur[slurNo] = 0;
}
}
else
qDebug("unknown slur type %s", qPrintable(slurType));
}
else
qDebug("ignoring duplicate slur '%s'", qPrintable(slurId));
}
else if (ee.tagName() == "tied") {
QString tiedType = ee.attribute(QString("type"));
if (tiedType == "start") {
if (tie) {
qDebug("Tie already active");
}
else if (note) {
tie = new Tie(score);
// qDebug("use Tie %p", tie);
note->setTieFor(tie);
tie->setStartNote(note);
tie->setTrack(track);
tie = 0;
}
QString tiedOrientation = e.attribute("orientation", "auto");
if (tiedOrientation == "over")
tie->setSlurDirection(MScore::UP);
else if (tiedOrientation == "under")
tie->setSlurDirection(MScore::DOWN);
else if (tiedOrientation == "auto")
; // ignore
else
qDebug("unknown tied orientation: %s", tiedOrientation.toLatin1().data());
}
else if (tiedType == "stop")
; // ignore
else
qDebug("unknown tied type %s", tiedType.toLatin1().data());
}
else if (ee.tagName() == "tuplet") {
// needed for tuplets, handled in xmlTuplet
}
else if (ee.tagName() == "dynamics") {
// IMPORT_LAYOUT
placement = ee.attribute("placement");
if (preferences.musicxmlImportLayout) {
ry = ee.attribute(QString("relative-y"), "0").toDouble() * -.1;
rx = ee.attribute(QString("relative-x"), "0").toDouble() * .1;
yoffset = ee.attribute("default-y").toDouble(&hasYoffset) * -0.1;
// xoffset = ee.attribute("default-x", "0.0").toDouble() * 0.1;
}
QDomElement eee = ee.firstChildElement();
if (!eee.isNull()) {
if (eee.tagName() == "other-dynamics")
dynamics.push_back(eee.text());
else
dynamics.push_back(eee.tagName());
}
}
else if (ee.tagName() == "articulations") {
for (QDomElement eee = ee.firstChildElement(); !eee.isNull(); eee = eee.nextSiblingElement()) {
if (readArticulations(cr, eee.tagName()))
continue;
else if (eee.tagName() == "breath-mark")
breath = 0;
else if (eee.tagName() == "caesura")
breath = 3;
else if (eee.tagName() == "doit"
|| eee.tagName() == "falloff"
|| eee.tagName() == "plop"
|| eee.tagName() == "scoop")
chordLineType = eee.tagName();
else if (eee.tagName() == "strong-accent") {
QString strongAccentType = eee.attribute(QString("type"));
if (strongAccentType == "up" || strongAccentType == "")
addArticulationToChord(cr, Articulation_Marcato, "up");
else if (strongAccentType == "down")
addArticulationToChord(cr, Articulation_Marcato, "down");
else
qDebug("unknown mercato type %s", strongAccentType.toLatin1().data());
}
else
domError(eee);
}
}
else if (ee.tagName() == "fermata")
xmlFermata(cr, ee);
else if (ee.tagName() == "ornaments") {
bool trillMark = false;
// <trill-mark placement="above"/>
for (QDomElement eee = ee.firstChildElement(); !eee.isNull(); eee = eee.nextSiblingElement()) {
if (readArticulations(cr, eee.tagName()))
continue;
else if (eee.tagName() == "trill-mark")
trillMark = true;
else if (eee.tagName() == "wavy-line")
wavyLineType = eee.attribute(QString("type"));
else if (eee.tagName() == "tremolo") {
tremolo = eee.text().toInt();
tremoloType = eee.attribute(QString("type"));
}
else if (eee.tagName() == "accidental-mark")
domNotImplemented(eee);
else if (eee.tagName() == "delayed-turn")
// TODO: actually this should be offset a bit to the right
addArticulationToChord(cr, Articulation_Turn, "");
else if (eee.tagName() == "inverted-mordent"
|| eee.tagName() == "mordent")
addMordentToChord(cr, eee.tagName(), eee.attribute("long"), eee.attribute("approach"), eee.attribute("departure"));
else
domError(eee);
}
// note that mscore wavy line already implicitly includes a trillsym
// so don't add an additional one
if (trillMark && wavyLineType != "start")
addArticulationToChord(cr, Articulation_Trill, "");
}
else if (ee.tagName() == "technical") {
for (QDomElement eee = ee.firstChildElement(); !eee.isNull(); eee = eee.nextSiblingElement()) {
if (readArticulations(cr, eee.tagName()))
continue;
else if (eee.tagName() == "fingering")
addTextToNote(eee.text(), TEXT_STYLE_FINGERING, score, note);
else if (eee.tagName() == "fret") {
if (note->staff()->isTabStaff())
note->setFret(eee.text().toInt());
}
else if (eee.tagName() == "pluck")
addTextToNote(eee.text(), TEXT_STYLE_FINGERING, score, note);
else if (eee.tagName() == "string") {
if (note->staff()->isTabStaff())
note->setString(eee.text().toInt() - 1);
else
addTextToNote(eee.text(), TEXT_STYLE_STRING_NUMBER, score, note);
}
else if (eee.tagName() == "pull-off")
domNotImplemented(eee);
else
domError(eee);
}
}
else if (ee.tagName() == "arpeggiate") {
arpeggioType = ee.attribute(QString("direction"));
if (arpeggioType == "") arpeggioType = "none";
}
else if (ee.tagName() == "non-arpeggiate")
arpeggioType = "non-arpeggiate";
// glissando and slide are added to the "stop" chord only
else if (ee.tagName() == "glissando") {
if (ee.attribute("type") == "stop") glissandoType = "glissando";
}
else if (ee.tagName() == "slide") {
if (ee.attribute("type") == "stop") glissandoType = "slide";
}
else
domError(ee);
}
if (!arpeggioType.isEmpty()) {
Arpeggio* a = new Arpeggio(score);
if (arpeggioType == "none")
a->setArpeggioType(ArpeggioType::NORMAL);
else if (arpeggioType == "up")
a->setArpeggioType(ArpeggioType::UP);
else if (arpeggioType == "down")
a->setArpeggioType(ArpeggioType::DOWN);
else if (arpeggioType == "non-arpeggiate")
a->setArpeggioType(ArpeggioType::BRACKET);
else {
qDebug("unknown arpeggio type %s", qPrintable(arpeggioType));
delete a;
a = 0;
}
if ((static_cast<Chord*>(cr))->arpeggio()) {
// there can be only one
delete a;
a = 0;
}
else
cr->add(a);
}
if (!glissandoType.isEmpty()) {
Glissando* g = new Glissando(score);
if (glissandoType == "slide")
g->setGlissandoType(GlissandoType::STRAIGHT);
else if (glissandoType == "glissando")
g->setGlissandoType(GlissandoType::WAVY);
else {
qDebug("unknown glissando type %s", glissandoType.toLatin1().data());
delete g;
g = 0;
}
if ((static_cast<Chord*>(cr))->glissando()) {
// there can be only one
delete g;
g = 0;
}
else
cr->add(g);
}
if (!wavyLineType.isEmpty()) {
if (wavyLineType == "start") {
if (trill) {
qDebug("overlapping wavy-line not supported");
delete trill;
trill = 0;
}
else {
trill = new Trill(score);
trill->setTrack(trk);
spanners[trill] = QPair<int, int>(tick, -1);
// qDebug("wedge trill=%p inserted at first tick %d", trill, tick);
}
}
else if (wavyLineType == "stop") {
if (!trill) {
qDebug("wavy-line stop without start");
}
else {
spanners[trill].second = tick + ticks;
// qDebug("wedge trill=%p second tick %d", trill, tick);
trill = 0;
}
}
else
qDebug("unknown wavy-line type %s", wavyLineType.toLatin1().data());
}
if (breath >= 0) {
Breath* b = new Breath(score);
// b->setTrack(trk + voice); TODO check next line
b->setTrack(track);
b->setBreathType(breath);
Segment* seg = measure->getSegment(Segment::SegBreath, tick);
seg->add(b);
}
if (tremolo) {
if (tremolo == 1 || tremolo == 2 || tremolo == 3 || tremolo == 4) {
if (tremoloType == "" || tremoloType == "single") {
Tremolo* t = new Tremolo(score);
switch (tremolo) {
case 1: t->setTremoloType(TREMOLO_R8); break;
case 2: t->setTremoloType(TREMOLO_R16); break;
case 3: t->setTremoloType(TREMOLO_R32); break;
case 4: t->setTremoloType(TREMOLO_R64); break;
}
cr->add(t);
}
else if (tremoloType == "start") {
if (tremStart) qDebug("MusicXML::import: double tremolo start");
tremStart = static_cast<Chord*>(cr);
}
else if (tremoloType == "stop") {
if (tremStart) {
Tremolo* t = new Tremolo(score);
switch (tremolo) {
case 1: t->setTremoloType(TREMOLO_C8); break;
case 2: t->setTremoloType(TREMOLO_C16); break;
case 3: t->setTremoloType(TREMOLO_C32); break;
case 4: t->setTremoloType(TREMOLO_C64); break;
}
t->setChords(tremStart, static_cast<Chord*>(cr));
// fixup chord duration and type
t->chord1()->setDurationType(ticks);
t->chord1()->setDuration(ticks);
t->chord2()->setDurationType(ticks);
t->chord2()->setDuration(ticks);
// add tremolo to first chord (only)
tremStart->add(t);
}
else qDebug("MusicXML::import: double tremolo stop w/o start");
tremStart = 0;
}
}
else
qDebug("unknown tremolo type %d", tremolo);
}
if (chordLineType != "") {
ChordLine* cl = new ChordLine(score);
if (chordLineType == "falloff")
cl->setChordLineType(CHORDLINE_FALL);
if (chordLineType == "doit")
cl->setChordLineType(CHORDLINE_DOIT);
if (chordLineType == "plop")
cl->setChordLineType(CHORDLINE_PLOP);
if (chordLineType == "scoop")
cl->setChordLineType(CHORDLINE_SCOOP);
note->chord()->add(cl);
}
// more than one dynamic ???
// LVIFIX: check import/export of <other-dynamics>unknown_text</...>
// TODO remove duplicate code (see MusicXml::direction)
for (QStringList::Iterator it = dynamics.begin(); it != dynamics.end(); ++it ) {
Dynamic* dyn = new Dynamic(score);
dyn->setDynamicType(*it);
if (hasYoffset) dyn->setYoff(yoffset);
addElement(dyn, hasYoffset, track / VOICES /* staff */, 0 /* rstaff */, score, placement,
rx, ry, 0 /*offset */, measure, tick);
}
}
//---------------------------------------------------------
// findLastFiguredBass
//---------------------------------------------------------
/**
* Find last figured bass on \a track before \a seg
*/
static FiguredBass* findLastFiguredBass(int track, Segment* seg)
{
// qDebug("findLastFiguredBass(track %d seg %p)", track, seg);
while ((seg = seg->prev1(Segment::SegChordRest))) {
// qDebug("findLastFiguredBass seg %p", seg);
foreach(Element* e, seg->annotations()) {
if (e->track() == track && e->type() == Element::FIGURED_BASS) {
FiguredBass* fb = static_cast<FiguredBass*>(e);
// qDebug("findLastFiguredBass found fb %p at seg %p", fb, seg);
return fb;
}
}
}
return 0;
}
//---------------------------------------------------------
// xmlNote
//---------------------------------------------------------
/**
Read a MusicXML note.
\a Staff is the number of first staff of the part this note belongs to.
*/
void MusicXml::xmlNote(Measure* measure, int staff, const QString& partId, Beam*& beam, QString& currentVoice, QDomElement e, QList<GraceNoteInfo>& gni)
{
int ticks = 0;
#ifdef DEBUG_TICK
qDebug("xmlNote start tick=%d (%d div) divisions=%d", tick, tick * divisions / MScore::division, divisions);
#endif
QDomNode pn = e; // TODO remove pn
QDomElement org_e = e; // save e for later
QString strVoice = "1";
int voice = 0;
int move = 0;
bool rest = false;
int relStaff = 0;
BeamMode bm = BeamMode::NONE;
MScore::Direction sd = MScore::AUTO;
int dots = 0;
bool grace = false;
QString graceSlash;
QString step;
int alter = 0;
int octave = 4;
Accidental::AccidentalType accidental = Accidental::ACC_NONE;
bool parentheses = false;
bool editorial = false;
bool cautionary = false;
TDuration durationType(TDuration::V_INVALID);
NoteHeadGroup headGroup = NoteHeadGroup::HEAD_NORMAL;
bool noStem = false;
QColor noteheadColor = QColor::Invalid;
bool chord = false;
int velocity = -1;
bool unpitched = false;
QString instrId;
QList<QDomElement> notations;
// first read all elements required for voice mapping
QDomElement e2 = e.firstChildElement();
for (; !e2.isNull(); e2 = e2.nextSiblingElement()) {
QString tag(e2.tagName());
QString s(e2.text());
if (tag == "voice")
strVoice = s;
else if (tag == "staff")
relStaff = s.toInt() - 1;
else if (tag == "grace") {
grace = true;
}
else if (tag == "duration") {
ticks = calcTicks(s, divisions);
}
else if (tag == "chord") {
chord = true;
}
// silently ignore others (will be handled later)
}
// Bug fix for Sibelius 7.1.3 which does not write <voice> for notes with <chord>
if (!chord)
// remember voice
currentVoice = strVoice;
else
// use voice from last note w/o <chord>
strVoice = currentVoice;
// Musicxml voices are counted for all staffs of an
// instrument. They are not limited. In mscore voices are associated
// with a staff. Every staff can have at most VOICES voices.
// The following lines map musicXml voices to mscore voices.
// If a voice crosses two staffs, this is expressed with the
// "move" parameter in mscore.
// Musicxml voices are unique within a part, but not across parts.
// qDebug("voice mapper before: relStaff=%d voice=%d staff=%d\n", relStaff, voice, staff);
int s; // staff mapped by voice mapper
int v; // voice mapped by voice mapper
if (voicelist.value(strVoice).overlaps()) {
// for overlapping voices, the staff does not change
// and the voice is mapped and staff-dependent
s = relStaff;
v = voicelist.value(strVoice).voice(s);
}
else {
// for non-overlapping voices, both staff and voice are
// set by the voice mapper
s = voicelist.value(strVoice).staff();
v = voicelist.value(strVoice).voice();
}
// qDebug("voice mapper before: relStaff=%d voice=%d s=%d v=%d", relStaff, voice, s, v);
if (s < 0 || v < 0) {
qDebug("ImportMusicXml: too many voices (staff %d, relStaff %d, voice %s at line %d col %d)",
staff + 1, relStaff, qPrintable(strVoice), e.lineNumber(), e.columnNumber());
return;
}
else {
int d = relStaff - s;
relStaff = s;
move += d;
voice = v;
}
// qDebug("voice mapper after: relStaff=%d move=%d voice=%d", relStaff, move, voice);
// note: relStaff is the staff number relative to the parts first staff
// voice is the voice number in the staff
// determine tick for note
int loc_tick = chord ? prevtick : tick;
// qDebug("chord %d prevtick %d tick %d loc_tick %d", chord, prevtick, tick, loc_tick);
// trk is first track of staff
int trk = (staff + relStaff) * VOICES;
// track is current track in staff
int track = trk + voice;
QString printObject = "yes";
if (pn.isElement() && pn.nodeName() == "note") {
QDomElement pne = pn.toElement();
printObject = pne.attribute("print-object", "yes");
}
velocity = round(e.attribute("dynamics", "-1").toDouble() * 0.9);
// storage for xmlLyric
QMap<int, Lyrics*> numberedLyrics; // lyrics with valid number
QMap<int, Lyrics*> defaultyLyrics; // lyrics with valid default-y
QList<Lyrics*> unNumberedLyrics; // lyrics with neither
for (e = e.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
QString tag(e.tagName());
QString s(e.text());
if (tag == "pitch") {
step = "C";
alter = 0;
octave = 4;
for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
if (ee.tagName() == "step") // A-G
step = ee.text();
else if (ee.tagName() == "alter") { // -1=flat 1=sharp (0.5=quarter sharp)
bool ok;
alter = MxmlSupport::stringToInt(ee.text(), &ok); // fractions not supported by mscore
if (!ok || alter < -2 || alter > 2) {
qDebug("ImportXml: bad 'alter' value: %s at line %d col %d",
qPrintable(ee.text()), ee.lineNumber(), ee.columnNumber());
alter = 0;
}
}
else if (ee.tagName() == "octave") // 0-9 4=middle C
octave = ee.text().toInt();
else
domError(ee);
}
}
else if (tag == "unpitched") {
//
// TODO: check semantic
//
unpitched = true;
for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
if (ee.tagName() == "display-step") // A-G
step = ee.text();
else if (ee.tagName() == "display-octave") // 0-9 4=middle C
octave = ee.text().toInt();
else
domError(ee);
}
}
else if (tag == "type")
durationType = TDuration(s);
else if (tag == "chord" || tag == "duration" || tag == "staff" || tag == "voice")
// already handled by voice mapper, ignore here but prevent
// spurious "Unknown Node <staff>" or "... <voice>" messages
;
else if (tag == "stem") {
if (s == "up")
sd = MScore::UP;
else if (s == "down")
sd = MScore::DOWN;
else if (s == "none")
noStem = true;
else if (s == "double")
;
else
qDebug("unknown stem direction %s", e.text().toLatin1().data());
}
else if (tag == "beam") {
int beamNo = e.attribute(QString("number"), "1").toInt();
if (beamNo == 1) {
if (s == "begin")
bm = BeamMode::BEGIN;
else if (s == "end")
bm = BeamMode::END;
else if (s == "continue")
bm = BeamMode::MID;
else if (s == "backward hook")
;
else if (s == "forward hook")
;
else
qDebug("unknown beam keyword <%s>", s.toLatin1().data());
}
}
else if (tag == "rest") {
rest = true;
for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
if (ee.tagName() == "display-step") // A-G
step = ee.text();
else if (ee.tagName() == "display-octave") // 0-9 4=middle C
octave = ee.text().toInt();
else
domError(ee);
}
}
else if (tag == "lyric")
xmlLyric(track, e, numberedLyrics, defaultyLyrics, unNumberedLyrics);
else if (tag == "dot")
++dots;
else if (tag == "accidental") {
accidental = convertAccidental(s);
if (e.attribute(QString("cautionary")) == "yes")
cautionary = true;
if (e.attribute(QString("editorial")) == "yes")
editorial = true;
if (e.attribute(QString("parentheses")) == "yes")
parentheses = true;
}
else if (tag == "notations") {
// save the QDomElement representing <notations> for later
// note that multiple notations elements may be present
notations << e;
}
else if (tag == "tie") {
QString tieType = e.attribute(QString("type"));
if (tieType == "start")
;
else if (tieType == "stop")
;
else
qDebug("unknown tie type %s", tieType.toLatin1().data());
}
else if (tag == "grace") {
graceSlash = e.attribute(QString("slash"));
}
else if (tag == "time-modification") {
// needed for tuplets, handled in xmlTuplet
}
else if (tag == "notehead") {
headGroup = convertNotehead(s);
QString color = e.attribute(QString("color"), 0);
if (color != 0)
noteheadColor = QColor(color);
}
else if (tag == "instrument") {
instrId = e.attribute("id");
}
else if (tag == "cue")
domNotImplemented(e);
else
domError(e);
}
// qDebug("staff=%d relStaff=%d VOICES=%d voice=%d track=%d",
// staff, relStaff, VOICES, voice, track);
// qDebug("%s at %d voice %d dur = %d, beat %d/%d div %d pitch %d ticks %d",
// rest ? "Rest" : "Note", loc_tick, voice, duration, beats, beatType,
// divisions, 0 /* pitch */, ticks);
ChordRest* cr = 0;
Note* note = 0;
if (rest) {
cr = new Rest(score);
// By convention, whole measure rests do not have a "type" element
// As of MusicXML 3.0, this can be indicated by an attribute "measure",
// but for backwards compatibility the "old" convention still has to be supported.
if (durationType.type() == TDuration::V_INVALID) {
// Verify the rest fits exactly in the measure, as some programs
// (e.g. Cakewalk SONAR X2 Studio [Version: 19.0.0.306]) leave out
// the type for all rests.
if (tick == measure->tick() && ticks == measure->ticks())
durationType.setType(TDuration::V_MEASURE);
else
durationType.setVal(ticks);
cr->setDurationType(durationType);
cr->setDuration(Fraction::fromTicks(ticks));
}
else {
cr->setDurationType(durationType);
cr->setDots(dots);
cr->setDuration(cr->durationType().fraction());
}
if (beam) {
if (beam->track() == track) {
cr->setBeamMode(BeamMode::MID);
beam->add(cr);
}
else
removeBeam(beam);
}
else
cr->setBeamMode(BeamMode::NONE);
cr->setTrack(track);
static_cast<Rest*>(cr)->setStaffMove(move);
Segment* s = measure->getSegment(cr, loc_tick);
// Sibelius might export two rests at the same place, ignore the 2nd one
// <?DoletSibelius Two NoteRests in same voice at same position may be an error?>
if (!s->element(cr->track()))
s->add(cr);
cr->setVisible(printObject == "yes");
if (step != "" && 0 <= octave && octave <= 9) {
// qDebug("rest step=%s oct=%d", qPrintable(step), octave);
ClefType clef = cr->staff()->clef(loc_tick);
int po = ClefInfo::pitchOffset(clef);
int istep = step[0].toLatin1() - 'A';
// qDebug(" clef=%d po=%d istep=%d", clef, po, istep);
if (istep < 0 || istep > 6) {
qDebug("rest: illegal display-step %d, <%s>", istep, qPrintable(step));
}
else {
// a b c d e f g
static int table2[7] = { 5, 6, 0, 1, 2, 3, 4 };
int dp = 7 * (octave + 2) + table2[istep];
// qDebug(" dp=%d po-dp=%d", dp, po-dp);
cr->setUserYoffset((po - dp + 3) * score->spatium() / 2);
}
}
}
else {
char c = step[0].toLatin1();
note = new Note(score);
note->setHeadGroup(headGroup);
if (noteheadColor != QColor::Invalid)
note->setColor(noteheadColor);
if (velocity > 0) {
note->setVeloType(MScore::USER_VAL);
note->setVeloOffset(velocity);
}
cr = measure->findChord(loc_tick, track);
if (cr == 0) {
if(!grace) {
cr = new Chord(score);
cr->setBeamMode(bm);
cr->setTrack(track);
if (durationType.type() == TDuration::V_INVALID)
durationType.setType(TDuration::V_QUARTER);
cr->setDurationType(durationType);
cr->setDots(dots);
cr->setDuration(cr->durationType().fraction());
Segment* s = measure->getSegment(cr, loc_tick);
s->add(cr);
}
}
if(cr)
cr->setStaffMove(move);
// pitch must be set before adding note to chord as note
// is inserted into pitch sorted list (ws)
if (unpitched
&& drumsets.contains(partId)
&& drumsets[partId].contains(instrId)) {
// step and oct are display-step and ...-oct
// get pitch from instrument definition in drumset instead
int pitch = drumsets[partId][instrId].pitch;
note->setPitch(pitch);
// TODO - does this need to be key-aware?
note->setTpc(pitch2tpc(pitch, KEY_C, PREFER_NEAREST)); // TODO: necessary ?
}
else
xmlSetPitch(note, c, alter, octave, ottava, track);
if(grace) {
NoteType nt = NOTE_APPOGGIATURA;
int len = MScore::division/2;
if (graceSlash == "yes")
nt = NOTE_ACCIACCATURA;
if (durationType.type() == TDuration::V_QUARTER) {
nt = NOTE_GRACE4;
len = MScore::division;
}
else if (durationType.type() == TDuration::V_16TH) {
nt = NOTE_GRACE16;
len = MScore::division/4;
}
else if (durationType.type() == TDuration::V_32ND) {
nt = NOTE_GRACE32;
len = MScore::division/8;
}
gni.append({nt, note->pitch(), note->tpc(), len});
delete note;
return;
}
cr->add(note);
if(!grace && gni.size() > 0) {
for (int i = gni.size() - 1; i >= 0; --i) {
GraceNoteInfo g = gni.at(i);
score->setGraceNote(static_cast<Chord*>(cr), g.pitch, g.type, false, g.len, g.tpc);
}
gni.clear();
}
static_cast<Chord*>(cr)->setNoStem(noStem);
// qDebug("staff for new note: %p (staff=%d, relStaff=%d)",
// score->staff(staff + relStaff), staff, relStaff);
if (editorial || cautionary || parentheses) {
Accidental* a = new Accidental(score);
a->setAccidentalType(accidental);
a->setHasBracket(cautionary || parentheses);
a->setRole(Accidental::ACC_USER);
note->add(a);
}
// LVIFIX: quarter tone accidentals support is "drawing only"
//WS-TODO if (accidental == 18
// || accidental == 19
// || accidental == 22
// || accidental == 25)
// note->setAccidentalType(accidental);
// remember beam mode last non-grace note
// bm == BeamMode::NONE means no <beam> was found
if (!grace && bm != BeamMode::NONE)
beamMode = bm;
// handle beam
if (!chord && !grace)
handleBeamAndStemDir(cr, bm, sd, beam);
note->setVisible(printObject == "yes");
// set drumset information
// note that in MuseScore, the drumset contains defaults for notehead,
// line and stem direction, while a MusicXML file contains actuals.
// the MusicXML values for each note are simply copied to the defaults
if (unpitched) {
// determine staff line based on display-step / -octave and clef type
ClefType clef = cr->staff()->clef(loc_tick);
int po = ClefInfo::pitchOffset(clef);
int pitch = MusicXMLStepAltOct2Pitch(step[0].toLatin1(), 0, octave);
int line = po - absStep(pitch);
// correct for number of staff lines
// see ExportMusicXml::unpitch2xml for explanation
// TODO handle other # staff lines ?
int staffLines = cr->staff()->lines();
if (staffLines == 1) line -= 8;
if (staffLines == 3) line -= 2;
// the drum palette cannot handle stem direction AUTO,
// overrule if necessary
if (sd == MScore::AUTO) {
if (line > 4)
sd = MScore::DOWN;
else
sd = MScore::UP;
}
if (drumsets.contains(partId)
&& drumsets[partId].contains(instrId)) {
drumsets[partId][instrId].notehead = headGroup;
drumsets[partId][instrId].line = line;
drumsets[partId][instrId].stemDirection = sd;
}
}
}
if (!chord && !grace) {
xmlTuplet(tuplets[voice + relStaff * VOICES], cr, ticks, org_e);
}
foreach(QDomElement de, notations) {
xmlNotations(note, cr, trk, ticks, de);
}
// add lyrics found by xmlLyric
addLyrics(cr, numberedLyrics, defaultyLyrics, unNumberedLyrics);
// add figured bass element
if (figBass) {
// qDebug("add figured bass %p at tick %d ticks %d trk %d", figBass, tick, ticks, trk);
figBass->setTrack(trk);
figBass->setTicks(ticks);
// TODO: set correct onNote value
Segment* s = measure->getSegment(Segment::SegChordRest, tick);
// TODO: use addelement() instead of Segment::add() ?
s->add(figBass);
figBass = 0;
}
else if (figBassExtend) {
// extend last figured bass to end of this chord
// qDebug("extend figured bass at tick %d ticks %d trk %d end %d", tick, ticks, trk, tick + ticks);
FiguredBass* fb = findLastFiguredBass((trk / VOICES) * VOICES, cr->segment());
if (fb)
fb->setTicks(tick + ticks - fb->segment()->tick());
}
figBassExtend = false;
if (!chord)
prevtick = tick; // remember tick where last chordrest was inserted
#ifdef DEBUG_TICK
qDebug(" after inserting note tick=%d prevtick=%d", tick, prevtick);
#endif
}
//---------------------------------------------------------
// readFretDiagram
//---------------------------------------------------------
static void readFretDiagram(FretDiagram* fd, QDomElement de)
{
for (QDomElement e = de.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
const QString& tag(e.tagName());
int val = e.text().toInt();
if (tag == "frame-frets") {
if (val > 0)
fd->setFrets(val);
else
qDebug("FretDiagram::readMusicXML: illegal frame-fret %d", val);
}
else if (tag == "frame-note") {
int fret = -1;
int string = -1;
for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
const QString& tag(ee.tagName());
int val = ee.text().toInt();
if (tag == "fret")
fret = val;
else if (tag == "string")
string = val;
else
domError(ee);
}
qDebug("FretDiagram::readMusicXML string %d fret %d", string, fret);
if (string > 0) {
if (fret == 0)
fd->setMarker(fd->strings() - string, 79 /* ??? */);
else if (fret > 0)
fd->setDot(fd->strings() - string, fret);
}
}
else if (tag == "frame-strings") {
if (val > 0) {
fd->setStrings(val);
for (int i = 0; i < val; ++i)
fd->setMarker(i, 88 /* ??? */);
}
else
qDebug("FretDiagram::readMusicXML: illegal frame-strings %d", val);
}
else
domError(e);
}
}
//---------------------------------------------------------
// xmlHarmony
//---------------------------------------------------------
void MusicXml::xmlHarmony(QDomElement e, int tick, Measure* measure, int staff)
{
// type:
// placement:
double rx = 0.1 * e.attribute("relative-x", "0").toDouble();
double ry = -0.1 * e.attribute("relative-y", "0").toDouble();
double styleYOff = score->textStyle(TEXT_STYLE_HARMONY).offset().y();
OffsetType offsetType = score->textStyle(TEXT_STYLE_HARMONY).offsetType();
if (offsetType == OFFSET_ABS) {
styleYOff = styleYOff * MScore::DPMM / score->spatium();
}
double dy = -0.1 * e.attribute("default-y", QString::number(styleYOff* -10)).toDouble();
QString printObject(e.attribute("print-object", "yes"));
QString printFrame(e.attribute("print-frame"));
QString printStyle(e.attribute("print-style"));
QString kind, kindText, symbols, parens;
QList<HDegree> degreeList;
if (harmony) {
qDebug("MusicXML::import: more than one harmony");
return;
};
Harmony* ha = new Harmony(score);
ha->setUserOff(QPointF(rx, ry + dy - styleYOff));
int offset = 0;
for (e = e.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
QString tag(e.tagName());
if (tag == "root") {
QString step;
int alter = 0;
bool invalidRoot = false;
for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
QString tag(ee.tagName());
if (tag == "root-step") {
// attributes: print-style
step = ee.text();
if (ee.hasAttribute("text")) {
QString rtext = ee.attribute("text");
if (rtext == "") {
invalidRoot = true;
}
}
}
else if (tag == "root-alter") {
// attributes: print-object, print-style
// location (left-right)
alter = ee.text().toInt();
}
else
domError(ee);
}
if (invalidRoot)
ha->setRootTpc(INVALID_TPC);
else
ha->setRootTpc(step2tpc(step, AccidentalVal(alter)));
}
else if (tag == "function") {
// attributes: print-style
domNotImplemented(e);
}
else if (tag == "kind") {
// attributes: use-symbols yes-no
// text, stack-degrees, parentheses-degree, bracket-degrees,
// print-style, halign, valign
kindText = e.attribute("text");
symbols = e.attribute("use-symbols");
parens = e.attribute("parentheses-degrees");
kind = e.text();
}
else if (tag == "inversion") {
// attributes: print-style
}
else if (tag == "bass") {
QString step;
int alter = 0;
for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
QString tag(ee.tagName());
if (tag == "bass-step") {
// attributes: print-style
step = ee.text();
}
else if (tag == "bass-alter") {
// attributes: print-object, print-style
// location (left-right)
alter = ee.text().toInt();
}
else
domError(ee);
}
ha->setBaseTpc(step2tpc(step, AccidentalVal(alter)));
}
else if (tag == "degree") {
int degreeValue = 0;
int degreeAlter = 0;
QString degreeType = "";
for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
QString tag(ee.tagName());
if (tag == "degree-value") {
degreeValue = ee.text().toInt();
}
else if (tag == "degree-alter") {
degreeAlter = ee.text().toInt();
}
else if (tag == "degree-type") {
degreeType = ee.text();
}
else
domError(ee);
}
if (degreeValue <= 0 || degreeValue > 13
|| degreeAlter < -2 || degreeAlter > 2
|| (degreeType != "add" && degreeType != "alter" && degreeType != "subtract")) {
qDebug("incorrect degree: degreeValue=%d degreeAlter=%d degreeType=%s",
degreeValue, degreeAlter, qPrintable(degreeType));
}
else {
if (degreeType == "add")
degreeList << HDegree(degreeValue, degreeAlter, ADD);
else if (degreeType == "alter")
degreeList << HDegree(degreeValue, degreeAlter, ALTER);
else if (degreeType == "subtract")
degreeList << HDegree(degreeValue, degreeAlter, SUBTRACT);
}
}
else if (tag == "frame") {
qDebug("xmlHarmony: found harmony frame");
FretDiagram* fd = new FretDiagram(score);
fd->setTrack(staff * VOICES);
// read frame into FretDiagram
readFretDiagram(fd, e);
Segment* s = measure->getSegment(Segment::SegChordRest, tick);
s->add(fd);
}
else if (tag == "level")
domNotImplemented(e);
else if (tag == "offset")
offset = calcTicks(e.text(), divisions);
else
domError(e);
}
const ChordDescription* d = 0;
if (ha->rootTpc() != INVALID_TPC)
d = ha->fromXml(kind, kindText, symbols, parens, degreeList);
if (d) {
ha->setId(d->id);
ha->setTextName(d->names.front());
}
else {
ha->setId(-1);
ha->setTextName(kindText);
}
ha->render();
ha->setVisible(printObject == "yes");
// TODO-LV: do this only if ha points to a valid harmony
// harmony = ha;
ha->setTrack(staff * VOICES);
Segment* s = measure->getSegment(Segment::SegChordRest, tick + offset);
s->add(ha);
}
//---------------------------------------------------------
// xmlClef
//---------------------------------------------------------
int MusicXml::xmlClef(QDomElement e, int staffIdx, Measure* measure)
{
ClefType clef = ClefType::G;
int res = STANDARD_STAFF_TYPE;
int clefno = e.attribute(QString("number"), "1").toInt() - 1;
QString c;
int i = 0;
int line = -1;
for (QDomElement ee = e.firstChildElement(); !ee.isNull(); ee = ee.nextSiblingElement()) {
if (ee.tagName() == "sign")
c = ee.text();
else if (ee.tagName() == "line")
line = ee.text().toInt();
else if (ee.tagName() == "clef-octave-change") {
i = ee.text().toInt();
if (i && !(c == "F" || c == "G"))
qDebug("clef-octave-change only implemented for F and G key");
}
else
domError(ee);
}
//some software (Primus) don't include line and assume some default
// it's permitted by MusicXML 2.0 XSD
if (line == -1) {
if (c == "G")
line = 2;
else if (c == "F")
line = 4;
else if (c == "C")
line = 3;
}
if (c == "G" && i == 0 && line == 2)
clef = ClefType::G;
else if (c == "G" && i == 1 && line == 2)
clef = ClefType::G1;
else if (c == "G" && i == 2 && line == 2)
clef = ClefType::G2;
else if (c == "G" && i == -1 && line == 2)
clef = ClefType::G3;
else if (c == "G" && i == 0 && line == 1)
clef = ClefType::G4;
else if (c == "F" && i == 0 && line == 3)
clef = ClefType::F_B;
else if (c == "F" && i == 0 && line == 4)
clef = ClefType::F;
else if (c == "F" && i == 1 && line == 4)
clef = ClefType::F_8VA;
else if (c == "F" && i == 2 && line == 4)
clef = ClefType::F_15MA;
else if (c == "F" && i == -1 && line == 4)
clef = ClefType::F8;
else if (c == "F" && i == -2 && line == 4)
clef = ClefType::F15;
else if (c == "F" && i == 0 && line == 5)
clef = ClefType::F_C;
else if (c == "C") {
if (line == 5)
clef = ClefType::C5;
else if (line == 4)
clef = ClefType::C4;
else if (line == 3)
clef = ClefType::C3;
else if (line == 2)
clef = ClefType::C2;
else if (line == 1)
clef = ClefType::C1;
}
else if (c == "percussion") {
clef = ClefType::PERC2;
res = PERC_DEFAULT_STAFF_TYPE;
}
else if (c == "TAB") {
clef = ClefType::TAB2;
res = TAB_DEFAULT_STAFF_TYPE;
}
else
qDebug("ImportMusicXML: unknown clef <sign=%s line=%d oct ch=%d>", qPrintable(c), line, i);
// note: also generate symbol for tick 0
// was not necessary before 0.9.6
Clef* clefs = new Clef(score);
clefs->setClefType(clef);
clefs->setTrack((staffIdx + clefno) * VOICES);
Segment* s = measure->getSegment(clefs, tick);
s->add(clefs);
return res;
}
}