526 lines
20 KiB
C++
526 lines
20 KiB
C++
#include "importmidi_meter.h"
|
|
#include "importmidi_fraction.h"
|
|
#include "libmscore/durationtype.h"
|
|
#include "libmscore/mscore.h"
|
|
#include "importmidi_tuplet.h"
|
|
#include "importmidi_inner.h"
|
|
|
|
|
|
namespace Ms {
|
|
namespace Meter {
|
|
|
|
bool isSimple(const ReducedFraction &barFraction) // 2/2, 3/4, 4/4, ...
|
|
{
|
|
return barFraction.numerator() < 5;
|
|
}
|
|
|
|
bool isCompound(const ReducedFraction &barFraction) // 6/8, 12/4, ...
|
|
{
|
|
return barFraction.numerator() % 3 == 0 && barFraction.numerator() > 3;
|
|
}
|
|
|
|
bool isComplex(const ReducedFraction &barFraction) // 5/4, 7/8, ...
|
|
{
|
|
return barFraction.numerator() == 5 || barFraction.numerator() == 7;
|
|
}
|
|
|
|
bool isDuple(const ReducedFraction &barFraction) // 2/2, 6/8, ...
|
|
{
|
|
return barFraction.numerator() == 2 || barFraction.numerator() == 6;
|
|
}
|
|
|
|
bool isTriple(const ReducedFraction &barFraction) // 3/4, 9/4, ...
|
|
{
|
|
return barFraction.numerator() == 3 || barFraction.numerator() == 9;
|
|
}
|
|
|
|
bool isQuadruple(const ReducedFraction &barFraction) // 4/4, 12/8, ...
|
|
{
|
|
return barFraction.numerator() % 4 == 0;
|
|
}
|
|
|
|
bool isQuintuple(const ReducedFraction &barFraction) // 5/4, 15/8, ...
|
|
{
|
|
return barFraction.numerator() % 5 == 0;
|
|
}
|
|
|
|
bool isSeptuple(const ReducedFraction &barFraction) // 7/8, 21/8, ...
|
|
{
|
|
return barFraction.numerator() % 7 == 0;
|
|
}
|
|
|
|
|
|
ReducedFraction minAllowedDuration()
|
|
{
|
|
return ReducedFraction::fromTicks(MScore::division / 32); // smallest allowed duration is 1/128
|
|
}
|
|
|
|
|
|
// list of bar division lengths in ticks (whole bar len, half bar len, ...)
|
|
// and its corresponding levels
|
|
// tuplets are not taken into account here
|
|
|
|
DivisionInfo metricDivisionsOfBar(const ReducedFraction &barFraction)
|
|
{
|
|
DivisionInfo barDivInfo;
|
|
barDivInfo.len = barFraction;
|
|
// first value of each element in list is a length (in ticks) of every part of bar
|
|
// on which bar is subdivided on each level
|
|
// the level value is a second value of each element
|
|
auto &divLengths = barDivInfo.divLengths;
|
|
int level = 0;
|
|
divLengths.push_back({barFraction, level});
|
|
// pulse-level division
|
|
if (isDuple(barFraction))
|
|
divLengths.push_back({barFraction / 2, --level});
|
|
else if (isTriple(barFraction))
|
|
divLengths.push_back({barFraction / 3, --level});
|
|
else if (isQuadruple(barFraction)) {
|
|
divLengths.push_back({barFraction / 2, --level}); // additional central accent
|
|
divLengths.push_back({barFraction / 4, --level});
|
|
}
|
|
else if (isQuintuple(barFraction)) {
|
|
divLengths.push_back({barFraction / 5, --level});
|
|
}
|
|
else if (isSeptuple(barFraction)) {
|
|
divLengths.push_back({barFraction / 7, --level});
|
|
}
|
|
else { // if other - complex meter
|
|
divLengths.push_back({barFraction / barFraction.numerator(), --level});
|
|
}
|
|
|
|
if (isCompound(barFraction)) {
|
|
--level; // additional min level for pulse divisions
|
|
// subdivide pulse of compound meter into 3 parts
|
|
divLengths.push_back({divLengths.back().len / 3, --level});
|
|
}
|
|
|
|
while (divLengths.back().len >= minAllowedDuration() * 2)
|
|
divLengths.push_back({divLengths.back().len / 2, --level});
|
|
|
|
return barDivInfo;
|
|
}
|
|
|
|
DivisionInfo metricDivisionsOfTuplet(const MidiTuplet::TupletData &tuplet,
|
|
int tupletStartLevel)
|
|
{
|
|
DivisionInfo tupletDivInfo;
|
|
tupletDivInfo.onTime = tuplet.onTime;
|
|
tupletDivInfo.len = tuplet.len;
|
|
tupletDivInfo.isTuplet = true;
|
|
tupletDivInfo.divLengths.push_back({tuplet.len, TUPLET_BOUNDARY_LEVEL});
|
|
const auto divLen = tuplet.len / tuplet.tupletNumber;
|
|
tupletDivInfo.divLengths.push_back({divLen, tupletStartLevel--});
|
|
while (tupletDivInfo.divLengths.back().len >= minAllowedDuration() * 2) {
|
|
tupletDivInfo.divLengths.push_back({
|
|
tupletDivInfo.divLengths.back().len / 2, tupletStartLevel--});
|
|
}
|
|
return tupletDivInfo;
|
|
}
|
|
|
|
ReducedFraction beatLength(const ReducedFraction &barFraction)
|
|
{
|
|
auto beatLen = barFraction / 4;
|
|
if (isDuple(barFraction))
|
|
beatLen = barFraction / 2;
|
|
else if (isTriple(barFraction))
|
|
beatLen = barFraction / 3;
|
|
else if (isQuadruple(barFraction))
|
|
beatLen = barFraction / 4;
|
|
else if (isComplex(barFraction))
|
|
beatLen = barFraction / barFraction.numerator();
|
|
return beatLen;
|
|
}
|
|
|
|
std::vector<ReducedFraction> divisionsOfBarForTuplets(const ReducedFraction &barFraction)
|
|
{
|
|
const DivisionInfo info = metricDivisionsOfBar(barFraction);
|
|
std::vector<ReducedFraction> divLengths;
|
|
const auto beatLen = beatLength(barFraction);
|
|
for (const auto &i: info.divLengths) {
|
|
// in compound meter tuplet starts from beat level, not the whole bar
|
|
if (isCompound(barFraction) && i.len > beatLen)
|
|
continue;
|
|
divLengths.push_back(i.len);
|
|
}
|
|
return divLengths;
|
|
}
|
|
|
|
// result in vector: first elements - all tuplets info, one at the end - bar division info
|
|
|
|
std::vector<DivisionInfo> divisionInfo(const ReducedFraction &barFraction,
|
|
const std::vector<MidiTuplet::TupletData> &tupletsInBar)
|
|
{
|
|
std::vector<DivisionInfo> divsInfo;
|
|
|
|
const auto barDivisionInfo = metricDivisionsOfBar(barFraction);
|
|
for (const auto &tuplet: tupletsInBar) {
|
|
int tupletStartLevel = 0;
|
|
for (const auto &divLenInfo: barDivisionInfo.divLengths) {
|
|
if (divLenInfo.len == tuplet.len) {
|
|
tupletStartLevel = divLenInfo.level;
|
|
break;
|
|
}
|
|
}
|
|
divsInfo.push_back(metricDivisionsOfTuplet(tuplet, --tupletStartLevel));
|
|
}
|
|
divsInfo.push_back(barDivisionInfo);
|
|
|
|
return divsInfo;
|
|
}
|
|
|
|
// tick is counted from the beginning of bar
|
|
|
|
int levelOfTick(const ReducedFraction &tick, const std::vector<DivisionInfo> &divsInfo)
|
|
{
|
|
for (const auto &divInfo: divsInfo) {
|
|
if (tick < divInfo.onTime || tick > divInfo.onTime + divInfo.len)
|
|
continue;
|
|
for (const auto &divLenInfo: divInfo.divLengths) {
|
|
const auto ratio = (tick - divInfo.onTime) / divLenInfo.len;
|
|
if (ratio.numerator() % ratio.denominator() == 0)
|
|
return divLenInfo.level;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// return level with pos == Fraction(-1, 1) if undefined - see MaxLevel class
|
|
|
|
Meter::MaxLevel maxLevelBetween(const ReducedFraction &startTickInBar,
|
|
const ReducedFraction &endTickInBar,
|
|
const DivisionInfo &divInfo)
|
|
{
|
|
MaxLevel level;
|
|
const auto startTickInDiv = startTickInBar - divInfo.onTime;
|
|
const auto endTickInDiv = endTickInBar - divInfo.onTime;
|
|
if (startTickInDiv >= endTickInDiv || startTickInDiv < ReducedFraction(0, 1)
|
|
|| endTickInDiv > divInfo.len)
|
|
return level;
|
|
|
|
for (const auto &divLengthInfo: divInfo.divLengths) {
|
|
const auto &divLen = divLengthInfo.len;
|
|
const auto ratio = endTickInDiv / divLen;
|
|
auto maxEndRaster = divLen * (ratio.numerator() / ratio.denominator());
|
|
if (maxEndRaster == endTickInDiv)
|
|
maxEndRaster -= divLen;
|
|
if (startTickInDiv < maxEndRaster) {
|
|
// max level is found
|
|
const auto ratio = startTickInDiv / divLen;
|
|
const auto maxStartRaster = divLen * (ratio.numerator() / ratio.denominator());
|
|
const auto count = (maxEndRaster - maxStartRaster) / divLen;
|
|
level.pos = maxStartRaster + divLen + divInfo.onTime;
|
|
level.levelCount = count.numerator() / count.denominator();
|
|
level.level = divLengthInfo.level;
|
|
break;
|
|
}
|
|
}
|
|
return level;
|
|
}
|
|
|
|
// vector<DivisionInfo>:
|
|
// first elements - tuplet division info, if there are any tuplets
|
|
// last element - always the whole bar division info
|
|
|
|
// here we use levelCount = 1 always for simplicity
|
|
// because TUPLET_BOUNDARY_LEVEL is 'max enough'
|
|
|
|
Meter::MaxLevel findMaxLevelBetween(const ReducedFraction &startTickInBar,
|
|
const ReducedFraction &endTickInBar,
|
|
const std::vector<DivisionInfo> &divsInfo)
|
|
{
|
|
MaxLevel level;
|
|
|
|
for (const auto &divInfo: divsInfo) {
|
|
if (divInfo.isTuplet) {
|
|
if (startTickInBar < divInfo.onTime + divInfo.len
|
|
&& endTickInBar > divInfo.onTime + divInfo.len) {
|
|
level.level = TUPLET_BOUNDARY_LEVEL;
|
|
level.levelCount = 1;
|
|
level.pos = divInfo.onTime + divInfo.len;
|
|
break;
|
|
}
|
|
if (startTickInBar < divInfo.onTime
|
|
&& endTickInBar > divInfo.onTime
|
|
&& endTickInBar <= divInfo.onTime + divInfo.len) {
|
|
level.level = TUPLET_BOUNDARY_LEVEL;
|
|
level.levelCount = 1;
|
|
level.pos = divInfo.onTime;
|
|
break;
|
|
}
|
|
if (startTickInBar >= divInfo.onTime
|
|
&& endTickInBar <= divInfo.onTime + divInfo.len) {
|
|
level = maxLevelBetween(startTickInBar, endTickInBar, divInfo);
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
level = maxLevelBetween(startTickInBar, endTickInBar, divInfo);
|
|
break;
|
|
}
|
|
}
|
|
return level;
|
|
}
|
|
|
|
int tupletNumberForDuration(const ReducedFraction &startTick,
|
|
const ReducedFraction &endTick,
|
|
const std::vector<MidiTuplet::TupletData> &tupletsInBar)
|
|
{
|
|
for (const auto &tupletData: tupletsInBar) {
|
|
if (startTick >= tupletData.onTime
|
|
&& endTick <= tupletData.onTime + tupletData.len)
|
|
return tupletData.tupletNumber;
|
|
}
|
|
return -1; // this duration is not inside any tuplet
|
|
}
|
|
|
|
bool isPowerOfTwo(unsigned int x)
|
|
{
|
|
return x && !(x & (x - 1));
|
|
}
|
|
|
|
bool isSimpleNoteDuration(const ReducedFraction &duration)
|
|
{
|
|
const auto division = ReducedFraction::fromTicks(MScore::division);
|
|
auto div = (duration > division) ? duration / division : division / duration;
|
|
if (div > ReducedFraction(0, 1)) {
|
|
div.reduce();
|
|
int minVal = qMin(div.numerator(), div.denominator());
|
|
int maxVal = qMax(div.numerator(), div.denominator());
|
|
return minVal == 1 && isPowerOfTwo((unsigned int)maxVal);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool isQuarterDuration(const ReducedFraction &ticks)
|
|
{
|
|
return (ticks.numerator() == 1 && ticks.denominator() == 4);
|
|
}
|
|
|
|
// If last 2/3 of beat in compound meter is rest,
|
|
// it should be splitted into 2 rests
|
|
|
|
bool is23EndOfBeatInCompoundMeter(const ReducedFraction &startTickInBar,
|
|
const ReducedFraction &endTickInBar,
|
|
const ReducedFraction &barFraction)
|
|
{
|
|
if (endTickInBar <= startTickInBar)
|
|
return false;
|
|
if (!isCompound(barFraction))
|
|
return false;
|
|
|
|
const auto beatLen = beatLength(barFraction);
|
|
const auto divLen = beatLen / 3;
|
|
const auto ratio1 = startTickInBar / beatLen;
|
|
const auto ratio2 = endTickInBar / beatLen;
|
|
if ((startTickInBar - beatLen * (ratio1.numerator() / ratio1.denominator()) == divLen)
|
|
&& (ratio2.numerator() % ratio2.denominator() == 0))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool is2of3RestInTripleMeter(const ReducedFraction &startTickInBar,
|
|
const ReducedFraction &endTickInBar,
|
|
const ReducedFraction &barFraction)
|
|
{
|
|
if (endTickInBar - startTickInBar <= ReducedFraction(0, 1))
|
|
return false;
|
|
if (isTriple(barFraction)
|
|
&& (startTickInBar == ReducedFraction(0, 1)
|
|
|| endTickInBar == barFraction)
|
|
&& endTickInBar - startTickInBar == (barFraction * 2) / 3)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
|
|
struct Node
|
|
{
|
|
Node(int edgeLevel, int midLevel)
|
|
: edgeLevel(edgeLevel)
|
|
, midLevel(midLevel)
|
|
{}
|
|
|
|
int edgeLevel;
|
|
int midLevel;
|
|
};
|
|
|
|
|
|
// all durations inside tuplets are smaller/larger than their regular versions
|
|
// this difference is represented by tuplet ratio: 3/2 for triplets, etc.
|
|
|
|
// if node duration is completely inside some tuplet
|
|
// then assign to the node tuplet-to-regular-duration conversion coefficient
|
|
|
|
ReducedFraction findTupletRatio(const ReducedFraction &startPos,
|
|
const ReducedFraction &endPos,
|
|
const std::vector<MidiTuplet::TupletData> &tupletsInBar)
|
|
{
|
|
ReducedFraction tupletRatio = {2, 2};
|
|
int tupletNumber = tupletNumberForDuration(startPos, endPos, tupletsInBar);
|
|
if (tupletNumber != -1)
|
|
tupletRatio = MidiTuplet::tupletLimits(tupletNumber).ratio;
|
|
|
|
return tupletRatio;
|
|
}
|
|
|
|
QList<std::pair<ReducedFraction, TDuration> >
|
|
collectDurations(const std::map<ReducedFraction, Node> &nodes,
|
|
const std::vector<MidiTuplet::TupletData> &tupletsInBar,
|
|
bool useDots)
|
|
{
|
|
QList<std::pair<ReducedFraction, TDuration>> resultDurations;
|
|
|
|
for (auto it1 = nodes.begin(); it1 != nodes.end(); ++it1) {
|
|
auto it2 = it1;
|
|
++it2;
|
|
if (it2 == nodes.end())
|
|
break;
|
|
const auto tupletRatio = findTupletRatio(it1->first, it2->first, tupletsInBar);
|
|
const auto duration = tupletRatio * (it2->first - it1->first);
|
|
auto list = toDurationList(duration.fraction(), useDots, 1);
|
|
for (const auto &duration: list)
|
|
resultDurations.push_back({tupletRatio, duration});
|
|
}
|
|
|
|
return resultDurations;
|
|
}
|
|
|
|
bool badLevelCondition(int startLevelDiff, int endLevelDiff, int tol)
|
|
{
|
|
return startLevelDiff > tol || endLevelDiff > tol;
|
|
}
|
|
|
|
int noteCount(const ReducedFraction &duration,
|
|
bool useDots)
|
|
{
|
|
return toDurationList(duration.fraction(), useDots, 1).size();
|
|
}
|
|
|
|
bool isLessNoteCount(const ReducedFraction &t1,
|
|
const ReducedFraction &t2,
|
|
const ReducedFraction &t3,
|
|
bool useDots)
|
|
{
|
|
return noteCount(t3 - t1, useDots) <
|
|
noteCount(t2 - t1, useDots) + noteCount(t3 - t2, useDots);
|
|
}
|
|
|
|
void excludeNodes(std::map<ReducedFraction, Node> &nodes,
|
|
int tol,
|
|
bool useDots)
|
|
{
|
|
if (tol == 0) // no nodes can be excluded
|
|
return;
|
|
|
|
auto p1 = nodes.begin();
|
|
if (p1 == nodes.end())
|
|
return;
|
|
auto p2 = p1;
|
|
++p2;
|
|
if (p2 == nodes.end())
|
|
return;
|
|
auto p3 = p2;
|
|
++p3;
|
|
if (p3 == nodes.end())
|
|
return;
|
|
|
|
while (p3 != nodes.end()) {
|
|
if (!badLevelCondition(p2->second.midLevel - p1->second.edgeLevel,
|
|
p2->second.midLevel - p3->second.edgeLevel, tol)
|
|
&& isLessNoteCount(p1->first, p2->first, p3->first, useDots)) {
|
|
p2 = nodes.erase(p2);
|
|
++p3;
|
|
continue;
|
|
}
|
|
++p1;
|
|
++p2;
|
|
++p3;
|
|
}
|
|
}
|
|
|
|
// set tuplet boundary level to regular, non-tuplet bar division level
|
|
// because there is no more need in tuplet boundary level after split
|
|
// and such big level may confuse the estimation algorithm
|
|
|
|
int adjustEdgeLevelIfTuplet(const Meter::MaxLevel &splitPoint,
|
|
const std::vector<DivisionInfo> &divInfo)
|
|
{
|
|
int tupletLevel = splitPoint.level;
|
|
if (splitPoint.level == TUPLET_BOUNDARY_LEVEL) {
|
|
std::vector<DivisionInfo> nonTupletDivs({divInfo.back()});
|
|
tupletLevel = levelOfTick(splitPoint.pos, nonTupletDivs);
|
|
}
|
|
|
|
return tupletLevel;
|
|
}
|
|
|
|
// duration start/end should be quantisized, quantum >= 1/128 note
|
|
// pair here represents the tuplet ratio of duration and the duration itself
|
|
// for regular (non-tuplet) durations fraction.numerator == fraction.denominator
|
|
|
|
// tol - max allowed difference between start/end level of duration and split point level
|
|
// 1 for notes, 0 for rests
|
|
|
|
QList<std::pair<ReducedFraction, TDuration> >
|
|
toDurationList(const ReducedFraction &startTickInBar,
|
|
const ReducedFraction &endTickInBar,
|
|
const ReducedFraction &barFraction,
|
|
const std::vector<MidiTuplet::TupletData> &tupletsInBar,
|
|
DurationType durationType,
|
|
bool useDots)
|
|
{
|
|
if (startTickInBar < ReducedFraction(0, 1)
|
|
|| endTickInBar <= startTickInBar || endTickInBar > barFraction)
|
|
return QList<std::pair<ReducedFraction, TDuration>>();
|
|
|
|
const auto divInfo = divisionInfo(barFraction, tupletsInBar); // mectric structure of bar
|
|
const auto minDuration = minAllowedDuration() * 2; // >= minAllowedDuration() after subdivision
|
|
|
|
std::map<ReducedFraction, Node> nodes; // <onTime, Node>
|
|
{
|
|
int level = levelOfTick(startTickInBar, divInfo);
|
|
nodes.insert({startTickInBar, Node(level, level)});
|
|
level = levelOfTick(endTickInBar, divInfo);
|
|
nodes.insert({endTickInBar, Node(level, level)});
|
|
}
|
|
|
|
QQueue<std::pair<ReducedFraction, ReducedFraction>> gapsToProcess;
|
|
gapsToProcess.enqueue({startTickInBar, endTickInBar});
|
|
|
|
while (!gapsToProcess.isEmpty()) {
|
|
const auto gap = gapsToProcess.dequeue();
|
|
// don't split gap if its duration is less than minDuration
|
|
if (gap.second - gap.first < minDuration)
|
|
continue;
|
|
auto splitPoint = findMaxLevelBetween(gap.first, gap.second, divInfo);
|
|
// sum levels if there are several positions (beats) with max level value
|
|
// for example, 8th + half duration + 8th in 3/4, and half is over two beats
|
|
if (splitPoint.pos == ReducedFraction(-1, 1)) // undefined
|
|
continue;
|
|
const int effectiveLevel = splitPoint.level + splitPoint.levelCount - 1;
|
|
const Node &startNode = nodes.find(gap.first)->second;
|
|
const Node &endNode = nodes.find(gap.second)->second;
|
|
|
|
if (badLevelCondition(effectiveLevel - startNode.edgeLevel,
|
|
effectiveLevel - endNode.edgeLevel, 0)
|
|
|| is2of3RestInTripleMeter(gap.first, gap.second, barFraction)
|
|
|| (durationType == DurationType::REST
|
|
&& is23EndOfBeatInCompoundMeter(gap.first, gap.second, barFraction)))
|
|
{
|
|
int edgeLevel = adjustEdgeLevelIfTuplet(splitPoint, divInfo);
|
|
// split gap in splitPoint position
|
|
nodes.insert({splitPoint.pos, Node(edgeLevel, effectiveLevel)});
|
|
gapsToProcess.enqueue({gap.first, splitPoint.pos});
|
|
gapsToProcess.enqueue({splitPoint.pos, gap.second});
|
|
}
|
|
}
|
|
|
|
const int tol = (durationType == DurationType::NOTE) ? 1 : 0;
|
|
excludeNodes(nodes, tol, useDots);
|
|
|
|
return collectDurations(nodes, tupletsInBar, useDots);
|
|
}
|
|
|
|
} // namespace Meter
|
|
} // namespace Ms
|