MuseScore/src/engraving/libmscore/durationtype.cpp

811 lines
28 KiB
C++

/*
* SPDX-License-Identifier: GPL-3.0-only
* MuseScore-CLA-applies
*
* MuseScore
* Music Composition & Notation
*
* Copyright (C) 2021 MuseScore BVBA 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 3 as
* published by the Free Software Foundation.
*
* 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, see <https://www.gnu.org/licenses/>.
*/
#include "durationtype.h"
#include "mscore.h"
#include "note.h"
#include "sig.h"
#include "measure.h"
#include "log.h"
using namespace mu;
namespace Ms {
//---------------------------------------------------------
// dots
//---------------------------------------------------------
static int getDots(int base, int rest, int* dots)
{
if (base < 16) {
return rest;
}
*dots = 0;
if (rest >= base / 2) {
*dots = *dots + 1;
rest -= base / 2;
}
if (rest >= base / 4) {
*dots = *dots + 1;
rest -= base / 4;
}
if (rest >= base / 8) {
*dots = *dots + 1;
rest -= base / 8;
}
if (rest >= base / 16) {
*dots = *dots + 1;
rest -= base / 16;
}
if (*dots > MAX_DOTS) {
*dots = MAX_DOTS;
}
return rest;
}
//---------------------------------------------------------
// setDots
//---------------------------------------------------------
void TDuration::setDots(int v)
{
if (v > MAX_DOTS) {
v = MAX_DOTS;
}
if (v < 0) {
v = 0;
}
_dots = v;
}
//---------------------------------------------------------
// setVal
//---------------------------------------------------------
void TDuration::setVal(int ticks)
{
if (ticks == 0) {
_val = DurationType::V_MEASURE;
} else {
TDuration dt;
for (int i = 0; i < int(DurationType::V_ZERO); ++i) {
dt.setType(DurationType(i));
int t = dt.ticks().ticks();
if (ticks / t) {
int remain = ticks % t;
const int SMALLEST_DOT_DIVISOR = 1 << MAX_DOTS;
if ((t - remain) < (t / SMALLEST_DOT_DIVISOR)) {
_val = DurationType(i - 1);
return;
}
_val = DurationType(i);
getDots(t, remain, &_dots);
return;
}
}
LOGD("2: no duration type for ticks %d", ticks);
_val = DurationType::V_QUARTER; // fallback default value
}
}
//---------------------------------------------------------
// ticks
//---------------------------------------------------------
Fraction TDuration::ticks() const
{
Fraction t;
switch (_val) {
case DurationType::V_QUARTER: t = Fraction(1, 4);
break;
case DurationType::V_1024TH: t = Fraction(1, 1024);
break;
case DurationType::V_512TH: t = Fraction(1, 512);
break;
case DurationType::V_256TH: t = Fraction(1, 256);
break;
case DurationType::V_128TH: t = Fraction(1, 128);
break;
case DurationType::V_64TH: t = Fraction(1, 64);
break;
case DurationType::V_32ND: t = Fraction(1, 32);
break;
case DurationType::V_16TH: t = Fraction(1, 16);
break;
case DurationType::V_EIGHTH: t = Fraction(1, 8);
break;
case DurationType::V_HALF: t = Fraction(1, 2);
break;
case DurationType::V_WHOLE: t = Fraction(1, 1);
break;
case DurationType::V_BREVE: t = Fraction(2, 1);
break;
case DurationType::V_LONG: t = Fraction(4, 1);
break;
case DurationType::V_ZERO:
case DurationType::V_MEASURE:
return Fraction(0, 1);
default:
case DurationType::V_INVALID:
return Fraction(-1, 1);
}
Fraction tmp = t;
for (int i = 0; i < _dots; ++i) {
tmp *= Fraction(1, 2);
t += tmp;
}
return t;
}
//---------------------------------------------------------
// headType
//---------------------------------------------------------
NoteHeadType TDuration::headType() const
{
NoteHeadType headType = NoteHeadType::HEAD_WHOLE;
switch (_val) {
case DurationType::V_1024TH:
case DurationType::V_512TH:
case DurationType::V_256TH:
case DurationType::V_128TH:
case DurationType::V_64TH:
case DurationType::V_32ND:
case DurationType::V_16TH:
case DurationType::V_EIGHTH:
case DurationType::V_QUARTER:
headType = NoteHeadType::HEAD_QUARTER;
break;
case DurationType::V_HALF:
headType = NoteHeadType::HEAD_HALF;
break;
case DurationType::V_MEASURE:
case DurationType::V_WHOLE:
headType = NoteHeadType::HEAD_WHOLE;
break;
case DurationType::V_BREVE:
headType = NoteHeadType::HEAD_BREVIS;
break;
case DurationType::V_LONG:
headType = NoteHeadType::HEAD_BREVIS;
break;
default:
case DurationType::V_INVALID:
case DurationType::V_ZERO:
headType = NoteHeadType::HEAD_QUARTER;
break;
}
return headType;
}
//---------------------------------------------------------
// hooks
//---------------------------------------------------------
int TDuration::hooks() const
{
static const int table[] = {
// V_LONG, V_BREVE, V_WHOLE, V_HALF, V_QUARTER, V_EIGHTH, V_16TH,
0, 0, 0, 0, 0, 1, 2,
// V_32ND, V_64TH, V_128TH, V_256TH, V_512TH, V_1024TH,
3, 4, 5, 6, 7, 8,
// V_ZERO, V_MEASURE, V_INVALID
0, 0, 0
};
return table[int(_val)];
}
//---------------------------------------------------------
// hasStem
//---------------------------------------------------------
bool TDuration::hasStem() const
{
switch (_val) {
case DurationType::V_1024TH:
case DurationType::V_512TH:
case DurationType::V_256TH:
case DurationType::V_128TH:
case DurationType::V_64TH:
case DurationType::V_32ND:
case DurationType::V_16TH:
case DurationType::V_EIGHTH:
case DurationType::V_QUARTER:
case DurationType::V_HALF:
case DurationType::V_LONG:
return true;
default:
return false;
}
}
//---------------------------------------------------------
// shiftType
// If stepDotted = false, duration type will inc/dec by
// nSteps with _dots remaining same.
//
// If stepDotted = true, duration will round toward zero
// to next single-dotted or undotted duration and then
// will included dotted durations when stepping
//---------------------------------------------------------
void TDuration::shiftType(int nSteps, bool stepDotted)
{
if (_val == DurationType::V_MEASURE || _val == DurationType::V_INVALID || _val == DurationType::V_ZERO) {
setType(DurationType::V_INVALID);
} else {
int newValue;
int newDots;
if (stepDotted) {
// figure out the new duration in terms of the number of single dotted or undotted steps from DurationType::V_LONG
int roundDownSingleDots = (_dots > 0) ? -1 : 0;
int newValAsNumSingleDotSteps = int(_val) * 2 + roundDownSingleDots + nSteps;
// convert that new duration back into terms of DurationType integer value and number of dots
newDots = newValAsNumSingleDotSteps % 2; // odd means there is a dot
newValue = newValAsNumSingleDotSteps / 2 + newDots; // if new duration has a dot, then that
} else {
newDots = _dots;
newValue = int(_val) + nSteps;
}
if ((newValue < int(DurationType::V_LONG)) || (newValue > int(DurationType::V_1024TH))
|| ((newValue >= int(DurationType::V_1024TH)) && (newDots >= 1))
|| ((newValue >= int(DurationType::V_512TH)) && (newDots >= 2))
|| ((newValue >= int(DurationType::V_256TH)) && (newDots >= 3))
|| ((newValue >= int(DurationType::V_128TH)) && (newDots >= 4))) {
setType(DurationType::V_INVALID);
} else {
setType(DurationType(newValue));
setDots(newDots);
}
}
}
//---------------------------------------------------------
// operator<
//---------------------------------------------------------
bool TDuration::operator<(const TDuration& t) const
{
if (t._val < _val) {
return true;
}
if (t._val == _val) {
if (_dots < t._dots) {
return true;
}
}
return false;
}
//---------------------------------------------------------
// operator>=
//---------------------------------------------------------
bool TDuration::operator>=(const TDuration& t) const
{
if (t._val > _val) {
return true;
}
if (t._val == _val) {
if (_dots >= t._dots) {
return true;
}
}
return false;
}
//---------------------------------------------------------
// operator<=
//---------------------------------------------------------
bool TDuration::operator<=(const TDuration& t) const
{
if (t._val < _val) {
return true;
}
if (t._val == _val) {
if (_dots <= t._dots) {
return true;
}
}
return false;
}
//---------------------------------------------------------
// operator>
//---------------------------------------------------------
bool TDuration::operator>(const TDuration& t) const
{
if (t._val > _val) {
return true;
}
if (t._val == _val) {
if (_dots > t._dots) {
return true;
}
}
return false;
}
//---------------------------------------------------------
// fraction
//---------------------------------------------------------
Fraction TDuration::fraction() const
{
int z = 1;
unsigned n;
switch (_val) {
case DurationType::V_1024TH: n = 1024;
break;
case DurationType::V_512TH: n = 512;
break;
case DurationType::V_256TH: n = 256;
break;
case DurationType::V_128TH: n = 128;
break;
case DurationType::V_64TH: n = 64;
break;
case DurationType::V_32ND: n = 32;
break;
case DurationType::V_16TH: n = 16;
break;
case DurationType::V_EIGHTH: n = 8;
break;
case DurationType::V_QUARTER: n = 4;
break;
case DurationType::V_HALF: n = 2;
break;
case DurationType::V_WHOLE: n = 1;
break;
case DurationType::V_BREVE: z = 2;
n = 1;
break;
case DurationType::V_LONG: z = 4;
n = 1;
break;
case DurationType::V_ZERO: z = 0;
n = 1;
break;
default: z = 0;
n = 0;
break; // zero+invalid fraction
}
//dots multiplier is (2^(n + 1) - 1)/(2^n) where n is the number of dots
int dotN = (1 << (static_cast<char>(_dots) + 1)) - 1;
int dotD = 1 << static_cast<char>(_dots);
return Fraction(z * dotN, n * dotD);
}
// Longest TDuration that fits into Fraction. Must fit exactly if truncate = false.
TDuration::TDuration(const Fraction& l, bool truncate, int maxDots, DurationType maxType)
{
#ifdef NDEBUG
Q_UNUSED(truncate);
#endif
setType(maxType); // use maxType to avoid testing all types if you know that l is smaller than a certain DurationType
setDots(maxDots);
truncateToFraction(l, maxDots);
Q_ASSERT(truncate || (fraction() - l).numerator() == 0); // check for exact fit
}
//---------------------------------------------------------
// truncateToFraction
//---------------------------------------------------------
void TDuration::truncateToFraction(const Fraction& l, int maxDots)
{
// try to fit in l by reducing number of duration dots
if (setDotsToFitFraction(l, _dots)) {
return;
}
// that wasn't enough so now change type too
for (shiftType(1); isValid(); shiftType(1)) {
if (setDotsToFitFraction(l, maxDots)) {
return; // duration fits in l
}
}
}
//---------------------------------------------------------
// setDotsToFitFraction
//---------------------------------------------------------
bool TDuration::setDotsToFitFraction(const Fraction& l, int maxDots)
{
for (; maxDots >= 0; maxDots--) {
_dots = maxDots; // ensures _dots >= 0 if function returns false.
if ((fraction() - l).numerator() <= 0) {
return true; // duration fits in l
}
}
return false; // doesn't fit by changing dots alone (type needs to be changed too)
}
//---------------------------------------------------------
// operator -=
//---------------------------------------------------------
TDuration& TDuration::operator-=(const TDuration& t)
{
Fraction f1 = fraction() - t.fraction();
TDuration d(f1);
_val = d._val;
_dots = d._dots;
return *this;
}
//---------------------------------------------------------
// operator +=
//---------------------------------------------------------
TDuration& TDuration::operator+=(const TDuration& t)
{
Fraction f1 = fraction() + t.fraction();
TDuration d(f1);
_val = d._val;
_dots = d._dots;
return *this;
}
//---------------------------------------------------------
// toDurationList
//---------------------------------------------------------
std::vector<TDuration> toDurationList(Fraction l, bool useDots, int maxDots, bool printRestRemains)
{
std::vector<TDuration> dList;
dList.reserve(8);
if (!useDots) {
maxDots = 0;
}
for (TDuration dd(l, true, maxDots); dd.isValid() && l.numerator() > 0; dd = TDuration(l, true, maxDots, dd.type())) {
dList.push_back(dd);
l -= dd.fraction();
}
if (printRestRemains && l.numerator() != 0) {
LOGD("toDurationList:: rest remains %d/%d", l.numerator(), l.denominator());
}
return dList;
}
//---------------------------------------------------------
// toRhythmicDurationList
//---------------------------------------------------------
std::vector<TDuration> toRhythmicDurationList(const Fraction& l, bool isRest, Fraction rtickStart,
const TimeSigFrac& nominal, Measure* msr, int maxDots)
{
std::vector<TDuration> dList;
dList.reserve(8);
if (msr->isAnacrusis()) {
rtickStart = Fraction::fromTicks(nominal.ticksPerMeasure()) - rtickStart;
} else if (isRest && l == msr->ticks()) {
TDuration d = TDuration(DurationType::V_MEASURE);
dList.push_back(d);
return dList;
}
if (nominal.isCompound()) {
splitCompoundBeatsForList(&dList, l, isRest, rtickStart, nominal, maxDots);
} else {
populateRhythmicList(&dList, l, isRest, rtickStart, nominal, maxDots);
}
return dList;
}
//---------------------------------------------------------
// populateRhythmicList
//---------------------------------------------------------
void populateRhythmicList(std::vector<TDuration>* dList, const Fraction& l, bool isRest, const Fraction& rtickStart,
const TimeSigFrac& nominal, int maxDots)
{
Fraction rtickEnd = rtickStart + l;
bool needToSplit = false; // do we need to split?
int rtickSplit = 0; // tick to split on if we need to
// CHECK AT SUBBEAT LEVEL
int startLevel = nominal.rtick2subbeatLevel(rtickStart.ticks());
int endLevel = nominal.rtick2subbeatLevel(rtickEnd.ticks());
int strongestLevelCrossed = nominal.strongestSubbeatLevelInRange(rtickStart.ticks(), rtickEnd.ticks(), &rtickSplit); // sets rtickSplit
if ((startLevel < 0) || (endLevel < 0) || (strongestLevelCrossed < 0)) {
// Beyond maximum subbeat level so just split into largest possible durations.
std::vector<TDuration> dList2 = toDurationList(l, maxDots > 0, maxDots, false);
dList->insert(dList->end(), dList2.begin(), dList2.end());
return;
}
// split if we cross something stronger than where we start and end
if ((strongestLevelCrossed < startLevel) && (strongestLevelCrossed < endLevel)) {
needToSplit = true;
}
// but don't split for level 1 syncopation (allow eight-note, quarter, quarter... to cross unstressed beats)
if (startLevel == endLevel && strongestLevelCrossed == startLevel - 1) {
needToSplit = false;
}
// nor for the next simplest case of level 2 syncopation (allow sixteenth-note, eighth, eighth... to cross unstressed beats)
if (startLevel == endLevel && strongestLevelCrossed == startLevel - 2) {
// but disallow sixteenth-note, quarter, quarter...
int ticksToNext = nominal.ticksToNextSubbeat(rtickStart.ticks(), startLevel - 1);
int ticksPastPrev = nominal.ticksPastSubbeat(rtickStart.ticks(), startLevel - 1);
needToSplit = ticksToNext != ticksPastPrev;
}
if (!needToSplit && strongestLevelCrossed == 0) {
// NOW CHECK AT DENOMINATOR UNIT LEVEL AND BEAT LEVEL
BeatType startBeat = nominal.rtick2beatType(rtickStart.ticks());
BeatType endBeat = nominal.rtick2beatType(rtickEnd.ticks());
int dUnitsCrossed = 0; // number of timeSig denominator units the note/rest crosses
// if there is a choice of which beat to split on, should we use the first or last?
bool useLast = startBeat <= BeatType::SIMPLE_UNSTRESSED; // split on the later beat if starting on a beat
BeatType strongestBeatCrossed = nominal.strongestBeatInRange(rtickStart.ticks(),
rtickEnd.ticks(), &dUnitsCrossed, &rtickSplit, useLast);
needToSplit = forceRhythmicSplit(isRest, startBeat, endBeat, dUnitsCrossed, strongestBeatCrossed, nominal);
}
if (!needToSplit) {
// CHECK THERE IS A DURATION THAT FITS
// crossed beats/subbeats were not important so try to avoid splitting
TDuration d = TDuration(l, true, maxDots);
if (d.fraction() == l) {
// we can use a single duration - no need to split!
dList->push_back(l);
return;
}
// no single TDuration fits so must split anyway
}
// Split on the strongest beat or subbeat crossed
Fraction leftSplit = Fraction::fromTicks(rtickSplit) - rtickStart;
Fraction rightSplit = l - leftSplit;
// Recurse to see if we need to split further before adding to list
populateRhythmicList(dList, leftSplit, isRest, rtickStart, nominal, maxDots);
populateRhythmicList(dList, rightSplit, isRest, Fraction::fromTicks(rtickSplit), nominal, maxDots);
}
//---------------------------------------------------------
// splitCompoundBeatsForList
// Split compound notes/rests where they enter a compound beat.
//---------------------------------------------------------
void splitCompoundBeatsForList(std::vector<TDuration>* dList, const Fraction& l, bool isRest,
const Fraction& rtickStart, const TimeSigFrac& nominal, int maxDots)
{
Fraction rtickEnd = rtickStart + l;
BeatType startBeat = nominal.rtick2beatType(rtickStart.ticks());
BeatType endBeat = nominal.rtick2beatType(rtickEnd.ticks());
if (startBeat > BeatType::COMPOUND_UNSTRESSED) {
// Not starting on a compound beat so mustn't extend into next compound beat
int splitTicks = nominal.ticksToNextBeat(rtickStart.ticks());
if ((rtickEnd - rtickStart).ticks() > splitTicks) {
// Duration extends into next beat so must split
Fraction leftSplit = Fraction::fromTicks(splitTicks);
Fraction rightSplit = l - leftSplit;
populateRhythmicList(dList, leftSplit, isRest, rtickStart, nominal, maxDots); // this side is ok to proceed
splitCompoundBeatsForList(dList, rightSplit, isRest, rtickStart + Fraction::fromTicks(splitTicks), nominal, maxDots); // not checked yet
return;
}
}
if (endBeat > BeatType::COMPOUND_UNSTRESSED) {
// Not ending on a compound beat so mustn't extend into previous compound beat
int splitTicks = nominal.ticksPastBeat(rtickEnd.ticks());
if ((rtickEnd - rtickStart).ticks() > splitTicks) {
// Duration extends into previous beat so must split
Fraction rightSplit = Fraction::fromTicks(splitTicks);
Fraction leftSplit = l - rightSplit;
populateRhythmicList(dList, leftSplit, isRest, rtickStart, nominal, maxDots); // must add leftSplit to list first
populateRhythmicList(dList, rightSplit, isRest, rtickEnd - Fraction::fromTicks(splitTicks), nominal, maxDots);
return;
}
}
// Duration either starts and ends on compound beats, or it remains within a single compound beat
populateRhythmicList(dList, l, isRest, rtickStart, nominal, maxDots);
}
//---------------------------------------------------------
// forceRhythmicSplit
// Where to split notes and force a tie to indicate rhythm.
//
// The function assumes the following:
// * Note/rest has already been split at measure boundaries.
// * Full measure rest was used if possible
// * Note/rest already split where it enters a compound beat.
//
// Usage: Set crossedBeat to the strongest BeatType crossed by
// the note. Split note if function returns true. Repeat with
// the two new notes, and so on, until function returns false.
//
// Note: If no single TDuration can fill the gap then the note
// *has* to be split, regardless of what this function returns.
// Non-rhythmic splits should still occur on the strongest beat.
//
// Implementation: When comparing BeatTypes use <, <=, >, >=
// instead of == and != as appropriate (see sig.h). E.g. to match
// all full beats: "<= SIMPLE_UNSTRESSED". This will match for
// all compound and simple full beats, and not any subbeats.
//---------------------------------------------------------
bool forceRhythmicSplit(bool isRest, BeatType startBeat, BeatType endBeat,
int dUnitsCrossed, BeatType strongestBeatCrossed, const TimeSigFrac& nominal)
{
// Assumption: Notes were split at measure boundary before this function was
// called. (Necessary because timeSig might be different in next measure.)
Q_ASSERT(strongestBeatCrossed != BeatType::DOWNBEAT);
// Assumption: compound notes have already been split where they enter a compound beat.
// (Necessary because the split beat is not always the strongest beat in this case.)
Q_ASSERT(!nominal.isCompound() || strongestBeatCrossed >= BeatType::COMPOUND_SUBBEAT
|| (startBeat <= BeatType::COMPOUND_UNSTRESSED && endBeat <= BeatType::COMPOUND_UNSTRESSED));
// SPECIAL CASES
// nothing can cross a stressed beat in an irregular time signature
if (strongestBeatCrossed <= BeatType::SIMPLE_STRESSED && !nominal.isTriple() && !nominal.isDuple()) {
return true;
}
if (isRest) {
// rests must not cross the middle of a bar with numerator == 2 (e.g. 2/4 bar) even though the beat is unstressed
if (strongestBeatCrossed <= BeatType::SIMPLE_UNSTRESSED && nominal.numerator() == 2) {
return true;
}
// rests must not cross a beat in a triple meter - simple (e.g. 3/4) or compound (e.g. 9/8)
if (strongestBeatCrossed <= BeatType::SIMPLE_UNSTRESSED && nominal.isTriple()) {
return true;
}
}
// GENERAL RULES
if (nominal.isCompound()) {
return forceRhythmicSplitCompound(isRest, startBeat, endBeat, dUnitsCrossed, strongestBeatCrossed);
} else {
return forceRhythmicSplitSimple(isRest, startBeat, endBeat, dUnitsCrossed, strongestBeatCrossed);
}
}
//---------------------------------------------------------
// forceRhythmicSplitCompound
//---------------------------------------------------------
bool forceRhythmicSplitCompound(bool isRest, BeatType startBeat, BeatType endBeat, int dUnitsCrossed, BeatType strongestBeatCrossed)
{
switch (strongestBeatCrossed) {
case BeatType::COMPOUND_STRESSED:
// Assumption: compound notes have already been split where they enter a compound beat.
Q_ASSERT(startBeat <= BeatType::COMPOUND_UNSTRESSED && endBeat <= BeatType::COMPOUND_UNSTRESSED);
// Notes are guaranteed to and start on a compound beat so we can pretend we have a simple measure.
return forceRhythmicSplitSimple(isRest, startBeat, endBeat, dUnitsCrossed / 3, BeatType::SIMPLE_STRESSED);
case BeatType::COMPOUND_UNSTRESSED:
// Same assumption as before
Q_ASSERT(startBeat <= BeatType::COMPOUND_UNSTRESSED && endBeat <= BeatType::COMPOUND_UNSTRESSED);
// No further conditions since note are guaranteed to start and end on a compound beat.
return false;
case BeatType::COMPOUND_SUBBEAT:
// don't split anything that takes up a full compound beat
if (startBeat <= BeatType::COMPOUND_UNSTRESSED && endBeat <= BeatType::COMPOUND_UNSTRESSED) {
return false;
}
// split rests that don't start on a compound beat
if (isRest && startBeat > BeatType::COMPOUND_UNSTRESSED) {
return true;
}
// Remaining groupings within compound triplets are the same as for simple triple (3/4, 3/8, etc.)
return forceRhythmicSplitSimple(isRest, startBeat, endBeat, dUnitsCrossed, BeatType::SIMPLE_UNSTRESSED);
default: // BeatType::SUBBEAT
return forceRhythmicSplitSimple(isRest, startBeat, endBeat, dUnitsCrossed, strongestBeatCrossed);
}
}
//---------------------------------------------------------
// forceRhythmicSplitSimple
// Implementation: This function is also called for compound
// measures so be careful when comparing BeatTypes. Use <, <=,
// >, >= instead of == and != when appropriate. (See sig.h)
//---------------------------------------------------------
bool forceRhythmicSplitSimple(bool isRest, BeatType startBeat, BeatType endBeat, int beatsCrossed, BeatType strongestBeatCrossed)
{
switch (strongestBeatCrossed) {
case BeatType::SIMPLE_STRESSED:
// Must split rests on a stressed beat.
if (isRest) {
return true;
}
// Don't split notes that start or end on a stressed beat. Enables double-dotting in 4/4.
// (Don't remove this to disable double-dotting, instead set maxDots = 1 elsewhere.)
if (startBeat <= BeatType::SIMPLE_STRESSED || endBeat <= BeatType::SIMPLE_STRESSED) {
return false;
}
// Don't split notes that both start and end on unstressed beats or stronger.
if (startBeat <= BeatType::SIMPLE_UNSTRESSED && endBeat <= BeatType::SIMPLE_UNSTRESSED) {
return false;
}
// anything else must split on stressed beat.
return true;
case BeatType::SIMPLE_UNSTRESSED:
// Don't split notes or rests if starting and ending on stressed beat.
if (startBeat <= BeatType::SIMPLE_STRESSED && endBeat <= BeatType::SIMPLE_STRESSED) {
return false;
}
// Split rests that don't start or end on a beat. Notes may cross 1 unstressed beat.
if (startBeat == BeatType::SUBBEAT || endBeat == BeatType::SUBBEAT) {
return isRest || (beatsCrossed > 1);
}
return false;
default: // BeatType::SUBBEAT
return false;
}
}
//---------------------------------------------------------
// setType
//---------------------------------------------------------
void TDuration::setType(DurationType t)
{
_val = t;
if (_val == DurationType::V_MEASURE) {
_dots = 0;
}
}
//---------------------------------------------------------
// isValid
//---------------------------------------------------------
bool TDuration::isValid(Fraction f)
{
TDuration t;
t.setType(DurationType::V_LONG);
t.setDots(4);
t.truncateToFraction(f, 4);
return t.isValid() && (t.fraction() - f).numerator() == 0;
}
}