Tests for tuplet recognition

This commit is contained in:
Andrey M. Tokarev 2013-06-23 01:39:48 +04:00
parent 7a7e98ccec
commit 720c404ea5
7 changed files with 560 additions and 217 deletions

View file

@ -69,7 +69,7 @@ class MTrack {
void processMeta(int tick, const MidiEvent& mm);
void fillGapsWithRests(Score *score, int voice, int ctick, int restLen, int track);
std::vector<TupletData> findTupletsForDuration(int voice, int barTick, int durationOnTime, int durationLen);
};
};
// remove overlapping notes with the same pitch

View file

@ -8,9 +8,11 @@ class Tie;
class MidiNote {
public:
int pitch, velo;
int onTime, len;
Tie* tie = 0;
int pitch;
int velo;
int onTime;
int len;
Tie* tie = nullptr;
};
class MidiChord {

View file

@ -24,14 +24,14 @@ void applyAdaptiveQuant(std::multimap<int, MidiChord> &/*chords*/,
}
int shortestNoteInBar(const std::multimap<int, MidiChord> &chords,
const std::multimap<int, MidiChord>::const_iterator &start,
int shortestNoteInBar(const std::multimap<int, MidiChord>::const_iterator &startBarChordIt,
const std::multimap<int, MidiChord>::const_iterator &endChordIt,
int endBarTick)
{
int division = MScore::division;
int minDuration = division;
// find shortest note in measure
for (auto it = start; it != chords.end(); ++it) {
for (auto it = startBarChordIt; it != endChordIt; ++it) {
if (it->first >= endBarTick)
break;
for (const auto &note: it->second.notes)
@ -94,21 +94,21 @@ int userQuantNoteToTicks(MidiOperation::QuantValue quantNote)
return userQuantValue;
}
int findQuantRaster(const std::multimap<int, MidiChord> &chords,
const std::multimap<int, MidiChord>::const_iterator &startChordIter,
int findQuantRaster(const std::multimap<int, MidiChord>::iterator &startBarChordIt,
const std::multimap<int, MidiChord>::iterator &endChordIt,
int endBarTick)
{
int raster;
auto operations = preferences.midiImportOperations.currentTrackOperations();
// find raster value for quantization
if (operations.quantize.value == MidiOperation::QuantValue::SHORTEST_IN_BAR)
raster = shortestNoteInBar(chords, startChordIter, endBarTick);
raster = shortestNoteInBar(startBarChordIt, endChordIt, endBarTick);
else {
int userQuantValue = userQuantNoteToTicks(operations.quantize.value);
// if user value larger than the smallest note in bar
// then use the smallest note to keep faster events
if (operations.quantize.reduceToShorterNotesInBar) {
raster = shortestNoteInBar(chords, startChordIter, endBarTick);
raster = shortestNoteInBar(startBarChordIt, endChordIt, endBarTick);
raster = qMin(userQuantValue, raster);
}
else
@ -119,14 +119,14 @@ int findQuantRaster(const std::multimap<int, MidiChord> &chords,
// chords onTime values don't repeat despite multimap
void doGridQuantizationOfBar(const std::multimap<int, MidiChord> &chords,
std::multimap<int, MidiChord> &quantizedChords,
const std::multimap<int, MidiChord>::const_iterator &startChordIter,
void doGridQuantizationOfBar(std::multimap<int, MidiChord> &quantizedChords,
const std::multimap<int, MidiChord>::iterator &startChordIt,
const std::multimap<int, MidiChord>::iterator &endChordIt,
int raster,
int endBarTick)
{
int raster2 = raster >> 1;
for (auto it = startChordIter; it != chords.end(); ++it) {
for (auto it = startChordIt; it != endChordIt; ++it) {
if (it->first >= endBarTick)
break;
if (quantizedChords.find(it->first) != quantizedChords.end())
@ -141,30 +141,56 @@ void doGridQuantizationOfBar(const std::multimap<int, MidiChord> &chords,
}
}
const std::multimap<int, MidiChord>::const_iterator
findFirstChordInBar(const std::multimap<int, MidiChord> &chords, int startBarTick, int endBarTick)
std::multimap<int, MidiChord>::iterator
findFirstChordInRange(int startRangeTick,
int endRangeTick,
const std::multimap<int, MidiChord>::iterator &startChordIt,
const std::multimap<int, MidiChord>::iterator &endChordIt)
{
auto it = chords.begin();
for (; it != chords.end(); ++it) {
if (it->first >= startBarTick) {
if (it->first >= endBarTick)
return chords.end();
auto it = startChordIt;
for (; it != endChordIt; ++it) {
if (it->first >= startRangeTick) {
if (it->first >= endRangeTick)
it = endChordIt;
break;
}
}
return it;
}
void quantizeChordsOfBar(const std::multimap<int, MidiChord> &chords,
std::multimap<int, MidiChord> &quantizedChords,
int startBarTick,
int endBarTick)
std::multimap<int, MidiChord>::iterator
findEndChordInRange(int endRangeTick,
const std::multimap<int, MidiChord>::iterator &startChordIt,
const std::multimap<int, MidiChord>::iterator &endChordIt)
{
auto startChordIter = findFirstChordInBar(chords, startBarTick, endBarTick);
if (startChordIter == chords.end()) // if no chords found in this bar
return;
int raster = findQuantRaster(chords, startChordIter, endBarTick);
doGridQuantizationOfBar(chords, quantizedChords, startChordIter, raster, endBarTick);
auto it = startChordIt;
for (; it != endChordIt; ++it) {
if (it->first > endRangeTick)
break;
}
return it;
}
void applyGridQuant(std::multimap<int, MidiChord> &chords,
std::multimap<int, MidiChord> &quantizedChords,
int lastTick,
const TimeSigMap* sigmap)
{
int startBarTick = 0;
auto startBarChordIt = chords.begin();
for (int i = 1;; ++i) { // iterate over all measures by indexes
int endBarTick = sigmap->bar2tick(i, 0);
startBarChordIt = findFirstChordInRange(startBarTick, endBarTick,
startBarChordIt, chords.end());
if (startBarChordIt != chords.end()) { // if no chords found in this bar
int raster = findQuantRaster(startBarChordIt, chords.end(), endBarTick);
doGridQuantizationOfBar(quantizedChords, startBarChordIt, chords.end(),
raster, endBarTick);
}
if (endBarTick > lastTick)
break;
startBarTick = endBarTick;
}
}
void applyGridQuant(std::multimap<int, MidiChord> &chords,
@ -172,155 +198,40 @@ void applyGridQuant(std::multimap<int, MidiChord> &chords,
int lastTick)
{
std::multimap<int, MidiChord> quantizedChords;
int startBarTick = 0;
for (int i = 1;; ++i) { // iterate over all measures by indexes
int endBarTick = sigmap->bar2tick(i, 0);
quantizeChordsOfBar(chords, quantizedChords, startBarTick, endBarTick);
if (endBarTick > lastTick)
break;
startBarTick = endBarTick;
}
applyGridQuant(chords, quantizedChords, lastTick, sigmap);
std::swap(chords, quantizedChords);
}
// TODO: optimize start and end chord iterators
struct TupletInfo
{
int voice;
int onTime;
int len;
int tupletNumber;
int tupletQuantValue;
int regularQuantValue;
// note number in tuplet, chord iterator
std::map<int, std::multimap<int, MidiChord>::iterator> chords;
int tupletOnTimeSumError = 0;
int regularSumError = 0;
};
std::multimap<int, MidiChord>::iterator
findFirstChordInBar(int startBarTick,
int endBarTick,
std::multimap<int, MidiChord> &chords)
{
std::multimap<int, MidiChord>::iterator startBarChordIt = chords.end();
for (auto it = chords.begin(); it != chords.end(); ++it) {
if (it->first >= startBarTick && it->first < endBarTick) {
startBarChordIt = it;
break;
}
}
return startBarChordIt;
}
std::multimap<int, MidiChord>::iterator
findEndChordInBar(int endBarTick,
const std::multimap<int, MidiChord>::iterator &startBarChordIt,
std::multimap<int, MidiChord> &chords)
{
std::multimap<int, MidiChord>::iterator endBarChordIt = chords.end();
for (auto it = startBarChordIt; it != chords.end(); ++it) {
if (it->first > endBarTick) {
endBarChordIt = it;
break;
}
}
return endBarChordIt;
}
std::multimap<int, MidiChord>::iterator
findFirstChordInDivision(int startDivTime,
int endDivTime,
const std::multimap<int, MidiChord>::iterator &startBarChordIt,
const std::multimap<int, MidiChord>::iterator &endBarChordIt)
{
std::multimap<int, MidiChord>::iterator startDivChordIt = endBarChordIt;
for (auto it = startBarChordIt; it != endBarChordIt; ++it) {
if (it->first >= startDivTime && it->first < endDivTime) {
startDivChordIt = it;
break;
}
}
return startDivChordIt;
}
std::multimap<int, MidiChord>::iterator
findEndChordInDivision(int endDivTime,
const std::multimap<int, MidiChord>::iterator &startDivChordIt,
const std::multimap<int, MidiChord>::iterator &endBarChordIt)
{
std::multimap<int, MidiChord>::iterator endDivChordIt = endBarChordIt;
for (auto it = startDivChordIt; it != endBarChordIt; ++it) {
if (it->first > endDivTime) {
endDivChordIt = it;
break;
}
}
return endDivChordIt;
}
struct BestChord
{
std::multimap<int, MidiChord>::iterator chordIter;
int minTupletError;
};
BestChord findBestChordForTupletNote(int tupletNotePos,
int quantValue,
const std::multimap<int, MidiChord>::iterator &startTupletChordIt,
const std::multimap<int, MidiChord>::iterator &endDivChordIt)
{
// choose the best chord, if any, for this tuplet note
BestChord bestChord;
bestChord.chordIter = endDivChordIt;
bestChord.minTupletError = std::numeric_limits<int>::max();
// check chords - whether they can be in tuplet without large error
for (auto chordIt = startTupletChordIt; chordIt != endDivChordIt; ++chordIt) {
int tupletError = std::abs(chordIt->first - tupletNotePos);
if (tupletError > quantValue)
continue;
if (tupletError < bestChord.minTupletError) {
bestChord.minTupletError = tupletError;
bestChord.chordIter = chordIt;
}
}
return bestChord;
}
bool isSpecialTupletAllowed(int tupletNumber,
int divLen,
int quantValue,
const TupletInfo &tupletInfo)
bool isTupletAllowed(int tupletNumber,
int tupletLen,
int tupletOnTimeSumError,
int regularSumError,
int quantValue,
const std::map<int, std::multimap<int, MidiChord>::iterator> &tupletChords)
{
// special check for duplets and triplets
std::vector<int> nums = {2, 3};
// for duplet: if note first and single - only 1/2*divLen duration is allowed
// for triplet: if note first and single - only 1/3*divLen duration is allowed
// for duplet: if note first and single - only 1/2*tupletLen duration is allowed
// for triplet: if note first and single - only 1/3*tupletLen duration is allowed
for (auto num: nums) {
if (tupletNumber == num && tupletInfo.chords.size() == 1
&& tupletInfo.chords.begin()->first == 0) {
auto &chordEventIt = tupletInfo.chords.begin()->second;
if (tupletNumber == num && tupletChords.size() == 1
&& tupletChords.begin()->first == 0) {
auto &chordEventIt = tupletChords.begin()->second;
for (const auto &note: chordEventIt->second.notes) {
if (std::abs(note.len - divLen / num) > quantValue)
if (std::abs(note.len - tupletLen / num) > quantValue)
return false;
}
}
}
return true;
}
bool isTupletAllowed(const TupletInfo &tupletInfo)
{
int minAllowedNoteCount = tupletInfo.tupletNumber / 2 + tupletInfo.tupletNumber / 4;
if ((int)tupletInfo.chords.size() < minAllowedNoteCount
|| tupletInfo.tupletOnTimeSumError >= tupletInfo.regularSumError) {
// for all tuplets
int minAllowedNoteCount = tupletNumber / 2 + tupletNumber / 4;
if ((int)tupletChords.size() < minAllowedNoteCount
|| tupletOnTimeSumError >= regularSumError) {
return false;
}
int tupletNoteLen = tupletInfo.len / tupletInfo.tupletNumber;
for (const auto &tupletChord: tupletInfo.chords) {
int tupletNoteLen = tupletLen / tupletNumber;
for (const auto &tupletChord: tupletChords) {
for (const auto &note: tupletChord.second->second.notes) {
if (note.len >= tupletNoteLen / 2)
return true;
@ -346,33 +257,64 @@ int findOnTimeRegularError(int onTime, int quantValue)
return std::abs(onTime - regularPos);
}
// return: <chordIter, minChordError>
std::pair<std::multimap<int, MidiChord>::iterator, int>
findBestChordForTupletNote(int tupletNotePos,
int quantValue,
const std::multimap<int, MidiChord>::iterator &startChordIt,
const std::multimap<int, MidiChord>::iterator &endChordIt)
{
// choose the best chord, if any, for this tuplet note
std::pair<std::multimap<int, MidiChord>::iterator, int> bestChord;
bestChord.first = endChordIt;
bestChord.second = std::numeric_limits<int>::max();
// check chords - whether they can be in tuplet without large error
for (auto chordIt = startChordIt; chordIt != endChordIt; ++chordIt) {
int tupletError = std::abs(chordIt->first - tupletNotePos);
if (tupletError > quantValue)
continue;
if (tupletError < bestChord.second) {
bestChord.first = chordIt;
bestChord.second = tupletError;
}
}
return bestChord;
}
TupletInfo findTupletApproximation(int tupletNumber,
int tupletNoteLen,
int quantValue,
int startDivTime,
const std::multimap<int, MidiChord>::iterator &startDivChordIt,
const std::multimap<int, MidiChord>::iterator &endDivChordIt)
int startTupletTime,
const std::multimap<int, MidiChord>::iterator &startChordIt,
const std::multimap<int, MidiChord>::iterator &endChordIt)
{
TupletInfo tupletInfo;
tupletInfo.tupletNumber = tupletNumber;
tupletInfo.onTime = startTupletTime;
tupletInfo.len = tupletNoteLen * tupletNumber;
tupletInfo.tupletQuantValue = tupletNoteLen;
while (tupletInfo.tupletQuantValue / 2 >= quantValue)
tupletInfo.tupletQuantValue /= 2;
tupletInfo.regularQuantValue = quantValue;
auto startTupletChordIt = startDivChordIt;
auto startTupletChordIt = startChordIt;
for (int k = 0; k != tupletNumber; ++k) {
int tupletNotePos = startDivTime + k * tupletNoteLen;
int tupletNotePos = startTupletTime + k * tupletNoteLen;
// choose the best chord, if any, for this tuplet note
BestChord bestChord = findBestChordForTupletNote(tupletNotePos, quantValue,
startTupletChordIt, endDivChordIt);
if (bestChord.chordIter == endDivChordIt)
auto bestChord = findBestChordForTupletNote(tupletNotePos, quantValue,
startTupletChordIt, endChordIt);
if (bestChord.first == endChordIt)
continue; // no chord fits to this tuplet note position
// chord can be in tuplet
tupletInfo.chords.insert({k, bestChord.chordIter});
tupletInfo.tupletOnTimeSumError += bestChord.minTupletError;
tupletInfo.chords.insert({k, bestChord.first});
tupletInfo.tupletOnTimeSumError += bestChord.second;
// for next tuplet note we start from the next chord
// because chord for the next tuplet note cannot be earlier
startTupletChordIt = bestChord.chordIter;
startTupletChordIt = bestChord.first;
++startTupletChordIt;
// find chord quant error for a regular grid
int regularError = findOnTimeRegularError(bestChord.chordIter->first, quantValue);
int regularError = findOnTimeRegularError(bestChord.first->first, quantValue);
tupletInfo.regularSumError += regularError;
}
@ -389,14 +331,17 @@ findTupletCandidatesOfBar(int startBarTick,
if (chords.empty() || startBarTick >= endBarTick) // invalid cases
return tupletCandidates;
auto startBarChordIt = findFirstChordInBar(startBarTick, endBarTick, chords);
int barLen = barFraction.ticks();
auto startBarChordIt = findFirstChordInRange(startBarTick - barLen / 4,
endBarTick,
chords.begin(),
chords.end());
if (startBarChordIt == chords.end()) // no chords in this bar
return tupletCandidates;
// end iterator, as usual, will point to the next - invalid chord
auto endBarChordIt = findEndChordInBar(endBarTick, startBarChordIt, chords);
auto endBarChordIt = findEndChordInRange(endBarTick + barLen / 4, startBarChordIt, chords.end());
int barLen = barFraction.ticks();
int quantValue = findQuantRaster(chords, startBarChordIt, endBarTick);
int quantValue = findQuantRaster(startBarChordIt, endBarChordIt, endBarTick);
auto divLengths = Meter::divisionsOfBarForTuplets(barFraction);
for (const auto &divLen: divLengths) {
@ -406,37 +351,32 @@ findTupletCandidatesOfBar(int startBarTick,
for (int i = 0; i != divCount; ++i) {
int startDivTime = startBarTick + i * divLen;
int endDivTime = startDivTime + divLen;
// check which chords can be inside tuplet period = [startDivTime, endDivTime]
auto startDivChordIt = findFirstChordInDivision(startDivTime, endDivTime,
startBarChordIt, endBarChordIt);
// check which chords can be inside tuplet period
// [startDivTime - quantValue, endDivTime + quantValue]
auto startDivChordIt = findFirstChordInRange(startDivTime - quantValue,
endDivTime + quantValue,
startBarChordIt,
endBarChordIt);
if (startDivChordIt == endBarChordIt) // no chords in this division
continue;
// end iterator, as usual, will point to the next - invalid chord
auto endDivChordIt = findEndChordInDivision(endDivTime, startDivChordIt, endBarChordIt);
auto endDivChordIt = findEndChordInRange(endDivTime + quantValue, startDivChordIt,
endBarChordIt);
// try different tuplets, nested tuplets are not allowed
for (const auto &tupletNumber: tupletNumbers) {
int tupletNoteLen = divLen / tupletNumber;
if (tupletNoteLen < quantValue)
continue;
TupletInfo tupletInfo = findTupletApproximation(tupletNumber, tupletNoteLen,
quantValue, startDivTime, startDivChordIt, endDivChordIt);
tupletInfo.onTime = startDivTime;
tupletInfo.len = divLen;
// check - is it a good tuplet approximation?
// check tuplet note count
// and tuplet error compared to the regular quantization error
if (!isTupletAllowed(tupletInfo))
continue;
// additional check: tuplet special cases
if (!isSpecialTupletAllowed(tupletNumber, divLen, quantValue, tupletInfo))
quantValue, startDivTime - quantValue, startDivChordIt, endDivChordIt);
// check - is it a valid tuplet approximation?
if (!isTupletAllowed(tupletNumber, divLen,
tupletInfo.tupletOnTimeSumError,
tupletInfo.regularSumError,
quantValue, tupletInfo.chords))
continue;
// --- tuplet found ---
double averageError = tupletInfo.tupletOnTimeSumError * 1.0 / tupletInfo.chords.size();
tupletInfo.tupletQuantValue = tupletNoteLen;
while (tupletInfo.tupletQuantValue / 2 >= quantValue)
tupletInfo.tupletQuantValue /= 2;
tupletInfo.regularQuantValue = quantValue;
tupletCandidates.insert({averageError, tupletInfo});
} // next tuplet type
}
@ -546,21 +486,6 @@ void quantizeTupletChord(MidiChord &midiChord, int onTime, const TupletInfo &tup
}
}
void quantizeNonTupletChords(const std::multimap<int, MidiChord> &chords,
std::multimap<int, MidiChord> &quantizedChords,
int lastTick,
const TimeSigMap* sigmap)
{
int startBarTick = 0;
for (int i = 1;; ++i) { // iterate over all measures by indexes
int endBarTick = sigmap->bar2tick(i, 0);
quantizeChordsOfBar(chords, quantizedChords, startBarTick, endBarTick);
if (endBarTick > lastTick)
break;
startBarTick = endBarTick;
}
}
// input chords - sorted by onTime value,
// onTime values don't repeat even in multimap below
@ -599,7 +524,7 @@ void quantizeChordsAndFindTuplets(std::multimap<int, TupletData> &tupletEvents,
startBarTick = endBarTick;
}
// quantize non-tuplet (remaining) chords with ordinary grid
quantizeNonTupletChords(chords, quantizedChords, lastTick, sigmap);
applyGridQuant(chords, quantizedChords, lastTick, sigmap);
std::swap(chords, quantizedChords);
}

View file

@ -12,6 +12,25 @@ struct TupletData
int tupletNumber;
};
class MidiChord;
namespace Quantize {
struct TupletInfo
{
int voice;
int onTime;
int len;
int tupletNumber;
int tupletQuantValue;
int regularQuantValue;
// <note index in tuplet, chord iterator>
std::map<int, std::multimap<int, MidiChord>::iterator> chords;
int tupletOnTimeSumError = 0;
int regularSumError = 0;
};
} // namespace Quantize
} // namespace Ms

View file

@ -39,6 +39,7 @@ add_library(
${PROJECT_SOURCE_DIR}/mscore/importmidi.cpp
${PROJECT_SOURCE_DIR}/mscore/importmidi_operations.cpp
${PROJECT_SOURCE_DIR}/mscore/importmidi_meter.cpp
${PROJECT_SOURCE_DIR}/mscore/importmidi_quant.cpp
${PROJECT_SOURCE_DIR}/mscore/exportmidi.cpp
${PROJECT_SOURCE_DIR}/mscore/importxml.cpp
${PROJECT_SOURCE_DIR}/mscore/importxmlfirstpass.cpp

View file

@ -0,0 +1,58 @@
#ifndef INNER_FUNC_DECL_H
#define INNER_FUNC_DECL_H
namespace Ms {
class MidiChord;
class Fraction;
namespace Quantize {
std::multimap<int, MidiChord>::iterator
findFirstChordInRange(int startDivTick,
int endDivTick,
const std::multimap<int, MidiChord>::iterator &startBarChordIt,
const std::multimap<int, MidiChord>::iterator &endBarChordIt);
std::multimap<int, MidiChord>::iterator
findEndChordInRange(int endDivTick,
const std::multimap<int, MidiChord>::iterator &startDivChordIt,
const std::multimap<int, MidiChord>::iterator &endBarChordIt);
std::pair<std::multimap<int, MidiChord>::iterator, int>
findBestChordForTupletNote(int tupletNotePos,
int quantValue,
const std::multimap<int, MidiChord>::iterator &startChordIt,
const std::multimap<int, MidiChord>::iterator &endChordIt);
bool isTupletAllowed(int tupletNumber,
int tupletLen,
int tupletOnTimeSumError,
int regularSumError,
int quantValue,
const std::map<int, std::multimap<int, MidiChord>::iterator> &tupletChords);
std::vector<int> findTupletNumbers(int divLen, const Fraction &barFraction);
int findOnTimeRegularError(int onTime, int quantValue);
struct TupletInfo;
TupletInfo findTupletApproximation(int tupletNumber,
int tupletNoteLen,
int quantValue,
int startTupletTime,
const std::multimap<int, MidiChord>::iterator &startChordIt,
const std::multimap<int, MidiChord>::iterator &endChordIt);
std::multimap<double, TupletInfo>
findTupletCandidatesOfBar(int startBarTick,
int endBarTick,
const Fraction &barFraction,
std::multimap<int, MidiChord> &chords);
} // namespace Quantize
} // namespace Ms
#endif // INNER_FUNC_DECL_H

View file

@ -24,6 +24,10 @@
#include "libmscore/mcursor.h"
#include "mtest/testutils.h"
#include "inner_func_decl.h"
#include "mscore/importmidi_chord.h"
#include "mscore/importmidi_tupletdata.h"
namespace Ms {
extern Score::FileError importMidi(Score*, const QString&);
@ -50,6 +54,15 @@ class TestImportMidi : public QObject, public MTest
void im3() { mf("m3"); } // voices, typeA, resolve with tie
void im4() { mf("m4"); } // voices, typeB, resolve with tie
void im5() { mf("m5"); } // same as m1 with division 240
// test tuplet recognition functions
void findChordInBar();
void bestChordForTupletNote();
void isTupletAllowed();
void findTupletNumbers();
void findOnTimeRegularError();
void findTupletApproximation();
void findTupletCandidatesOfBar();
};
//---------------------------------------------------------
@ -76,6 +89,331 @@ void TestImportMidi::mf(const char* name)
delete score;
}
//---------------------------------------------------------
// tuplet recognition fuctions
//---------------------------------------------------------
void TestImportMidi::findChordInBar()
{
std::multimap<int, MidiChord> chords;
chords.insert({10, MidiChord()});
chords.insert({360, MidiChord()});
chords.insert({480, MidiChord()});
chords.insert({1480, MidiChord()});
chords.insert({2000, MidiChord()});
chords.insert({3201, MidiChord()});
int startBarTick = 0;
int endBarTick = 4 * MScore::division; // 4/4
auto firstChordIt = Quantize::findFirstChordInRange(startBarTick, endBarTick,
chords.begin(), chords.end());
QCOMPARE(firstChordIt, chords.begin());
auto endChordIt = Quantize::findEndChordInRange(endBarTick, firstChordIt, chords.end());
QCOMPARE(endChordIt, chords.find(2000));
endBarTick = 0;
firstChordIt = Quantize::findFirstChordInRange(startBarTick, endBarTick,
chords.begin(), chords.end());
QCOMPARE(firstChordIt, chords.end());
endChordIt = Quantize::findEndChordInRange(endBarTick, firstChordIt, chords.end());
QCOMPARE(endChordIt, chords.end());
startBarTick = 10;
endBarTick = -100;
firstChordIt = Quantize::findFirstChordInRange(startBarTick, endBarTick,
chords.begin(), chords.end());
QCOMPARE(firstChordIt, chords.end());
endChordIt = Quantize::findEndChordInRange(endBarTick, firstChordIt, chords.end());
QCOMPARE(endChordIt, chords.end());
}
void TestImportMidi::bestChordForTupletNote()
{
int tupletLen = MScore::division;
int quantValue = MScore::division / 4;
int tupletNumber = 3;
int tupletNoteLen = tupletLen / tupletNumber;
std::multimap<int, MidiChord> chords;
chords.insert({10, MidiChord()});
chords.insert({160, MidiChord()});
chords.insert({360, MidiChord()});
chords.insert({480, MidiChord()});
chords.insert({1480, MidiChord()});
chords.insert({2000, MidiChord()});
chords.insert({3201, MidiChord()});
int tupletNotePos = 1 * tupletNoteLen;
auto bestChord = Quantize::findBestChordForTupletNote(tupletNotePos, quantValue,
chords.begin(), chords.end());
QCOMPARE(bestChord.first, chords.find(160));
QCOMPARE(bestChord.second, 0);
tupletNotePos = 2 * tupletNoteLen;
bestChord = Quantize::findBestChordForTupletNote(tupletNotePos, quantValue,
chords.begin(), chords.end());
QCOMPARE(bestChord.first, chords.find(360));
QCOMPARE(bestChord.second, 40);
}
// tupletNoteNumber - number of note in tuplet (like index):
// i.e. for triplet notes can have numbers 1, 2, 3
void isSingleNoteInTupletAllowed(int tupletNumber,
int tupletNoteNumber,
double noteLenInTupletLen,
bool expectedResult)
{
int tupletLen = MScore::division;
int quantValue = MScore::division / 4; // 1/16
std::multimap<int, MidiChord> chords;
MidiChord chord;
MidiNote note;
note.len = std::round(tupletLen * noteLenInTupletLen);
chord.notes.push_back(note);
chord.onTime = 10;
chords.insert({chord.onTime, chord});
std::map<int, std::multimap<int, MidiChord>::iterator> tupletChords;
tupletChords.insert({tupletNoteNumber - 1, chords.begin()});
// tuplet error is less than regular error => allowed
int tupletOnTimeSumError = 0;
int regularSumError = 1;
QCOMPARE(Quantize::isTupletAllowed(tupletNumber, tupletLen, tupletOnTimeSumError,
regularSumError, quantValue, tupletChords),
expectedResult);
}
void isChordCountInTupletAllowed(int tupletNumber,
int chordCount,
bool expectedResult)
{
int tupletLen = MScore::division;
int quantValue = MScore::division / 4; // 1/16
std::map<int, std::multimap<int, MidiChord>::iterator> tupletChords;
std::multimap<int, MidiChord> chords;
for (int i = 0; i != chordCount; ++i) {
MidiChord chord;
MidiNote note;
note.len = tupletLen / tupletNumber; // allowed
chord.notes.push_back(note);
chord.onTime = note.len * i;
auto lastChordIt = chords.insert({chord.onTime, chord});
tupletChords.insert({i, lastChordIt});
}
// tuplet error is less than regular error => allowed
int tupletOnTimeSumError = 0;
int regularSumError = 1;
QCOMPARE(Quantize::isTupletAllowed(tupletNumber, tupletLen, tupletOnTimeSumError,
regularSumError, quantValue, tupletChords),
expectedResult);
}
void isTupletErrorAllowed(int tupletOnTimeSumError,
int regularSumError,
bool expectedResult)
{
int tupletNumber = 3;
int tupletLen = MScore::division;
int quantValue = MScore::division / 4; // 1/16
std::multimap<int, MidiChord> chords;
MidiChord chord;
MidiNote note;
note.len = tupletLen / tupletNumber; // allowed
chord.notes.push_back(note);
chord.onTime = 10;
chords.insert({chord.onTime, chord});
std::map<int, std::multimap<int, MidiChord>::iterator> tupletChords;
tupletChords.insert({0, chords.begin()});
QCOMPARE(Quantize::isTupletAllowed(tupletNumber, tupletLen, tupletOnTimeSumError,
regularSumError, quantValue, tupletChords),
expectedResult);
}
void TestImportMidi::isTupletAllowed()
{
// special cases
//
// duplets
isSingleNoteInTupletAllowed(2, 1, 1.0 / 2, true);
isSingleNoteInTupletAllowed(2, 1, 2.0 / 2, false);
isSingleNoteInTupletAllowed(2, 1, 1.0, false);
isSingleNoteInTupletAllowed(2, 2, 1.0 / 2, true);
isSingleNoteInTupletAllowed(2, 2, 2.0 / 2, true);
isSingleNoteInTupletAllowed(2, 2, 1.0, true);
// triplets
isSingleNoteInTupletAllowed(3, 1, 1.0 / 3, true);
isSingleNoteInTupletAllowed(3, 1, 2.0 / 3, false);
isSingleNoteInTupletAllowed(3, 1, 1.0, false);
isSingleNoteInTupletAllowed(3, 2, 1.0 / 3, true);
isSingleNoteInTupletAllowed(3, 2, 2.0 / 3, true);
isSingleNoteInTupletAllowed(3, 2, 1.0, true);
// quintuplets
// too small note count - all these cases are not allowed
isSingleNoteInTupletAllowed(5, 1, 1.0 / 5, false);
isSingleNoteInTupletAllowed(5, 1, 2.0 / 5, false);
isSingleNoteInTupletAllowed(5, 1, 1.0, false);
isSingleNoteInTupletAllowed(5, 2, 1.0 / 5, false);
isSingleNoteInTupletAllowed(5, 2, 2.0 / 5, false);
isSingleNoteInTupletAllowed(5, 2, 1.0, false);
// all tuplet cases
//
isChordCountInTupletAllowed(2, 1, true);
isChordCountInTupletAllowed(2, 2, true);
isChordCountInTupletAllowed(2, 3, true); // special case with tuplet subdivision
isChordCountInTupletAllowed(3, 1, true);
isChordCountInTupletAllowed(3, 2, true);
isChordCountInTupletAllowed(3, 3, true);
isChordCountInTupletAllowed(3, 4, true); // special case with tuplet subdivision
isChordCountInTupletAllowed(4, 1, false);
isChordCountInTupletAllowed(4, 2, false);
isChordCountInTupletAllowed(4, 3, true);
isChordCountInTupletAllowed(4, 4, true);
isChordCountInTupletAllowed(4, 5, true); // special case with tuplet subdivision
isChordCountInTupletAllowed(5, 1, false);
isChordCountInTupletAllowed(5, 2, false);
isChordCountInTupletAllowed(5, 3, true);
isChordCountInTupletAllowed(5, 4, true);
isChordCountInTupletAllowed(5, 5, true);
isChordCountInTupletAllowed(5, 6, true); // special case with tuplet subdivision
isChordCountInTupletAllowed(7, 1, false);
isChordCountInTupletAllowed(7, 2, false);
isChordCountInTupletAllowed(7, 3, false);
isChordCountInTupletAllowed(7, 4, true);
isChordCountInTupletAllowed(7, 5, true);
isChordCountInTupletAllowed(7, 6, true);
isChordCountInTupletAllowed(7, 7, true);
isChordCountInTupletAllowed(7, 8, true); // special case with tuplet subdivision
// tuplet error should be less than regular error
isTupletErrorAllowed(4, 5, true);
isTupletErrorAllowed(5, 5, false);
isTupletErrorAllowed(6, 5, false);
}
void TestImportMidi::findTupletNumbers()
{
{
Fraction barFraction(4, 4);
int divLen = barFraction.ticks() / 4;
auto numbers = Quantize::findTupletNumbers(divLen, barFraction);
QVERIFY(numbers.size() == 3);
QCOMPARE(numbers[0], 3);
QCOMPARE(numbers[1], 5);
QCOMPARE(numbers[2], 7);
}
{
Fraction barFraction(6, 8);
int divLen = barFraction.ticks() / 2;
auto numbers = Quantize::findTupletNumbers(divLen, barFraction);
QVERIFY(numbers.size() == 2);
QCOMPARE(numbers[0], 2);
QCOMPARE(numbers[1], 4);
}
}
void TestImportMidi::findOnTimeRegularError()
{
int quantValue = MScore::division / 4; // 1/16
int onTime = quantValue + 12;
QCOMPARE(Quantize::findOnTimeRegularError(onTime, quantValue), 12);
}
void TestImportMidi::findTupletApproximation()
{
int tupletNumber = 3;
int tupletLen = MScore::division;
int tupletNoteLen = tupletLen / tupletNumber;
int quantValue = MScore::division / 4; // 1/16
std::multimap<int, MidiChord> chords;
chords.insert({0, MidiChord()});
chords.insert({160, MidiChord()});
chords.insert({320, MidiChord()});
chords.insert({480, MidiChord()});
chords.insert({1480, MidiChord()});
chords.insert({2000, MidiChord()});
chords.insert({3201, MidiChord()});
{
int startTupletTime = 0;
Ms::Quantize::TupletInfo tupletApprox = Quantize::findTupletApproximation(
tupletNumber, tupletNoteLen, quantValue,
startTupletTime, chords.begin(), chords.end()
);
QCOMPARE(tupletApprox.onTime, startTupletTime);
QCOMPARE(tupletApprox.len, tupletLen);
QCOMPARE(tupletApprox.tupletNumber, tupletNumber);
QCOMPARE(tupletApprox.tupletQuantValue, MScore::division / 3);
QCOMPARE(tupletApprox.regularQuantValue, quantValue);
QVERIFY(tupletApprox.chords.size() == 3);
QCOMPARE(tupletApprox.tupletOnTimeSumError, 0);
QCOMPARE(tupletApprox.regularSumError, 80);
QCOMPARE(tupletApprox.chords.find(0)->second, chords.find(0));
QCOMPARE(tupletApprox.chords.find(1)->second, chords.find(160));
QCOMPARE(tupletApprox.chords.find(2)->second, chords.find(320));
}
{
int startTupletTime = 960;
Ms::Quantize::TupletInfo tupletApprox = Quantize::findTupletApproximation(
tupletNumber, tupletNoteLen, quantValue,
startTupletTime, chords.begin(), chords.end()
);
QVERIFY(tupletApprox.chords.size() == 0);
}
{
int startTupletTime = 1440;
Ms::Quantize::TupletInfo tupletApprox = Quantize::findTupletApproximation(
tupletNumber, tupletNoteLen, quantValue,
startTupletTime, chords.begin(), chords.end()
);
QVERIFY(tupletApprox.chords.size() == 1);
QCOMPARE(tupletApprox.tupletOnTimeSumError, 40);
QCOMPARE(tupletApprox.regularSumError, 40);
QCOMPARE(tupletApprox.chords.find(0)->second, chords.find(1480));
}
}
void TestImportMidi::findTupletCandidatesOfBar()
{
// int startBarTick = 0;
// int endBarTick = 1920;
// Fraction barFraction(4, 4);
// std::multimap<int, MidiChord> chords;
// chords.insert({0, MidiChord()});
// chords.insert({160, MidiChord()});
// chords.insert({320, MidiChord()});
// chords.insert({480, MidiChord()});
// chords.insert({1480, MidiChord()});
// chords.insert({2000, MidiChord()});
// chords.insert({3201, MidiChord()});
// std::multimap<double, TupletInfo> candidates
// = Quantize::findTupletCandidatesOfBar(startBarTick, endBarTick,
// barFraction, chords);
// QVERIFY(candidates.size() == 2);
}
QTEST_MAIN(TestImportMidi)
#include "tst_importmidi.moc"