ec3be9a99a
- tick names a position on the time axis - tick is always a Fraction() - only Measure() and Segment() (and Tuplet?) have a tick value - tick() for an generic element return only a sensible value if isMeasure() or isSegment() or isSegment(parent()) - "ticks" names a duration stored in a Fraction() - the tick value for an Segment is relative to its measure - rename "duration" to "ticks" - rename afrac() to tick() - rename rfrac() to rtick() - rename some variables, changing "fraction" into "tick" (example: actualFraction() into actualTicks()) - Lyrics ticks are written as Fraction, on read if xmlreader sees a "/" it reads a fraction else midi ticks for backwards compatibility
593 lines
22 KiB
C++
593 lines
22 KiB
C++
//=============================================================================
|
|
// MusE Score
|
|
// Linux Music Score Editor
|
|
//
|
|
// Copyright (C) 2012 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 support.
|
|
*/
|
|
|
|
#include "globals.h"
|
|
#include "musicxmlsupport.h"
|
|
#include "libmscore/sym.h"
|
|
#include "libmscore/accidental.h"
|
|
|
|
namespace Ms {
|
|
|
|
NoteList::NoteList()
|
|
{
|
|
for (int i = 0; i < MAX_STAVES; ++i)
|
|
_staffNoteLists << StartStopList();
|
|
}
|
|
|
|
void NoteList::addNote(const int startTick, const int endTick, const int staff)
|
|
{
|
|
if (staff >= 0 && staff < _staffNoteLists.size())
|
|
_staffNoteLists[staff] << StartStop(startTick, endTick);
|
|
}
|
|
|
|
void NoteList::dump(const QString& voice) const
|
|
{
|
|
// dump contents
|
|
for (int i = 0; i < MAX_STAVES; ++i) {
|
|
printf("voice %s staff %d:", qPrintable(voice), i);
|
|
for (int j = 0; j < _staffNoteLists.at(i).size(); ++j)
|
|
printf(" %d-%d", _staffNoteLists.at(i).at(j).first, _staffNoteLists.at(i).at(j).second);
|
|
printf("\n");
|
|
}
|
|
// show overlap
|
|
printf("overlap voice %s:", qPrintable(voice));
|
|
for (int i = 0; i < MAX_STAVES - 1; ++i)
|
|
for (int j = i + 1; j < MAX_STAVES; ++j)
|
|
stavesOverlap(i, j);
|
|
printf("\n");
|
|
}
|
|
|
|
/**
|
|
Determine if notes n1 and n2 overlap.
|
|
This is NOT the case if
|
|
- n1 starts when or after n2 stops
|
|
- or n2 starts when or after n1 stops
|
|
*/
|
|
|
|
static bool notesOverlap(const StartStop& n1, const StartStop& n2)
|
|
{
|
|
return !(n1.first >= n2.second || n1.second <= n2.first);
|
|
}
|
|
|
|
/**
|
|
Determine if any note in staff1 and staff2 overlaps.
|
|
*/
|
|
|
|
bool NoteList::stavesOverlap(const int staff1, const int staff2) const
|
|
{
|
|
for (int i = 0; i < _staffNoteLists.at(staff1).size(); ++i)
|
|
for (int j = 0; j < _staffNoteLists.at(staff2).size(); ++j)
|
|
if (notesOverlap(_staffNoteLists.at(staff1).at(i), _staffNoteLists.at(staff2).at(j))) {
|
|
//printf(" %d-%d", staff1, staff2);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
Determine if any note in any staff overlaps.
|
|
*/
|
|
|
|
bool NoteList::anyStaffOverlaps() const
|
|
{
|
|
for (int i = 0; i < MAX_STAVES - 1; ++i)
|
|
for (int j = i + 1; j < MAX_STAVES; ++j)
|
|
if (stavesOverlap(i, j))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
VoiceOverlapDetector::VoiceOverlapDetector()
|
|
{
|
|
// qDebug("VoiceOverlapDetector::VoiceOverlapDetector(staves %d)", MAX_STAVES);
|
|
}
|
|
|
|
void VoiceOverlapDetector::addNote(const int startTick, const int endTick, const QString& voice, const int staff)
|
|
{
|
|
// if necessary, create the note list for voice
|
|
if (!_noteLists.contains(voice))
|
|
_noteLists.insert(voice, NoteList());
|
|
_noteLists[voice].addNote(startTick, endTick, staff);
|
|
}
|
|
|
|
void VoiceOverlapDetector::dump() const
|
|
{
|
|
// qDebug("VoiceOverlapDetector::dump()");
|
|
QMapIterator<QString, NoteList> i(_noteLists);
|
|
while (i.hasNext()) {
|
|
i.next();
|
|
i.value().dump(i.key());
|
|
}
|
|
}
|
|
|
|
void VoiceOverlapDetector::newMeasure()
|
|
{
|
|
// qDebug("VoiceOverlapDetector::newMeasure()");
|
|
_noteLists.clear();
|
|
}
|
|
|
|
bool VoiceOverlapDetector::stavesOverlap(const QString& voice) const
|
|
{
|
|
if (_noteLists.contains(voice))
|
|
return _noteLists.value(voice).anyStaffOverlaps();
|
|
else
|
|
return false;
|
|
}
|
|
|
|
QString MusicXMLDrumInstrument::toString() const
|
|
{
|
|
return QString("chan %1 prog %2 vol %3 pan %4 pitch %5 name '%6' sound '%7' head %8 line %9 stemDir %10")
|
|
.arg(midiChannel)
|
|
.arg(midiProgram)
|
|
.arg(midiVolume)
|
|
.arg(midiPan)
|
|
.arg(pitch)
|
|
.arg(name)
|
|
.arg(sound)
|
|
.arg(int(notehead))
|
|
.arg(line)
|
|
.arg(int(stemDirection));
|
|
}
|
|
|
|
void ValidatorMessageHandler::handleMessage(QtMsgType type, const QString& description,
|
|
const QUrl& /* identifier */, const QSourceLocation& sourceLocation)
|
|
{
|
|
// convert description from html to text
|
|
QDomDocument desc;
|
|
QString contentError;
|
|
int contentLine;
|
|
int contentColumn;
|
|
if (!desc.setContent(description, false, &contentError, &contentLine,
|
|
&contentColumn)) {
|
|
qDebug("ValidatorMessageHandler: could not parse validation error line %d column %d: %s",
|
|
contentLine, contentColumn, qPrintable(contentError));
|
|
return;
|
|
}
|
|
QDomElement e = desc.documentElement();
|
|
if (e.tagName() != "html") {
|
|
qDebug("ValidatorMessageHandler: description is not html");
|
|
return;
|
|
}
|
|
QString descText = e.text();
|
|
|
|
QString strType;
|
|
switch (type) {
|
|
case 0: strType = tr("Debug"); break;
|
|
case 1: strType = tr("Warning"); break;
|
|
case 2: strType = tr("Critical"); break;
|
|
case 3: strType = tr("Fatal"); break;
|
|
default: strType = tr("Unknown"); break;
|
|
}
|
|
|
|
QString errorStr = QString(tr("%1 error: line %2 column %3 %4"))
|
|
.arg(strType)
|
|
.arg(sourceLocation.line())
|
|
.arg(sourceLocation.column())
|
|
.arg(descText);
|
|
|
|
// append error, separated by newline if necessary
|
|
if (errors != "")
|
|
errors += "\n";
|
|
errors += errorStr;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// printDomElementPath
|
|
//---------------------------------------------------------
|
|
|
|
static QString domElementPath(const QDomElement& e)
|
|
{
|
|
QString s;
|
|
QDomNode dn(e);
|
|
while (!dn.parentNode().isNull()) {
|
|
dn = dn.parentNode();
|
|
const QDomElement& de = dn.toElement();
|
|
const QString k(de.tagName());
|
|
if (!s.isEmpty())
|
|
s += ":";
|
|
s += k;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// domError
|
|
//---------------------------------------------------------
|
|
|
|
void domError(const QDomElement& e)
|
|
{
|
|
QString m;
|
|
QString s = domElementPath(e);
|
|
// if (!docName.isEmpty())
|
|
// m = QString("<%1>:").arg(docName);
|
|
int ln = e.lineNumber();
|
|
if (ln != -1)
|
|
m += QString("line:%1 ").arg(ln);
|
|
int col = e.columnNumber();
|
|
if (col != -1)
|
|
m += QString("col:%1 ").arg(col);
|
|
m += QString("%1: Unknown Node <%2>, type %3").arg(s).arg(e.tagName()).arg(e.nodeType());
|
|
if (e.isText())
|
|
m += QString(" text node <%1>").arg(e.toText().data());
|
|
qDebug("%s", qPrintable(m));
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// domNotImplemented
|
|
//---------------------------------------------------------
|
|
|
|
void domNotImplemented(const QDomElement& e)
|
|
{
|
|
if (!MScore::debugMode)
|
|
return;
|
|
QString s = domElementPath(e);
|
|
// if (!docName.isEmpty())
|
|
// qDebug("<%s>:", qPrintable(docName));
|
|
qDebug("%s: Node not implemented: <%s>, type %d",
|
|
qPrintable(s), qPrintable(e.tagName()), e.nodeType());
|
|
if (e.isText())
|
|
qDebug(" text node <%s>", qPrintable(e.toText().data()));
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------
|
|
// stringToInt
|
|
//---------------------------------------------------------
|
|
|
|
/**
|
|
Convert a string in \a s into an int. Set *ok to true iff conversion was
|
|
successful. \a s may end with ".0", as is generated by Audiveris 3.2 and up,
|
|
in elements <divisions>, <duration>, <alter> and <sound> attributes
|
|
dynamics and tempo.
|
|
In case of error val return a default value of 0.
|
|
Note that non-integer values cannot be handled by mscore.
|
|
*/
|
|
|
|
int MxmlSupport::stringToInt(const QString& s, bool* ok)
|
|
{
|
|
int res = 0;
|
|
QString str = s;
|
|
if (s.endsWith(".0"))
|
|
str = s.left(s.size() - 2);
|
|
res = str.toInt(ok);
|
|
return res;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// durationAsFraction
|
|
//---------------------------------------------------------
|
|
|
|
/**
|
|
Return duration specified in the element e as Fraction.
|
|
Caller must ensure divisions is valid.
|
|
*/
|
|
|
|
Fraction MxmlSupport::durationAsFraction(const int divisions, const QDomElement e)
|
|
{
|
|
Fraction f;
|
|
if (e.tagName() == "duration") {
|
|
bool ok;
|
|
int val = MxmlSupport::stringToInt(e.text(), &ok);
|
|
f = Fraction(val, 4 * divisions); // note divisions = ticks / quarter note
|
|
f.reduce();
|
|
}
|
|
else {
|
|
qDebug() << "durationAsFraction tagname error" << f.print();
|
|
}
|
|
return f;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// noteTypeToFraction
|
|
//---------------------------------------------------------
|
|
|
|
/**
|
|
Convert MusicXML note type to fraction.
|
|
*/
|
|
|
|
Fraction MxmlSupport::noteTypeToFraction(QString type)
|
|
{
|
|
if (type == "1024th")
|
|
return Fraction(1, 1024);
|
|
else if (type == "512th")
|
|
return Fraction(1, 512);
|
|
else if (type == "256th")
|
|
return Fraction(1, 256);
|
|
else if (type == "128th")
|
|
return Fraction(1, 128);
|
|
else if (type == "64th")
|
|
return Fraction(1, 64);
|
|
else if (type == "32nd")
|
|
return Fraction(1, 32);
|
|
else if (type == "16th")
|
|
return Fraction(1, 16);
|
|
else if (type == "eighth")
|
|
return Fraction(1, 8);
|
|
else if (type == "quarter")
|
|
return Fraction(1, 4);
|
|
else if (type == "half")
|
|
return Fraction(1, 2);
|
|
else if (type == "whole")
|
|
return Fraction(1, 1);
|
|
else if (type == "breve")
|
|
return Fraction(2, 1);
|
|
else if (type == "long")
|
|
return Fraction(4, 1);
|
|
else if (type == "maxima")
|
|
return Fraction(8, 1);
|
|
else
|
|
return Fraction(0, 0);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// calculateFraction
|
|
//---------------------------------------------------------
|
|
|
|
/**
|
|
Convert note type, number of dots and actual and normal notes into a duration
|
|
*/
|
|
|
|
Fraction MxmlSupport::calculateFraction(QString type, int dots, int normalNotes, int actualNotes)
|
|
{
|
|
// type
|
|
Fraction f = MxmlSupport::noteTypeToFraction(type);
|
|
if (f.isValid()) {
|
|
// dot(s)
|
|
Fraction f_no_dots = f;
|
|
for (int i = 0; i < dots; ++i)
|
|
f += (f_no_dots / Fraction(2 << i, 1));
|
|
// tuplet
|
|
if (actualNotes > 0 && normalNotes > 0) {
|
|
f *= normalNotes;
|
|
f /= Fraction(actualNotes,1);
|
|
}
|
|
// clean up (just in case)
|
|
f.reduce();
|
|
}
|
|
return f;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// accSymId2MxmlString
|
|
//---------------------------------------------------------
|
|
|
|
QString accSymId2MxmlString(const SymId id)
|
|
{
|
|
QString s;
|
|
switch (id) {
|
|
case SymId::accidentalNatural: s = "natural"; break;
|
|
case SymId::accidentalFlat: s = "flat"; break;
|
|
case SymId::accidentalSharp: s = "sharp"; break;
|
|
case SymId::accidentalDoubleSharp: s = "double-sharp"; break;
|
|
case SymId::accidentalDoubleFlat: s = "flat-flat"; break;
|
|
case SymId::accidentalQuarterToneFlatStein: s = "quarter-flat"; break;
|
|
case SymId::accidentalQuarterToneSharpStein: s = "quarter-sharp"; break;
|
|
case SymId::accidentalKucukMucennebSharp: s = "slash-quarter-sharp"; break;
|
|
case SymId::accidentalBuyukMucennebSharp: s = "slash-sharp"; break;
|
|
case SymId::accidentalBakiyeFlat: s = "slash-flat"; break;
|
|
case SymId::accidentalBuyukMucennebFlat: s = "double-slash-flat"; break;
|
|
/* TODO
|
|
case AccidentalType::FLAT_ARROW_UP: s = "flat-up"; break;
|
|
case AccidentalType::NATURAL_ARROW_DOWN: s = "natural-down"; break;
|
|
case AccidentalType::SHARP_ARROW_DOWN: s = "sharp-down"; break;
|
|
case AccidentalType::NATURAL_ARROW_UP: s = "natural-up"; break;
|
|
case AccidentalType::MIRRORED_FLAT2: s = "three-quarters-flat"; break;
|
|
case AccidentalType::FLAT_FLAT_SLASH: s = "three-quarters-flat"; break;
|
|
case AccidentalType::FLAT_ARROW_DOWN: s = "flat-down"; break;
|
|
case AccidentalType::SHARP_SLASH4: s = "three-quarters-sharp"; break;
|
|
case AccidentalType::SHARP_ARROW_UP: s = "sharp-up"; break;
|
|
case AccidentalType::SORI: s = "sori"; break;
|
|
case AccidentalType::KORON: s = "koron"; break;
|
|
*/
|
|
default:
|
|
qDebug("accSymId2MxmlString: unknown accidental %d", static_cast<int>(id));
|
|
}
|
|
return s;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// mxmlString2accSymId
|
|
//---------------------------------------------------------
|
|
|
|
SymId mxmlString2accSymId(const QString mxmlName)
|
|
{
|
|
QMap<QString, SymId> map; // map MusicXML accidental name to MuseScore enum SymId
|
|
map["natural"] = SymId::accidentalDoubleSharp;
|
|
map["flat"] = SymId::accidentalFlat;
|
|
map["sharp"] = SymId::accidentalSharp;
|
|
map["double-sharp"] = SymId::accidentalDoubleSharp;
|
|
map["sharp-sharp"] = SymId::accidentalDoubleSharp;
|
|
map["flat-flat"] = SymId::accidentalDoubleFlat;
|
|
|
|
//map["double-flat"] = SymId::accidentalDoubleFlat;
|
|
//map["natural-flat"] = AccidentalType::FLAT;
|
|
|
|
map["quarter-flat"] = SymId::accidentalQuarterToneFlatStein;
|
|
map["quarter-sharp"] = SymId::accidentalQuarterToneSharpStein;
|
|
//map["three-quarters-flat"] = AccidentalType::MIRRORED_FLAT2;
|
|
//map["three-quarters-sharp"] = AccidentalType::SHARP_SLASH4;
|
|
|
|
//map["sharp-down"] = AccidentalType::SHARP_ARROW_DOWN;
|
|
//map["sharp-up"] = AccidentalType::SHARP_ARROW_UP;
|
|
//map["natural-down"] = AccidentalType::NATURAL_ARROW_DOWN;
|
|
//map["natural-up"] = AccidentalType::NATURAL_ARROW_UP;
|
|
//map["flat-down"] = AccidentalType::FLAT_ARROW_DOWN;
|
|
//map["flat-up"] = AccidentalType::FLAT_ARROW_UP;
|
|
|
|
map["slash-quarter-sharp"] = SymId::accidentalKucukMucennebSharp;
|
|
map["slash-sharp"] = SymId::accidentalBuyukMucennebSharp;
|
|
map["slash-flat"] = SymId::accidentalBakiyeFlat;
|
|
map["double-slash-flat"] = SymId::accidentalBuyukMucennebFlat;
|
|
|
|
//map["sori"] = AccidentalType::SORI;
|
|
//map["koron"] = AccidentalType::KORON;
|
|
|
|
//map["natural-sharp"] = AccidentalType::SHARP;
|
|
|
|
if (map.contains(mxmlName))
|
|
return map.value(mxmlName);
|
|
else
|
|
qDebug("mxmlString2accSymId: unknown accidental '%s'", qPrintable(mxmlName));
|
|
|
|
// default
|
|
return SymId::noSym;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// accidentalType2MxmlString
|
|
//---------------------------------------------------------
|
|
|
|
QString accidentalType2MxmlString(const AccidentalType type)
|
|
{
|
|
QString s;
|
|
switch (type) {
|
|
case AccidentalType::SHARP: s = "sharp"; break;
|
|
case AccidentalType::FLAT: s = "flat"; break;
|
|
case AccidentalType::SHARP2: s = "double-sharp"; break;
|
|
case AccidentalType::FLAT2: s = "flat-flat"; break;
|
|
case AccidentalType::NATURAL: s = "natural"; break;
|
|
case AccidentalType::FLAT_SLASH: s = "slash-flat"; break;
|
|
case AccidentalType::MIRRORED_FLAT: s = "quarter-flat"; break;
|
|
case AccidentalType::FLAT_ARROW_UP: s = "flat-up"; break;
|
|
case AccidentalType::NATURAL_ARROW_DOWN: s = "natural-down"; break;
|
|
case AccidentalType::SHARP_SLASH: s = "quarter-sharp"; break;
|
|
case AccidentalType::SHARP_ARROW_DOWN: s = "sharp-down"; break;
|
|
case AccidentalType::NATURAL_ARROW_UP: s = "natural-up"; break;
|
|
case AccidentalType::MIRRORED_FLAT2: s = "three-quarters-flat"; break;
|
|
//case AccidentalType::FLAT_FLAT_SLASH: s = "three-quarters-flat"; break;
|
|
case AccidentalType::FLAT_ARROW_DOWN: s = "flat-down"; break;
|
|
case AccidentalType::SHARP_SLASH4: s = "three-quarters-sharp"; break;
|
|
case AccidentalType::SHARP_ARROW_UP: s = "sharp-up"; break;
|
|
case AccidentalType::SHARP_SLASH3: s = "slash-quarter-sharp"; break;
|
|
case AccidentalType::FLAT_SLASH2: s = "double-slash-flat"; break;
|
|
case AccidentalType::SHARP_SLASH2: s = "slash-sharp"; break;
|
|
case AccidentalType::SORI: s = "sori"; break;
|
|
case AccidentalType::KORON: s = "koron"; break;
|
|
default:
|
|
qDebug("accidentalType2MxmlString: unknown accidental %d", static_cast<int>(type));
|
|
}
|
|
return s;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// mxmlString2accidentalType
|
|
//---------------------------------------------------------
|
|
|
|
/**
|
|
Convert a MusicXML accidental name to a MuseScore enum AccidentalType.
|
|
*/
|
|
|
|
AccidentalType mxmlString2accidentalType(const QString mxmlName)
|
|
{
|
|
QMap<QString, AccidentalType> map; // map MusicXML accidental name to MuseScore enum AccidentalType
|
|
map["natural"] = AccidentalType::NATURAL;
|
|
map["flat"] = AccidentalType::FLAT;
|
|
map["sharp"] = AccidentalType::SHARP;
|
|
map["double-sharp"] = AccidentalType::SHARP2;
|
|
map["sharp-sharp"] = AccidentalType::SHARP2;
|
|
map["flat-flat"] = AccidentalType::FLAT2;
|
|
map["double-flat"] = AccidentalType::FLAT2;
|
|
map["natural-flat"] = AccidentalType::FLAT;
|
|
|
|
map["quarter-flat"] = AccidentalType::MIRRORED_FLAT;
|
|
map["quarter-sharp"] = AccidentalType::SHARP_SLASH;
|
|
map["three-quarters-flat"] = AccidentalType::MIRRORED_FLAT2;
|
|
map["three-quarters-sharp"] = AccidentalType::SHARP_SLASH4;
|
|
|
|
map["sharp-down"] = AccidentalType::SHARP_ARROW_DOWN;
|
|
map["sharp-up"] = AccidentalType::SHARP_ARROW_UP;
|
|
map["natural-down"] = AccidentalType::NATURAL_ARROW_DOWN;
|
|
map["natural-up"] = AccidentalType::NATURAL_ARROW_UP;
|
|
map["flat-down"] = AccidentalType::FLAT_ARROW_DOWN;
|
|
map["flat-up"] = AccidentalType::FLAT_ARROW_UP;
|
|
|
|
map["slash-quarter-sharp"] = AccidentalType::SHARP_SLASH3; // MIRRORED_FLAT_SLASH; ?
|
|
map["slash-sharp"] = AccidentalType::SHARP_SLASH2; // SHARP_SLASH; ?
|
|
map["slash-flat"] = AccidentalType::FLAT_SLASH;
|
|
map["double-slash-flat"] = AccidentalType::FLAT_SLASH2;
|
|
|
|
map["sori"] = AccidentalType::SORI;
|
|
map["koron"] = AccidentalType::KORON;
|
|
|
|
map["natural-sharp"] = AccidentalType::SHARP;
|
|
|
|
if (map.contains(mxmlName))
|
|
return map.value(mxmlName);
|
|
else
|
|
qDebug("mxmlString2accidentalType: unknown accidental '%s'", qPrintable(mxmlName));
|
|
// default: return AccidentalType::NONE
|
|
return AccidentalType::NONE;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// isAppr
|
|
//---------------------------------------------------------
|
|
|
|
/**
|
|
Check if v approximately equals ref.
|
|
Used to prevent floating point comparison for equality from failing
|
|
*/
|
|
|
|
static bool isAppr(const double v, const double ref, const double epsilon)
|
|
{
|
|
return v > ref - epsilon && v < ref + epsilon;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// microtonalGuess
|
|
//---------------------------------------------------------
|
|
|
|
/**
|
|
Convert a MusicXML alter tag into a microtonal accidental in MuseScore enum AccidentalType.
|
|
Works only for quarter tone, half tone, three-quarters tone and whole tone accidentals.
|
|
*/
|
|
|
|
AccidentalType microtonalGuess(double val)
|
|
{
|
|
const double eps = 0.001;
|
|
if (isAppr(val, -2, eps))
|
|
return AccidentalType::FLAT2;
|
|
else if (isAppr(val, -1.5, eps))
|
|
return AccidentalType::MIRRORED_FLAT2;
|
|
else if (isAppr(val, -1, eps))
|
|
return AccidentalType::FLAT;
|
|
else if (isAppr(val, -0.5, eps))
|
|
return AccidentalType::MIRRORED_FLAT;
|
|
else if (isAppr(val, 0, eps))
|
|
return AccidentalType::NATURAL;
|
|
else if (isAppr(val, 0.5, eps))
|
|
return AccidentalType::SHARP_SLASH;
|
|
else if (isAppr(val, 1, eps))
|
|
return AccidentalType::SHARP;
|
|
else if (isAppr(val, 1.5, eps))
|
|
return AccidentalType::SHARP_SLASH4;
|
|
else if (isAppr(val, 2, eps))
|
|
return AccidentalType::SHARP2;
|
|
else
|
|
qDebug("Guess for microtonal accidental corresponding to value %f failed.", val); // TODO
|
|
|
|
// default
|
|
return AccidentalType::NONE;
|
|
}
|
|
|
|
}
|