Tests for tuplet recognition
This commit is contained in:
parent
7a7e98ccec
commit
720c404ea5
7 changed files with 560 additions and 217 deletions
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 ¬e: 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 ¬e: 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 ¬e: 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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
58
mtest/importmidi/inner_func_decl.h
Normal file
58
mtest/importmidi/inner_func_decl.h
Normal 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
|
|
@ -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"
|
||||
|
|
Loading…
Reference in a new issue