MuseScore/mscore/importmidi_tuplet_voice.cpp
2014-07-30 21:41:57 +04:00

882 lines
37 KiB
C++

#include "importmidi_tuplet_voice.h"
#include "importmidi_tuplet.h"
#include "importmidi_chord.h"
#include "importmidi_quant.h"
#include "importmidi_inner.h"
#include "importmidi_voice.h"
#include "importmidi_operations.h"
#include "libmscore/mscore.h"
#include "preferences.h"
#include <set>
namespace Ms {
namespace MidiTuplet {
int tupletVoiceLimit()
{
const auto &opers = preferences.midiImportOperations.data()->trackOpers;
const int currentTrack = preferences.midiImportOperations.currentTrack();
const int allowedVoices = MidiVoice::toIntVoiceCount(opers.maxVoiceCount.value(currentTrack));
Q_ASSERT_X(allowedVoices <= VOICES,
"MidiTuplet::tupletVoiceLimit",
"Allowed voice count exceeds MuseScore voice limit");
// for multiple voices: one voice is reserved for non-tuplet chords
return (allowedVoices == 1) ? 1 : allowedVoices - 1;
}
std::pair<ReducedFraction, ReducedFraction>
chordInterval(const std::pair<const ReducedFraction, MidiChord> &chord,
const std::multimap<ReducedFraction, MidiChord> &chords,
const ReducedFraction &basicQuant,
const ReducedFraction &barStart)
{
const auto onTime = MidiTuplet::findOnTimeBetweenChords(chord, chords, basicQuant, barStart);
auto offTime = Quantize::findMaxQuantizedOffTime(chord, basicQuant);
if (offTime < onTime)
offTime = onTime;
if (offTime == onTime)
offTime += Quantize::quantForLen(MChord::minNoteLen(chord), basicQuant);
Q_ASSERT_X(offTime > onTime, "MidiTuplet::chordInterval", "Off time <= On time");
return std::make_pair(onTime, offTime);
}
int findTupletWithChord(const MidiChord &midiChord,
const std::vector<TupletInfo> &tuplets)
{
for (int i = 0; i != (int)tuplets.size(); ++i) {
for (const auto &chord: tuplets[i].chords) {
if (&(chord.second->second) == &midiChord)
return i;
}
}
return -1;
}
std::pair<ReducedFraction, ReducedFraction>
backTiedInterval(const TiedTuplet &tiedTuplet,
const std::vector<TupletInfo> &tuplets,
const std::multimap<ReducedFraction, MidiChord> &chords,
const ReducedFraction &basicQuant,
const ReducedFraction &barStart)
{
const TupletInfo &tuplet = tupletFromId(tiedTuplet.tupletId, tuplets);
const auto end = tupletInterval(tuplet, basicQuant).second;
const MidiChord &midiChord = tiedTuplet.chord->second;
const int tupletIndex = findTupletWithChord(midiChord, tuplets);
const auto beg = (tupletIndex != -1)
? tupletInterval(tuplets[tupletIndex], basicQuant).first
: chordInterval(*tiedTuplet.chord, chords, basicQuant, barStart).first;
return std::make_pair(beg, end);
}
void setTupletVoice(
std::map<ReducedFraction,
std::multimap<ReducedFraction, MidiChord>::iterator> &tupletChords,
int voice)
{
for (auto &tupletChord: tupletChords) {
MidiChord &midiChord = tupletChord.second->second;
midiChord.voice = voice;
}
}
void setTupletVoices(
std::vector<TupletInfo> &tuplets,
std::set<int> &pendingTuplets,
std::map<int, std::vector<std::pair<ReducedFraction, ReducedFraction>>> &tupletIntervals,
const ReducedFraction &basicQuant)
{
const int limit = tupletVoiceLimit();
int voice = 0;
while (!pendingTuplets.empty() && voice < limit) {
for (auto it = pendingTuplets.begin(); it != pendingTuplets.end(); ) {
TupletInfo &tuplet = tupletFromId(*it, tuplets);
const auto interval = tupletInterval(tuplet, basicQuant);
if (!haveIntersection(interval, tupletIntervals[voice])) {
setTupletVoice(tuplet.chords, voice);
tupletIntervals[voice].push_back(interval);
it = pendingTuplets.erase(it);
continue;
}
++it;
}
++voice;
}
}
int findPitchDist(
const QList<MidiNote> &notes,
const std::vector<TupletInfo> &tuplets,
int voice)
{
int pitchDist = std::numeric_limits<int>::max(); // bad value - only for the last choice
if (tuplets.empty())
return pitchDist;
int tupletPitch = 0;
for (const auto &tuplet: tuplets) {
if (tuplet.chords.begin()->second->second.voice != voice)
continue;
int counter = 0;
for (const auto &chord: tuplet.chords) {
const MidiChord &c = chord.second->second;
tupletPitch += MChord::chordAveragePitch(c.notes);
++counter;
}
tupletPitch = qRound(tupletPitch * 1.0 / counter);
break;
}
if (tupletPitch == 0)
return pitchDist;
const int chordPitch = MChord::chordAveragePitch(notes);
pitchDist = qAbs(chordPitch - tupletPitch);
return pitchDist;
}
void setNonTupletVoices(
std::set<std::pair<const ReducedFraction, MidiChord> *> &pendingNonTuplets,
const std::map<int, std::vector<std::pair<ReducedFraction, ReducedFraction>>> &tupletIntervals,
const std::vector<TupletInfo> &tuplets,
const std::multimap<ReducedFraction, MidiChord> &chords,
const ReducedFraction &basicQuant,
const ReducedFraction &barStart)
{
const int limit = MidiVoice::voiceLimit();
while (!pendingNonTuplets.empty()) {
auto chord = *pendingNonTuplets.begin();
const auto interval = chordInterval(*chord, chords, basicQuant, barStart);
// pick the voice such that the average pitch difference
// between the non-tuplet chord and tuplets with this voice
// is the smallest
int bestVoice = -1;
int minPitchDist = -1;
for (int voice = 0; voice < limit; ++voice) {
const auto fit = tupletIntervals.find(voice);
if (fit == tupletIntervals.end() || !haveIntersection(interval, fit->second)) {
const int pitchDist = findPitchDist(chord->second.notes, tuplets, voice);
if (minPitchDist == -1 || pitchDist < minPitchDist) {
minPitchDist = pitchDist;
bestVoice = voice;
}
}
}
Q_ASSERT_X(bestVoice >= 0,
"MidiTuplet::setNonTupletVoices", "Best voice not found");
chord->second.voice = bestVoice;
pendingNonTuplets.erase(pendingNonTuplets.begin());
// don't insert chord interval here
}
}
#ifdef QT_DEBUG
bool areAllElementsUnique(
const std::list<std::multimap<ReducedFraction, MidiChord>::iterator> &nonTuplets)
{
std::set<std::pair<const ReducedFraction, MidiChord> *> chords;
for (const auto &chord: nonTuplets) {
if (chords.find(&*chord) == chords.end())
chords.insert(&*chord);
else
return false;
}
return true;
}
bool haveTupletsEmptyChords(const std::vector<TupletInfo> &tuplets)
{
for (const auto &tuplet: tuplets) {
if (tuplet.chords.empty())
return true;
}
return false;
}
bool doTupletChordsHaveSameVoice(const std::vector<TupletInfo> &tuplets)
{
for (const auto &tuplet: tuplets) {
auto it = tuplet.chords.cbegin();
const int voice = it->second->second.voice;
++it;
for ( ; it != tuplet.chords.cend(); ++it) {
if (it->second->second.voice != voice)
return false;
}
}
return true;
}
// back tied tuplets are not checked here
bool haveOverlappingVoices(
const std::list<std::multimap<ReducedFraction, MidiChord>::iterator> &nonTuplets,
const std::vector<TupletInfo> &tuplets,
const std::list<TiedTuplet> &backTiedTuplets,
const std::multimap<ReducedFraction, MidiChord> &chords,
const ReducedFraction &basicQuant,
const ReducedFraction &barStart)
{
// <voice, intervals>
std::map<int, std::vector<std::pair<ReducedFraction, ReducedFraction>>> intervals;
for (const auto &tuplet: tuplets) {
const int voice = tuplet.chords.begin()->second->second.voice;
const auto interval = std::make_pair(tuplet.onTime, tuplet.onTime + tuplet.len);
if (haveIntersection(interval, intervals[voice]))
return true;
else
intervals[voice].push_back(interval);
}
for (const auto &chord: nonTuplets) {
const int voice = chord->second.voice;
const auto interval = chordInterval(*chord, chords, basicQuant, barStart);
if (haveIntersection(interval, intervals[voice])) {
bool flag = false;
// if chord is tied then it can intersect tuplet
for (const TiedTuplet &tiedTuplet: backTiedTuplets) {
if (tiedTuplet.chord == (&*chord)
&& (tiedTuplet.voice == -1 || tiedTuplet.voice == voice)) {
flag = true;
break;
}
}
if (!flag)
return true;
}
}
return false;
}
size_t chordCount(
const std::vector<TupletInfo> &tuplets,
const std::list<std::multimap<ReducedFraction, MidiChord>::iterator> &nonTuplets)
{
size_t sum = nonTuplets.size();
for (const auto &tuplet: tuplets) {
sum += tuplet.chords.size();
}
return sum;
}
bool voiceDontExceedLimit(
const std::list<std::multimap<ReducedFraction, MidiChord>::iterator> &nonTuplets,
const std::vector<TupletInfo> &tuplets)
{
for (const auto &tuplet: tuplets) {
const int voice = tuplet.chords.begin()->second->second.voice;
if (voice >= MidiVoice::voiceLimit())
return true;
}
for (const auto &chord: nonTuplets) {
const int voice = chord->second.voice;
if (voice >= MidiVoice::voiceLimit())
return true;
}
return false;
}
#endif
void eraseBackTiedTuplet(
int tupletId,
std::list<TiedTuplet> &backTiedTuplets)
{
for (auto it = backTiedTuplets.begin(); it != backTiedTuplets.end(); ++it) {
if (it->tupletId == tupletId) {
backTiedTuplets.erase(it);
break;
}
}
}
// for the case when voice limit = 1
bool excludeExtraVoiceTuplets(
std::vector<TupletInfo> &tuplets,
std::list<std::multimap<ReducedFraction, MidiChord>::iterator> &nonTuplets,
std::list<TiedTuplet> &backTiedTuplets,
const std::multimap<ReducedFraction, MidiChord> &chords,
const ReducedFraction &basicQuant,
const ReducedFraction &barStart,
int barIndex)
{
size_t sz = tuplets.size();
if (sz == 0)
return false;
std::list<std::multimap<ReducedFraction, MidiChord>::iterator> newNonTuplets;
size_t addedCount = nonTuplets.size();
while (addedCount > 0) {
nonTuplets.splice(nonTuplets.begin(), newNonTuplets);
// remove tuplets that are overlapped with non-tuplets
for (size_t i = 0; i < sz; ) {
const auto interval = tupletInterval(tuplets[i], basicQuant);
bool shift = false;
size_t counter = 0;
for (const auto &nonTuplet: nonTuplets) {
++counter;
if (counter > addedCount)
break;
if (haveIntersection(interval, chordInterval(*nonTuplet, chords,
basicQuant, barStart))) {
bool isTied = false;
for (const TiedTuplet &tiedTuplet: backTiedTuplets) {
if (tiedTuplet.tupletId == tuplets[i].id
&& tiedTuplet.chord == &*nonTuplet) {
isTied = true;
break;
}
}
if (!isTied) {
for (const auto &chord: tuplets[i].chords) {
if (chord.second->second.barIndex == barIndex)
newNonTuplets.push_back(chord.second);
}
eraseBackTiedTuplet(tuplets[i].id, backTiedTuplets);
--sz;
if (i < sz) {
shift = true;
tuplets[i] = tuplets[sz];
}
break;
}
}
}
if (shift)
continue;
++i;
}
addedCount = newNonTuplets.size();
}
Q_ASSERT_X(areAllElementsUnique(nonTuplets),
"MidiTuplet::excludeExtraVoiceTuplets", "Non unique chords in non-tuplets");
bool excluded = (sz != tuplets.size());
tuplets.resize(sz);
return excluded;
}
void removeUnusedTuplets(
std::vector<TupletInfo> &tuplets,
std::list<std::multimap<ReducedFraction, MidiChord>::iterator> &nonTuplets,
std::set<int> &pendingTuplets,
std::list<TiedTuplet> &backTiedTuplets,
std::set<std::pair<const ReducedFraction, MidiChord> *> &pendingNonTuplets,
int barIndex)
{
if (pendingTuplets.empty())
return;
std::vector<TupletInfo> newTuplets;
for (int i = 0; i != (int)tuplets.size(); ++i) {
if (pendingTuplets.find(tuplets[i].id) == pendingTuplets.end()) {
newTuplets.push_back(tuplets[i]);
}
else {
eraseBackTiedTuplet(tuplets[i].id, backTiedTuplets);
for (const auto &chord: tuplets[i].chords) {
if (chord.second->second.barIndex == barIndex) {
nonTuplets.push_back(chord.second);
pendingNonTuplets.insert(&*chord.second);
}
}
}
}
pendingTuplets.clear();
std::swap(tuplets, newTuplets);
}
std::set<int> findPendingTuplets(const std::vector<TupletInfo> &tuplets)
{
std::set<int> pendingTuplets; // tuplet indexes
for (int i = 0; i != (int)tuplets.size(); ++i) {
pendingTuplets.insert(tuplets[i].id);
}
return pendingTuplets;
}
std::set<std::pair<const ReducedFraction, MidiChord> *>
findPendingNonTuplets(
const std::list<std::multimap<ReducedFraction, MidiChord>::iterator> &nonTuplets)
{
std::set<std::pair<const ReducedFraction, MidiChord> *> pendingNonTuplets;
for (const auto &c: nonTuplets) {
pendingNonTuplets.insert(&*c);
}
return pendingNonTuplets;
}
std::list<TiedTuplet>::iterator
eraseBackTiedTuplet(const std::list<TiedTuplet>::iterator &it,
std::list<TiedTuplet> &backTiedTuplets,
const TupletInfo &tuplet)
{
for (const auto &chord: tuplet.chords) {
for (auto it2 = backTiedTuplets.begin(); it2 != backTiedTuplets.end(); ++it2) {
if (&(chord.second->second) == &(it2->chord->second)) {
backTiedTuplets.erase(it2);
break;
}
}
}
return backTiedTuplets.erase(it);
}
void setVoicesFromPrevBars(
std::list<TiedTuplet> &backTiedTuplets,
std::vector<TupletInfo> &tuplets,
std::set<int> &pendingTuplets,
const std::set<std::pair<const ReducedFraction, MidiChord> *> &pendingNonTuplets,
std::map<int, std::vector<std::pair<ReducedFraction, ReducedFraction>>> &tupletIntervals,
std::map<int, std::vector<std::pair<ReducedFraction, ReducedFraction>>> &backTupletIntervals,
const std::multimap<ReducedFraction, MidiChord> &chords,
const ReducedFraction &basicQuant,
const ReducedFraction &barStart)
{
bool loopAgain = false;
do {
for (auto it = backTiedTuplets.begin(); it != backTiedTuplets.end(); ) {
const TiedTuplet &tiedTuplet = *it;
TupletInfo &tuplet = tupletFromId(tiedTuplet.tupletId, tuplets);
const auto backInterval = std::make_pair(tuplet.onTime, tuplet.onTime + tuplet.len);
if (tiedTuplet.voice == -1) {
++it;
continue;
}
if (haveIntersection(backInterval, backTupletIntervals[tiedTuplet.voice])) {
it = backTiedTuplets.erase(it);
continue;
}
for (const auto &chord: tuplet.chords) {
for (auto it2 = backTiedTuplets.begin();
it2 != backTiedTuplets.end(); ++it2) {
if (it2 == it)
continue;
if (&(chord.second->second) == &(it2->chord->second)
&& it2->voice == -1) {
it2->voice = tiedTuplet.voice;
loopAgain = true;
break;
}
}
}
setTupletVoice(tuplet.chords, tiedTuplet.voice);
backTupletIntervals[tiedTuplet.voice].push_back(backInterval);
tupletIntervals[tiedTuplet.voice].push_back(backInterval);
// add to intervals chord from previous bar
tupletIntervals[tiedTuplet.voice].push_back(
chordInterval(*tiedTuplet.chord, chords, basicQuant, barStart));
pendingTuplets.erase(tiedTuplet.tupletId);
Q_ASSERT_X(pendingNonTuplets.find(tiedTuplet.chord) == pendingNonTuplets.end(),
"MidiTuplet::setBackTiedVoices",
"Tied non-tuplet chord should not be here");
++it;
}
} while (loopAgain);
}
void setTiedChordVoice(
std::list<TiedTuplet> &backTiedTuplets,
std::vector<TupletInfo> &tuplets,
std::set<int> &pendingTuplets,
std::set<std::pair<const ReducedFraction, MidiChord> *> &pendingNonTuplets,
std::map<int, std::vector<std::pair<ReducedFraction, ReducedFraction>>> &tupletIntervals,
std::map<int, std::vector<std::pair<ReducedFraction, ReducedFraction>>> &backTupletIntervals,
const std::multimap<ReducedFraction, MidiChord> &chords,
const std::list<TiedTuplet>::iterator &backTiedIt,
bool isNonTupletBackChord,
const ReducedFraction &basicQuant,
const ReducedFraction &barStart)
{
const TiedTuplet &tiedTuplet = *backTiedIt;
if (isNonTupletBackChord) {
// non-tuplet chord tied
const auto interval = chordInterval(*tiedTuplet.chord, chords, basicQuant, barStart);
backTupletIntervals[tiedTuplet.voice].push_back(interval);
tiedTuplet.chord->second.voice = tiedTuplet.voice;
if (tupletVoiceLimit() > 1)
tupletIntervals[tiedTuplet.voice].push_back(interval);
pendingNonTuplets.erase(tiedTuplet.chord);
}
else {
// tuplet tied
const int i = findTupletWithChord(tiedTuplet.chord->second, tuplets);
Q_ASSERT_X(i != -1, "MidiTuplet::setBackTiedVoices",
"Tuplet chord not found in tuplets");
auto it2 = std::next(backTiedIt);
for ( ; it2 != backTiedTuplets.end(); ++it2) {
if (it2->tupletId == tuplets[i].id)
break;
}
if (it2 != backTiedTuplets.end()) {
if (it2->voice == -1)
it2->voice = tiedTuplet.voice;
}
else {
// set voice of not back-tied tuplet that have tied chord
setTupletVoice(tuplets[i].chords, tiedTuplet.voice);
const auto interval = tupletInterval(tuplets[i], basicQuant);
tupletIntervals[tiedTuplet.voice].push_back(interval);
backTupletIntervals[tiedTuplet.voice].push_back(interval);
pendingTuplets.erase(tuplets[i].id);
}
}
}
void setVoiceOfConnectedBackTied(
std::list<TiedTuplet> &backTiedTuplets,
int tiedTupletVoice,
const std::set<int> &pendingTuplets,
const TupletInfo &tuplet,
const std::list<TiedTuplet>::iterator &backTiedIt)
{
for (const auto &chord: tuplet.chords) {
for (auto it2 = std::next(backTiedIt); it2 != backTiedTuplets.end(); ++it2) {
if (pendingTuplets.find(it2->tupletId) == pendingTuplets.end())
continue;
if (&(chord.second->second) == &(it2->chord->second) && it2->voice == -1) {
it2->voice = tiedTupletVoice;
break;
}
}
}
}
int findVoiceForBackTied(
const std::pair<const ReducedFraction, MidiChord> &tiedTupletChord,
int voiceLimit,
const std::pair<ReducedFraction, ReducedFraction> &backInterval,
std::map<int, std::vector<std::pair<ReducedFraction, ReducedFraction>>> &backTupletIntervals,
const std::multimap<ReducedFraction, MidiChord> &chords,
bool isNonTupletBackChord,
const ReducedFraction &basicQuant,
const ReducedFraction &barStart)
{
int voice = 0;
for ( ; voice != voiceLimit; ++voice) {
if (haveIntersection(backInterval, backTupletIntervals[voice])
|| (isNonTupletBackChord && haveIntersection(
chordInterval(tiedTupletChord, chords, basicQuant, barStart),
backTupletIntervals[voice]))) {
continue;
}
break;
}
return voice;
}
std::map<int, std::vector<std::pair<ReducedFraction, ReducedFraction> > >
findBackTupletIntervals(
const std::list<TiedTuplet> &backTiedTuplets,
const std::vector<TupletInfo> &tuplets)
{
std::map<int, std::vector<std::pair<ReducedFraction, ReducedFraction>>> backTupletIntervals;
for (const auto &t: backTiedTuplets) {
const auto &tuplet = tupletFromId(t.tupletId, tuplets);
const auto interval = std::make_pair(tuplet.onTime, tuplet.onTime + tuplet.len);
backTupletIntervals[t.voice].push_back(interval);
}
return backTupletIntervals;
}
void setBackTiedVoices(
std::list<TiedTuplet> &backTiedTuplets,
std::vector<TupletInfo> &tuplets,
std::set<int> &pendingTuplets,
std::set<std::pair<const ReducedFraction, MidiChord> *> &pendingNonTuplets,
std::map<int, std::vector<std::pair<ReducedFraction, ReducedFraction>>> &tupletIntervals,
const std::multimap<ReducedFraction, MidiChord> &chords,
const ReducedFraction &basicQuant,
const ReducedFraction &barStart)
{
auto backTupletIntervals = findBackTupletIntervals(backTiedTuplets, tuplets);
// set voices that are already set from one of previous bars
setVoicesFromPrevBars(backTiedTuplets, tuplets, pendingTuplets, pendingNonTuplets,
tupletIntervals, backTupletIntervals, chords, basicQuant, barStart);
// set yet unset back tied voices
const int limit = tupletVoiceLimit();
for (auto it = backTiedTuplets.begin(); it != backTiedTuplets.end(); ) {
TiedTuplet &tiedTuplet = *it;
if (pendingTuplets.find(tiedTuplet.tupletId) == pendingTuplets.end()) {
++it;
continue;
}
TupletInfo &tuplet = tupletFromId(tiedTuplet.tupletId, tuplets);
const auto backInterval = std::make_pair(tuplet.onTime, tuplet.onTime + tuplet.len);
bool isNonTupletBackChord
= (pendingNonTuplets.find(tiedTuplet.chord) != pendingNonTuplets.end());
if (tiedTuplet.voice == -1) {
const int voice = findVoiceForBackTied(
*tiedTuplet.chord, limit, backInterval, backTupletIntervals,
chords, isNonTupletBackChord, basicQuant, barStart);
if (voice < limit) {
tiedTuplet.voice = voice;
}
else { // no available voices
it = eraseBackTiedTuplet(it, backTiedTuplets, tuplet);
continue;
}
}
else {
if (haveIntersection(backInterval, backTupletIntervals[tiedTuplet.voice])
|| (isNonTupletBackChord
&& haveIntersection(chordInterval(*tiedTuplet.chord, chords,
basicQuant, barStart),
backTupletIntervals[tiedTuplet.voice]))) {
it = eraseBackTiedTuplet(it, backTiedTuplets, tuplet);
continue;
}
}
setVoiceOfConnectedBackTied(backTiedTuplets, tiedTuplet.voice,
pendingTuplets, tuplet, it);
// set voices of tied tuplet chords
setTupletVoice(tuplet.chords, tiedTuplet.voice);
backTupletIntervals[tiedTuplet.voice].push_back(backInterval);
tupletIntervals[tiedTuplet.voice].push_back(tupletInterval(tuplet, basicQuant));
pendingTuplets.erase(tiedTuplet.tupletId);
setTiedChordVoice(backTiedTuplets, tuplets, pendingTuplets, pendingNonTuplets,
tupletIntervals, backTupletIntervals, chords, it,
isNonTupletBackChord, basicQuant, barStart);
++it;
}
}
std::map<std::pair<const ReducedFraction, MidiChord> *, int>
findMappedTupletChords(const std::vector<TupletInfo> &tuplets)
{
// <chord address, tupletIndex>
std::map<std::pair<const ReducedFraction, MidiChord> *, int> tupletChords;
for (int i = 0; i != (int)tuplets.size(); ++i) {
for (const auto &tupletChord: tuplets[i].chords) {
auto tupletIt = tupletChord.second;
tupletChords.insert({&*tupletIt, i});
}
}
return tupletChords;
}
bool areTupletsIntersect(const TupletInfo &t1, const TupletInfo &t2)
{
const auto onTime1 = t1.onTime;
const auto endTime1 = onTime1 + t1.len;
const auto onTime2 = t2.onTime;
const auto endTime2 = onTime1 + t2.len;
return (endTime1 > onTime2 && onTime1 < endTime2);
}
// result: tied notes indexes
std::vector<int> findTiedNotes(
const TupletInfo &tuplet,
const std::multimap<ReducedFraction, MidiChord>::iterator &chordIt,
const std::multimap<ReducedFraction, MidiChord> &chords,
const ReducedFraction &startBarTick,
const ReducedFraction &basicQuant)
{
std::vector<int> tiedNotes;
const auto tupletRatio = tupletLimits(tuplet.tupletNumber).ratio;
const auto firstTupletChordOnTime = Quantize::findQuantizedTupletChordOnTime(
*tuplet.chords.begin()->second, tuplet.len,
tupletRatio, startBarTick);
const auto maxChordOffTime = Quantize::findMaxQuantizedTupletOffTime(
*chordIt, tuplet.len, tupletRatio, startBarTick);
if (maxChordOffTime > firstTupletChordOnTime)
return tiedNotes;
const auto onTime = MidiTuplet::findOnTimeBetweenChords(*chordIt, chords,
basicQuant, startBarTick);
if (onTime >= tuplet.onTime)
return tiedNotes;
for (int i = 0; i != chordIt->second.notes.size(); ++i) {
const MidiNote &note = chordIt->second.notes[i];
const auto offTimeInTuplet = Quantize::findQuantizedTupletNoteOffTime(
chordIt->first, note.offTime, tuplet.len, tupletRatio, startBarTick).first;
if (offTimeInTuplet < startBarTick || offTimeInTuplet <= tuplet.onTime)
continue;
const auto regularOffTime = Quantize::findQuantizedNoteOffTime(
*chordIt, note.offTime, basicQuant).first;
const auto regularError = (note.offTime - regularOffTime).absValue();
const auto tupletError = (note.offTime - offTimeInTuplet).absValue();
if (tupletError > regularError)
continue;
tiedNotes.push_back(i);
}
return tiedNotes;
}
// prepare tied tuplets - pairs of tuplet and chord back-tied to it
// voices of back-tied chords from previous bar are set explicitly
// other voices = -1
// tied tuplets with voice != -1 don't intersect each other
// tuplets can be tied only to one chord or tuplet (no 'branches')
std::list<TiedTuplet>
findBackTiedTuplets(
const std::multimap<ReducedFraction, MidiChord> &chords,
const std::vector<TupletInfo> &tuplets,
const ReducedFraction &prevBarStart,
const ReducedFraction &startBarTick,
const ReducedFraction &basicQuant,
int currentBarIndex)
{
std::list<TiedTuplet> tiedTuplets;
// <voice, intervals>
std::map<int, std::vector<std::pair<ReducedFraction, ReducedFraction>>> backTupletIntervals;
std::set<int> usedTuplets;
std::set<std::pair<const ReducedFraction, MidiChord> *> usedChords;
const auto tupletChords = findMappedTupletChords(tuplets);
for (int i = 0; i != (int)tuplets.size(); ++i) {
Q_ASSERT_X(!tuplets[i].chords.empty(),
"MidiTuplets::findBackTiedTuplets", "Tuplet chords are empty");
auto chordIt = tuplets[i].chords.begin()->second;
while (chordIt != chords.begin() && chordIt->first >= prevBarStart) {
--chordIt;
const auto tupletIt = tupletChords.find(&*chordIt);
const bool isInTupletOfThisBar = (tupletIt != tupletChords.end());
// don't make back tie to the chord in overlapping tuplet
if (isInTupletOfThisBar
&& areTupletsIntersect(tuplets[tupletIt->second], tuplets[i])) {
continue;
}
// remember voices of tuplets that have tied chords from previous bar
// and that chords don't belong to the tuplets of this bar
const int voice = (chordIt->second.barIndex < currentBarIndex)
? chordIt->second.voice : -1;
const auto interval = std::make_pair(tuplets[i].onTime,
tuplets[i].onTime + tuplets[i].len);
// if voice is specified and the new interval have intersection
// with already found back tuplets with the save voice
// then discard the interval
if (voice != -1 && haveIntersection(interval, backTupletIntervals[voice]))
continue;
const auto tiedNotes = findTiedNotes(tuplets[i], chordIt, chords,
startBarTick, basicQuant);
if (!tiedNotes.empty()) {
// don't tie back twice to the same chord or tuplet
if (usedChords.find(&*chordIt) != usedChords.end())
continue;
// don't tie back twice to the same tuplet
const int tupletIndex = findTupletWithChord(chordIt->second, tuplets);
if (usedTuplets.find(tupletIndex) != usedTuplets.end())
continue;
// we can add back-tied tuplet; voice here can be -1
tiedTuplets.push_back({tuplets[i].id, voice, &*chordIt, tiedNotes});
backTupletIntervals[voice].push_back(interval);
usedChords.insert(&*chordIt);
usedTuplets.insert(tupletIndex);
break;
}
}
}
return tiedTuplets;
}
// chord notes should not be rearranged here
// because note indexes are stored in tied tuplets
void assignVoices(
std::vector<TupletInfo> &tuplets,
std::list<std::multimap<ReducedFraction, MidiChord>::iterator> &nonTuplets,
std::list<TiedTuplet> &backTiedTuplets,
const std::multimap<ReducedFraction, MidiChord> &chords,
const ReducedFraction &basicQuant,
const ReducedFraction &barStart,
int barIndex)
{
Q_ASSERT_X(!haveTupletsEmptyChords(tuplets),
"MIDI tuplets: assignVoices", "Empty tuplet chords");
auto pendingTuplets = findPendingTuplets(tuplets);
auto pendingNonTuplets = findPendingNonTuplets(nonTuplets);
// <voice, intervals>
std::map<int, std::vector<std::pair<ReducedFraction, ReducedFraction>>> tupletIntervals;
setBackTiedVoices(backTiedTuplets, tuplets, pendingTuplets, pendingNonTuplets,
tupletIntervals, chords, basicQuant, barStart);
setTupletVoices(tuplets, pendingTuplets, tupletIntervals, basicQuant);
removeUnusedTuplets(tuplets, nonTuplets, pendingTuplets, backTiedTuplets,
pendingNonTuplets, barIndex);
if (tupletVoiceLimit() == 1) {
bool excluded = excludeExtraVoiceTuplets(tuplets, nonTuplets, backTiedTuplets,
chords, basicQuant, barStart, barIndex);
if (excluded) { // to exlude tuplet intervals - rebuild all intervals
tupletIntervals.clear();
for (const auto &tuplet: tuplets) {
const int voice = tuplet.chords.begin()->second->second.voice;
tupletIntervals[voice].push_back(tupletInterval(tuplet, basicQuant));
}
}
}
setNonTupletVoices(pendingNonTuplets, tupletIntervals, tuplets,
chords, basicQuant, barStart);
Q_ASSERT_X(pendingNonTuplets.empty(),
"MIDI tuplets: assignVoices", "Unused non-tuplets");
Q_ASSERT_X(!haveTupletsEmptyChords(tuplets),
"MIDI tuplets: assignVoices", "Empty tuplet chords");
Q_ASSERT_X(doTupletChordsHaveSameVoice(tuplets),
"MIDI tuplets: assignVoices", "Tuplet chords have different voices");
Q_ASSERT_X(!haveOverlappingVoices(nonTuplets, tuplets, backTiedTuplets, chords,
basicQuant, barStart),
"MIDI tuplets: assignVoices", "Overlapping tuplets of the same voice");
Q_ASSERT_X(!voiceDontExceedLimit(nonTuplets, tuplets),
"MIDI tuplets: assignVoices", "Voice exceeds the limit");
}
} // namespace MidiTuplet
} // namespace Ms