#include "importmidi_quant.h"
#include "libmscore/sig.h"
#include "importmidi_fraction.h"
#include "libmscore/mscore.h"
#include "mscore/preferences.h"
#include "importmidi_chord.h"
#include "importmidi_meter.h"
#include "importmidi_tuplet.h"
#include "importmidi_inner.h"
#include "importmidi_beat.h"
#include "importmidi_voice.h"
#include "importmidi_tempo.h"
#include "importmidi_operations.h"
#include <set>
#include <deque>
namespace Ms {
extern Preferences preferences;
namespace Quantize {
ReducedFraction quantValueToFraction(MidiOperations::QuantValue quantValue)
{
const auto division = ReducedFraction::fromTicks(MScore::division);
ReducedFraction fraction;
switch (quantValue) {
case MidiOperations::QuantValue::Q_4:
fraction = division;
break;
case MidiOperations::QuantValue::Q_8:
fraction = division / 2;
break;
case MidiOperations::QuantValue::Q_16:
fraction = division / 4;
break;
case MidiOperations::QuantValue::Q_32:
fraction = division / 8;
break;
case MidiOperations::QuantValue::Q_64:
fraction = division / 16;
break;
case MidiOperations::QuantValue::Q_128:
fraction = division / 32;
break;
default:
Q_ASSERT_X(false, "Quantize::quantValueToFraction", "Unknown quant value");
break;
}
return fraction;
}
MidiOperations::QuantValue fractionToQuantValue(const ReducedFraction &fraction)
{
const auto division = ReducedFraction::fromTicks(MScore::division);
MidiOperations::QuantValue quantValue = MidiOperations::QuantValue::Q_4;
if (fraction == division)
quantValue = MidiOperations::QuantValue::Q_4;
else if (fraction == division / 2)
quantValue = MidiOperations::QuantValue::Q_8;
else if (fraction == division / 4)
quantValue = MidiOperations::QuantValue::Q_16;
else if (fraction == division / 8)
quantValue = MidiOperations::QuantValue::Q_32;
else if (fraction == division / 16)
quantValue = MidiOperations::QuantValue::Q_64;
else if (fraction == division / 32)
quantValue = MidiOperations::QuantValue::Q_128;
else {
qDebug("unknown quant fraction %d/%d division %d/%d", fraction.numerator(), fraction.denominator(),
division.numerator(), division.denominator());
Q_ASSERT_X(false, "Quantize::fractionToQuantValue", "Unknown quant fraction");
}
return quantValue;
}
MidiOperations::QuantValue defaultQuantValueFromPreferences()
{
const auto fraction = ReducedFraction::fromTicks(preferences.shortestNote);
return fractionToQuantValue(fraction);
}
ReducedFraction shortestQuantizedNoteInRange(
const std::multimap<ReducedFraction, MidiChord>::const_iterator &beg,
const std::multimap<ReducedFraction, MidiChord>::const_iterator &end)
{
const auto division = ReducedFraction::fromTicks(MScore::division);
auto minDuration = division;
for (auto it = beg; it != end; ++it) {
for (const auto ¬e: it->second.notes) {
if (note.offTime - it->first < minDuration)
minDuration = note.offTime - it->first;
}
}
const auto minAllowedDuration = MChord::minAllowedDuration();
auto shortest = division;
for ( ; shortest > minAllowedDuration; shortest /= 2) {
if (shortest <= minDuration)
break;
}
return shortest;
}
ReducedFraction reduceQuantIfDottedNote(const ReducedFraction ¬eLen,
const ReducedFraction &raster)
{
auto newRaster = raster;
const auto div = noteLen / raster;
const double ratio = div.toDouble();
if (ratio > 1.45 && ratio < 1.55) // 1.5: dotted note that is larger than quantization value
newRaster /= 2; // reduce quantization error for dotted notes
return newRaster;
}
ReducedFraction quantizeValue(const ReducedFraction &value,
const ReducedFraction &quant)
{
const auto valueReduced = value.reduced();
const auto rasterReduced = quant.reduced();
int valNum = valueReduced.numerator() * rasterReduced.denominator();
const int rastNum = rasterReduced.numerator() * valueReduced.denominator();
const int commonDen = valueReduced.denominator() * rasterReduced.denominator();
valNum = ((valNum + rastNum / 2) / rastNum) * rastNum;
return ReducedFraction(valNum, commonDen).reduced();
}
ReducedFraction quantForLen(const ReducedFraction ¬eLen,
const ReducedFraction &basicQuant)
{
auto quant = basicQuant;
while (quant > noteLen && quant >= MChord::minAllowedDuration() * 2)
quant /= 2;
if (quant >= MChord::minAllowedDuration() * 2)
quant = reduceQuantIfDottedNote(noteLen, quant);
return quant;
}
ReducedFraction quantForTuplet(const ReducedFraction &tupletLen,
const ReducedFraction &tupletRatio)
{
const auto quant = tupletLen / tupletRatio.numerator();
Q_ASSERT_X(quant >= MChord::minAllowedDuration(),
"Quantize::quantForTuplet", "Too small quant value");
if (quant >= MChord::minAllowedDuration() * 2)
return quant / 2;
return quant;
}
ReducedFraction findMinQuant(
const std::pair<const ReducedFraction, MidiChord> &chord,
const ReducedFraction &basicQuant)
{
ReducedFraction minQuant(-1, 1);
for (const auto ¬e: chord.second.notes) {
const auto quant = quantForLen(note.offTime - chord.first, basicQuant);
if (minQuant == ReducedFraction(-1, 1) || quant < minQuant)
minQuant = quant;
}
return minQuant;
}
ReducedFraction findQuantizedTupletChordOnTime(
const std::pair<const ReducedFraction, MidiChord> &chord,
const ReducedFraction &tupletLen,
const ReducedFraction &tupletRatio,
const ReducedFraction &rangeStart)
{
if (chord.first <= rangeStart)
return rangeStart;
const auto quant = quantForTuplet(tupletLen, tupletRatio);
return rangeStart + quantizeValue(chord.first - rangeStart, quant);
}
ReducedFraction findQuantizedChordOnTime(
const std::pair<const ReducedFraction, MidiChord> &chord,
const ReducedFraction &basicQuant)
{
const ReducedFraction quant = findMinQuant(chord, basicQuant);
return quantizeValue(chord.first, quant);
}
std::pair<ReducedFraction, ReducedFraction>
findQuantizedTupletNoteOffTime(
const ReducedFraction &onTime,
const ReducedFraction &offTime,
const ReducedFraction &tupletLen,
const ReducedFraction &tupletRatio,
const ReducedFraction &rangeStart)
{
if (offTime <= rangeStart)
return {rangeStart, tupletLen};
ReducedFraction qOffTime;
auto quant = quantForTuplet(tupletLen, tupletRatio);
while (true) {
qOffTime = rangeStart + quantizeValue(offTime - rangeStart, quant);
if (qOffTime <= onTime) {
if (quant >= MChord::minAllowedDuration() * 2) {
quant /= 2;
continue;
}
qOffTime = onTime + quant;
}
break;
}
return {qOffTime, quant};
}
std::pair<ReducedFraction, ReducedFraction>
findQuantizedNoteOffTime(
const std::pair<const ReducedFraction, MidiChord> &chord,
const ReducedFraction &offTime,
const ReducedFraction &basicQuant)
{
ReducedFraction qOffTime;
auto quant = quantForLen(offTime - chord.first, basicQuant);
while (true) {
qOffTime = quantizeValue(offTime, quant);
if (qOffTime <= chord.first) {
if (quant >= MChord::minAllowedDuration() * 2) {
quant /= 2;
continue;
}
qOffTime = chord.first + quant;
}
break;
}
return {qOffTime, quant};
}
ReducedFraction findMinQuantizedOnTime(
const std::pair<const ReducedFraction, MidiChord> &chord,
const ReducedFraction &basicQuant)
{
ReducedFraction minOnTime(-1, 1);
for (const auto ¬e: chord.second.notes) {
const auto quant = quantForLen(note.offTime - chord.first, basicQuant);
const auto onTime = quantizeValue(chord.first, quant);
if (minOnTime == ReducedFraction(-1, 1) || onTime < minOnTime)
minOnTime = onTime;
}
return minOnTime;
}
ReducedFraction findMaxQuantizedTupletOffTime(
const std::pair<const ReducedFraction, MidiChord> &chord,
const ReducedFraction &tupletLen,
const ReducedFraction &tupletRatio,
const ReducedFraction &rangeStart)
{
ReducedFraction maxOffTime(0, 1);
for (const auto ¬e: chord.second.notes) {
if (note.offTime <= rangeStart)
continue;
const auto offTime = findQuantizedTupletNoteOffTime(
chord.first, note.offTime, tupletLen, tupletRatio, rangeStart).first;
if (offTime > maxOffTime)
maxOffTime = offTime;
}
return maxOffTime;
}
ReducedFraction findMaxQuantizedOffTime(
const std::pair<const ReducedFraction, MidiChord> &chord,
const ReducedFraction &basicQuant)
{
ReducedFraction maxOffTime(0, 1);
for (const auto ¬e: chord.second.notes) {
const auto offTime = findQuantizedNoteOffTime(chord, note.offTime, basicQuant).first;
if (offTime > maxOffTime)
maxOffTime = offTime;
}
return maxOffTime;
}
ReducedFraction findOnTimeTupletQuantError(
const std::pair<const ReducedFraction, MidiChord> &chord,
const ReducedFraction &tupletLen,
const ReducedFraction &tupletRatio,
const ReducedFraction &rangeStart)
{
const auto qOnTime = findQuantizedTupletChordOnTime(chord, tupletLen,
tupletRatio, rangeStart);
return (chord.first - qOnTime).absValue();
}
ReducedFraction findOnTimeQuantError(
const std::pair<const ReducedFraction, MidiChord> &chord,
const ReducedFraction &basicQuant)
{
const auto qOnTime = findQuantizedChordOnTime(chord, basicQuant);
return (chord.first - qOnTime).absValue();
}
ReducedFraction findOffTimeTupletQuantError(
const ReducedFraction &onTime,
const ReducedFraction &offTime,
const ReducedFraction &tupletLen,
const ReducedFraction &tupletRatio,
const ReducedFraction &rangeStart)
{
const auto qOffTime = findQuantizedTupletNoteOffTime(onTime, offTime, tupletLen,
tupletRatio, rangeStart).first;
return (offTime - qOffTime).absValue();
}
ReducedFraction findOffTimeQuantError(
const std::pair<const ReducedFraction, MidiChord> &chord,
const ReducedFraction &offTime,
const ReducedFraction &basicQuant)
{
const auto qOffTime = findQuantizedNoteOffTime(chord, offTime, basicQuant).first;
return (offTime - qOffTime).absValue();
}
ReducedFraction findQuantForRange(
const std::multimap<ReducedFraction, MidiChord>::const_iterator &beg,
const std::multimap<ReducedFraction, MidiChord>::const_iterator &end,
const ReducedFraction &basicQuant)
{
const auto shortestLen = shortestQuantizedNoteInRange(beg, end);
return quantForLen(shortestLen, basicQuant);
}
//--------------------------------------------------------------------------------------------
bool isHumanPerformance(
const std::multimap<ReducedFraction, MidiChord> &chords,
const TimeSigMap *sigmap)
{
if (chords.empty())
return false;
const auto basicQuant = ReducedFraction::fromTicks(MScore::division) / 4; // 1/16
int matches = 0;
int count = 0;
std::set<ReducedFraction> usedOnTimes;
for (const auto &chord: chords) {
const auto quant = qMin(basicQuant,
quantForLen(MChord::maxNoteLen(chord), basicQuant));
const auto onTime = quantizeValue(chord.first, quant);
int barIndex, beat, tick;
sigmap->tickValues(onTime.ticks(), &barIndex, &beat, &tick);
const auto barStart = ReducedFraction::fromTicks(sigmap->bar2tick(barIndex, 0));
const auto barFraction = ReducedFraction(sigmap->timesig(barStart.ticks()).timesig());
const auto beatLen = Meter::beatLength(barFraction);
if (((onTime - barStart) / beatLen).reduced().denominator() == 1
&& usedOnTimes.find(onTime) == usedOnTimes.end()) {
usedOnTimes.insert(onTime);
++count;
const auto diff = (onTime - chord.first).absValue();
if (diff < MChord::minAllowedDuration())
++matches;
}
}
const double TOL = 0.6;
const double matched = matches * 1.0 / count;
return matched < TOL;
}
std::multimap<int, MTrack>
getTrackWithAllChords(const std::multimap<int, MTrack> &tracks)
{
std::multimap<int, MTrack> singleTrack{{0, MTrack()}};
auto &allChords = singleTrack.begin()->second.chords;
for (const auto &track: tracks) {
const MTrack &t = track.second;
for (const auto &chord: t.chords) {
allChords.insert(chord);
}
}
return singleTrack;
}
void setIfHumanPerformance(
const std::multimap<int, MTrack> &tracks,
TimeSigMap *sigmap)
{
auto allChordsTrack = getTrackWithAllChords(tracks);
MChord::collectChords(allChordsTrack, {2, 1}, {1, 2});
const MTrack &track = allChordsTrack.begin()->second;
const auto &allChords = track.chords;
if (allChords.empty())
return;
const bool isHuman = isHumanPerformance(allChords, sigmap);
auto &opers = preferences.midiImportOperations.data()->trackOpers;
if (opers.isHumanPerformance.canRedefineDefaultLater())
opers.isHumanPerformance.setDefaultValue(isHuman);
if (isHuman) {
if (opers.quantValue.canRedefineDefaultLater())
opers.quantValue.setDefaultValue(MidiOperations::QuantValue::Q_8);
if (opers.maxVoiceCount.canRedefineDefaultLater())
opers.maxVoiceCount.setDefaultValue(MidiOperations::VoiceCount::V_2);
const double ticksPerSec = MidiTempo::findBasicTempo(tracks, true) * MScore::division;
MidiBeat::findBeatLocations(allChords, sigmap, ticksPerSec); // and set time sig
}
}
//--------------------------------------------------------------------------------------------
// remove small intersection with the next chord
void preserveLegato(
ReducedFraction &offTime,
bool isOffTimeInTuplet,
const std::multimap<ReducedFraction, MidiChord>::iterator &chordIt,
const std::multimap<ReducedFraction, MidiChord> &chords,
const ReducedFraction &basicQuant)
{
auto it = std::next(chordIt);
while (it != chords.end() && it->second.voice != chordIt->second.voice)
++it;
if (it != chords.end()) {
const auto ioi = it->first - chordIt->first;
const auto cross = offTime - it->first;
if (it->second.isInTuplet && isOffTimeInTuplet) {
// while we don't split tuplet into voices we don't want to have
// note intersections smaller than tuplet note length
const auto &tuplet = it->second.tuplet->second;
const auto tupletNoteLen = tuplet.len / tuplet.tupletNumber;
if (cross > ReducedFraction(0, 1) && cross < tupletNoteLen)
offTime = it->first;
}
else if (!it->second.isInTuplet && !isOffTimeInTuplet) {
if (cross > ReducedFraction(0, 1) && cross < ioi / 2 && cross < basicQuant / 2)
offTime = it->first;
}
}
}
std::pair<ReducedFraction, ReducedFraction>
quantizeOffTimeForTuplet(
const ReducedFraction ¬eOffTime,
const std::multimap<ReducedFraction, MidiChord>::iterator &chordIt,
const std::multimap<ReducedFraction, MidiChord> &chords,
const ReducedFraction &basicQuant,
const MidiTuplet::TupletData &tuplet)
{
Q_ASSERT_X(chordIt->first < noteOffTime,
"Quantize::quantizeOffTimeForTuplet", "Negative or zero note length");
const auto tupletRatio = MidiTuplet::tupletLimits(tuplet.tupletNumber).ratio;
const auto result = findQuantizedTupletNoteOffTime(
chordIt->first, noteOffTime, tuplet.len, tupletRatio, tuplet.onTime);
auto offTime = result.first;
auto quant = result.second;
preserveLegato(offTime, true, chordIt, chords, basicQuant);
// verify that offTime is still inside tuplet
if (offTime < tuplet.onTime) {
offTime = tuplet.onTime;
quant = tuplet.len;
}
else if (offTime > tuplet.onTime + tuplet.len) {
offTime = tuplet.onTime + tuplet.len;
quant = tuplet.len;
}
return {offTime, quant};
}
std::pair<ReducedFraction, ReducedFraction>
quantizeOffTimeForNonTuplet(
const ReducedFraction ¬eOffTime,
const std::multimap<ReducedFraction, MidiChord>::iterator &chordIt,
const std::multimap<ReducedFraction, MidiChord> &chords,
const ReducedFraction &basicQuant)
{
Q_ASSERT_X(chordIt->first < noteOffTime,
"Quantize::quantizeOffTimeForNonTuplet", "Negative or zero note length");
const MidiChord &chord = chordIt->second;
const auto result = findQuantizedNoteOffTime(*chordIt, noteOffTime, basicQuant);
auto offTime = result.first;
auto quant = result.second;
preserveLegato(offTime, false, chordIt, chords, basicQuant);
// verify that offTime is still outside tuplets
if (chord.isInTuplet) {
const auto &tuplet = chord.tuplet->second;
if (offTime < tuplet.onTime + tuplet.len) {
offTime = tuplet.onTime + tuplet.len;
quant = tuplet.len;
return {offTime, quant};
}
}
auto next = std::next(chordIt);
while (true) {
if (next == chords.end())
break;
if (!next->second.isInTuplet || next->second.voice != chord.voice) {
++next;
continue;
}
const auto &tuplet = next->second.tuplet->second;
if (next->second.tuplet == chord.tuplet
|| tuplet.onTime + tuplet.len <= offTime) {
++next;
continue;
}
if (offTime > tuplet.onTime) {
offTime = tuplet.onTime;
quant = tuplet.len;
}
break;
}
return {offTime, quant};
}
struct QuantPos
{
ReducedFraction time;
int metricalLevel;
double penalty;
int prevPos;
};
struct QuantData
{
std::multimap<ReducedFraction, MidiChord>::const_iterator chord;
ReducedFraction quant;
ReducedFraction quantForLen;
ReducedFraction chordRangeStart;
ReducedFraction chordRangeEnd;
// if inter on time interval with previous chord
// is less than min allowed duration
// then chord can be merged with previous chord
bool canMergeWithPrev = false;
int metricalLevelForLen;
std::vector<QuantPos> positions;
};
#ifdef QT_DEBUG
bool areAllVoicesSame(
const std::deque<std::multimap<ReducedFraction, MidiChord>::const_iterator> &chords)
{
auto it = chords.begin();
const int voice = (*it)->second.voice;
for (++it; it != chords.end(); ++it) {
if ((*it)->second.voice != voice)
return false;
}
return true;
}
bool areChordsDifferent(
const std::deque<std::multimap<ReducedFraction, MidiChord>::const_iterator> &chords)
{
std::set<const std::pair<const ReducedFraction, MidiChord> *> chordSet;
for (const auto &chord: chords) {
const auto it = chordSet.find(&*chord);
if (it == chordSet.end())
chordSet.insert(&*chord);
else
return false;
}
return true;
}
bool notLessThanPrev(
const std::vector<QuantData>::iterator &it,
const std::vector<QuantData> &data)
{
if (it != data.begin()) {
const auto prev = std::prev(it);
if (prev->chordRangeStart > it->chordRangeStart)
return false;
}
return true;
}
bool isTupletRangeCorrect(
const MidiTuplet::TupletData &tuplet,
const ReducedFraction &rangeStart,
const ReducedFraction &rangeEnd)
{
return (rangeStart == tuplet.onTime && rangeEnd == tuplet.onTime + tuplet.len);
}
void checkOffTime(
const MidiNote ¬e,
const std::multimap<ReducedFraction, MidiChord>::iterator &chordIt,
const std::multimap<ReducedFraction, MidiChord> &chords)
{
Q_ASSERT_X(note.offTime - chordIt->first >= MChord::minAllowedDuration(),
"Quantize::checkOffTime", "Too small note length");
if (note.isInTuplet) {
const auto &tuplet = note.tuplet->second;
Q_ASSERT_X(note.offTime >= tuplet.onTime
&& note.offTime <= tuplet.onTime + tuplet.len,
"Quantize::checkOffTime",
"Note off time is outside tuplet but should be inside");
}
else {
const auto &chord = chordIt->second;
auto next = std::next(chordIt);
while (true) {
if (next == chords.end())
break;
if (!next->second.isInTuplet || next->second.voice != chord.voice) {
++next;
continue;
}
const auto &tuplet = next->second.tuplet->second;
if ((chord.isInTuplet && next->second.tuplet == chord.tuplet)
|| tuplet.onTime + tuplet.len <= note.offTime) {
++next;
continue;
}
Q_ASSERT_X(note.offTime <= tuplet.onTime,
"Quantize::checkOffTime",
"Note off time is inside next tuplet but it shouldn't");
break;
}
auto prev = chordIt;
while (true) {
if (prev->second.barIndex != chord.barIndex)
break;
if (prev->second.voice != chord.voice
|| !prev->second.isInTuplet
|| (chord.isInTuplet && prev->second.tuplet == chord.tuplet)) {
if (prev != chords.begin()) {
--prev;
continue;
}
}
else {
const auto &tuplet = prev->second.tuplet->second;
Q_ASSERT_X(note.offTime >= tuplet.onTime + tuplet.len,
"Quantize::checkOffTime",
"Note off time is inside prev tuplet but it shouldn't");
}
break;
}
}
}
bool areOnTimeValuesDifferent(const std::multimap<ReducedFraction, MidiChord> &chords)
{
std::set<std::tuple<ReducedFraction, int, int>> onTimeVoicesBars;
for (const auto &chord: chords) {
const auto tuple = std::make_tuple(chord.first,
chord.second.voice,
chord.second.barIndex);
if (onTimeVoicesBars.find(tuple) == onTimeVoicesBars.end())
onTimeVoicesBars.insert(tuple);
else
return false;
}
return true;
}
bool areTupletChordsConsistent(const std::multimap<ReducedFraction, MidiChord> &chords)
{
std::multimap<ReducedFraction, MidiTuplet::TupletData>::iterator prevTuplet;
bool prevTupletSet = false;
bool isInTuplet = false;
const int limit = MidiVoice::voiceLimit();
for (int voice = 0; voice != limit; ++voice) {
for (const auto &chord: chords) {
const MidiChord &c = chord.second;
if (c.voice != voice)
continue;
if (c.isInTuplet) {
if (!isInTuplet && prevTupletSet && c.tuplet == prevTuplet) {
qDebug() << "Inconsistent tuplets, bar (from 1):"
<< (c.barIndex + 1);
return false; // there is a non-tuplet chord inside tuplet
}
isInTuplet = true;
prevTuplet = c.tuplet;
prevTupletSet = true;
}
else {
isInTuplet = false;
}
}
}
return true;
}
bool areTupletChordsConsistent(
const std::deque<std::multimap<ReducedFraction, MidiChord>::const_iterator> &chords)
{
auto it = chords.begin();
const bool isInTuplet = (*it)->second.isInTuplet;
for (std::next(it); it != chords.end(); ++it) {
if (isInTuplet && (!(*it)->second.isInTuplet
|| (*it)->second.tuplet != (*chords.begin())->second.tuplet)) {
return false;
}
if (!isInTuplet && (*it)->second.isInTuplet)
return false;
}
return true;
}
bool areChordsSortedByOnTime(
const std::deque<std::multimap<ReducedFraction, MidiChord>::const_iterator> &chords)
{
for (size_t i = 0; i != chords.size() - 1; ++i) {
if (chords[i]->first >= chords[i + 1]->first)
return false;
}
return true;
}
#endif
ReducedFraction quantizeToLarge(
const ReducedFraction &time,
const ReducedFraction &quant)
{
const auto ratio = time / quant;
auto quantized = quant * (ratio.numerator() / ratio.denominator());
if (quantized < time)
quantized += quant;
return quantized;
}
ReducedFraction quantizeToSmall(
const ReducedFraction &time,
const ReducedFraction &quant)
{
const auto ratio = time / quant;
auto quantized = quant * (ratio.numerator() / ratio.denominator());
if (quantized >= time)
quantized -= quant;
return quantized;
}
void findMetricalLevels(
std::vector<QuantData> &data,
const std::deque<std::multimap<ReducedFraction, MidiChord>::const_iterator> &chords,
const ReducedFraction &tupletQuant,
const ReducedFraction &barStart,
const ReducedFraction &barFraction)
{
const auto divsInfo = (tupletQuant != ReducedFraction(-1, 1))
? Meter::divisionInfo(barFraction, {(*chords.begin())->second.tuplet->second})
: Meter::divisionInfo(barFraction, {});
for (QuantData &d: data) {
for (auto t = d.chordRangeStart; t <= d.chordRangeEnd; t += d.quant) {
QuantPos p;
p.time = t;
p.metricalLevel = Meter::levelOfTick(t - barStart, divsInfo);
d.positions.push_back(p);
}
int minLevel = std::numeric_limits<int>::max();
while (true) {
for (auto t = d.chordRangeStart; t <= d.chordRangeEnd; t += d.quant) {
if (((t - barStart) / d.quantForLen).reduced().denominator() != 1)
continue;
int level = Meter::levelOfTick(t - barStart, divsInfo);
if (level < minLevel)
minLevel = level;
}
if (minLevel == std::numeric_limits<int>::max()) {
d.quantForLen /= 2;
Q_ASSERT_X(d.quantForLen >= MChord::minAllowedDuration(),
"Quantize::findMetricalLevels",
"quantForLen < min allowed duration");
continue;
}
break;
}
d.metricalLevelForLen = minLevel;
}
}
void findChordRangeEnds(
std::vector<QuantData> &data,
const ReducedFraction &rangeStart,
const ReducedFraction &rangeEnd,
const ReducedFraction &barStart,
const ReducedFraction &beatLen)
{
#ifdef NDEBUG
(void)rangeStart;
#endif
for (auto it = data.rbegin(); it != data.rend(); ++it) {
QuantData &d = *it;
d.chordRangeEnd = barStart + quantizeToSmall(rangeEnd - barStart, d.quant);
Q_ASSERT_X(d.chord->first + beatLen >= rangeStart,
"Quantize::findChordRangeEnds", "chord on time + beatLen < rangeStart");
if (d.chord->first + beatLen < rangeEnd) {
d.chordRangeEnd = barStart + quantizeToSmall(
d.chord->first + beatLen - barStart, d.quant);
}
if (it != data.rbegin()) {
const auto prev = std::prev(it); // next in terms of time
if (prev->chordRangeEnd < d.chordRangeEnd) {
d.chordRangeEnd = barStart + quantizeToSmall(
prev->chordRangeEnd - barStart, d.quant);
}
if (!prev->canMergeWithPrev && d.chordRangeEnd == prev->chordRangeEnd)
d.chordRangeEnd -= d.quant;
}
if (d.chordRangeEnd < d.chordRangeStart)
d.chordRangeEnd = d.chordRangeStart;
Q_ASSERT_X(d.chordRangeEnd < rangeEnd,
"Quantize::findChordRangeEnds", "chordRangeEnd > rangeEnd");
Q_ASSERT_X(d.chordRangeStart <= d.chordRangeEnd,
"Quantize::findChordRangeEnds", "chordRangeStart is greater than chordRangeEnd");
Q_ASSERT_X(((d.chordRangeEnd - barStart) / d.quant).reduced().denominator() == 1,
"Quantize::findChordRangeEnds",
"chordRangeEnd - barStart is not dividable by quant");
Q_ASSERT_X(((d.chordRangeEnd - d.chordRangeStart) / d.quant).reduced().denominator() == 1,
"Quantize::findChordRangeEnds",
"chordRangeEnd - chordRangeStart is not dividable by quant");
}
}
void findChordRangeStarts(
std::vector<QuantData> &data,
const ReducedFraction &rangeStart,
const ReducedFraction &rangeEnd,
const ReducedFraction &barStart,
const ReducedFraction &beatLen)
{
for (auto it = data.begin(); it != data.end(); ++it) {
QuantData &d = *it;
while (true) {
d.chordRangeStart = barStart + quantizeToLarge(rangeStart - barStart, d.quant);
Q_ASSERT_X(d.chord->first - beatLen <= rangeEnd,
"Quantize::findChordRangeStarts", "chord on time - beatLen > rangeEnd");
if (d.chord->first - beatLen > rangeStart) {
d.chordRangeStart = barStart + quantizeToLarge(
d.chord->first - beatLen - barStart, d.quant);
}
if (it != data.begin()) {
const auto prev = std::prev(it);
if (prev->chordRangeStart > d.chordRangeStart) {
d.chordRangeStart = barStart + quantizeToLarge(
prev->chordRangeStart - barStart, d.quant);
}
if (!d.canMergeWithPrev && d.chordRangeStart == prev->chordRangeStart)
d.chordRangeStart += d.quant;
}
if (d.chordRangeStart >= rangeEnd) {
if (d.quant >= MChord::minAllowedDuration() * 2)
d.quant /= 2;
else
d.canMergeWithPrev = true;
continue;
}
break;
}
Q_ASSERT_X(notLessThanPrev(it, data),
"Quantize::findChordRangeStarts",
"chordRangeStart is less than previous chordRangeStart");
Q_ASSERT_X(d.chordRangeStart >= rangeStart,
"Quantize::findChordRangeStarts", "chordRangeStart < rangeStart");
Q_ASSERT_X(d.chordRangeStart < rangeEnd,
"Quantize::findChordRangeStarts", "chordRangeStart >= rangeEnd");
Q_ASSERT_X(((d.chordRangeStart - barStart) / d.quant).reduced().denominator() == 1,
"Quantize::findChordRangeStarts",
"chordRangeStart - barStart is not dividable by quant");
}
}
void findQuants(
std::vector<QuantData> &data,
const std::deque<std::multimap<ReducedFraction, MidiChord>::const_iterator> &chords,
const ReducedFraction &rangeStart,
const ReducedFraction &rangeEnd,
const ReducedFraction &basicQuant,
const ReducedFraction &tupletQuant,
const ReducedFraction &barFraction)
{
for (auto it = chords.begin(); it != chords.end(); ++it) {
const auto chordIt = *it;
QuantData d;
d.chord = chordIt;
auto len = MChord::minNoteLen(*chordIt);
if (rangeEnd - rangeStart < len)
len = rangeEnd - rangeStart;
if (it != chords.begin()) {
const auto prevChordIt = *std::prev(it);
if (chordIt->first - prevChordIt->first < len)
len = chordIt->first - prevChordIt->first;
if (len < MChord::minAllowedDuration())
d.canMergeWithPrev = true;
}
if (tupletQuant != ReducedFraction(-1, 1)) {
const MidiTuplet::TupletData &tuplet = (*chords.begin())->second.tuplet->second;
d.quant = tupletQuant;
d.quantForLen = tuplet.len / tuplet.tupletNumber;
}
else {
d.quant = quantForLen(len, basicQuant);
auto maxQuant = basicQuant;
while (maxQuant < barFraction)
maxQuant *= 2;
d.quantForLen = quantForLen(
qMin(MChord::minNoteLen(*chordIt), rangeEnd - rangeStart), maxQuant);
}
Q_ASSERT_X(d.quant <= rangeEnd - rangeStart,
"Quantize::findQuants", "Quant value is larger than range interval");
data.push_back(d);
}
}
ReducedFraction findTupletQuant(
const std::deque<std::multimap<ReducedFraction, MidiChord>::const_iterator> &chords)
{
ReducedFraction tupletQuant(-1, 1);
if ((*chords.begin())->second.isInTuplet) {
const MidiTuplet::TupletData &tuplet = (*chords.begin())->second.tuplet->second;
const auto tupletRatio = MidiTuplet::tupletLimits(tuplet.tupletNumber).ratio;
tupletQuant = quantForTuplet(tuplet.len, tupletRatio);
}
return tupletQuant;
}
std::vector<QuantData> findQuantData(
const std::deque<std::multimap<ReducedFraction, MidiChord>::const_iterator> &chords,
const ReducedFraction &rangeStart,
const ReducedFraction &rangeEnd,
const ReducedFraction &basicQuant,
const ReducedFraction &barStart,
const ReducedFraction &barFraction)
{
Q_ASSERT_X(!chords.empty(), "Quantize::findQuantData", "Empty chords");
std::vector<QuantData> data;
const auto tupletQuant = findTupletQuant(chords);
const auto beatLen = Meter::beatLength(barFraction);
findQuants(data, chords, rangeStart, rangeEnd, basicQuant, tupletQuant, barFraction);
findChordRangeStarts(data, rangeStart, rangeEnd, barStart, beatLen);
findChordRangeEnds(data, rangeStart, rangeEnd, barStart, beatLen);
findMetricalLevels(data, chords, tupletQuant, barStart, barFraction);
return data;
}
struct QuantInfo {
ReducedFraction onTime;
double penalty = 0.0;
std::multimap<ReducedFraction, MidiChord>::const_iterator chord;
};
int findLastChordPosition(const std::vector<QuantData> &quantData)
{
int posIndex = -1;
double minPenalty = std::numeric_limits<double>::max();
const auto &lastPositions = quantData[quantData.size() - 1].positions;
for (int i = 0; i != (int)lastPositions.size(); ++i) {
if (lastPositions[i].penalty < minPenalty) {
minPenalty = lastPositions[i].penalty;
posIndex = i;
}
}
Q_ASSERT_X(posIndex != -1,
"Quantize::findLastChordPosition", "Last index was not found");
return posIndex;
}
void applyDynamicProgramming(std::vector<QuantData> &quantData)
{
const auto &opers = preferences.midiImportOperations.data()->trackOpers;
const bool isHuman = opers.isHumanPerformance.value();
const double MERGE_PENALTY_COEFF = 5.0;
for (int chordIndex = 0; chordIndex != (int)quantData.size(); ++chordIndex) {
QuantData &d = quantData[chordIndex];
for (int pos = 0; pos != (int)d.positions.size(); ++pos) {
QuantPos &p = d.positions[pos];
const auto timePenalty = (d.chord->first - p.time).absValue().toDouble();
const double levelDiff = qAbs(d.metricalLevelForLen - p.metricalLevel);
if (isHuman) {
if (p.metricalLevel <= d.metricalLevelForLen)
p.penalty = timePenalty + levelDiff * d.quantForLen.toDouble();
else
p.penalty = timePenalty / (1 + levelDiff);
}
else {
if (p.metricalLevel <= d.metricalLevelForLen)
p.penalty = timePenalty * (1 + levelDiff);
else
p.penalty = timePenalty;
}
if (chordIndex == 0)
continue;
const QuantData &dPrev = quantData[chordIndex - 1];
double minPenalty = std::numeric_limits<double>::max();
int minPos = -1;
for (int posPrev = 0; posPrev != (int)dPrev.positions.size(); ++posPrev) {
const QuantPos &pPrev = dPrev.positions[posPrev];
if (pPrev.time > p.time)
continue;
double penalty = pPrev.penalty;
if (pPrev.time == p.time) {
if (!d.canMergeWithPrev)
continue;
penalty += d.quant.toDouble() * MERGE_PENALTY_COEFF;
}
if (penalty < minPenalty) {
minPenalty = penalty;
minPos = posPrev;
}
}
Q_ASSERT_X(minPos != -1,
"Quantize::applyDynamicProgramming", "Min pos was not found");
p.penalty += minPenalty;
p.prevPos = minPos;
}
}
}
void quantizeOnTimesInRange(
const std::deque<std::multimap<ReducedFraction, MidiChord>::const_iterator> &chords,
std::map<const std::pair<const ReducedFraction, MidiChord> *, QuantInfo> &foundOnTimes,
const ReducedFraction &rangeStart,
const ReducedFraction &rangeEnd,
const ReducedFraction &basicQuant,
const ReducedFraction &barStart,
const ReducedFraction &barFraction)
{
Q_ASSERT_X(!chords.empty(), "Quantize::quantizeOnTimesInRange", "Empty chords");
Q_ASSERT_X(areAllVoicesSame(chords),
"Quantize::quantizeOnTimesInRange", "Chord voices are not the same");
Q_ASSERT_X(areChordsDifferent(chords),
"Quantize::quantizeOnTimesInRange", "There are chord duplicates");
Q_ASSERT_X(areTupletChordsConsistent(chords), "Quantize::quantizeOnTimesInRange",
"Tuplet and non-tuplet chords mismatch");
Q_ASSERT_X(areChordsSortedByOnTime(chords),
"Quantize::quantizeOnTimesInRange", "Chords are not sorted by on time values");
Q_ASSERT_X(rangeStart != ReducedFraction(-1, 1)
&& rangeEnd != ReducedFraction(-1, 1)
&& rangeStart < rangeEnd,
"Quantize::quantizeOnTimesInRange",
"Range start and/or range end are incorrect");
Q_ASSERT_X((chords.front()->second.isInTuplet) ? isTupletRangeCorrect(
chords.front()->second.tuplet->second, rangeStart, rangeEnd) : true,
"Quantize::quantizeOnTimesInRange", "Tuplet range is incorrect");
std::vector<QuantData> quantData = findQuantData(chords, rangeStart, rangeEnd,
basicQuant, barStart, barFraction);
applyDynamicProgramming(quantData);
// backward dynamic programming step - collect optimal chord positions
int posIndex = findLastChordPosition(quantData);
Q_ASSERT_X(quantData.size() == chords.size(),
"Quantize::quantizeOnTimesInRange",
"Sizes of quant data and chords are not equal");
for (int chordIndex = quantData.size() - 1; ; --chordIndex) {
const QuantPos &p = quantData[chordIndex].positions[posIndex];
const auto onTime = p.time;
const auto chordIt = chords[chordIndex];
const auto found = foundOnTimes.find(&*chordIt);
if (found == foundOnTimes.end()) {
QuantInfo info;
info.chord = chordIt;
info.onTime = onTime;
info.penalty = p.penalty;
foundOnTimes.insert({&*chordIt, info});
}
else if (p.penalty < found->second.penalty) {
found->second.onTime = onTime;
found->second.penalty = p.penalty;
}
if (chordIndex == 0)
break;
posIndex = p.prevPos;
}
}
// input chords - sorted by onTime value
void applyTupletStaccato(std::multimap<ReducedFraction, MidiChord> &chords)
{
for (auto chordIt = chords.begin(); chordIt != chords.end(); ++chordIt) {
for (MidiNote ¬e: chordIt->second.notes) {
if (note.isInTuplet && note.staccato) {
const MidiTuplet::TupletData &tuplet = note.tuplet->second;
// decrease tuplet error by enlarging staccato notes:
// make note.len = tuplet note length
const auto tupletNoteLen = tuplet.len / tuplet.tupletNumber;
note.offTime = chordIt->first + tupletNoteLen;
}
}
}
}
void quantizeOffTimes(
std::multimap<ReducedFraction, MidiChord> &quantizedChords,
const ReducedFraction &basicQuant)
{
for (auto chordIt = quantizedChords.begin(); chordIt != quantizedChords.end(); ++chordIt) {
MidiChord &chord = chordIt->second;
// quantize off times
for (auto ¬e: chord.notes) {
// check if note is not in tuplet anymore
if (note.isInTuplet) {
if (chordIt->first >= note.tuplet->first + note.tuplet->second.len)
note.isInTuplet = false;
}
const auto result = (note.isInTuplet)
? quantizeOffTimeForTuplet(note.offTime, chordIt, quantizedChords,
basicQuant, note.tuplet->second)
: quantizeOffTimeForNonTuplet(note.offTime, chordIt,
quantizedChords, basicQuant);
note.offTime = result.first;
note.offTimeQuant = result.second;
#ifdef QT_DEBUG
checkOffTime(note, chordIt, quantizedChords);
#endif
}
}
}
void addChordsFromPrevRange(
std::deque<std::multimap<ReducedFraction, MidiChord>::const_iterator> &chordsToQuant,
const std::multimap<ReducedFraction, MidiChord> &chords,
bool currentlyInTuplet,
int currentBarIndex,
int voice,
const ReducedFraction &barStart,
const ReducedFraction &rangeStart,
const ReducedFraction &basicQuant)
{
if (currentlyInTuplet)
return;
auto it = chordsToQuant.front();
if (it == chords.begin())
return;
--it;
if (rangeStart == barStart) { // new bar
while (it->second.barIndex >= currentBarIndex - 1) {
if (it->second.voice == voice && it->second.barIndex == currentBarIndex) {
Q_ASSERT_X(!it->second.isInTuplet, "Quantize::addChordsFromPrevRange",
"Tuplet chord from previous bar belongs to the current bar");
chordsToQuant.push_front(it);
}
if (it == chords.begin())
break;
--it;
}
}
else {
const auto tol = basicQuant / 2;
while (it->first > rangeStart - tol) {
if (it->second.voice == voice && !it->second.isInTuplet)
chordsToQuant.push_front(it);
if (it == chords.begin())
break;
--it;
}
}
}
void quantizeOnTimes(
const std::multimap<ReducedFraction, MidiChord> &chords,
std::map<const std::pair<const ReducedFraction, MidiChord> *, QuantInfo> &foundOnTimes,
const ReducedFraction &basicQuant,
const TimeSigMap *sigmap)
{
int maxVoice = 0;
for (int voice = 0; voice <= maxVoice; ++voice) {
int currentBarIndex = -1;
ReducedFraction rangeStart(-1, 1);
ReducedFraction rangeEnd(-1, 1);
ReducedFraction barFraction(-1, 1);
ReducedFraction barStart(-1, 1);
bool currentlyInTuplet = false;
std::deque<std::multimap<ReducedFraction, MidiChord>::const_iterator> chordsToQuant;
for (auto chordIt = chords.begin(); chordIt != chords.end(); ++chordIt) {
Q_ASSERT_X(MChord::minNoteLen(*chordIt) >= MChord::minAllowedDuration(),
"Quantize::quantizeOnTimes",
"Note length is less than min allowed duration");
if (chordIt->second.voice > maxVoice)
maxVoice = chordIt->second.voice;
if (chordIt->second.voice != voice)
continue;
if (chordsToQuant.empty()) {
rangeStart = rangeEnd;
currentlyInTuplet = chordIt->second.isInTuplet;
if (currentBarIndex != chordIt->second.barIndex) {
currentBarIndex = chordIt->second.barIndex;
barStart = ReducedFraction::fromTicks(
sigmap->bar2tick(currentBarIndex, 0));
barFraction = ReducedFraction(sigmap->timesig(barStart.ticks()).timesig());
if (!currentlyInTuplet)
rangeStart = barStart;
}
if (currentlyInTuplet) {
const auto &tuplet = chordIt->second.tuplet->second;
rangeStart = tuplet.onTime;
rangeEnd = tuplet.onTime + tuplet.len;
}
}
chordsToQuant.push_back(chordIt);
auto nextChord = std::next(chordIt);
while (nextChord != chords.end() && nextChord->second.voice != voice)
++nextChord;
if (nextChord == chords.end()
|| nextChord->second.barIndex != currentBarIndex
|| nextChord->second.isInTuplet != currentlyInTuplet
|| (nextChord->second.isInTuplet && currentlyInTuplet
&& nextChord->second.tuplet != chordIt->second.tuplet)) {
if (!currentlyInTuplet) {
if (nextChord != chords.end()) {
if (nextChord->second.barIndex != currentBarIndex) {
rangeEnd = ReducedFraction::fromTicks(
sigmap->bar2tick(currentBarIndex + 1, 0));
}
else if (nextChord->second.isInTuplet) {
rangeEnd = nextChord->second.tuplet->second.onTime;
}
}
else {
rangeEnd = ReducedFraction::fromTicks(
sigmap->bar2tick(currentBarIndex + 1, 0));
}
}
addChordsFromPrevRange(chordsToQuant, chords,
currentlyInTuplet, currentBarIndex, voice,
barStart, rangeStart, basicQuant);
quantizeOnTimesInRange(chordsToQuant, foundOnTimes, rangeStart, rangeEnd,
basicQuant, barStart, barFraction);
chordsToQuant.clear();
}
}
}
}
// if on time after quantization become >= note off time
// then shift note off time preserving old note length
void moveOffTimes(
const ReducedFraction &oldOnTime,
const ReducedFraction &newOnTime,
QList<MidiNote> ¬es)
{
for (auto ¬e: notes) {
if (newOnTime >= note.offTime) {
Q_ASSERT_X(newOnTime > oldOnTime,
"Quantize::moveOffTimes", "Invalid note on time or off time");
Q_ASSERT_X(note.offTime > oldOnTime,
"Quantize::moveOffTimes", "Invalid old note length");
note.offTime += newOnTime - oldOnTime;
}
}
}
std::multimap<ReducedFraction, MidiChord>
findQuantizedChords(
const std::map<const std::pair<const ReducedFraction, MidiChord> *, QuantInfo> &foundOnTimes)
{
std::multimap<ReducedFraction, MidiChord> quantizedChords;
for (const auto &f: foundOnTimes) {
const QuantInfo &i = f.second;
const MidiChord &chord = i.chord->second;
auto found = quantizedChords.end();
const auto range = quantizedChords.equal_range(i.onTime);
for (auto it = range.first; it != range.second; ++it) {
if (it->second.voice == chord.voice) {
found = it;
break;
}
}
if (found != quantizedChords.end()) {
MidiChord &fc = found->second;
fc.notes.append(chord.notes); // merge chords with equal on times
moveOffTimes(i.chord->first, i.onTime, fc.notes);
if (chord.isInTuplet) {
if (!fc.isInTuplet) {
fc.isInTuplet = true;
fc.tuplet = chord.tuplet;
}
#ifdef QT_DEBUG
else {
Q_ASSERT_X(fc.tuplet == chord.tuplet,
"Quantize::findQuantizedChords",
"Tuplets of merged chords are different");
}
#endif
}
}
else {
MidiChord midiChord(chord); // copy chord
moveOffTimes(i.chord->first, i.onTime, midiChord.notes);
quantizedChords.insert({i.onTime, midiChord});
}
}
return quantizedChords;
}
void removeUselessTupletReferences(std::multimap<ReducedFraction, MidiChord> &chords)
{
for (auto &chord: chords) {
if (chord.second.isInTuplet) {
const auto &tuplet = chord.second.tuplet->second;
if (chord.first >= tuplet.onTime + tuplet.len)
chord.second.isInTuplet = false;
}
for (auto ¬e: chord.second.notes) {
if (note.isInTuplet) {
const auto &tuplet = note.tuplet->second;
if (note.offTime <= tuplet.onTime)
note.isInTuplet = false;
}
}
}
}
void quantizeChords(
std::multimap<ReducedFraction, MidiChord> &chords,
const TimeSigMap *sigmap,
const ReducedFraction &basicQuant)
{
Q_ASSERT_X(MidiTuplet::areTupletReferencesValid(chords), "Quantize::quantizeChords",
"Some tuplet references are invalid");
Q_ASSERT_X(areOnTimeValuesDifferent(chords), "Quantize::quantizeChords",
"Chords of the same voices have equal on time values");
Q_ASSERT_X(areTupletChordsConsistent(chords), "Quantize::quantizeChords",
"There are non-tuplet chords between tuplet chords");
Q_ASSERT_X(MChord::areNotesLongEnough(chords), "Quantize::quantizeChords",
"There are too short notes");
Q_ASSERT_X(MChord::areBarIndexesSuccessive(chords), "Quantize::quantizeChords",
"Bar indexes are not successive");
applyTupletStaccato(chords); // apply staccato for tuplet off times
std::map<const std::pair<const ReducedFraction, MidiChord> *, QuantInfo> foundOnTimes;
quantizeOnTimes(chords, foundOnTimes, basicQuant, sigmap);
auto quantizedChords = findQuantizedChords(foundOnTimes);
Q_ASSERT_X(MidiTuplet::areTupletReferencesValid(quantizedChords), "Quantize::quantizeChords",
"Some tuplet references are invalid");
quantizeOffTimes(quantizedChords, basicQuant);
std::swap(chords, quantizedChords);
removeUselessTupletReferences(chords);
}
} // namespace Quantize
} // namespace Ms
↑ V730 Not all members of a class are initialized inside the compiler generated constructor. Consider inspecting: metricalLevelForLen.