MuseScore/mscore/exportxml.cpp
2013-07-13 08:53:18 -06:00

4632 lines
183 KiB
C++

//=============================================================================
// MusE Score
// Linux Music Score Editor
// $Id: exportxml.cpp 5598 2012-04-30 07:20:39Z lvinken $
//
// Copyright (C) 2002-2009 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.
//=============================================================================
/**
MusicXML export.
*/
// TODO: trill lines need to be handled the same way as slurs
// in MuseScore they are measure level elements, while in MusicXML
// they are attached to notes (as ornaments)
//=========================================================
// LVI FIXME
//
// Evaluate paramenter handling between the various classes, could be simplified
//=========================================================
// TODO LVI 2011-10-30: determine how to report export errors.
// Currently all output (both debug and error reports) are done using qDebug.
#include <math.h>
#include "config.h"
//#include "musescore.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/segment.h"
#include "libmscore/xml.h"
#include "libmscore/beam.h"
#include "libmscore/staff.h"
#include "libmscore/part.h"
#include "libmscore/measure.h"
#include "libmscore/style.h"
#include "musicxml.h"
#include "libmscore/slur.h"
#include "libmscore/hairpin.h"
#include "libmscore/dynamic.h"
#include "libmscore/barline.h"
#include "libmscore/timesig.h"
#include "libmscore/ottava.h"
#include "libmscore/pedal.h"
#include "libmscore/text.h"
#include "libmscore/tuplet.h"
#include "libmscore/lyrics.h"
#include "libmscore/volta.h"
#include "libmscore/keysig.h"
#include "libmscore/bracket.h"
#include "libmscore/arpeggio.h"
#include "libmscore/jump.h"
#include "libmscore/marker.h"
#include "libmscore/tremolo.h"
#include "libmscore/trill.h"
#include "libmscore/harmony.h"
#include "libmscore/tempotext.h"
#include "libmscore/sym.h"
#include "libmscore/pitchspelling.h"
#include "libmscore/utils.h"
#include "libmscore/articulation.h"
#include "libmscore/page.h"
#include "libmscore/system.h"
#include "libmscore/element.h"
#include "libmscore/glissando.h"
#include "libmscore/navigate.h"
#include "libmscore/spanner.h"
#include "libmscore/drumset.h"
#include "preferences.h"
#include "libmscore/mscore.h"
#include "libmscore/accidental.h"
#include "libmscore/breath.h"
#include "libmscore/chordline.h"
#include "libmscore/figuredbass.h"
#include "libmscore/tablature.h"
#include "libmscore/rehearsalmark.h"
#include "libmscore/qzipwriter_p.h"
#include "libmscore/fret.h"
namespace Ms {
//---------------------------------------------------------
// local defines for debug output
//---------------------------------------------------------
// #define DEBUG_CLEF true
// #define DEBUG_REPEATS true
// #define DEBUG_TICK true
//---------------------------------------------------------
// typedefs
//---------------------------------------------------------
typedef QMap<int, const FiguredBass*> FigBassMap;
//---------------------------------------------------------
// attributes -- prints <attributes> tag when necessary
//---------------------------------------------------------
class Attributes {
bool inAttributes;
public:
Attributes() { start(); }
void doAttr(Xml& xml, bool attr);
void start();
void stop(Xml& xml);
};
//---------------------------------------------------------
// doAttr - when necessary change state and print <attributes> tag
//---------------------------------------------------------
void Attributes::doAttr(Xml& xml, bool attr)
{
if (!inAttributes && attr) {
xml.stag("attributes");
inAttributes = true;
}
else if (inAttributes && !attr) {
xml.etag();
inAttributes = false;
}
}
//---------------------------------------------------------
// start -- initialize
//---------------------------------------------------------
void Attributes::start()
{
inAttributes = false;
}
//---------------------------------------------------------
// stop -- print </attributes> tag when necessary
//---------------------------------------------------------
void Attributes::stop(Xml& xml)
{
if (inAttributes) {
xml.etag();
inAttributes = false;
}
}
//---------------------------------------------------------
// notations -- prints <notations> tag when necessary
//---------------------------------------------------------
class Notations {
bool notationsPrinted;
public:
Notations() { notationsPrinted = false; }
void tag(Xml& xml);
void etag(Xml& xml);
};
//---------------------------------------------------------
// articulations -- prints <articulations> tag when necessary
//---------------------------------------------------------
class Articulations {
bool articulationsPrinted;
public:
Articulations() { articulationsPrinted = false; }
void tag(Xml& xml);
void etag(Xml& xml);
};
//---------------------------------------------------------
// ornaments -- prints <ornaments> tag when necessary
//---------------------------------------------------------
class Ornaments {
bool ornamentsPrinted;
public:
Ornaments() { ornamentsPrinted = false; }
void tag(Xml& xml);
void etag(Xml& xml);
};
//---------------------------------------------------------
// technical -- prints <technical> tag when necessary
//---------------------------------------------------------
class Technical {
bool technicalPrinted;
public:
Technical() { technicalPrinted = false; }
void tag(Xml& xml);
void etag(Xml& xml);
};
//---------------------------------------------------------
// slur handler -- prints <slur> tags
//---------------------------------------------------------
class SlurHandler {
const Slur* slur[MAX_NUMBER_LEVEL];
bool started[MAX_NUMBER_LEVEL];
int findSlur(const Slur* s) const;
public:
SlurHandler();
void doSlurStart(Chord* chord, Notations& notations, Xml& xml);
void doSlurStop(Chord* chord, Notations& notations, Xml& xml);
};
//---------------------------------------------------------
// glissando handler -- prints <glissando> tags
//---------------------------------------------------------
class GlissandoHandler {
const Chord* glissChrd[MAX_NUMBER_LEVEL];
const Chord* slideChrd[MAX_NUMBER_LEVEL];
int findChord(const Chord* c, int st) const;
public:
GlissandoHandler();
void doGlissandoStart(Chord* chord, Notations& notations, Xml& xml);
void doGlissandoStop(Chord* chord, Notations& notations, Xml& xml);
};
//---------------------------------------------------------
// ExportMusicXml
//---------------------------------------------------------
typedef QHash<const Chord*, const Trill*> TrillHash;
class ExportMusicXml {
Score* score;
Xml xml;
SlurHandler sh;
GlissandoHandler gh;
int tick;
Attributes attr;
TextLine const* bracket[MAX_BRACKETS];
int div;
double millimeters;
int tenths;
TrillHash trillStart;
TrillHash trillStop;
int findBracket(const TextLine* tl) const;
void chord(Chord* chord, int staff, const QList<Lyrics*>* ll, bool useDrumset);
void rest(Rest* chord, int staff);
void clef(int staff, int clef);
void timesig(TimeSig* tsig);
void keysig(int key, int staff = 0, bool visible = true);
void barlineLeft(Measure* m);
void barlineRight(Measure* m);
void pitch2xml(Note* note, char& c, int& alter, int& octave);
void unpitch2xml(Note* note, char& c, int& octave);
void lyrics(const QList<Lyrics*>* ll, const int trk);
void work(const MeasureBase* measure);
void calcDivMoveToTick(int t);
void calcDivisions();
double getTenthsFromInches(double);
double getTenthsFromDots(double);
void keysigTimesig(Measure* m, int strack, int etrack);
public:
ExportMusicXml(Score* s)
{
score = s; tick = 0; div = 1; tenths = 40;
millimeters = score->spatium() * tenths / (10 * MScore::DPMM);
}
void write(QIODevice* dev);
void credits(Xml& xml);
void moveToTick(int t);
void words(Text const* const text, int staff);
void rehearsal(RehearsalMark const* const rmk, int staff);
void hairpin(Hairpin const* const hp, int staff, int tick);
void ottava(Ottava const* const ot, int staff, int tick);
void pedal(Pedal const* const pd, int staff, int tick);
void textLine(TextLine const* const tl, int staff, int tick);
void dynamic(Dynamic const* const dyn, int staff);
void symbol(Symbol const* const sym, int staff);
void tempoText(TempoText const* const text, int staff);
void harmony(Harmony const* const, FretDiagram const* const fd);
};
//---------------------------------------------------------
// tag
//---------------------------------------------------------
void Notations::tag(Xml& xml)
{
if (!notationsPrinted)
xml.stag("notations");
notationsPrinted = true;
}
//---------------------------------------------------------
// etag
//---------------------------------------------------------
void Notations::etag(Xml& xml)
{
if (notationsPrinted)
xml.etag();
notationsPrinted = false;
}
//---------------------------------------------------------
// tag
//---------------------------------------------------------
void Articulations::tag(Xml& xml)
{
if (!articulationsPrinted)
xml.stag("articulations");
articulationsPrinted = true;
}
//---------------------------------------------------------
// etag
//---------------------------------------------------------
void Articulations::etag(Xml& xml)
{
if (articulationsPrinted)
xml.etag();
articulationsPrinted = false;
}
//---------------------------------------------------------
// tag
//---------------------------------------------------------
void Ornaments::tag(Xml& xml)
{
if (!ornamentsPrinted)
xml.stag("ornaments");
ornamentsPrinted = true;
}
//---------------------------------------------------------
// etag
//---------------------------------------------------------
void Ornaments::etag(Xml& xml)
{
if (ornamentsPrinted)
xml.etag();
ornamentsPrinted = false;
}
//---------------------------------------------------------
// tag
//---------------------------------------------------------
void Technical::tag(Xml& xml)
{
if (!technicalPrinted)
xml.stag("technical");
technicalPrinted = true;
}
//---------------------------------------------------------
// etag
//---------------------------------------------------------
void Technical::etag(Xml& xml)
{
if (technicalPrinted)
xml.etag();
technicalPrinted = false;
}
//---------------------------------------------------------
// slurHandler
//---------------------------------------------------------
SlurHandler::SlurHandler()
{
for (int i = 0; i < MAX_NUMBER_LEVEL; ++i) {
slur[i] = 0;
started[i] = false;
}
}
//---------------------------------------------------------
// findSlur -- get index of slur in slur table
// return -1 if not found
//---------------------------------------------------------
int SlurHandler::findSlur(const Slur* s) const
{
for (int i = 0; i < MAX_NUMBER_LEVEL; ++i)
if (slur[i] == s) return i;
return -1;
}
//---------------------------------------------------------
// doSlurStart
//---------------------------------------------------------
void SlurHandler::doSlurStart(Chord* chord, Notations& notations, Xml& xml)
{
// search for slur(s) starting at this chord
int tick = chord->tick();
auto sl = chord->score()->spanner();
for (auto it = sl.lower_bound(tick); it != sl.upper_bound(tick); ++it) {
Spanner* sp = it->second;
if (sp->type() != Element::SLUR || sp->track() != chord->track())
continue;
const Slur* s = static_cast<const Slur*>(sp);
// check if on slur list (i.e. stop already seen)
int i = findSlur(s);
//define line type
QString rest;
QString lineType;
switch (s->lineType()) {
case 1:
lineType = "dotted";
break;
case 2:
lineType = "dashed";
break;
default:
lineType = "";
}
if (!lineType.isEmpty())
rest += QString(" line-type=\"%1\"").arg(lineType);
if (i >= 0) {
// remove from list and print start
slur[i] = 0;
started[i] = false;
notations.tag(xml);
xml.tagE(QString("slur%1 type=\"start\"%2 number=\"%3\"").arg(rest).arg(s->slurDirection() == MScore::UP ? " placement=\"above\"" : "").arg(i + 1));
}
else {
// find free slot to store it
i = findSlur(0);
if (i >= 0) {
slur[i] = s;
started[i] = true;
notations.tag(xml);
xml.tagE(QString("slur%1 type=\"start\" number=\"%2\"").arg(rest).arg(i + 1));
}
else
qDebug("no free slur slot");
}
}
}
//---------------------------------------------------------
// doSlurStop
//---------------------------------------------------------
// Note: a slur may start in a higher voice in the same measure.
// In that case it is not yet started (i.e. on the active slur list)
// when doSlurStop() is executed. Handle this slur as follows:
// - generate stop anyway and put it on the slur list
// - doSlurStart() starts slur but doesn't store it
void SlurHandler::doSlurStop(Chord* chord, Notations& notations, Xml& xml)
{
// search for slur(s) stopping at this chord but not on slur list yet
for (auto it : chord->score()->spanner()) {
Spanner* sp = it.second;
if (sp->type() != Element::SLUR || sp->tick2() != chord->tick() || sp->track() != chord->track())
continue;
const Slur* s = static_cast<const Slur*>(sp);
// check if on slur list
int i = findSlur(s);
if (i < 0) {
// if not, find free slot to store it
i = findSlur(0);
if (i >= 0) {
slur[i] = s;
started[i] = false;
notations.tag(xml);
xml.tagE("slur type=\"stop\" number=\"%d\"", i + 1);
}
else
qDebug("no free slur slot");
}
}
// search slur list for already started slur(s) stopping at this chord
for (int i = 0; i < MAX_NUMBER_LEVEL; ++i) {
if (slur[i] && slur[i]->tick2() == chord->tick() && slur[i]->track() == chord->track()) {
if (started[i]) {
slur[i] = 0;
started[i] = false;
notations.tag(xml);
xml.tagE("slur type=\"stop\" number=\"%d\"", i + 1);
}
}
}
}
//---------------------------------------------------------
// glissando
//---------------------------------------------------------
// <notations>
// <slide line-type="solid" number="1" type="start"/>
// </notations>
// <notations>
// <glissando line-type="wavy" number="1" type="start"/>
// </notations>
static void glissando(Glissando* gli, int number, bool start, Notations& notations, Xml& xml)
{
GlissandoType st = gli->glissandoType();
switch (st) {
case GlissandoType::STRAIGHT:
notations.tag(xml);
xml.tagE("slide line-type=\"solid\" number=\"%d\" type=\"%s\"",
number, start ? "start" : "stop");
break;
case GlissandoType::WAVY:
notations.tag(xml);
xml.tagE("glissando line-type=\"wavy\" number=\"%d\" type=\"%s\"",
number, start ? "start" : "stop");
break;
default:
qDebug("unknown glissando subtype %d", st);
break;
}
}
//---------------------------------------------------------
// GlissandoHandler
//---------------------------------------------------------
GlissandoHandler::GlissandoHandler()
{
for (int i = 0; i < MAX_NUMBER_LEVEL; ++i) {
glissChrd[i] = 0;
slideChrd[i] = 0;
}
}
//---------------------------------------------------------
// findChord -- get index of chord in chord table for subtype st
// return -1 if not found
//---------------------------------------------------------
int GlissandoHandler::findChord(const Chord* c, int st) const
{
if (st != 0 && st != 1) {
qDebug("GlissandoHandler::findChord: unknown glissando subtype %d", st);
return -1;
}
for (int i = 0; i < MAX_NUMBER_LEVEL; ++i) {
if (st == 0 && slideChrd[i] == c) return i;
if (st == 1 && glissChrd[i] == c) return i;
}
return -1;
}
//---------------------------------------------------------
// doGlissandoStart
//---------------------------------------------------------
void GlissandoHandler::doGlissandoStart(Chord* chord, Notations& notations, Xml& xml)
{
GlissandoType st = chord->glissando()->glissandoType();
if (st != GlissandoType::STRAIGHT && st != GlissandoType::WAVY) {
qDebug("doGlissandoStart: unknown glissando subtype %d", st);
return;
}
// check if on chord list
int i = findChord(chord, int(st));
if (i >= 0) {
// print error and remove from list
qDebug("doGlissandoStart: chord %p already on list", chord);
if (st == GlissandoType::STRAIGHT) slideChrd[i] = 0;
if (st == GlissandoType::WAVY) glissChrd[i] = 0;
}
// find free slot to store it
i = findChord(0, int(st));
if (i >= 0) {
if (st == GlissandoType::STRAIGHT) slideChrd[i] = chord;
if (st == GlissandoType::WAVY) glissChrd[i] = chord;
glissando(chord->glissando(), i + 1, true, notations, xml);
}
else
qDebug("doGlissandoStart: no free slot");
}
//---------------------------------------------------------
// doGlissandoStop
//---------------------------------------------------------
void GlissandoHandler::doGlissandoStop(Chord* chord, Notations& notations, Xml& xml)
{
GlissandoType st = chord->glissando()->glissandoType();
if (st != GlissandoType::STRAIGHT && st != GlissandoType::WAVY) {
qDebug("doGlissandoStart: unknown glissando subtype %d", st);
return;
}
for (int i = 0; i < MAX_NUMBER_LEVEL; ++i) {
if (st == GlissandoType::STRAIGHT && slideChrd[i] == chord) {
slideChrd[i] = 0;
glissando(chord->glissando(), i + 1, false, notations, xml);
return;
}
if (st == GlissandoType::WAVY && glissChrd[i] == chord) {
glissChrd[i] = 0;
glissando(chord->glissando(), i + 1, false, notations, xml);
return;
}
}
qDebug("doGlissandoStop: glissando chord %p not found", chord);
}
//---------------------------------------------------------
// directions anchor -- anchor directions at another element or a specific tick
//---------------------------------------------------------
class DirectionsAnchor {
Element* direct; // the element containing the direction
Element* anchor; // the element it is attached to
bool start; // whether it is attached to start or end
int tick; // the timestamp
public:
DirectionsAnchor(Element* a, bool s, int t) { direct = 0; anchor = a; start = s; tick = t; }
DirectionsAnchor(int t) { direct = 0; anchor = 0; start = true; tick = t; }
Element* getDirect() { return direct; }
Element* getAnchor() { return anchor; }
bool getStart() { return start; }
int getTick() { return tick; }
void setDirect(Element* d) { direct = d; }
};
//---------------------------------------------------------
// trill hadling
//---------------------------------------------------------
// Find chords to attach trills to. This is necessary because in MuseScore
// trills are spanners (thus attached to segments), while in MusicXML trills
// are attached to notes.
// TBD: must trill end be in the same staff as trill started ?
// if so, no need to pass in strack and etrack (trill has a track)
static void findTrillAnchors(const Trill* trill, Chord*& startChord, Chord*& stopChord)
{
const Segment* seg = trill->startSegment();
const int endTick = trill->tick2();
const int strack = trill->track();
// try to find chords in the same track:
// find a track with suitable chords both for start and stop
for (int i = 0; i < VOICES; ++i) {
Element* el = seg->element(strack + i);
if (!el)
continue;
if (el->type() != Element::CHORD)
continue;
startChord = static_cast<Chord*>(el);
Segment* s = trill->score()->tick2segmentEnd(strack + i, endTick);
if (!s)
continue;
el = s->element(strack + i);
if (!el)
continue;
if (el->type() != Element::CHORD)
continue;
stopChord = static_cast<Chord*>(el);
return;
}
// try to find start/stop chords in different tracks
for (int i = 0; i < VOICES; ++i) {
Element* el = seg->element(strack + i);
if (!el)
continue;
if (el->type() != Element::CHORD)
continue;
startChord = static_cast<Chord*>(el);
break; // first chord found is OK
}
for (int i = 0; i < VOICES; ++i) {
Segment* s = trill->score()->tick2segmentEnd(strack + i, endTick);
if (!s)
continue;
Element* el = s->element(strack + i);
if (!el)
continue;
if (el->type() != Element::CHORD)
continue;
stopChord = static_cast<Chord*>(el);
break; // first chord found is OK
}
}
// find all trills in this measure and this part
static void findTrills(Measure* measure, int strack, int etrack, TrillHash& trillStart, TrillHash& trillStop)
{
// loop over all segments in this measure
for (auto it : measure->score()->spanner()) {
Spanner* e = it.second;
if (e->type() == Element::TRILL && strack <= e->track() && e->track() < etrack
&& e->tick() >= measure->tick() && e->tick() < measure->tick())
{
// a trill is found starting in this segment, trill end time is known
// determine notes to write trill start and stop
const Trill* tr = static_cast<const Trill*>(e);
Chord* startChord = 0; // chord where trill starts
Chord* stopChord = 0; // chord where trill stops
findTrillAnchors(tr, startChord, stopChord);
if (startChord && stopChord) {
trillStart.insert(startChord, tr);
trillStop.insert(stopChord, tr);
}
}
}
}
//---------------------------------------------------------
// helpers for ::calcDivisions
//---------------------------------------------------------
typedef QList<int> IntVector;
static IntVector integers;
static IntVector primes;
// check if all integers can be divided by d
static bool canDivideBy(int d)
{
bool res = true;
for (int i = 0; i < integers.count(); i++) {
if ((integers[i] <= 1) || ((integers[i] % d) != 0)) {
res = false;
}
}
return res;
}
// divide all integers by d
static void divideBy(int d)
{
for (int i = 0; i < integers.count(); i++) {
integers[i] /= d;
}
}
static void addInteger(int len)
{
if (!integers.contains(len)) {
integers.append(len);
}
}
//---------------------------------------------------------
// calcDivMoveToTick
//---------------------------------------------------------
void ExportMusicXml::calcDivMoveToTick(int t)
{
if (t < tick) {
#ifdef DEBUG_TICK
qDebug("backup %d\n", tick - t);
#endif
addInteger(tick - t);
}
else if (t > tick) {
#ifdef DEBUG_TICK
qDebug("forward %d\n", t - tick);
#endif
addInteger(t - tick);
}
tick = t;
}
//---------------------------------------------------------
// isTwoNoteTremolo - determine is chord is part of two note tremolo
//---------------------------------------------------------
static bool isTwoNoteTremolo(Chord* chord)
{
return (chord->tremolo() && chord->tremolo()->twoNotes());
}
//---------------------------------------------------------
// calcDivisions
//---------------------------------------------------------
// Loop over all voices in all staffs and determine a suitable value for divisions.
// Length of time in MusicXML is expressed in "units", which should allow expressing all time values
// as an integral number of units. Divisions contains the number of units in a quarter note.
// MuseScore uses division (480) midi ticks to represent a quarter note, which expresses all note values
// plus triplets and quintuplets as integer values. Solution is to collect all time values required,
// and divide them by the highest common denominator, which is implemented as a series of
// divisions by prime factors. Initialize the list with division to make sure a quarter note can always
// be written as an integral number of units.
/**
*/
void ExportMusicXml::calcDivisions()
{
// init
integers.clear();
primes.clear();
integers.append(MScore::division);
primes.append(2);
primes.append(3);
primes.append(5);
const QList<Part*>& il = score->parts();
for (int idx = 0; idx < il.size(); ++idx) {
Part* part = il.at(idx);
tick = 0;
int staves = part->nstaves();
int strack = score->staffIdx(part) * VOICES;
int etrack = strack + staves * VOICES;
for (MeasureBase* mb = score->measures()->first(); mb; mb = mb->next()) {
if (mb->type() != Element::MEASURE)
continue;
Measure* m = (Measure*)mb;
for (int st = strack; st < etrack; ++st) {
// sstaff - xml staff number, counting from 1 for this
// instrument
// special number 0 -> dont show staff number in
// xml output (because there is only one staff)
int sstaff = (staves > 1) ? st - strack + VOICES : 0;
sstaff /= VOICES;
for (Segment* seg = m->first(); seg; seg = seg->next()) {
Element* el = seg->element(st);
if (!el)
continue;
// must ignore start repeat to prevent spurious backup/forward
if (el->type() == Element::BAR_LINE && static_cast<BarLine*>(el)->barLineType() == START_REPEAT)
continue;
if (tick != seg->tick())
calcDivMoveToTick(seg->tick());
if (el->isChordRest()) {
int l = static_cast<ChordRest*>(el)->actualTicks();
if (el->type() == Element::CHORD) {
if (isTwoNoteTremolo(static_cast<Chord*>(el)))
l /= 2;
}
#ifdef DEBUG_TICK
qDebug("chordrest %d", l);
#endif
addInteger(l);
tick += l;
}
}
}
// move to end of measure (in case of incomplete last voice)
calcDivMoveToTick(m->tick() + m->ticks());
}
}
// do it: divide by all primes as often as possible
for (int u = 0; u < primes.count(); u++)
while (canDivideBy(primes[u]))
divideBy(primes[u]);
div = MScore::division / integers[0];
#ifdef DEBUG_TICK
qDebug("divisions=%d div=%d", integers[0], div);
#endif
}
//---------------------------------------------------------
// writePageFormat
//---------------------------------------------------------
static void writePageFormat(const PageFormat* pf, Xml& xml, double conversion)
{
xml.stag("page-layout");
//qreal t = 2 * PPI * 10 / 9;
xml.tag("page-height", pf->size().height() * conversion);
xml.tag("page-width", pf->size().width() * conversion);
QString type("both");
if (pf->twosided()) {
type = "even";
xml.stag(QString("page-margins type=\"%1\"").arg(type));
xml.tag("left-margin", pf->evenLeftMargin() * conversion);
xml.tag("right-margin", pf->evenRightMargin() * conversion);
xml.tag("top-margin", pf->evenTopMargin() * conversion);
xml.tag("bottom-margin", pf->evenBottomMargin() * conversion);
xml.etag();
type = "odd";
}
xml.stag(QString("page-margins type=\"%1\"").arg(type));
xml.tag("left-margin", pf->oddLeftMargin() * conversion);
xml.tag("right-margin", pf->oddRightMargin() * conversion);
xml.tag("top-margin", pf->oddTopMargin() * conversion);
xml.tag("bottom-margin", pf->oddBottomMargin() * conversion);
xml.etag();
xml.etag();
}
//---------------------------------------------------------
// defaults
//---------------------------------------------------------
// _spatium = MScore::DPMM * (millimeter * 10.0 / tenths);
static void defaults(Xml& xml, Score* s, double& millimeters, const int& tenths)
{
xml.stag("defaults");
xml.stag("scaling");
xml.tag("millimeters", millimeters);
xml.tag("tenths", tenths);
xml.etag();
const PageFormat* pf = s->pageFormat();
if (pf)
writePageFormat(pf, xml, INCH / millimeters * tenths);
// TODO: also write default system layout here
// when exporting only manual or no breaks, system-distance is not written at all
xml.etag();
}
//---------------------------------------------------------
// creditWords
//---------------------------------------------------------
static void creditWords(Xml& xml, double x, double y, int fs, QString just, QString val, QString words)
{
xml.stag("credit page=\"1\"");
QString tagname = QString("credit-words");
tagname += QString(" default-x=\"%1\"").arg(x);
tagname += QString(" default-y=\"%1\"").arg(y);
tagname += QString(" font-size=\"%1\"").arg(fs);
tagname += " justify=\"" + just + "\"";
tagname += " valign=\"" + val + "\"";
xml.tag(tagname, words);
xml.etag();
}
//---------------------------------------------------------
// credits
//---------------------------------------------------------
void ExportMusicXml::credits(Xml& xml)
{
// debug
qDebug("credits:");
const MeasureBase* measure = score->measures()->first();
if (measure) {
foreach(const Element* element, *measure->el()) {
if (element->type() == Element::TEXT) {
const Text* text = (const Text*)element;
bool mustPrint = true;
if (mustPrint) qDebug("text styled %d style %d(%s) '%s' at %f,%f",
text->styled(),
text->textStyleType(),
qPrintable(text->textStyle().name()),
qPrintable(text->text()),
text->pagePos().x(),
text->pagePos().y()
);
}
}
}
QString rights = score->metaTag("copyright");
if (!rights.isEmpty())
qDebug("copyright '%s'", qPrintable(rights));
qDebug("end credits");
// determine formatting
const PageFormat* pf = score->pageFormat();
if (!pf) return;
//const double t = 2 * PPI * 10 / 9;
//const double t = INCH / millimeters * tenths;
const double h = getTenthsFromInches(pf->size().height());
const double w = getTenthsFromInches(pf->size().width());
const double lm = getTenthsFromInches(pf->oddLeftMargin());
const double rm = getTenthsFromInches(pf->oddRightMargin());
const double tm = getTenthsFromInches(pf->oddTopMargin());
const double bm = getTenthsFromInches(pf->oddBottomMargin());
qDebug(" h=%g w=%g lm=%g rm=%g tm=%g bm=%g", h, w, lm, rm, tm, bm);
// write the credits
// TODO add real font size
if (measure) {
foreach(const Element* element, *measure->el()) {
if (element->type() == Element::TEXT) {
const Text* text = (const Text*)element;
qDebug("x=%g, y=%g fs=%d",
text->pagePos().x(),
h - text->pagePos().y(),
text->font().pointSize()
);
const double ty = h - getTenthsFromDots(text->pagePos().y());
const int fs = text->font().pointSize();
// MusicXML credit-words are untyped and simple list position and font info.
// TODO: these parameters should be extracted from text layout and style
// instead of relying on the style name
if (text->styled()) {
QString styleName = text->textStyle().name();
if (styleName == "Title")
creditWords(xml, w / 2, ty, fs, "center", "top", text->text());
else if (styleName == "Subtitle")
creditWords(xml, w / 2, ty, fs, "center", "top", text->text());
else if (styleName == "Composer")
creditWords(xml, w - rm, ty, fs, "right", "top", text->text());
else if (styleName == "Lyricist")
creditWords(xml, lm, ty, fs, "left", "top", text->text());
else
qDebug("credits: text style %s not supported", qPrintable(styleName));
}
}
}
}
if (!rights.isEmpty()) {
const int fs = 8; // score->copyright()->font().pointSize();
creditWords(xml, w / 2, bm, fs, "center", "bottom", rights);
}
}
//---------------------------------------------------------
// midipitch2xml
//---------------------------------------------------------
static int alterTab[12] = { 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0 };
static char noteTab[12] = { 'C', 'C', 'D', 'D', 'E', 'F', 'F', 'G', 'G', 'A', 'A', 'B' };
static void midipitch2xml(int pitch, char& c, int& alter, int& octave)
{
// 60 = C 4
c = noteTab[pitch % 12];
alter = alterTab[pitch % 12];
octave = pitch / 12 - 1;
qDebug("midipitch2xml(pitch %d) step %c, alter %d, octave %d", pitch, c, alter, octave);
}
//---------------------------------------------------------
// tabpitch2xml
//---------------------------------------------------------
static char TpcNames[] = "FCGDAEB";
static void tabpitch2xml(int pitch, int tpc, char& c, int& alter, int& octave)
{
c = TpcNames[(tpc + 1) % 7];
alter = (tpc + 1) / 7 - 2;
octave = pitch / 12 - 1;
if (alter < -2 || 2 < alter)
qDebug("tabpitch2xml(pitch %d, tpc %d) problem: step %c, alter %d, octave %d",
pitch, tpc, c, alter, octave);
else
qDebug("tabpitch2xml(pitch %d, tpc %d) step %c, alter %d, octave %d",
pitch, tpc, c, alter, octave);
}
//---------------------------------------------------------
// pitch2xml
//---------------------------------------------------------
// TODO cleanup / validation
// TBD: use tpc/pitch instead (like tabpitch2xml)
void ExportMusicXml::pitch2xml(Note* note, char& c, int& alter, int& octave)
{
static char table1[] = "FEDCBAG";
Chord* chord = note->chord();
int tick = chord->tick();
int staffIdx = chord->staffIdx() + chord->staffMove();
Staff* i = note->score()->staff(staffIdx);
ClefType clef = i->clef(tick);
int offset = clefTable[clef].pitchOffset - 45; // HACK
int step = (note->line() - offset + 700) % 7;
// step = 6 - ((absoluteStaffLine(note->line(), clef) + 3)%7));
// c = "CDEFGA"[absoluteStaffLine(note->line(), clef) % 7];
c = table1[step];
// printf("====<%c> <%c>\n", c, "CDEFGAB"[absoluteStaffLine(note->line(), clef) % 7]);
int pitch = note->pitch() - 12;
octave = pitch / 12;
static int table2[7] = { 5, 4, 2, 0, 11, 9, 7 };
int npitch = table2[step] + (octave + 1) * 12;
//
// HACK:
// On percussion clefs there is no relationship between
// note->pitch() and note->line()
// note->line() is determined by drumMap
//
if (clef == CLEF_PERC || clef == CLEF_PERC2) {
alter = 0;
pitch = line2pitch(note->line(), clef, 0);
octave = (pitch / 12) - 1;
}
else
alter = note->pitch() - npitch;
// correct for ottava lines
int ottava = 0;
switch (note->ppitch() - note->pitch()) {
case 24: ottava = 2; break;
case 12: ottava = 1; break;
case 0: ottava = 0; break;
case -12: ottava = -1; break;
case -24: ottava = -2; break;
default: qDebug("pitch2xml() tick=%d pitch()=%d ppitch()=%dd",
tick, note->pitch(), note->ppitch());
}
octave += ottava;
//deal with Cb and B#
if (alter > 2) {
qDebug("pitch2xml problem: alter %d step %d(line %d) octave %d clef %d(offset %d)\n",
alter, step, note->line(), octave, clef, offset);
//HACK:
alter -= 12;
octave += 1;
}
if (alter < -2) {
qDebug("pitch2xml problem: alter %d step %d(line %d) octave %d clef %d(offset %d)\n",
alter, step, note->line(), octave, clef, offset);
//HACK:
alter += 12;
octave -= 1;
}
}
// unpitch2xml -- calculate display-step and display-octave for an unpitched note
// note:
// even though this produces the correct step/octave according to Recordare's tutorial
// Finale Notepad 2012 does not import a three line staff with percussion clef correctly
// Same goes for Sibelius 6 in case of three or five line staff with percussion clef
void ExportMusicXml::unpitch2xml(Note* note, char& step, int& octave)
{
static char table1[] = "FEDCBAG";
int tick = note->chord()->tick();
Staff* st = note->staff();
ClefType ct = st->clef(tick);
// offset in lines between staff with current clef and with G clef
int clefOffset = clefTable[ct].pitchOffset - clefTable[CLEF_G].pitchOffset;
// line note would be on on a five line staff with G clef
// note top line is line 0, bottom line is line 8
int line5g = note->line() - clefOffset;
// in MusicXML with percussion clef, step and octave are determined as if G clef is used
// when stafflines is not equal to five, in MusicXML the bottom line is still E4.
// in MuseScore assumes line 0 is F5
// MS line numbers (top to bottom) plus correction to get lowest line at E4 (line 8)
// 1 line staff: 0 -> correction 8
// 3 line staff: 2, 4, 6 -> correction 2
// 5 line staff: 0, 2, 4, 6, 8 -> correction 0
// TODO handle other # staff lines ?
if (st->lines() == 1) line5g += 8;
if (st->lines() == 3) line5g += 2;
// index in table1 to get step
int stepIdx = (line5g + 700) % 7;
// get step
step = table1[stepIdx];
// calculate octave, offset "3" correcting for the fact that an octave starts
// with C instead of F
octave =(3 - line5g + 700) / 7 + 5 - 100;
// qDebug("ExportMusicXml::unpitch2xml(%p) clef %d clef.po %d clefOffset %d staff.lines %d note.line %d line5g %d step %c oct %d",
// note, ct, clefTable[ct].pitchOffset, clefOffset, st->lines(), note->line(), line5g, step, octave);
}
//---------------------------------------------------------
// tick2xml
// set type + dots depending on tick len
//---------------------------------------------------------
static QString tick2xml(const int ticks, int* dots)
{
TDuration t;
t.setVal(ticks);
*dots = t.dots();
return t.name();
}
//---------------------------------------------------------
// findVolta -- find volta starting in measure m
//---------------------------------------------------------
static Volta* findVolta(Measure* m, bool left)
{
#if 0 // TODO-S
for (Spanner* el = left ? m->spannerFor() : m->spannerBack(); el; el = el->next()) {
if (el->type() != Element::VOLTA)
continue;
return (Volta*) el;
}
#endif
return 0;
}
//---------------------------------------------------------
// ending
//---------------------------------------------------------
static void ending(Xml& xml, Volta* v, bool left)
{
QString number = "";
QString type = "";
foreach(int i, v->endings()) {
if (!number.isEmpty())
number += ", ";
number += QString("%1").arg(i);
}
if (left) {
type = "start";
}
else {
VoltaType st = v->voltaType();
switch (st) {
case VoltaType::OPEN:
type = "discontinue";
break;
case VoltaType::CLOSED:
type = "stop";
break;
default:
qDebug("unknown volta subtype %d\n", st);
type = "unknown";
break;
}
}
xml.tagE("ending number=\"%s\" type=\"%s\"",
number.toLatin1().data(),
type.toLatin1().data());
}
//---------------------------------------------------------
// barlineLeft -- search for and handle barline left
//---------------------------------------------------------
void ExportMusicXml::barlineLeft(Measure* m)
{
bool rs = m->repeatFlags() & RepeatStart;
Volta* volta = findVolta(m, true);
if (!rs && !volta) return;
attr.doAttr(xml, false);
xml.stag(QString("barline location=\"left\""));
if (rs) {
xml.tag("bar-style", QString("heavy-light"));
xml.tagE("repeat direction=\"forward\"");
}
if (volta) {
ending(xml, volta, true);
}
xml.etag();
}
//---------------------------------------------------------
// barlineRight -- search for and handle barline right
//---------------------------------------------------------
void ExportMusicXml::barlineRight(Measure* m)
{
int bst = m->endBarLineType();
bool visible = m->endBarLineVisible();
bool needBarStyle = (bst != NORMAL_BAR && bst != START_REPEAT) || !visible;
Volta* volta = findVolta(m, false);
if (!needBarStyle && !volta)
return;
xml.stag(QString("barline location=\"right\""));
if (needBarStyle) {
if (!visible) {
xml.tag("bar-style", QString("none"));
} else {
switch (bst) {
case DOUBLE_BAR:
xml.tag("bar-style", QString("light-light"));
break;
case END_REPEAT:
xml.tag("bar-style", QString("light-heavy"));
break;
case BROKEN_BAR:
xml.tag("bar-style", QString("dashed"));
break;
case DOTTED_BAR:
xml.tag("bar-style", QString("dotted"));
break;
case END_BAR:
case END_START_REPEAT:
xml.tag("bar-style", QString("light-heavy"));
break;
default:
qDebug("ExportMusicXml::bar(): bar subtype %d not supported\n", bst);
break;
}
}
}
if (volta)
ending(xml, volta, false);
if (bst == END_REPEAT || bst == END_START_REPEAT)
xml.tagE("repeat direction=\"backward\"");
xml.etag();
}
//---------------------------------------------------------
// moveToTick
//---------------------------------------------------------
void ExportMusicXml::moveToTick(int t)
{
// qDebug("ExportMusicXml::moveToTick(t=%d) tick=%d\n", t, tick);
if (t < tick) {
#ifdef DEBUG_TICK
qDebug(" -> backup");
#endif
attr.doAttr(xml, false);
xml.stag("backup");
xml.tag("duration", (tick - t) / div);
xml.etag();
}
else if (t > tick) {
#ifdef DEBUG_TICK
qDebug(" -> forward");
#endif
attr.doAttr(xml, false);
xml.stag("forward");
xml.tag("duration", (t - tick) / div);
xml.etag();
}
tick = t;
}
//---------------------------------------------------------
// timesig
//---------------------------------------------------------
void ExportMusicXml::timesig(TimeSig* tsig)
{
int st = tsig->timeSigType();
Fraction ts = tsig->sig();
int z = ts.numerator();
int n = ts.denominator();
QString ns = tsig->numeratorString();
attr.doAttr(xml, true);
if (st == TSIG_FOUR_FOUR)
xml.stag("time symbol=\"common\"");
else if (st == TSIG_ALLA_BREVE)
xml.stag("time symbol=\"cut\"");
else
xml.stag("time");
QRegExp rx("^\\d+(\\+\\d+)+$"); // matches a compound numerator
if (rx.exactMatch(ns))
// if compound numerator, exported as is
xml.tag("beats", ns);
else
// else fall back and use the numerator as integer
xml.tag("beats", z);
xml.tag("beat-type", n);
xml.etag();
}
//---------------------------------------------------------
// keysig
//---------------------------------------------------------
void ExportMusicXml::keysig(int key, int staff, bool visible)
{
QString tg = "key";
if (staff)
tg += QString(" number=\"%1\"").arg(staff);
if (!visible)
tg += " print-object=\"no\"";
attr.doAttr(xml, true);
xml.stag(tg);
xml.tag("fifths", key);
xml.tag("mode", QString("major"));
xml.etag();
}
//---------------------------------------------------------
// clef
//---------------------------------------------------------
void ExportMusicXml::clef(int staff, int clef)
{
#ifdef DEBUG_CLEF
qDebug("ExportMusicXml::clef(staff %d, clef %d)", staff, clef);
#endif
attr.doAttr(xml, true);
if (staff)
xml.stag(QString("clef number=\"%1\"").arg(staff));
else
xml.stag("clef");
QString sign = clefTable[clef].sign;
int line = clefTable[clef].line;
xml.tag("sign", sign);
xml.tag("line", line);
if (clefTable[clef].octChng)
xml.tag("clef-octave-change", clefTable[clef].octChng);
xml.etag();
}
//---------------------------------------------------------
// tupletStartStop
//---------------------------------------------------------
// LVIFIX: add placement to tuplet support
// <notations>
// <tuplet type="start" placement="above" bracket="no"/>
// </notations>
static void tupletStartStop(ChordRest* cr, Notations& notations, Xml& xml)
{
Tuplet* t = cr->tuplet();
if (!t) return;
if (cr == t->elements().front()) {
notations.tag(xml);
QString tupletTag = "tuplet type=\"start\"";
tupletTag += " bracket=";
tupletTag += t->hasBracket() ? "\"yes\"" : "\"no\"";
if (t->numberType() == Tuplet::SHOW_RELATION)
tupletTag += " show-number=\"both\"";
if (t->numberType() == Tuplet::NO_TEXT)
tupletTag += " show-number=\"none\"";
xml.tagE(tupletTag);
}
if (cr == t->elements().back()) {
notations.tag(xml);
xml.tagE("tuplet type=\"stop\"");
}
}
//---------------------------------------------------------
// wavyLineStartStop
//---------------------------------------------------------
static void wavyLineStartStop(Chord* chord, Notations& notations, Ornaments& ornaments, Xml& xml,
TrillHash& trillStart, TrillHash& trillStop)
{
// TODO LVI this does not support overlapping trills
if (trillStop.contains(chord)) {
notations.tag(xml);
ornaments.tag(xml);
xml.tagE("wavy-line type=\"stop\"");
trillStop.remove(chord);
}
if (trillStart.contains(chord)) {
notations.tag(xml);
ornaments.tag(xml);
// mscore only supports wavy-line with trill-mark
xml.tagE("trill-mark");
xml.tagE("wavy-line type=\"start\"");
trillStart.remove(chord);
}
}
//---------------------------------------------------------
// hasBreathMark - determine if chord has breath-mark
//---------------------------------------------------------
static Breath* hasBreathMark(Chord* ch)
{
Segment* s = ch->segment();
s = s->next1();
Breath* b = 0;
if (s->segmentType() == Segment::SegBreath)
b = static_cast<Breath*>(s->element(ch->track()));
return b;
}
//---------------------------------------------------------
// tremoloSingleStartStop
//---------------------------------------------------------
static void tremoloSingleStartStop(Chord* chord, Notations& notations, Ornaments& ornaments, Xml& xml)
{
if (chord->tremolo()) {
Tremolo* tr = chord->tremolo();
int count = 0;
int st = tr->tremoloType();
QString type = "";
if (chord->tremoloChordType() == TremoloSingle) {
type = "single";
switch (st) {
case TREMOLO_R8: count = 1; break;
case TREMOLO_R16: count = 2; break;
case TREMOLO_R32: count = 3; break;
case TREMOLO_R64: count = 4; break;
default: qDebug("unknown tremolo single %d\n", st); break;
}
}
else if (chord->tremoloChordType() == TremoloFirstNote) {
type = "start";
switch (st) {
case TREMOLO_C8: count = 1; break;
case TREMOLO_C16: count = 2; break;
case TREMOLO_C32: count = 3; break;
case TREMOLO_C64: count = 4; break;
default: qDebug("unknown tremolo double %d\n", st); break;
}
}
else if (chord->tremoloChordType() == TremoloSecondNote) {
type = "stop";
switch (st) {
case TREMOLO_C8: count = 1; break;
case TREMOLO_C16: count = 2; break;
case TREMOLO_C32: count = 3; break;
case TREMOLO_C64: count = 4; break;
default: qDebug("unknown tremolo double %d\n", st); break;
}
}
else qDebug("unknown tremolo subtype %d\n", st);
if (type != "" && count > 0) {
notations.tag(xml);
ornaments.tag(xml);
xml.tag(QString("tremolo type=\"%1\"").arg(type), count);
}
}
}
//---------------------------------------------------------
// chordAttributes
//---------------------------------------------------------
static void chordAttributes(Chord* chord, Notations& notations, Technical& technical, Xml& xml,
TrillHash& trillStart, TrillHash& trillStop)
{
const QList<Articulation*>& na = chord->articulations();
// first output the fermatas
foreach (const Articulation* a, na) {
ArticulationType at = a->articulationType();
if (at == Articulation_Fermata
|| at == Articulation_Shortfermata
|| at == Articulation_Longfermata
|| at == Articulation_Verylongfermata) {
notations.tag(xml);
QString type = a->up() ? "upright" : "inverted";
if (at == Articulation_Fermata)
xml.tagE(QString("fermata type=\"%1\"").arg(type));
else if (at == Articulation_Shortfermata)
xml.tag(QString("fermata type=\"%1\"").arg(type), "angled");
// MusicXML does not support the very long fermata,
// export as long fermata (better than not exporting at all)
else if (at == Articulation_Longfermata
|| at == Articulation_Verylongfermata)
xml.tag(QString("fermata type=\"%1\"").arg(type), "square");
}
}
// then the attributes whose elements are children of <articulations>
Articulations articulations;
foreach (const Articulation* a, na) {
switch (a->articulationType()) {
case Articulation_Fermata:
case Articulation_Shortfermata:
case Articulation_Longfermata:
case Articulation_Verylongfermata:
// ignore, already handled
break;
case Articulation_Sforzatoaccent:
{
notations.tag(xml);
articulations.tag(xml);
xml.tagE("accent");
}
break;
case Articulation_Staccato:
{
notations.tag(xml);
articulations.tag(xml);
xml.tagE("staccato");
}
break;
case Articulation_Staccatissimo:
{
notations.tag(xml);
articulations.tag(xml);
xml.tagE("staccatissimo");
}
break;
case Articulation_Tenuto:
{
notations.tag(xml);
articulations.tag(xml);
xml.tagE("tenuto");
}
break;
case Articulation_Marcato:
{
notations.tag(xml);
articulations.tag(xml);
if (a->up())
xml.tagE("strong-accent type=\"up\"");
else
xml.tagE("strong-accent type=\"down\"");
}
break;
case Articulation_Portato:
{
notations.tag(xml);
articulations.tag(xml);
xml.tagE("detached-legato");
}
break;
case Articulation_Reverseturn:
case Articulation_Turn:
case Articulation_Trill:
case Articulation_Prall:
case Articulation_Mordent:
case Articulation_PrallPrall:
case Articulation_PrallMordent:
case Articulation_UpPrall:
case Articulation_DownPrall:
case Articulation_UpMordent:
case Articulation_DownMordent:
case Articulation_PrallDown:
case Articulation_PrallUp:
case Articulation_LinePrall:
// ignore, handled with ornaments
break;
case Articulation_Plusstop:
case Articulation_Upbow:
case Articulation_Downbow:
case Articulation_Snappizzicato:
case Articulation_Thumb:
// ignore, handled with technical
break;
default:
qDebug("unknown chord attribute %s", qPrintable(a->subtypeUserName()));
break;
}
}
if (Breath* b = hasBreathMark(chord)) {
notations.tag(xml);
articulations.tag(xml);
int st = b->breathType();
if (st == 0 || st == 1)
xml.tagE("breath-mark");
else
xml.tagE("caesura");
}
foreach(Element* e, chord->el()) {
qDebug("chordAttributes: el %p type %d (%s)", e, e->type(), e->name());
if (e->type() == Element::CHORDLINE) {
ChordLine const* const cl = static_cast<ChordLine const* const>(e);
QString subtype;
switch (cl->chordLineType()) {
case CHORDLINE_FALL:
subtype = "falloff";
break;
case CHORDLINE_DOIT:
subtype = "doit";
break;
case CHORDLINE_PLOP:
subtype = "plop";
break;
case CHORDLINE_SCOOP:
subtype = "scoop";
break;
default:
qDebug("unknown ChordLine subtype %d", cl->chordLineType());
}
if (subtype != "") {
notations.tag(xml);
articulations.tag(xml);
xml.tagE(subtype);
}
}
}
articulations.etag(xml);
// then the attributes whose elements are children of <ornaments>
Ornaments ornaments;
foreach (const Articulation* a, na) {
switch (a->articulationType()) {
case Articulation_Fermata:
case Articulation_Shortfermata:
case Articulation_Longfermata:
case Articulation_Verylongfermata:
case Articulation_Sforzatoaccent:
case Articulation_Staccato:
case Articulation_Staccatissimo:
case Articulation_Tenuto:
case Articulation_Marcato:
case Articulation_Portato:
// ignore, already handled
break;
case Articulation_Reverseturn:
{
notations.tag(xml);
ornaments.tag(xml);
xml.tagE("inverted-turn");
}
break;
case Articulation_Turn:
{
notations.tag(xml);
ornaments.tag(xml);
xml.tagE("turn");
}
break;
case Articulation_Trill:
{
notations.tag(xml);
ornaments.tag(xml);
xml.tagE("trill-mark");
}
break;
case Articulation_Prall:
{
notations.tag(xml);
ornaments.tag(xml);
xml.tagE("inverted-mordent");
}
break;
case Articulation_Mordent:
{
notations.tag(xml);
ornaments.tag(xml);
xml.tagE("mordent");
}
break;
case Articulation_PrallPrall:
{
notations.tag(xml);
ornaments.tag(xml);
xml.tagE("inverted-mordent long=\"yes\"");
}
break;
case Articulation_PrallMordent:
{
notations.tag(xml);
ornaments.tag(xml);
xml.tagE("mordent long=\"yes\"");
}
break;
case Articulation_UpPrall:
{
notations.tag(xml);
ornaments.tag(xml);
xml.tagE("inverted-mordent long=\"yes\" approach=\"below\"");
}
break;
case Articulation_DownPrall:
{
notations.tag(xml);
ornaments.tag(xml);
xml.tagE("inverted-mordent long=\"yes\" approach=\"above\"");
}
break;
case Articulation_UpMordent:
{
notations.tag(xml);
ornaments.tag(xml);
xml.tagE("mordent long=\"yes\" approach=\"below\"");
}
break;
case Articulation_DownMordent:
{
notations.tag(xml);
ornaments.tag(xml);
xml.tagE("mordent long=\"yes\" approach=\"above\"");
}
break;
case Articulation_PrallDown:
{
notations.tag(xml);
ornaments.tag(xml);
xml.tagE("inverted-mordent long=\"yes\" departure=\"below\"");
}
break;
case Articulation_PrallUp:
{
notations.tag(xml);
ornaments.tag(xml);
xml.tagE("inverted-mordent long=\"yes\" departure=\"above\"");
}
break;
case Articulation_LinePrall:
{
// MusicXML 3.0 does not distinguish between downprall and lineprall
notations.tag(xml);
ornaments.tag(xml);
xml.tagE("inverted-mordent long=\"yes\" approach=\"above\"");
}
break;
case Articulation_Schleifer:
{
notations.tag(xml);
ornaments.tag(xml);
xml.tagE("schleifer");
}
break;
case Articulation_Plusstop:
case Articulation_Upbow:
case Articulation_Downbow:
case Articulation_Snappizzicato:
case Articulation_Thumb:
// ignore, handled with technical
break;
default:
qDebug("unknown chord attribute %s\n", qPrintable(a->subtypeUserName()));
break;
}
}
tremoloSingleStartStop(chord, notations, ornaments, xml);
wavyLineStartStop(chord, notations, ornaments, xml, trillStart, trillStop);
ornaments.etag(xml);
// and finally the attributes whose elements are children of <technical>
foreach (const Articulation* a, na) {
switch (a->articulationType()) {
case Articulation_Plusstop:
{
notations.tag(xml);
technical.tag(xml);
xml.tagE("stopped");
}
break;
case Articulation_Upbow:
{
notations.tag(xml);
technical.tag(xml);
xml.tagE("up-bow");
}
break;
case Articulation_Downbow:
{
notations.tag(xml);
technical.tag(xml);
xml.tagE("down-bow");
}
break;
case Articulation_Snappizzicato:
{
notations.tag(xml);
technical.tag(xml);
xml.tagE("snap-pizzicato");
}
break;
case Articulation_Ouvert:
{
notations.tag(xml);
technical.tag(xml);
xml.tagE("open-string");
}
break;
case Articulation_Thumb:
{
notations.tag(xml);
technical.tag(xml);
xml.tagE("thumb-position");
}
break;
default:
// others silently ignored
// qDebug("unknown chord attribute %s", qPrintable(a->subtypeUserName()));
break;
}
}
}
//---------------------------------------------------------
// arpeggiate
//---------------------------------------------------------
// <notations>
// <arpeggiate direction="up"/>
// </notations>
static void arpeggiate(Arpeggio* arp, bool front, bool back, Xml& xml, Notations& notations)
{
switch (arp->arpeggioType()) {
case ArpeggioType::NORMAL:
notations.tag(xml);
xml.tagE("arpeggiate");
break;
case ArpeggioType::UP:
notations.tag(xml);
xml.tagE("arpeggiate direction=\"up\"");
break;
case ArpeggioType::DOWN:
notations.tag(xml);
xml.tagE("arpeggiate direction=\"down\"");
break;
case ArpeggioType::BRACKET:
if (front) {
notations.tag(xml);
xml.tagE("non-arpeggiate type=\"bottom\"");
}
if (back) {
notations.tag(xml);
xml.tagE("non-arpeggiate type=\"top\"");
}
break;
default:
qDebug("unknown arpeggio subtype %d\n", int(arp->arpeggioType()));
break;
}
}
// find the next chord in the same track
static Chord* nextChord(Chord* ch)
{
Segment* s = ch->segment();
s = s->next1();
while (s) {
if (s->segmentType() == Segment::SegChordRest && s->element(ch->track()))
break;
s = s->next1();
}
if (s == 0) {
// qDebug("no segment for second note of glissando found\n");
return 0;
}
Chord* c = static_cast<Chord*>(s->element(ch->track()));
if (c == 0 || c->type() != Element::CHORD) {
// qDebug("no second note for glissando found, track %d\n", track());
return 0;
}
return c;
}
//---------------------------------------------------------
// determineTupletNormalTicks
//---------------------------------------------------------
/**
Determine the ticks in the normal type for the tuplet \a chord.
This is non-zero only if chord if part of a tuplet containing
different length duration elements.
TODO determine how to handle baselen with dots and verify correct behaviour.
TODO verify if baseLen should always be correctly set
(it seems after MusicXMLimport this is not the case)
*/
static int determineTupletNormalTicks(ChordRest const* const chord)
{
Tuplet const* const t = chord->tuplet();
if (!t)
return 0;
qDebug("determineTupletNormalTicks t %p baselen %d", t, t->baseLen().ticks());
for (int i = 0; i < t->elements().size(); ++i)
qDebug("determineTupletNormalTicks t %p i %d ticks %d", t, i, t->elements().at(i)->duration().ticks());
for (int i = 1; i < t->elements().size(); ++i)
if (t->elements().at(0)->duration().ticks() != t->elements().at(i)->duration().ticks())
return t->baseLen().ticks();
return 0;
}
//---------------------------------------------------------
// writeBeam
//---------------------------------------------------------
static void writeBeam(Xml& xml, ChordRest* cr, Beam* b)
{
const QList<ChordRest*>& elements = b->elements();
int idx = elements.indexOf(cr);
if (idx == -1) {
qDebug("Beam::writeMusicXml(): cannot find ChordRest\n");
return;
}
int blp = -1; // beam level previous chord
int blc = -1; // beam level current chord
int bln = -1; // beam level next chord
// find beam level previous chord
for (int i = idx - 1; blp == -1 && i >= 0; --i) {
ChordRest* crst = elements[i];
if (crst->type() == Element::CHORD)
blp = (static_cast<Chord*>(crst))->beams();
}
// find beam level current chord
if (cr->type() == Element::CHORD)
blc = (static_cast<Chord*>(cr))->beams();
// find beam level next chord
for (int i = idx + 1; bln == -1 && i < elements.size(); ++i) {
ChordRest* crst = elements[i];
if (crst->type() == Element::CHORD)
bln = (static_cast<Chord*>(crst))->beams();
}
for (int i = 1; i <= blc; ++i) {
QString s;
if (blp < i && bln >= i) s = "begin";
else if (blp < i && bln < i) {
if (bln > 0) s = "forward hook";
else if (blp > 0) s = "backward hook";
}
else if (blp >= i && bln < i)
s = "end";
else if (blp >= i && bln >= i)
s = "continue";
if (s != "")
xml.tag(QString("beam number=\"%1\"").arg(i), s);
}
}
//---------------------------------------------------------
// chord
//---------------------------------------------------------
/**
Write \a chord on \a staff with lyriclist \a ll.
For a single-staff part, \a staff equals zero, suppressing the <staff> element.
*/
void ExportMusicXml::chord(Chord* chord, int staff, const QList<Lyrics*>* ll, bool useDrumset)
{
QList<Note*> nl = chord->notes();
NoteType gracen = nl.front()->noteType();
bool grace = (gracen == NOTE_ACCIACCATURA
|| gracen == NOTE_APPOGGIATURA
|| gracen == NOTE_GRACE4
|| gracen == NOTE_GRACE16
|| gracen == NOTE_GRACE32);
int tremCorr = 1; // duration correction for two note tremolo
if (isTwoNoteTremolo(chord)) tremCorr = 2;
if (!grace) tick += chord->actualTicks();
#ifdef DEBUG_TICK
qDebug("ExportMusicXml::chord() oldtick=%d", tick);
qDebug("notetype=%d grace=%d", gracen, grace);
qDebug(" newtick=%d", tick);
#endif
const PageFormat* pf = score->pageFormat();
const double pageHeight = getTenthsFromInches(pf->size().height());
// const double pageWidth = getTenthsFromInches(pf->size().width());
foreach(Note* note, nl) {
QString val;
attr.doAttr(xml, false);
QString noteTag = QString("note");
if (preferences.musicxmlExportLayout && pf) {
double measureX = getTenthsFromDots(chord->measure()->pagePos().x());
double measureY = pageHeight - getTenthsFromDots(chord->measure()->pagePos().y());
double noteX = getTenthsFromDots(note->pagePos().x());
double noteY = pageHeight - getTenthsFromDots(note->pagePos().y());
noteTag += QString(" default-x=\"%1\"").arg(QString::number(noteX - measureX,'f',2));
noteTag += QString(" default-y=\"%1\"").arg(QString::number(noteY - measureY,'f',2));
}
if (!note->visible()) {
noteTag += QString(" print-object=\"no\"");
}
//TODO support for OFFSET_VAL
if (note->veloType() == MScore::USER_VAL) {
int velo = note->veloOffset();
noteTag += QString(" dynamics=\"%1\"").arg(QString::number(velo * 100.0 / 90.0,'f',2));
}
xml.stag(noteTag);
if (grace) {
if (note->noteType() == NOTE_ACCIACCATURA)
xml.tagE("grace slash=\"yes\"");
else
xml.tagE("grace");
}
if (note != nl.front())
xml.tagE("chord");
char c;
int alter;
int octave;
char buffer[2];
// TODO: following code requires further cleanup and validation
if (chord->staff() && chord->staff()->isTabStaff()) {
tabpitch2xml(note->pitch(), note->tpc(), c, alter, octave);
buffer[0] = c;
buffer[1] = 0;
// pitch
xml.stag("pitch");
xml.tag("step", QString(buffer));
if (alter)
xml.tag("alter", alter);
xml.tag("octave", octave);
xml.etag();
}
else {
if (!useDrumset) {
pitch2xml(note, c, alter, octave);
buffer[0] = c;
buffer[1] = 0;
// pitch
xml.stag("pitch");
xml.tag("step", QString(buffer));
if (alter)
xml.tag("alter", alter);
xml.tag("octave", octave);
xml.etag();
}
else {
// unpitched
unpitch2xml(note, c, octave);
buffer[0] = c;
buffer[1] = 0;
xml.stag("unpitched");
xml.tag("display-step", QString(buffer));
xml.tag("display-octave", octave);
xml.etag();
}
}
// duration
if (!grace)
xml.tag("duration", note->chord()->actualTicks() / div);
if (note->tieBack())
xml.tagE("tie type=\"stop\"");
if (note->tieFor())
xml.tagE("tie type=\"start\"");
//instrument for unpitched
if (useDrumset)
xml.tagE(QString("instrument id=\"P%1-I%2\"").arg(score->parts().indexOf(note->staff()->part()) + 1).arg(note->pitch() + 1));
// voice
// for a single-staff part, staff is 0, which needs to be corrected
// to calculate the correct voice number
int voice = (staff-1) * VOICES + note->chord()->voice() + 1;
if (staff == 0)
voice += VOICES;
xml.tag("voice", voice);
// type
int dots = 0;
Tuplet* t = note->chord()->tuplet();
int actNotes = 1;
int nrmNotes = 1;
int nrmTicks = 0;
if (t) {
actNotes = t->ratio().numerator();
nrmNotes = t->ratio().denominator();
nrmTicks = determineTupletNormalTicks(chord);
}
QString s = tick2xml(note->chord()->actualTicks() * actNotes * tremCorr / nrmNotes, &dots);
if (s.isEmpty()) {
qDebug("no note type found for ticks %d\n",
note->chord()->actualTicks());
}
xml.tag("type", s);
for (int ni = dots; ni > 0; ni--)
xml.tagE("dot");
// accidental
Accidental* acc = note->accidental();
if (acc) {
/*
MusicXML accidental names include:
sharp,natural, flat, double-sharp, sharp-sharp, flat-flat,
natural-sharp, natural-flat, quarter-flat, quarter-sharp,
three-quarters-flat, and three-quarters-sharp
*/
QString s;
switch (acc->accidentalType()) {
case Accidental::ACC_SHARP: s = "sharp"; break;
case Accidental::ACC_FLAT: s = "flat"; break;
case Accidental::ACC_SHARP2: s = "double-sharp"; break;
case Accidental::ACC_FLAT2: s = "flat-flat"; break;
case Accidental::ACC_NATURAL: s = "natural"; break;
case Accidental::ACC_FLAT_SLASH: s = "quarter-flat"; break; // (alternative)
case Accidental::ACC_MIRRORED_FLAT: s = "quarter-flat"; break; // (recommended by Michael)
case Accidental::ACC_FLAT_ARROW_UP: s = "quarter-flat"; break; // (alternative)
case Accidental::ACC_NATURAL_ARROW_DOWN: s = "quarter-flat"; break; // (alternative)
case Accidental::ACC_SHARP_SLASH: s = "quarter-sharp"; break; // (recommended by Michael)
case Accidental::ACC_SHARP_ARROW_DOWN: s = "quarter-sharp"; break; // (alternative)
case Accidental::ACC_NATURAL_ARROW_UP: s = "quarter-sharp"; break; // (alternative)
case Accidental::ACC_MIRRORED_FLAT2: s = "three-quarters-flat"; break; // (recommended by Michael)
case Accidental::ACC_FLAT_FLAT_SLASH: s = "three-quarters-flat"; break; // (alternative)
case Accidental::ACC_FLAT_ARROW_DOWN: s = "three-quarters-flat"; break; // (alternative)
case Accidental::ACC_SHARP_SLASH4: s = "three-quarters-sharp"; break; // (recommended by Michael)
case Accidental::ACC_SHARP_ARROW_UP: s = "three-quarters-sharp"; break; // (alternate)
case Accidental::ACC_SORI: s = "sori"; break; //sori
case Accidental::ACC_KORON: s = "koron"; break; //koron
default:
qDebug("unknown accidental %d\n", int(acc->accidentalType()));
}
if (s != "") {
if (note->accidental()->hasBracket())
xml.tag("accidental parentheses=\"yes\"", s);
else
xml.tag("accidental", s);
}
}
// time modification for two note tremolo
// TODO: support tremolo in tuplet ?
if (tremCorr == 2) {
xml.stag("time-modification");
xml.tag("actual-notes", 2);
xml.tag("normal-notes", 1);
xml.etag();
}
// time modification for tuplet
if (t) {
// TODO: remove following duplicated code (present for both notes and rests)
xml.stag("time-modification");
xml.tag("actual-notes", actNotes);
xml.tag("normal-notes", nrmNotes);
qDebug("nrmTicks %d", nrmTicks);
if (nrmTicks > 0) {
int nrmDots = 0;
QString nrmType = tick2xml(nrmTicks, &nrmDots);
if (nrmType.isEmpty())
qDebug("no note type found for ticks %d", nrmTicks);
else {
xml.tag("normal-type", nrmType);
for (int ni = nrmDots; ni > 0; ni--)
xml.tagE("normal-dot");
}
}
xml.etag();
}
// no stem for whole notes and beyond
if (chord->noStem() || chord->measure()->slashStyle(chord->staffIdx())) {
xml.tag("stem", QString("none"));
}
else if ((note->chord()->actualTicks() * actNotes * tremCorr / nrmNotes) < (4 * MScore::division)) {
xml.tag("stem", QString(note->chord()->up() ? "up" : "down"));
}
QString noteheadTagname = QString("notehead");
QColor noteheadColor = note->color();
if (noteheadColor != MScore::defaultColor)
noteheadTagname += " color=\"" + noteheadColor.name().toUpper() + "\"";
if (note->headGroup() == 5)
xml.tag(noteheadTagname, "slash");
else if (note->headGroup() == 3)
xml.tag(noteheadTagname, "triangle");
else if (note->headGroup() == 2)
xml.tag(noteheadTagname, "diamond");
else if (note->headGroup() == 1)
xml.tag(noteheadTagname, "x");
else if (note->headGroup() == 6)
xml.tag(noteheadTagname, "circle-x");
else if (note->headGroup() == 7)
xml.tag(noteheadTagname, "do");
else if (note->headGroup() == 8)
xml.tag(noteheadTagname, "re");
else if (note->headGroup() == 4)
xml.tag(noteheadTagname, "mi");
else if (note->headGroup() == 9)
xml.tag(noteheadTagname, "fa");
else if (note->headGroup() == 10)
xml.tag(noteheadTagname, "la");
else if (note->headGroup() == 11)
xml.tag(noteheadTagname, "ti");
else if (note->headGroup() == 12)
xml.tag(noteheadTagname, "so");
else if (noteheadColor != MScore::defaultColor)
xml.tag(noteheadTagname, "normal");
// LVIFIX: check move() handling
if (staff)
xml.tag("staff", staff + note->chord()->staffMove());
// beaming
// <beam number="1">start</beam>
// <beam number="1">end</beam>
// <beam number="1">continue</beam>
// <beam number="1">backward hook</beam>
// <beam number="1">forward hook</beam>
if (note == nl.front() && chord->beam())
writeBeam(xml, chord, chord->beam());
Notations notations;
Technical technical;
if (note->tieBack()) {
notations.tag(xml);
xml.tagE("tied type=\"stop\"");
}
if (note->tieFor()) {
notations.tag(xml);
xml.tagE("tied type=\"start\"");
}
if (note == nl.front()) {
tupletStartStop(chord, notations, xml);
sh.doSlurStop(chord, notations, xml);
sh.doSlurStart(chord, notations, xml);
chordAttributes(chord, notations, technical, xml, trillStart, trillStop);
}
foreach (const Element* e, note->el()) {
if (e->type() == Element::FINGERING) {
Text* f = (Text*)e;
notations.tag(xml);
technical.tag(xml);
QString t = f->text();
if (f->textStyleType() == TEXT_STYLE_FINGERING) {
// p, i, m, a, c represent the plucking finger
if (t == "p" || t == "i" || t == "m" || t == "a" || t == "c")
xml.tag("pluck", t);
else
xml.tag("fingering", t);
}
else if (f->textStyleType() == TEXT_STYLE_STRING_NUMBER) {
xml.tag("string", t);
}
else
qDebug("unknown fingering style");
}
else {
// TODO
}
}
// write tablature string / fret
if (chord->staff() && chord->staff()->isTabStaff())
if (note->fret() >= 0 && note->string() >= 0) {
notations.tag(xml);
technical.tag(xml);
xml.tag("string", note->string() + 1);
xml.tag("fret", note->fret());
}
technical.etag(xml);
if (chord->arpeggio()) {
arpeggiate(chord->arpeggio(), note == nl.front(), note == nl.back(), xml, notations);
}
// write glissando (only for last note)
Chord* ch = nextChord(chord);
if ((note == nl.back()) && ch && ch->glissando()) {
gh.doGlissandoStart(ch, notations, xml);
}
if (chord->glissando()) {
gh.doGlissandoStop(chord, notations, xml);
}
notations.etag(xml);
// write lyrics (only for first note)
if ((note == nl.front()) && ll)
lyrics(ll, chord->track());
xml.etag();
}
}
//---------------------------------------------------------
// rest
//---------------------------------------------------------
/**
Write \a rest on \a staff.
For a single-staff part, \a staff equals zero, suppressing the <staff> element.
*/
void ExportMusicXml::rest(Rest* rest, int staff)
{
static char table2[] = "CDEFGAB";
#ifdef DEBUG_TICK
qDebug("ExportMusicXml::rest() oldtick=%d", tick);
#endif
attr.doAttr(xml, false);
QString noteTag = QString("note");
if (!rest->visible() ) {
noteTag += QString(" print-object=\"no\"");
}
xml.stag(noteTag);
int yOffsSt = 0;
int oct = 0;
int stp = 0;
ClefType clef = rest->staff()->clef(rest->tick());
int po = clefTable[clef].pitchOffset;
// Determine y position, but leave at zero in case of tablature staff
// as no display-step or display-octave should be written for a tablature staff,
if (clef != CLEF_TAB && clef != CLEF_TAB2) {
double yOffsSp = rest->userOff().y() / rest->spatium(); // y offset in spatium (negative = up)
yOffsSt = -2 * int(yOffsSp > 0.0 ? yOffsSp + 0.5 : yOffsSp - 0.5); // same rounded to int (positive = up)
po -= 4; // pitch middle staff line (two lines times two steps lower than top line)
po += yOffsSt; // rest "pitch"
oct = po / 7; // octave
stp = po % 7; // step
}
// Either <rest/>
// or <rest><display-step>F</display-step><display-octave>5</display-octave></rest>
if (yOffsSt == 0) {
xml.tagE("rest");
}
else {
xml.stag("rest");
xml.tag("display-step", QString(QChar(table2[stp])));
xml.tag("display-octave", oct - 1);
xml.etag();
}
TDuration d = rest->durationType();
int tickLen = rest->actualTicks();
if (d.type() == TDuration::V_MEASURE) {
// to avoid forward since rest->ticklen=0 in this case.
tickLen = rest->measure()->ticks();
}
tick += tickLen;
#ifdef DEBUG_TICK
qDebug(" tickLen=%d newtick=%d", tickLen, tick);
#endif
xml.tag("duration", tickLen / div);
// for a single-staff part, staff is 0, which needs to be corrected
// to calculate the correct voice number
int voice = (staff-1) * VOICES + rest->voice() + 1;
if (staff == 0)
voice += VOICES;
xml.tag("voice", voice);
// do not output a "type" element for whole measure rest
if (d.type() != TDuration::V_MEASURE) {
QString s = d.name();
int dots = rest->dots();
xml.tag("type", s);
for (int i = dots; i > 0; i--)
xml.tagE("dot");
}
if (rest->tuplet()) {
Tuplet* t = rest->tuplet();
xml.stag("time-modification");
xml.tag("actual-notes", t->ratio().numerator());
xml.tag("normal-notes", t->ratio().denominator());
int nrmTicks = determineTupletNormalTicks(rest);
if (nrmTicks > 0) {
int nrmDots = 0;
QString nrmType = tick2xml(nrmTicks, &nrmDots);
if (nrmType.isEmpty())
qDebug("no note type found for ticks %d", nrmTicks);
else {
xml.tag("normal-type", nrmType);
for (int ni = nrmDots; ni > 0; ni--)
xml.tagE("normal-dot");
}
}
xml.etag();
}
if (staff)
xml.tag("staff", staff);
Notations notations;
tupletStartStop(rest, notations, xml);
notations.etag(xml);
xml.etag();
}
//---------------------------------------------------------
// directionTag
//---------------------------------------------------------
static void directionTag(Xml& xml, Attributes& attr, Element const* const el = 0)
{
attr.doAttr(xml, false);
QString tagname = QString("direction");
if (el) {
/*
qDebug("directionTag() spatium=%g nelem tp=%d (%s,%s)\ndirectionTag() x=%g y=%g xsp,ysp=%g,%g w=%g h=%g userOff.y=%g",
el->spatium(),
el->type(),
el->name(), el->subtypeName().toUtf8().data(),
el->x(), el->y(),
el->x()/el->spatium(), el->y()/el->spatium(),
el->width(), el->height(),
el->userOff().y()
);
if (el->type() == Element::HAIRPIN || el->type() == Element::OTTAVA || el->type() == Element::TEXTLINE) {
SLine* sl = static_cast<const SLine*>(el);
qDebug("directionTag() slin segsz=%d", sl->spannerSegments().size());
if (sl->spannerSegments().size() > 0) {
LineSegment* seg = (LineSegment*)sl->spannerSegments().at(0);
qDebug(" x=%g y=%g w=%g h=%g cpx=%g cpy=%g userOff.y=%g",
seg->x(), seg->y(),
seg->width(), seg->height(),
seg->pagePos().x(), seg->pagePos().y(),
seg->userOff().y());
}
} // if (el->type() == ...
*/
Element* pel = el->parent();
Element* ppel = 0;
if (pel) ppel = pel->parent();
/*
if (pel) {
qDebug("directionTag() prnt tp=%d (%s,%s) x=%g y=%g w=%g h=%g userOff.y=%g",
pel->type(),
pel->name(), pel->subtypeName().toUtf8().data(),
pel->x(), pel->y(),
pel->width(), pel->height(),
pel->userOff().y());
}
if (ppel) {
qDebug("directionTag() pprnt tp=%d (%s,%s) x=%g y=%g w=%g h=%g userOff.y=%g",
ppel->type(),
ppel->name(), ppel->subtypeName().toUtf8().data(),
ppel->x(), ppel->y(),
ppel->width(), ppel->height(),
ppel->userOff().y());
}
*/
if (ppel && ppel->type() == Element::MEASURE) {
// Measure* m = static_cast<Measure*>(ppel);
// System* sys = m->system();
// QRectF bb = sys->staff(el->staffIdx())->bbox();
/*
qDebug("directionTag() syst x=%g y=%g cpx=%g cpy=%g",
sys->pos().x(), sys->pos().y(),
sys->pagePos().x(),
sys->pagePos().y()
);
qDebug("directionTag() staf x=%g y=%g w=%g h=%g",
bb.x(), bb.y(),
bb.width(), bb.height());
// element is above the staff if center of bbox is above center of staff
qDebug("directionTag() center diff=%g", el->y() + el->height() / 2 - bb.y() - bb.height() / 2);
*/
if (el->placement() == Element::ABOVE)
tagname += " placement=\"above\"";
else if (el->placement() == Element::BELOW)
tagname += " placement=\"below\"";
#if 0
if (el->type() == Element::HAIRPIN || el->type() == Element::OTTAVA || el->type() == Element::PEDAL || el->type() == Element::TEXTLINE) {
SLine const* const sl = static_cast<SLine const* const>(el);
if (sl->spannerSegments().size() > 0) {
LineSegment* seg = (LineSegment*)sl->spannerSegments().at(0);
// for the line type elements the reference point is vertically centered
// actual position info is in the segments
// compare the segment's canvas ypos with the staff's center height
if (seg->pagePos().y() < sys->pagePos().y() + bb.y() + bb.height() / 2)
tagname += " placement=\"above\"";
else
tagname += " placement=\"below\"";
}
}
else {
if (el->y() + el->height() / 2 < bb.y() + bb.height() / 2)
tagname += " placement=\"above\"";
else
tagname += " placement=\"below\"";
}
#endif
} // if (ppel && ...
}
xml.stag(tagname);
}
//---------------------------------------------------------
// directionETag
//---------------------------------------------------------
static void directionETag(Xml& xml, int staff, int offs = 0)
{
if (offs)
xml.tag("offset", offs);
if (staff)
xml.tag("staff", staff);
xml.etag();
}
//---------------------------------------------------------
// partGroupStart
//---------------------------------------------------------
static void partGroupStart(Xml& xml, int number, int bracket)
{
xml.stag(QString("part-group type=\"start\" number=\"%1\"").arg(number));
QString br = "";
switch (bracket) {
case BRACKET_NORMAL:
br = "bracket";
break;
case BRACKET_BRACE:
br = "brace";
break;
case BRACKET_SQUARE:
br = "square";
break;
default:
qDebug("bracket subtype %d not understood\n", bracket);
}
if (br != "")
xml.tag("group-symbol", br);
xml.etag();
}
//---------------------------------------------------------
// words
//---------------------------------------------------------
// The note and dot symbols are in Unicode plane, they are stored
// in a QString as a surrogate pair.
// Example: quarter note is 0x1d15f, stored as 0xd834 0xdd5f.
// a line containing only a note and zero or more dots
QRegExp metro("^\\xd834([\\xdd5c-\\xdd5f]|[\\xdd60-\\xdd63])(\\xd834\\xdd6d)?$");
// a note, zero or more dots, zero or more spaces, an equals sign, zero or more spaces
QRegExp metroPlusEquals("\\xd834([\\xdd5c-\\xdd5f]|[\\xdd60-\\xdd63])(\\xd834\\xdd6d)? ?= ?");
// a parenthesis open, zero or more spaces at end of line
QRegExp leftParen("\\( ?$");
// zero or more spaces, an equals sign, zero or more spaces at end of line
QRegExp equals(" ?= ?$");
// note: findUnitAndDots does not check the first char of the surrogate pair
// this has already been done by findMetronome using the regexps above
static bool findUnitAndDots(QString words, QString& unit, int& dots)
{
unit = "";
dots = 0;
// qDebug("findUnitAndDots('%s') slen=%d", qPrintable(words), words.length());
if (!metro.exactMatch(words))
return false;
switch (words.at(1).unicode()) {
case 0xdd5c: unit = "breve"; break;
case 0xdd5d: unit = "whole"; break;
case 0xdd5e: unit = "half"; break;
case 0xdd5f: unit = "quarter"; break;
case 0xdd60: unit = "eighth"; break;
case 0xdd61: unit = "16th"; break;
case 0xdd62: unit = "32nd"; break;
case 0xdd63: unit = "64th"; break;
default: qDebug("findUnitAndDots: unknown char '%s'(0x%0xd)",
qPrintable(words.mid(0, 1)), words.at(0).unicode());
}
for (int i = 3; i < words.length(); i += 2)
switch (words.at(i).unicode()) {
case '.': // fall through
case 0xdd6d: ++dots; break;
// TODO case 0xe10b: ++dots; ++dots; break;
default: qDebug("findUnitAndDots: unknown char '%s'(0x%0xd)",
qPrintable(words.mid(i, 1)), words.at(i).unicode());
}
// qDebug(" unit='%s' dots=%d", qPrintable(unit), dots);
return true;
}
static bool findMetronome(QString words,
QString& wordsLeft, // words left of metronome
bool& hasParen, // parenthesis
QString& metroLeft, // left part of metronome
QString& metroRight, // right part of metronome
QString& wordsRight // words right of metronome
)
{
QString hexWords;
for (int i = 0; i < words.length(); ++i) {
QString n;
n.setNum(words.at(i).unicode(),16);
if (i != 0) hexWords += " ";
hexWords += "0x";
hexWords += n;
}
// qDebug("findMetronome('%s') (%s) slen=%d", qPrintable(words), qPrintable(hexWords), words.length());
wordsLeft = "";
hasParen = false;
metroLeft = "";
metroRight = "";
wordsRight = "";
int pos = metroPlusEquals.indexIn(words);
if (pos != -1) {
int len = metroPlusEquals.matchedLength();
/*
qDebug(" mpos=%d mlen=%d",
pos, len
);
*/
if (words.length() > pos + len) {
QString s1 = words.mid(0, pos); // string to the left of metronome
QString s2 = words.mid(pos, len); // first note and equals sign
QString s3 = words.mid(pos + len); // string to the right of equals sign
/*
qDebug("found metronome: '%s'%s'%s'",
qPrintable(s1),
qPrintable(s2),
qPrintable(s3)
);
*/
// determine if metronome has parentheses
// left part of string must end with parenthesis plus optional spaces
// right part of string must have parenthesis (but not in first pos)
int lparen = leftParen.indexIn(s1);
int rparen = s3.indexOf(")");
hasParen = (lparen != -1 && rparen > 0);
// qDebug(" lparen=%d rparen=%d hasP=%d", lparen, rparen, hasParen);
if (hasParen) wordsLeft = s1.mid(0, lparen);
else wordsLeft = s1;
int equalsPos = equals.indexIn(s2);
if (equalsPos != -1) metroLeft = s2.mid(0, equalsPos);
// else qDebug("\ncan't find equals in s2\n");
if (hasParen) {
metroRight = s3.mid(0, rparen);
wordsRight = s3.mid(rparen + 1, s3.length() - rparen - 1);
}
else {
metroRight = s3;
}
/*
qDebug(" '%s'%s'%s'%s'",
qPrintable(wordsLeft),
qPrintable(metroLeft),
qPrintable(metroRight),
qPrintable(wordsRight)
);
*/
QString unit;
int dots;
return findUnitAndDots(metroLeft, unit, dots);
}
}
return false;
}
static void wordsMetrome(Xml& xml, Text const* const text)
{
QString wordsLeft; // words left of metronome
bool hasParen; // parenthesis
QString metroLeft; // left part of metronome
QString metroRight; // right part of metronome
QString wordsRight; // words right of metronome
if (findMetronome(text->text(), wordsLeft, hasParen, metroLeft, metroRight, wordsRight)) {
if (wordsLeft != "") {
xml.stag("direction-type");
xml.tag("words", wordsLeft);
xml.etag();
}
xml.stag("direction-type");
xml.stag(QString("metronome parentheses=\"%1\"").arg(hasParen ? "yes" : "no"));
QString unit;
int dots;
findUnitAndDots(metroLeft, unit, dots);
xml.tag("beat-unit", unit);
while (dots > 0) {
xml.tagE("beat-unit-dot");
--dots;
}
if (findUnitAndDots(metroRight, unit, dots)) {
xml.tag("beat-unit", unit);
while (dots > 0) {
xml.tagE("beat-unit-dot");
--dots;
}
}
else
xml.tag("per-minute", metroRight);
xml.etag();
xml.etag();
if (wordsRight != "") {
xml.stag("direction-type");
xml.tag("words", wordsRight);
xml.etag();
}
}
else {
xml.stag("direction-type");
xml.tag("words", text->text());
xml.etag();
}
}
void ExportMusicXml::tempoText(TempoText const* const text, int staff)
{
/*
qDebug("ExportMusicXml::tempoText(TempoText='%s')", qPrintable(text->text()));
*/
attr.doAttr(xml, false);
xml.stag(QString("direction placement=\"%1\"").arg((text->parent()->y()-text->y() < 0.0) ? "below" : "above"));
wordsMetrome(xml, text);
/*
int offs = text->mxmlOff();
if (offs)
xml.tag("offset", offs);
*/
if (staff)
xml.tag("staff", staff);
xml.tagE(QString("sound tempo=\"%1\"").arg(QString::number(text->tempo()*60.0)));
xml.etag();
}
//---------------------------------------------------------
// words
//---------------------------------------------------------
void ExportMusicXml::words(Text const* const text, int staff)
{
/*
qDebug("ExportMusicXml::words userOff.x=%f userOff.y=%f xoff=%g yoff=%g text='%s'",
text->userOff().x(), text->userOff().y(), text->xoff(), text->yoff(),
text->text().toUtf8().data());
*/
directionTag(xml, attr, text);
if (text->type() == Element::REHEARSAL_MARK) {
// TODO: check if dead code (see rehearsal below)
xml.stag("direction-type");
xml.tag("rehearsal", text->text());
xml.etag();
}
else
wordsMetrome(xml, text);
directionETag(xml, staff);
}
//---------------------------------------------------------
// rehearsal
//---------------------------------------------------------
void ExportMusicXml::rehearsal(RehearsalMark const* const rmk, int staff)
{
directionTag(xml, attr, rmk);
xml.stag("direction-type");
xml.tag("rehearsal", rmk->text());
xml.etag();
directionETag(xml, staff);
}
//---------------------------------------------------------
// hairpin
//---------------------------------------------------------
void ExportMusicXml::hairpin(Hairpin const* const hp, int staff, int tick)
{
directionTag(xml, attr, hp);
xml.stag("direction-type");
if (hp->tick() == tick)
xml.tagE("wedge type=\"%s\"", hp->hairpinType() ? "diminuendo" : "crescendo");
else
xml.tagE("wedge type=\"stop\"");
xml.etag();
directionETag(xml, staff);
}
//---------------------------------------------------------
// ottava
// <octave-shift type="down" size="8" relative-y="14"/>
// <octave-shift type="stop" size="8"/>
//---------------------------------------------------------
void ExportMusicXml::ottava(Ottava const* const ot, int staff, int tick)
{
int st = ot->ottavaType();
directionTag(xml, attr, ot);
xml.stag("direction-type");
if (ot->tick() == tick) {
const char* sz = 0;
const char* tp = 0;
switch (st) {
case 0:
sz = "8";
tp = "down";
break;
case 1:
sz = "15";
tp = "down";
break;
case 2:
sz = "8";
tp = "up";
break;
case 3:
sz = "15";
tp = "up";
break;
default:
qDebug("ottava subtype %d not understood\n", st);
}
if (sz && tp)
xml.tagE("octave-shift type=\"%s\" size=\"%s\"", tp, sz);
}
else {
if (st == 0 || st == 2)
xml.tagE("octave-shift type=\"stop\" size=\"8\"");
else if (st == 1 || st == 3)
xml.tagE("octave-shift type=\"stop\" size=\"15\"");
else
qDebug("ottava subtype %d not understood\n", st);
}
xml.etag();
directionETag(xml, staff);
}
//---------------------------------------------------------
// pedal
//---------------------------------------------------------
void ExportMusicXml::pedal(Pedal const* const pd, int staff, int tick)
{
directionTag(xml, attr, pd);
xml.stag("direction-type");
if (pd->tick() == tick)
xml.tagE("pedal type=\"start\" line=\"yes\"");
else
xml.tagE("pedal type=\"stop\" line=\"yes\"");
xml.etag();
directionETag(xml, staff);
}
//---------------------------------------------------------
// findBracket -- get index of bracket in bracket table
// return -1 if not found
//---------------------------------------------------------
int ExportMusicXml::findBracket(const TextLine* tl) const
{
for (int i = 0; i < MAX_BRACKETS; ++i)
if (bracket[i] == tl) return i;
return -1;
}
//---------------------------------------------------------
// textLine
//---------------------------------------------------------
void ExportMusicXml::textLine(TextLine const* const tl, int staff, int tick)
{
QString rest;
QPointF p;
// special case: a dashed line w/o hooks is written as dashes
bool dashes = tl->lineStyle() == Qt::DashLine && !tl->beginHook() && !tl->endHook();
QString lineEnd = "none";
QString type;
bool hook = false;
double hookHeight = 0.0;
int n = 0;
if (tl->tick() == tick) {
if (!dashes) {
QString lineType;
switch (tl->lineStyle()) {
case Qt::SolidLine:
lineType = "solid";
break;
case Qt::DashLine:
lineType = "dashed";
break;
case Qt::DotLine:
lineType = "dotted";
break;
default:
lineType = "solid";
}
rest += QString(" line-type=\"%1\"").arg(lineType);
}
hook = tl->beginHook();
hookHeight = tl->beginHookHeight().val();
p = tl->spannerSegments().first()->userOff();
// offs = tl->mxmlOff();
type = "start";
}
else {
hook = tl->endHook();
hookHeight = tl->endHookHeight().val();
p = ((LineSegment*)tl->spannerSegments().last())->userOff2();
// offs = tl->mxmlOff2();
type = "stop";
}
if (hook) {
if (hookHeight < 0.0) {
lineEnd = "up";
hookHeight *= -1.0;
}
else
lineEnd = "down";
rest += QString(" end-length=\"%1\"").arg(hookHeight * 10);
}
n = findBracket(tl);
if (n >= 0)
bracket[n] = 0;
else {
n = findBracket(0);
bracket[n] = tl;
}
if (preferences.musicxmlExportLayout && p.x() != 0)
rest += QString(" default-x=\"%1\"").arg(p.x() * 10 / tl->spatium());
if (preferences.musicxmlExportLayout && p.y() != 0)
rest += QString(" default-y=\"%1\"").arg(p.y() * -10 / tl->spatium());
directionTag(xml, attr, tl);
if (tl->beginText() && tl->tick() == tick) {
xml.stag("direction-type");
xml.tag("words", tl->beginText()->text());
xml.etag();
}
xml.stag("direction-type");
if (dashes)
xml.tagE(QString("dashes type=\"%1\" number=\"%2\"").arg(type, QString::number(n + 1)));
else
xml.tagE(QString("bracket type=\"%1\" number=\"%2\" line-end=\"%3\"%4").arg(type, QString::number(n + 1), lineEnd, rest));
xml.etag();
/*
if (offs)
xml.tag("offset", offs);
*/
directionETag(xml, staff);
}
//---------------------------------------------------------
// dynamic
//---------------------------------------------------------
// In MuseScore dynamics are essentially user-defined texts, therefore the ones
// supported by MusicXML need to be filtered out. Everything not recognized
// as MusicXML dynamics is written as words.
void ExportMusicXml::dynamic(Dynamic const* const dyn, int staff)
{
QString t = dyn->text();
Dynamic::DynamicType st = dyn->dynamicType();
directionTag(xml, attr, dyn);
xml.stag("direction-type");
if (st == Dynamic::DYNAMIC_p
|| st == Dynamic::DYNAMIC_pp
|| st == Dynamic::DYNAMIC_ppp
|| st == Dynamic::DYNAMIC_pppp
|| st == Dynamic::DYNAMIC_ppppp
|| st == Dynamic::DYNAMIC_pppppp
|| st == Dynamic::DYNAMIC_f
|| st == Dynamic::DYNAMIC_ff
|| st == Dynamic::DYNAMIC_fff
|| st == Dynamic::DYNAMIC_ffff
|| st == Dynamic::DYNAMIC_fffff
|| st == Dynamic::DYNAMIC_ffffff
|| st == Dynamic::DYNAMIC_mp
|| st == Dynamic::DYNAMIC_mf
|| st == Dynamic::DYNAMIC_sf
|| st == Dynamic::DYNAMIC_sfp
|| st == Dynamic::DYNAMIC_sfpp
|| st == Dynamic::DYNAMIC_fp
|| st == Dynamic::DYNAMIC_rf
|| st == Dynamic::DYNAMIC_rfz
|| st == Dynamic::DYNAMIC_sfz
|| st == Dynamic::DYNAMIC_sffz
|| st == Dynamic::DYNAMIC_fz) {
xml.stag("dynamics");
xml.tagE(dyn->dynamicTypeName());
xml.etag();
}
else if (st == Dynamic::DYNAMIC_m || st == Dynamic::DYNAMIC_z) {
xml.stag("dynamics");
xml.tag("other-dynamics", dyn->dynamicTypeName());
xml.etag();
}
else
xml.tag("words", t);
xml.etag();
/*
int offs = dyn->mxmlOff();
if (offs)
xml.tag("offset", offs);
*/
if (staff)
xml.tag("staff", staff);
if (dyn->velocity() > 0)
xml.tagE(QString("sound dynamics=\"%1\"").arg(QString::number(dyn->velocity() * 100.0 / 90.0, 'f', 2)));
xml.etag();
}
//---------------------------------------------------------
// symbol
//---------------------------------------------------------
// TODO: remove dependency on symbol name and replace by a more stable interface
// changes in sym.cpp r2494 broke MusicXML export of pedals (again)
void ExportMusicXml::symbol(Symbol const* const sym, int staff)
{
QString name = Sym::id2name(sym->sym());
const char* mxmlName = "";
if (name == "pedal.Ped")
mxmlName = "pedal type=\"start\"";
else if (name == "pedal.*")
mxmlName = "pedal type=\"stop\"";
else {
qDebug("ExportMusicXml::symbol(): %s not supported", name.toLatin1().data());
return;
}
directionTag(xml, attr, sym);
xml.stag("direction-type");
xml.tagE(mxmlName);
xml.etag();
directionETag(xml, staff);
}
//---------------------------------------------------------
// lyrics
//---------------------------------------------------------
void ExportMusicXml::lyrics(const QList<Lyrics*>* ll, const int trk)
{
foreach(const Lyrics* l, *ll) {
if (l) {
if ((l)->track() == trk) {
xml.stag(QString("lyric number=\"%1\"").arg((l)->no() + 1));
int syl = (l)->syllabic();
QString s = "";
switch (syl) {
case Lyrics::SINGLE: s = "single"; break;
case Lyrics::BEGIN: s = "begin"; break;
case Lyrics::END: s = "end"; break;
case Lyrics::MIDDLE: s = "middle"; break;
default:
qDebug("unknown syllabic %d\n", syl);
}
xml.tag("syllabic", s);
xml.tag("text", (l)->text());
/*
Temporarily disabled because it doesn't work yet (and thus breaks the regression test).
See MusicXml::xmlLyric: "// TODO-WS l->setTick(tick);"
if((l)->endTick() > 0)
xml.tagE("extend");
*/
xml.etag();
}
}
}
}
//---------------------------------------------------------
// directionJump -- write jump
//---------------------------------------------------------
// LVIFIX: TODO coda and segno should be numbered uniquely
static void directionJump(Xml& xml, const Jump* const jp)
{
JumpType jtp = jp->jumpType();
QString words = "";
QString type = "";
QString sound = "";
if (jtp == JumpType::DC) {
if (jp->text() == "")
words = "D.C.";
else
words = jp->text();
sound = "dacapo=\"yes\"";
}
else if (jtp == JumpType::DC_AL_FINE) {
if (jp->text() == "")
words = "D.C. al Fine";
else
words = jp->text();
sound = "dacapo=\"yes\"";
}
else if (jtp == JumpType::DC_AL_CODA) {
if (jp->text() == "")
words = "D.C. al Coda";
else
words = jp->text();
sound = "dacapo=\"yes\"";
}
else if (jtp == JumpType::DS_AL_CODA) {
if (jp->text() == "")
words = "D.S. al Coda";
else
words = jp->text();
if (jp->jumpTo() == "")
sound = "dalsegno=\"1\"";
else
sound = "dalsegno=\"" + jp->jumpTo() + "\"";
}
else if (jtp == JumpType::DS_AL_FINE) {
if (jp->text() == "")
words = "D.S. al Fine";
else
words = jp->text();
if (jp->jumpTo() == "")
sound = "dalsegno=\"1\"";
else
sound = "dalsegno=\"" + jp->jumpTo() + "\"";
}
else if (jtp == JumpType::DS) {
words = "D.S.";
if (jp->jumpTo() == "")
sound = "dalsegno=\"1\"";
else
sound = "dalsegno=\"" + jp->jumpTo() + "\"";
}
else
qDebug("jump type=%d not implemented\n", jtp);
if (sound != "") {
xml.stag("direction placement=\"above\"");
xml.stag("direction-type");
if (type != "") xml.tagE(type);
if (words != "") xml.tag("words", words);
xml.etag();
if (sound != "") xml.tagE(QString("sound ") + sound);
xml.etag();
}
}
//---------------------------------------------------------
// directionMarker -- write marker
//---------------------------------------------------------
static void directionMarker(Xml& xml, const Marker* const m)
{
MarkerType mtp = m->markerType();
QString words = "";
QString type = "";
QString sound = "";
if (mtp == MarkerType::CODA) {
type = "coda";
if (m->label() == "")
sound = "coda=\"1\"";
else
// LVIFIX hack: force label to "coda" to match to coda label
// sound = "coda=\"" + m->label() + "\"";
sound = "coda=\"coda\"";
}
else if (mtp == MarkerType::SEGNO) {
type = "segno";
if (m->label() == "")
sound = "segno=\"1\"";
else
sound = "segno=\"" + m->label() + "\"";
}
else if (mtp == MarkerType::FINE) {
words = "Fine";
sound = "fine=\"yes\"";
}
else if (mtp == MarkerType::TOCODA) {
if (m->text() == "")
words = "To Coda";
else
words = m->text();
if (m->label() == "")
sound = "tocoda=\"1\"";
else
sound = "tocoda=\"" + m->label() + "\"";
}
else
qDebug("marker type=%d not implemented\n", mtp);
if (sound != "") {
xml.stag("direction placement=\"above\"");
xml.stag("direction-type");
if (type != "") xml.tagE(type);
if (words != "") xml.tag("words", words);
xml.etag();
if (sound != "") xml.tagE(QString("sound ") + sound);
xml.etag();
}
}
//---------------------------------------------------------
// findTrackForAnnotations
//---------------------------------------------------------
// An annotation is attched to the staff, with track set
// to the lowest track in the staff. Find a track for it
// (the lowest track in this staff that has a chord or rest)
static int findTrackForAnnotations(int track, Segment* seg)
{
if (seg->segmentType() != Segment::SegChordRest)
return -1;
int staff = track / VOICES;
int strack = staff * VOICES; // start track of staff containing track
int etrack = strack + VOICES; // end track of staff containing track + 1
for (int i = strack; i < etrack; i++)
if (seg->element(i))
return i;
return -1;
}
//---------------------------------------------------------
// repeatAtMeasureStart -- write repeats at begin of measure
//---------------------------------------------------------
static void repeatAtMeasureStart(Xml& xml, Attributes& attr, Measure* m, int strack, int etrack, int track)
{
// loop over all segments
for (Segment* seg = m->first(); seg; seg = seg->next()) {
if (seg->segmentType() == Segment::SegChordRest) {
foreach(const Element* e, seg->annotations()) {
#ifdef DEBUG_REPEATS
qDebug("repeatAtMeasureStart seg %p elem %p type %d (%s) track %d",
seg, e, e->type(), qPrintable(e->subtypeName()), e->track());
#endif
int wtrack = -1; // track to write jump
if (strack <= e->track() && e->track() < etrack)
wtrack = findTrackForAnnotations(e->track(), seg);
if (track == wtrack) {
switch (e->type()) {
case Element::SYMBOL:
case Element::TEMPO_TEXT:
case Element::STAFF_TEXT:
case Element::TEXT:
case Element::DYNAMIC:
case Element::HARMONY:
case Element::FIGURED_BASS:
case Element::REHEARSAL_MARK:
case Element::FRET_DIAGRAM:
case Element::JUMP: // note: all jumps are handled at measure stop
break;
case Element::MARKER:
{
// filter out the markers at measure Start
const Marker* const mk = static_cast<const Marker* const>(e);
MarkerType mtp = mk->markerType();
#ifdef DEBUG_REPEATS
qDebug("repeatAtMeasureStart: marker type %d", mtp);
#endif
if ( mtp == MarkerType::SEGNO
|| mtp == MarkerType::CODA
) {
qDebug(" -> handled");
attr.doAttr(xml, false);
directionMarker(xml, mk);
}
else if ( mtp == MarkerType::FINE
|| mtp == MarkerType::TOCODA
) {
#ifdef DEBUG_REPEATS
qDebug(" -> ignored");
#endif
// ignore
}
else {
#ifdef DEBUG_REPEATS
qDebug(" -> not implemented");
#endif
qDebug("repeatAtMeasureStart: marker %d not implemented", mtp);
}
}
break;
default:
qDebug("repeatAtMeasureStart: direction type %s at tick %d not implemented",
Element::name(e->type()), seg->tick());
break;
}
}
else {
#ifdef DEBUG_REPEATS
qDebug("repeatAtMeasureStart: no track found");
#endif
}
} // foreach
}
}
}
//---------------------------------------------------------
// repeatAtMeasureStop -- write repeats at end of measure
//---------------------------------------------------------
static void repeatAtMeasureStop(Xml& xml, Measure* m, int strack, int etrack, int track)
{
// loop over all segments
for (Segment* seg = m->first(); seg; seg = seg->next()) {
if (seg->segmentType() == Segment::SegChordRest) {
foreach(const Element* e, seg->annotations()) {
#ifdef DEBUG_REPEATS
qDebug("repeatAtMeasureStop seg %p elem %p type %d (%s) track %d",
seg, e, e->type(), qPrintable(e->subtypeName()), e->track());
#endif
int wtrack = -1; // track to write jump
if (strack <= e->track() && e->track() < etrack)
wtrack = findTrackForAnnotations(e->track(), seg);
if (track == wtrack) {
switch (e->type()) {
case Element::SYMBOL:
case Element::TEMPO_TEXT:
case Element::STAFF_TEXT:
case Element::TEXT:
case Element::DYNAMIC:
case Element::HARMONY:
case Element::FIGURED_BASS:
case Element::REHEARSAL_MARK:
case Element::FRET_DIAGRAM:
break;
case Element::MARKER:
{
// filter out the markers at measure stop
const Marker* const mk = static_cast<const Marker* const>(e);
MarkerType mtp = mk->markerType();
#ifdef DEBUG_REPEATS
qDebug("repeatAtMeasureStop: marker type %d", mtp);
#endif
if ( mtp == MarkerType::FINE
|| mtp == MarkerType::TOCODA
) {
#ifdef DEBUG_REPEATS
qDebug(" -> handled");
#endif
directionMarker(xml, mk);
}
else if ( mtp == MarkerType::SEGNO
|| mtp == MarkerType::CODA
) {
#ifdef DEBUG_REPEATS
qDebug(" -> ignored");
#endif
// ignore
}
else {
#ifdef DEBUG_REPEATS
qDebug(" -> not implemented");
#endif
qDebug("repeatAtMeasureStop: marker %d not implemented", mtp);
}
}
break;
case Element::JUMP:
directionJump(xml, static_cast<const Jump* const>(e));
break;
default:
qDebug("repeatAtMeasureStop: direction type %s at tick %d not implemented",
Element::name(e->type()), seg->tick());
break;
}
}
else {
#ifdef DEBUG_REPEATS
qDebug("repeatAtMeasureStop: no track found");
#endif
}
} // foreach
}
}
}
//---------------------------------------------------------
// work -- write the <work> element
// note that order must be work-number, work-title
// also write <movement-number> and <movement-title>
// data is taken from the score metadata instead of the Text elements
//---------------------------------------------------------
void ExportMusicXml::work(const MeasureBase* /*measure*/)
{
QString workTitle = score->metaTag("workTitle");
QString workNumber = score->metaTag("workNumber");
if (!(workTitle.isEmpty() && workNumber.isEmpty())) {
xml.stag("work");
if (!workNumber.isEmpty())
xml.tag("work-number", workNumber);
if (!workTitle.isEmpty())
xml.tag("work-title", workTitle);
xml.etag();
}
if (!score->metaTag("movementNumber").isEmpty())
xml.tag("movement-number", score->metaTag("movementNumber"));
if (!score->metaTag("movementTitle").isEmpty())
xml.tag("movement-title", score->metaTag("movementTitle"));
}
//---------------------------------------------------------
// elementRighter // used for harmony order
//---------------------------------------------------------
static bool elementRighter(const Element* e1, const Element* e2)
{
return e1->x() < e2->x();
}
//---------------------------------------------------------
// measureStyle -- write measure-style
//---------------------------------------------------------
// this is done at the first measure of a multi-meaure rest
// note: the measure count is stored in the last measure
// see measure.h _multiMeasure
static void measureStyle(Xml& xml, Attributes& attr, Measure* m)
{
if (m->multiMeasure() > 0) {
attr.doAttr(xml, true);
xml.stag("measure-style");
xml.tag("multiple-rest", m->multiMeasure());
xml.etag();
}
}
//---------------------------------------------------------
// findFretDiagram
//---------------------------------------------------------
static const FretDiagram* findFretDiagram(int strack, int etrack, int track, Segment* seg)
{
if (seg->segmentType() == Segment::SegChordRest) {
foreach(const Element* e, seg->annotations()) {
int wtrack = -1; // track to write annotation
if (strack <= e->track() && e->track() < etrack)
wtrack = findTrackForAnnotations(e->track(), seg);
if (track == wtrack && e->type() == Element::FRET_DIAGRAM)
return static_cast<const FretDiagram*>(e);
}
}
return 0;
}
//---------------------------------------------------------
// annotations
//---------------------------------------------------------
// In MuseScore, Element::FRET_DIAGRAM and Element::HARMONY are separate annotations,
// in MusicXML they are combined in the harmony element. This means they have to be matched.
// TODO: replace/repair current algorithm (which can only handle one FRET_DIAGRAM and one HARMONY)
static void annotations(ExportMusicXml* exp, Xml& xml, int strack, int etrack, int track, int sstaff, Segment* seg)
{
if (seg->segmentType() == Segment::SegChordRest) {
const FretDiagram* fd = findFretDiagram(strack, etrack, track, seg);
// if (fd) qDebug("annotations seg %p found fret diagram %p", seg, fd);
foreach(const Element* e, seg->annotations()) {
int wtrack = -1; // track to write annotation
if (strack <= e->track() && e->track() < etrack)
wtrack = findTrackForAnnotations(e->track(), seg);
if (track == wtrack) {
switch (e->type()) {
case Element::SYMBOL:
exp->symbol(static_cast<const Symbol*>(e), sstaff);
break;
case Element::TEMPO_TEXT:
exp->tempoText(static_cast<const TempoText*>(e), sstaff);
break;
case Element::STAFF_TEXT:
case Element::TEXT:
exp->words(static_cast<const Text*>(e), sstaff);
break;
case Element::DYNAMIC:
exp->dynamic(static_cast<const Dynamic*>(e), sstaff);
break;
case Element::HARMONY:
// qDebug("annotations seg %p found harmony %p", seg, e);
exp->harmony(static_cast<const Harmony*>(e), fd /*, sstaff */);
fd = 0; // make sure to write only once ...
break;
case Element::REHEARSAL_MARK:
exp->rehearsal(static_cast<const RehearsalMark*>(e), sstaff);
break;
case Element::FIGURED_BASS: // handled separately by figuredBass()
case Element::FRET_DIAGRAM: // handled using findFretDiagram()
case Element::JUMP: // ignore
break;
default:
qDebug("annotations: direction type %s at tick %d not implemented\n",
Element::name(e->type()), seg->tick());
break;
}
}
} // foreach
if (fd)
// found fd but no harmony, cannot write (MusicXML would be invalid)
qDebug("annotations seg %p found fret diagram %p w/o harmony: cannot write",
seg, fd);
}
}
//---------------------------------------------------------
// figuredBass
//---------------------------------------------------------
static void figuredBass(Xml& xml, int strack, int etrack, int track, const ChordRest* cr, FigBassMap& fbMap)
{
Segment* seg = cr->segment();
if (seg->segmentType() == Segment::SegChordRest) {
foreach(const Element* e, seg->annotations()) {
int wtrack = -1; // track to write annotation
if (strack <= e->track() && e->track() < etrack)
wtrack = findTrackForAnnotations(e->track(), seg);
if (track == wtrack) {
if (e->type() == Element::FIGURED_BASS) {
const FiguredBass* fb = static_cast<const FiguredBass*>(e);
//qDebug("figuredbass() track %d seg %p fb %p seg %p tick %d ticks %d cr %p tick %d ticks %d",
// track, seg, fb, fb->segment(), fb->segment()->tick(), fb->ticks(), cr, cr->tick(), cr->actualTicks());
bool extend = fb->ticks() > cr->actualTicks();
if (extend) {
//qDebug("figuredbass() extend to %d + %d = %d",
// cr->tick(), fb->ticks(), cr->tick() + fb->ticks());
fbMap.insert(strack, fb);
}
else
fbMap.remove(strack);
fb->writeMusicXML(xml, true, extend);
// there can only be one FB, if one was found
// no extend can be pending
return;
}
}
}
// check for extend pending
if (fbMap.contains(strack)) {
const FiguredBass* fb = fbMap.value(strack);
int endTick = fb->segment()->tick() + fb->ticks();
if (cr->tick() < endTick) {
//qDebug("figuredbass() at tick %d extend only", cr->tick());
// write figured bass element with extend only
fb->writeMusicXML(xml, false, true);
}
if (endTick <= cr->tick() + cr->actualTicks()) {
//qDebug("figuredbass() at tick %d extend done", cr->tick() + cr->actualTicks());
fbMap.remove(strack);
}
}
}
}
//---------------------------------------------------------
// spannerStart
//---------------------------------------------------------
// for each spanner start:
// find start track
// find stop track
// if stop track < start track
// get data from list of already stopped spanners
// else
// calculate data
// write start if in right track
static void spannerStart(ExportMusicXml* exp, int strack, int etrack, int track, int sstaff, Segment* seg)
{
#if 0 // TODO-S
if (seg->segmentType() == Segment::SegChordRest) {
for (Spanner* e = seg->spannerFor(); e; e = e->next()) {
int wtrack = -1; // track to write spanner
if (strack <= e->track() && e->track() < etrack)
wtrack = findTrackForAnnotations(e->track(), seg);
if (track == wtrack) {
switch (e->type()) {
case Element::HAIRPIN:
exp->hairpin(static_cast<const Hairpin*>(e), sstaff, seg->tick());
break;
case Element::OTTAVA:
exp->ottava(static_cast<const Ottava*>(e), sstaff, seg->tick());
break;
case Element::PEDAL:
exp->pedal(static_cast<const Pedal*>(e), sstaff, seg->tick());
break;
case Element::TEXTLINE:
exp->textLine(static_cast<const TextLine*>(e), sstaff, seg->tick());
break;
case Element::TRILL:
// ignore (written as <note><notations><ornaments><wavy-line>
break;
default:
qDebug("spannerStart: direction type %s at tick %d not implemented\n",
Element::name(e->type()), seg->tick());
break;
}
}
} // foreach
}
#endif
}
//---------------------------------------------------------
// spannerStop
//---------------------------------------------------------
// see spanner start
static void spannerStop(ExportMusicXml* exp, int strack, int etrack, int track, int sstaff, Segment* seg)
{
#if 0 // TODO-S
if (seg->segmentType() == Segment::SegChordRest) {
for (Spanner* e = seg->spannerBack(); e; e = e->next()) {
int wtrack = -1; // track to write spanner
if (strack <= e->track() && e->track() < etrack)
wtrack = findTrackForAnnotations(e->track(), seg);
if (track == wtrack) {
switch (e->type()) {
case Element::HAIRPIN:
exp->hairpin(static_cast<const Hairpin*>(e), sstaff, -1);
break;
case Element::OTTAVA:
exp->ottava(static_cast<const Ottava*>(e), sstaff, -1);
break;
case Element::PEDAL:
exp->pedal(static_cast<const Pedal*>(e), sstaff, -1);
break;
case Element::TEXTLINE:
exp->textLine(static_cast<const TextLine*>(e), sstaff, -1);
break;
case Element::TRILL:
// ignore (written as <note><notations><ornaments><wavy-line>
break;
default:
qDebug("spannerStop: direction type %s at tick %d not implemented\n",
Element::name(e->type()), seg->tick());
break;
}
}
} // foreach
}
#endif
}
//---------------------------------------------------------
// keysigTimesig
//---------------------------------------------------------
/**
Output attributes at start of measure: key, time
*/
void ExportMusicXml::keysigTimesig(Measure* m, int strack, int etrack)
{
// search all staves for non-generated key signatures
QMap<int, KeySig*> keysigs; // map staff to key signature
for (Segment* seg = m->first(); seg; seg = seg->next()) {
if (seg->tick() > m->tick())
break;
for (int t = strack; t < etrack; t += VOICES) {
Element* el = seg->element(t);
if (!el)
continue;
if (el->type() == Element::KEYSIG) {
int st = (t - strack) / VOICES;
if (!el->generated())
keysigs[st] = static_cast<KeySig*>(el);
}
}
}
// write the key signatues
if (!keysigs.isEmpty()) {
// determine if all staves have a keysig and all keysigs are identical
// in that case a single <key> is written, without number=... attribute
int nstaves = (etrack - strack) / VOICES;
bool singleKey = true;
// check if all staves have a keysig
for (int i = 0; i < nstaves; i++)
if (!keysigs.contains(i))
singleKey = false;
// check if all keysigs are identical
if (singleKey)
for (int i = 1; i < nstaves; i++)
if (!(keysigs.value(i)->keySignature() == keysigs.value(0)->keySignature()))
singleKey = false;
// write the keysigs
if (singleKey) {
// keysig applies to all staves
keysig(keysigs.value(0)->keySignature(), 0, keysigs.value(0)->visible());
}
else {
// staff-specific keysigs
foreach(int st, keysigs.keys())
keysig(keysigs.value(st)->keySignature(), st + 1, keysigs.value(st)->visible());
}
}
else {
// always write a keysig at tick = 0
if (m->tick() == 0)
keysig(0);
}
TimeSig* tsig = 0;
for (Segment* seg = m->first(); seg; seg = seg->next()) {
if (seg->tick() > m->tick())
break;
Element* el = seg->element(strack);
if (el && el->type() == Element::TIMESIG)
tsig = (TimeSig*) el;
}
if (tsig)
timesig(tsig);
}
//---------------------------------------------------------
// write
//---------------------------------------------------------
/**
Write the score to \a dev in MusicXML format.
*/
void ExportMusicXml::write(QIODevice* dev)
{
calcDivisions();
for (int i = 0; i < MAX_BRACKETS; ++i)
bracket[i] = 0;
xml.setDevice(dev);
xml.setCodec("utf8");
xml << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
xml << "<!DOCTYPE score-partwise PUBLIC \"-//Recordare//DTD MusicXML 3.0 Partwise//EN\" \"http://www.musicxml.org/dtds/partwise.dtd\">\n";
xml.stag("score-partwise");
const MeasureBase* measure = score->measures()->first();
work(measure);
// LVI TODO: write these text elements as credit-words
// use meta data here instead
xml.stag("identification");
for (int i = 0; i < score->numberOfCreators(); ++i) {
qDebug("creator type='%s' text='%s'",
score->getCreator(i)->crType().toUtf8().data(),
score->getCreator(i)->crText().toUtf8().data()
);
const MusicXmlCreator* crt = score->getCreator(i);
xml.tag(QString("creator type=\"%1\"").arg(crt->crType()), crt->crText());
}
if (!score->metaTag("copyright").isEmpty())
xml.tag("rights", score->metaTag("copyright"));
xml.stag("encoding");
if (MScore::debugMode) {
xml.tag("software", QString("MuseScore 0.7.0"));
xml.tag("encoding-date", QString("2007-09-10"));
}
else {
xml.tag("software", QString("MuseScore ") + QString(VERSION));
xml.tag("encoding-date", QDate::currentDate().toString(Qt::ISODate));
}
xml.etag();
if (!score->metaTag("source").isEmpty())
xml.tag("source", score->metaTag("source"));
xml.etag();
if (preferences.musicxmlExportLayout) {
defaults(xml, score, millimeters, tenths);
credits(xml);
}
xml.stag("part-list");
const QList<Part*>& il = score->parts();
int staffCount = 0; // count sum of # staves in parts
int partGroupEnd[MAX_PART_GROUPS]; // staff where part group ends
for (int i = 0; i < MAX_PART_GROUPS; i++)
partGroupEnd[i] = -1;
for (int idx = 0; idx < il.size(); ++idx) {
Part* part = il.at(idx);
for (int i = 0; i < part->nstaves(); i++) {
Staff* st = part->staff(i);
if (st) {
for (int j = 0; j < st->bracketLevels(); j++) {
if (st->bracket(j) != NO_BRACKET) {
if (i == 0) {
// OK, found bracket in first staff of part
// filter out implicit brackets
if (st->bracketSpan(j) != part->nstaves()) {
// find part group number
int number = 0;
for (; number < MAX_PART_GROUPS; number++)
if (partGroupEnd[number] == -1)
break;
if (number < MAX_PART_GROUPS) {
partGroupStart(xml, number + 1, st->bracket(j));
partGroupEnd[number] = idx + st->bracketSpan(j);
}
else
qDebug("no free part group number");
}
}
else {
// bracket in other staff not supported in MusicXML
qDebug("bracket starting in staff %d not supported\n", i + 1);
}
}
}
}
}
xml.stag(QString("score-part id=\"P%1\"").arg(idx+1));
xml.tag("part-name", part->longName().toPlainText());
if (!part->shortName().isEmpty())
xml.tag("part-abbreviation", part->shortName().toPlainText());
if (part->instr()->useDrumset()) {
Drumset* drumset = part->instr()->drumset();
for (int i = 0; i < 128; ++i) {
DrumInstrument di = drumset->drum(i);
if (di.notehead >= 0) {
xml.stag(QString("score-instrument id=\"P%1-I%2\"").arg(idx+1).arg(i + 1));
xml.tag("instrument-name", di.name);
xml.etag();
}
}
for (int i = 0; i < 128; ++i) {
DrumInstrument di = drumset->drum(i);
if (di.notehead >= 0) {
xml.stag(QString("midi-instrument id=\"P%1-I%2\"").arg(idx+1).arg(i + 1));
if (part->midiChannel() >= 0) // <0 is not valid
xml.tag("midi-channel", part->midiChannel() + 1);
if (part->midiProgram() >= 0) // <0 is not valid
xml.tag("midi-program", part->midiProgram() + 1);
xml.tag("midi-unpitched", i + 1);
xml.tag("volume", (part->volume() / 127.0) * 100); //percent
xml.tag("pan", ((int)((part->pan() - 63.5) / 63.5)) * 90); //-90 hard left, +90 hard right
xml.etag();
}
}
}
else {
xml.stag(QString("score-instrument id=\"P%1-I%2\"").arg(idx+1).arg(3));
xml.tag("instrument-name", part->longName().toPlainText());
xml.etag();
xml.stag(QString("midi-instrument id=\"P%1-I%2\"").arg(idx+1).arg(3));
if (part->midiChannel() >= 0) // <0 is not valid
xml.tag("midi-channel", part->midiChannel() + 1);
if (part->midiProgram() >= 0) // <0 is not valid
xml.tag("midi-program", part->midiProgram() + 1);
xml.tag("volume", (part->volume() / 127.0) * 100); //percent
xml.tag("pan", ((int)((part->pan() - 63.5) / 63.5)) * 90); //-90 hard left, +90 hard right
xml.etag();
}
xml.etag();
staffCount += part->nstaves();
for (int i = MAX_PART_GROUPS - 1; i >= 0; i--) {
int end = partGroupEnd[i];
if (end >= 0) {
if (staffCount >= end) {
xml.tagE("part-group type=\"stop\" number=\"%d\"", i + 1);
partGroupEnd[i] = -1;
}
}
}
}
xml.etag();
staffCount = 0;
for (int idx = 0; idx < il.size(); ++idx) {
Part* part = il.at(idx);
tick = 0;
xml.stag(QString("part id=\"P%1\"").arg(idx+1));
int staves = part->nstaves();
int strack = score->staffIdx(part) * VOICES;
int etrack = strack + staves * VOICES;
trillStart.clear();
trillStop.clear();
int measureNo = 1; // number of next regular measure
int irregularMeasureNo = 1; // number of next irregular measure
int pickupMeasureNo = 1; // number of next pickup measure
FigBassMap fbMap; // pending figure base extends
for (MeasureBase* mb = score->measures()->first(); mb; mb = mb->next()) {
if (mb->type() != Element::MEASURE)
continue;
Measure* m = static_cast<Measure*>(mb);
const PageFormat* pf = score->pageFormat();
// pickup and other irregular measures need special care
QString measureTag = "measure number=";
if ((irregularMeasureNo + measureNo) == 2 && m->irregular()) {
measureTag += "\"0\" implicit=\"yes\"";
pickupMeasureNo++;
}
else if (m->irregular())
measureTag += QString("\"X%1\" implicit=\"yes\"").arg(irregularMeasureNo++);
else
measureTag += QString("\"%1\"").arg(measureNo++);
if (preferences.musicxmlExportLayout)
measureTag += QString(" width=\"%1\"").arg(QString::number(m->bbox().width() / MScore::DPMM / millimeters * tenths,'f',2));
xml.stag(measureTag);
// Handle the <print> element.
// When exporting layout and all breaks, a <print> with layout informations
// is generated for the measure types TopSystem, NewSystem and newPage.
// When exporting layout but only manual or no breaks, a <print> with
// layout informations is generated only for the measure type TopSystem,
// as it is assumed the system layout is broken by the importing application
// anyway and is thus useless.
int currentSystem = NoSystem;
Measure* previousMeasure = 0;
for (MeasureBase* currentMeasureB = m->prev(); currentMeasureB; currentMeasureB = currentMeasureB->prev()) {
if (currentMeasureB->type() == Element::MEASURE) {
previousMeasure = (Measure*) currentMeasureB;
break;
}
}
if (!previousMeasure)
currentSystem = TopSystem;
else if (m->parent()->parent() != previousMeasure->parent()->parent())
currentSystem = NewPage;
else if (m->parent() != previousMeasure->parent())
currentSystem = NewSystem;
bool prevMeasLineBreak = false;
bool prevMeasPageBreak = false;
if (previousMeasure) {
prevMeasLineBreak = previousMeasure->lineBreak();
prevMeasPageBreak = previousMeasure->pageBreak();
}
if (currentSystem != NoSystem) {
// determine if a new-system or new-page is required
QString newThing; // new-[system|page]="yes" or empty
if (preferences.musicxmlExportBreaks == ALL_BREAKS) {
if (currentSystem == NewSystem)
newThing = " new-system=\"yes\"";
else if (currentSystem == NewPage)
newThing = " new-page=\"yes\"";
}
else if (preferences.musicxmlExportBreaks == MANUAL_BREAKS) {
if (currentSystem == NewSystem && prevMeasLineBreak)
newThing = " new-system=\"yes\"";
else if (currentSystem == NewPage && prevMeasPageBreak)
newThing = " new-page=\"yes\"";
}
// determine if layout information is required
bool doLayout = false;
if (preferences.musicxmlExportLayout) {
if (currentSystem == TopSystem
|| (preferences.musicxmlExportBreaks == ALL_BREAKS && newThing != "")) {
doLayout = true;
}
}
if (doLayout) {
xml.stag(QString("print%1").arg(newThing));
const double pageWidth = getTenthsFromInches(pf->size().width());
const double lm = getTenthsFromInches(pf->oddLeftMargin());
const double rm = getTenthsFromInches(pf->oddRightMargin());
const double tm = getTenthsFromInches(pf->oddTopMargin());
// System Layout
// Put the system print suggestions only for the first part in a score...
if (idx == 0) {
// Find the right margin of the system.
double systemLM = getTenthsFromDots(m->pagePos().x() - m->system()->page()->pagePos().x()) - lm;
double systemRM = pageWidth - rm - (getTenthsFromDots(m->system()->bbox().width()) + lm);
xml.stag("system-layout");
xml.stag("system-margins");
xml.tag("left-margin", QString("%1").arg(QString::number(systemLM,'f',2)));
xml.tag("right-margin", QString("%1").arg(QString::number(systemRM,'f',2)) );
xml.etag();
if (currentSystem == NewPage || currentSystem == TopSystem)
xml.tag("top-system-distance", QString("%1").arg(QString::number(getTenthsFromDots(m->pagePos().y()) - tm,'f',2)) );
if (currentSystem == NewSystem)
xml.tag("system-distance", QString("%1").arg(QString::number(getTenthsFromDots(m->pagePos().y() - previousMeasure->pagePos().y() - previousMeasure->bbox().height()),'f',2)));
xml.etag();
}
// Staff layout elements.
for (int staffIdx = (staffCount == 0) ? 1 : 0; staffIdx < staves; staffIdx++) {
xml.stag(QString("staff-layout number=\"%1\"").arg(staffIdx + 1));
xml.tag("staff-distance", QString("%1").arg(QString::number(getTenthsFromDots(mb->system()->staff(staffCount + staffIdx - 1)->distanceDown()),'f',2)));
xml.etag();
}
xml.etag();
}
else {
// !doLayout
if (newThing != "")
xml.tagE(QString("print%1").arg(newThing));
}
} // if (currentSystem ...
attr.start();
findTrills(m, strack, etrack, trillStart, trillStop);
// barline left must be the first element in a measure
barlineLeft(m);
// output attributes with the first actual measure (pickup or regular)
if ((irregularMeasureNo + measureNo + pickupMeasureNo) == 4) {
attr.doAttr(xml, true);
xml.tag("divisions", MScore::division / div);
}
// output attributes at start of measure: key, time
keysigTimesig(m, strack, etrack);
// output attributes with the first actual measure (pickup or regular) only
if ((irregularMeasureNo + measureNo + pickupMeasureNo) == 4) {
if (staves > 1)
xml.tag("staves", staves);
}
// output attribute at start of measure: clef
for (Segment* seg = m->first(); seg; seg = seg->next()) {
if (seg->tick() > m->tick())
break;
Element* el = seg->element(strack);
if (!el)
continue;
if (el->type() == Element::CLEF)
for (int st = strack; st < etrack; st += VOICES) {
// sstaff - xml staff number, counting from 1 for this
// instrument
// special number 0 -> dont show staff number in
// xml output (because there is only one staff)
int sstaff = (staves > 1) ? st - strack + VOICES : 0;
sstaff /= VOICES;
el = seg->element(st);
if (el && el->type() == Element::CLEF) {
Clef* cle = static_cast<Clef*>(el);
int ct = cle->clefType();
int ti = cle->segment()->tick();
#ifdef DEBUG_CLEF
qDebug("exportxml: clef at start measure ti=%d ct=%d gen=%d", ti, ct, el->generated());
#endif
// output only clef changes, not generated clefs at line beginning
// exception: at tick=0, export clef anyway
if (ti == 0 || !cle->generated()) {
#ifdef DEBUG_CLEF
qDebug("exportxml: clef exported");
#endif
clef(sstaff, ct);
}
else {
#ifdef DEBUG_CLEF
qDebug("exportxml: clef not exported");
#endif
}
}
}
}
// output attributes with the first actual measure (pickup or regular) only
if ((irregularMeasureNo + measureNo + pickupMeasureNo) == 4) {
const Instrument* instrument = part->instr();
// staff details
// TODO: decide how to handle linked regular / TAB staff
// currently exported as a two staff part ...
for (int i = 0; i < staves; i++) {
Staff* st = part->staff(i);
if (st->lines() != 5) {
if (staves > 1)
xml.stag(QString("staff-details number=\"%1\"").arg(i+1));
else
xml.stag("staff-details");
xml.tag("staff-lines", st->lines());
if (st->isTabStaff() && instrument->tablature()) {
QList<int> l = instrument->tablature()->stringList();
for (int i = 0; i < l.size(); i++) {
char step = ' ';
int alter = 0;
int octave = 0;
midipitch2xml(l.at(i), step, alter, octave);
xml.stag(QString("staff-tuning line=\"%1\"").arg(i+1));
xml.tag("tuning-step", QString("%1").arg(step));
if (alter)
xml.tag("tuning-alter", alter);
xml.tag("tuning-octave", octave);
xml.etag();
}
}
xml.etag();
}
}
// instrument details
if (instrument->transpose().chromatic) {
xml.stag("transpose");
xml.tag("diatonic", instrument->transpose().diatonic);
xml.tag("chromatic", instrument->transpose().chromatic);
xml.etag();
}
}
// output attribute at start of measure: measure-style
measureStyle(xml, attr, m);
// MuseScore limitation: repeats are always in the first part
// and are implicitly placed at either measure start or stop
if (idx == 0)
repeatAtMeasureStart(xml, attr, m, strack, etrack, strack);
for (int st = strack; st < etrack; ++st) {
// sstaff - xml staff number, counting from 1 for this
// instrument
// special number 0 -> dont show staff number in
// xml output (because there is only one staff)
int sstaff = (staves > 1) ? st - strack + VOICES : 0;
sstaff /= VOICES;
for (Segment* seg = m->first(); seg; seg = seg->next()) {
Element* el = seg->element(st);
if (!el)
continue;
// must ignore start repeat to prevent spurious backup/forward
if (el->type() == Element::BAR_LINE && static_cast<BarLine*>(el)->barLineType() == START_REPEAT)
continue;
// look for harmony element for this tick position
if (el->isChordRest()) {
QList<Element*> list;
#if 0 // TODO-WS
foreach(Element* he, *m->el()) {
if ((he->type() == Element::HARMONY) && (he->staffIdx() == sstaff)
&& (he->tick() == el->tick())) {
list << he;
}
}
#endif
qSort(list.begin(), list.end(), elementRighter);
foreach (Element* hhe, list) {
attr.doAttr(xml, false);
qDebug("writing harmony");
harmony((Harmony*)hhe, 0);
}
}
// generate backup or forward to the start time of the element
// but not for breath, which has the same start time as the
// previous note, while tick is already at the end of that note
if (tick != seg->tick()) {
attr.doAttr(xml, false);
if (el->type() != Element::BREATH)
moveToTick(seg->tick());
}
// handle annotations and spanners (directions attached to this note or rest)
if (el->isChordRest()) {
attr.doAttr(xml, false);
annotations(this, xml, strack, etrack, st, sstaff, seg);
figuredBass(xml, strack, etrack, st, static_cast<const ChordRest*>(el), fbMap);
spannerStop(this, strack, etrack, st, sstaff, seg);
spannerStart(this, strack, etrack, st, sstaff, seg);
}
switch (el->type()) {
case Element::CLEF:
{
// output only clef changes, not generated clefs
// at line beginning
// also ignore clefs at the start of a measure,
// these have already been output
int ct = ((Clef*)el)->clefType();
#ifdef DEBUG_CLEF
int ti = seg->tick();
qDebug("exportxml: clef in measure ti=%d ct=%d gen=%d", ti, ct, el->generated());
#endif
if (el->generated()) {
#ifdef DEBUG_CLEF
qDebug("exportxml: generated clef not exported");
#endif
break;
}
if (!el->generated() && seg->tick() != m->tick())
clef(sstaff, ct);
else {
#ifdef DEBUG_CLEF
qDebug("exportxml: clef not exported");
#endif
}
}
break;
case Element::KEYSIG:
// ignore
break;
case Element::TIMESIG:
// ignore
break;
case Element::CHORD:
{
Chord* c = static_cast<Chord*>(el);
const QList<Lyrics*>* ll = &c->lyricsList();
chord(c, sstaff, ll, part->instr()->useDrumset());
break;
}
case Element::REST:
rest((Rest*)el, sstaff);
break;
case Element::BAR_LINE:
// Following must be enforced (ref MusicXML barline.dtd):
// If location is left, it should be the first element in the measure;
// if location is right, it should be the last element.
// implementation note: START_REPEAT already written by barlineLeft()
// any bars left should be "middle"
// TODO: print barline only if middle
// if (el->subtype() != START_REPEAT)
// bar((BarLine*) el);
break;
case Element::BREATH:
// ignore, already exported as note articulation
break;
default:
qDebug("ExportMusicXml::write unknown segment type %s\n", el->name());
break;
}
} // for (Segment* seg = ...
attr.stop(xml);
} // for (int st = ...
// move to end of measure (in case of incomplete last voice)
#ifdef DEBUG_TICK
qDebug("end of measure");
#endif
moveToTick(m->tick() + m->ticks());
if (idx == 0)
repeatAtMeasureStop(xml, m, strack, etrack, strack);
// note: don't use "m->repeatFlags() & RepeatEnd" here, because more
// barline types need to be handled besides repeat end ("light-heavy")
barlineRight(m);
xml.etag();
}
staffCount += staves;
xml.etag();
}
xml.etag();
}
//---------------------------------------------------------
// saveXml
// return false on error
//---------------------------------------------------------
/**
Save Score as MusicXML file \a name.
Return false on error.
*/
bool saveXml(Score* score, const QString& name)
{
QFile f(name);
if (!f.open(QIODevice::WriteOnly))
return false;
ExportMusicXml em(score);
em.write(&f);
return f.error() == QFile::NoError;
}
//---------------------------------------------------------
// saveMxl
// return false on error
//---------------------------------------------------------
/**
Save Score as compressed MusicXML file \a name.
Return false on error.
*/
// META-INF/container.xml:
// <?xml version="1.0" encoding="UTF-8"?>
// <container>
// <rootfiles>
// <rootfile full-path="testHello.xml"/>
// </rootfiles>
// </container>
bool saveMxl(Score* score, const QString& name)
{
QZipWriter uz(name);
QFileInfo fi(name);
#if 0
QDateTime dt;
if (MScore::debugMode)
dt = QDateTime(QDate(2007, 9, 10), QTime(12, 0));
else
dt = QDateTime::currentDateTime();
#endif
QString fn = fi.completeBaseName() + ".xml";
QBuffer cbuf;
cbuf.open(QIODevice::ReadWrite);
Xml xml;
xml.setDevice(&cbuf);
xml << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
xml.stag("container");
xml.stag("rootfiles");
xml.stag(QString("rootfile full-path=\"%1\"").arg(fn));
xml.etag();
xml.etag();
xml.etag();
cbuf.seek(0);
//uz.addDirectory("META-INF");
uz.addFile("META-INF/container.xml", cbuf.data());
QBuffer dbuf;
dbuf.open(QIODevice::ReadWrite);
ExportMusicXml em(score);
em.write(&dbuf);
dbuf.seek(0);
uz.addFile(fn, dbuf.data());
uz.close();
return true;
}
double ExportMusicXml::getTenthsFromInches(double inches)
{
return inches * INCH / millimeters * tenths;
}
double ExportMusicXml::getTenthsFromDots(double dots)
{
return dots / MScore::DPMM / millimeters * tenths;
}
//---------------------------------------------------------
// harmony
//---------------------------------------------------------
void ExportMusicXml::harmony(Harmony const* const h, FretDiagram const* const fd)
{
double rx = h->userOff().x()*10;
QString relative;
if (rx > 0) {
relative = QString(" relative-x=\"%1\"").arg(QString::number(rx,'f',2));
}
int rootTpc = h->rootTpc();
if (rootTpc != INVALID_TPC) {
if (h->hasFrame())
xml.stag(QString("harmony print-frame=\"yes\"").append(relative));
else
xml.stag(QString("harmony print-frame=\"no\"").append(relative));
xml.stag("root");
xml.tag("root-step", tpc2stepName(rootTpc));
int alter = tpc2alter(rootTpc);
if (alter)
xml.tag("root-alter", alter);
xml.etag();
if (!h->xmlKind().isEmpty()) {
QString s = "kind";
if (h->xmlText() != "")
s += " text=\"" + h->xmlText() + "\"";
if (h->xmlSymbols() == "yes")
s += " use-symbols=\"yes\"";
if (h->xmlParens() == "yes")
s += " parentheses-degrees=\"yes\"";
xml.tag(s, h->xmlKind());
QStringList l = h->xmlDegrees();
if (!l.isEmpty()) {
foreach(QString tag, l) {
xml.stag("degree");
int alter = 0;
int idx = 3;
if (tag[idx] == '#') {
alter = 1;
++idx;
}
else if (tag[idx] == 'b') {
alter = -1;
++idx;
}
xml.tag("degree-value", tag.mid(idx));
xml.tag("degree-alter", alter); // finale insists on this even if 0
if (tag.startsWith("add"))
xml.tag("degree-type", "add");
else if (tag.startsWith("sub"))
xml.tag("degree-type", "subtract");
else if (tag.startsWith("alt"))
xml.tag("degree-type", "alter");
xml.etag();
}
}
}
else {
if (h->extensionName() == 0)
xml.tag("kind", "");
else
xml.tag(QString("kind text=\"%1\"").arg(h->extensionName()), "");
}
int baseTpc = h->baseTpc();
if (baseTpc != INVALID_TPC) {
xml.stag("bass");
xml.tag("bass-step", tpc2stepName(baseTpc));
int alter = tpc2alter(baseTpc);
if (alter) {
xml.tag("bass-alter", alter);
}
xml.etag();
}
if (fd)
fd->writeMusicXML(xml);
xml.etag();
}
else {
//
// export an unrecognized Chord
// which may contain arbitrary text
//
if (h->hasFrame())
xml.stag(QString("harmony print-frame=\"yes\"").append(relative));
else
xml.stag(QString("harmony print-frame=\"no\"").append(relative));
xml.stag("root");
xml.tag("root-step text=\"\"", "C");
xml.etag(); // root
QString k = "kind text=\"" + h->hTextName() + "\"";
xml.tag(k, "none");
xml.etag(); // harmony
#if 0
// prior to 2.0, MuseScore exported unrecognized chords as plain text
xml.stag("direction");
xml.stag("direction-type");
xml.tag("words", h->text());
xml.etag();
xml.etag();
#endif
}
#if 0
// this is very old code that may never have actually been used
xml.tag(QString("kind text=\"%1\"").arg(h->extensionName()), extension);
for (int i = 0; i < h->numberOfDegrees(); i++) {
HDegree hd = h->degree(i);
int tp = hd.type();
if (tp == ADD || tp == ALTER || tp == SUBTRACT) {
xml.stag("degree");
xml.tag("degree-value", hd.value());
xml.tag("degree-alter", hd.alter());
switch (tp) {
case ADD:
xml.tag("degree-type", "add");
break;
case ALTER:
xml.tag("degree-type", "alter");
break;
case SUBTRACT:
xml.tag("degree-type", "subtract");
break;
default:
break;
}
xml.etag();
}
}
#endif
}
}