Raw code for tuplets

This commit is contained in:
Andrey M. Tokarev 2013-06-12 01:14:05 +04:00
parent 4d10a554bf
commit 7a7e98ccec
11 changed files with 851 additions and 286 deletions

View file

@ -151,6 +151,7 @@ QT4_WRAP_CPP (mocs
importmidi_panel.h importmidi_operations.h
importmidi_opmodel.h importmidi_trmodel.h importmidi_opdelegate.h
importmidi_meter.h importmidi_operation.h importmidi_quant.h importmidi_chord.h
importmidi_tupletdata.h
debugger/debugger.h
${OMR_MOCS}
${SCRIPT_MOCS}

View file

@ -42,6 +42,7 @@
#include "importmidi_meter.h"
#include "importmidi_chord.h"
#include "importmidi_quant.h"
#include "importmidi_tupletdata.h"
namespace Ms {
@ -61,11 +62,14 @@ class MTrack {
bool hasKey = false;
std::multimap<int, MidiChord> chords;
std::multimap<int, TupletData> tuplets;
void convertTrack(int lastTick);
void processPendingNotes(QList<MidiChord>& notes, int voice, int ctick, int tick);
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
@ -74,10 +78,10 @@ void removeOverlappingNotes(QList<MTrack> &tracks)
{
for (auto &track: tracks) {
auto &chords = track.chords;
for (auto i = chords.begin(); i != chords.end(); ++i) {
auto &firstChord = i->second;
for (auto it = chords.begin(); it != chords.end(); ++it) {
auto &firstChord = it->second;
for (auto &note1: firstChord.notes) {
auto ii = i;
auto ii = it;
++ii;
bool overlapFound = false;
for (; ii != chords.end(); ++ii) {
@ -138,31 +142,30 @@ void collectChords(QList<MTrack> &tracks, int minNoteDuration)
int startTime = -1; // invalid
int curThreshTime = -1;
bool useDrumset = false;
// if intersection of note durations is less than min(minNoteDuration, threshTime)
// then this is not a chord
int tol = -1; // invalid
// if intersection of note durations is less than min(minNoteDuration, threshTime)
// then this is not a chord
int tol = -1; // invalid
int beg = -1;
int end = -1;
// chords here consist of a single note
// because notes are not united into chords yet
for (auto i = chords.begin(); i != chords.end(); ) {
const auto &note = i->second.notes[0];
// chords here consist of a single note
// because notes are not united into chords yet
for (auto it = chords.begin(); it != chords.end(); ) {
const auto &note = it->second.notes[0];
if (note.onTime <= startTime + curThreshTime) {
if (!useDrumset || (drumset->isValid(note.pitch)
&& drumset->voice(note.pitch) == i->second.voice)) {
&& drumset->voice(note.pitch) == it->second.voice)) {
beg = std::max(beg, note.onTime);
end = std::min(end, note.onTime + note.len);
tol = std::min(tol, note.len);
if (end - beg >= tol) {
auto prev = i;
// add current note to the previous chord
auto prev = it;
--prev;
prev->second.notes.push_back(note);
if (note.onTime >= startTime + curThreshTime - fudgeTime)
curThreshTime += threshExtTime;
i = chords.erase(i);
it = chords.erase(it);
continue;
}
}
@ -173,7 +176,7 @@ void collectChords(QList<MTrack> &tracks, int minNoteDuration)
int pitch = note.pitch;
if (drumset->isValid(pitch)) {
useDrumset = true;
i->second.voice = drumset->voice(pitch);
it->second.voice = drumset->voice(pitch);
}
}
startTime = note.onTime;
@ -183,7 +186,7 @@ void collectChords(QList<MTrack> &tracks, int minNoteDuration)
if (curThreshTime != threshTime)
curThreshTime = threshTime;
}
++i;
++it;
}
}
}
@ -198,7 +201,7 @@ void sortNotesByPitch(std::multimap<int, MidiChord> &chords)
} pitchSort;
for (auto &chordEvent: chords) {
// in each chord sort notes by pitches
// in each chord sort notes by pitches
auto &notes = chordEvent.second.notes;
qSort(notes.begin(), notes.end(), pitchSort);
}
@ -214,7 +217,7 @@ void sortNotesByLength(std::multimap<int, MidiChord> &chords)
} lenSort;
for (auto &chordEvent: chords) {
// in each chord sort notes by pitches
// in each chord sort notes by pitches
auto &notes = chordEvent.second.notes;
qSort(notes.begin(), notes.end(), lenSort);
}
@ -234,23 +237,23 @@ void splitUnequalChords(QList<MTrack> &tracks)
auto &chord = chordEvent.second;
auto &notes = chord.notes;
int len;
for (auto i = notes.begin(); i != notes.end(); ) {
if (i == notes.begin())
len = i->len;
for (auto it = notes.begin(); it != notes.end(); ) {
if (it == notes.begin())
len = it->len;
else {
int newLen = i->len;
int newLen = it->len;
if (newLen != len) {
auto newChord = chord;
newChord.notes.clear();
newChord.duration = len;
for (int j = i - notes.begin(); j > 0; --j)
for (int j = it - notes.begin(); j > 0; --j)
newChord.notes.push_back(notes[j - 1]);
newChordEvents.push_back({chord.onTime, newChord});
i = notes.erase(notes.begin(), i);
it = notes.erase(notes.begin(), it);
continue;
}
}
++i;
++it;
}
chord.duration = notes.first().len;
}
@ -270,10 +273,12 @@ void quantizeAllTracks(QList<MTrack>& tracks, TimeSigMap* sigmap, int lastTick)
}
else {
for (int i = 0; i < tracks.size(); ++i) {
// pass current track index through MidiImportOperations
// for further usage
// pass current track index through MidiImportOperations
// for further usage
opers.setCurrentTrack(i);
Quantize::applyGridQuant(tracks[i].chords, sigmap, lastTick);
// Quantize::applyGridQuant(tracks[i].chords, sigmap, lastTick);
Quantize::quantizeChordsAndFindTuplets(tracks[i].tuplets, tracks[i].chords,
sigmap, lastTick);
}
}
}
@ -380,12 +385,45 @@ void MTrack::processMeta(int tick, const MidiEvent& mm)
}
}
// find tuplets over which duration lies
std::vector<TupletData>
MTrack::findTupletsForDuration(int voice, int barTick, int durationOnTime, int durationLen)
{
std::vector<TupletData> tupletsData;
if (tuplets.empty())
return tupletsData;
auto tupletIt = tuplets.lower_bound(durationOnTime + durationLen);
--tupletIt;
while (durationOnTime + durationLen > tupletIt->first
&& durationOnTime < tupletIt->first + tupletIt->second.len) {
if (tupletIt->second.voice == voice) {
// if tuplet and duration intersect each other
auto tupletData = tupletIt->second;
// convert tuplet onTime to local bar ticks
tupletData.onTime -= barTick;
tupletsData.push_back(tupletData);
}
--tupletIt;
}
struct {
bool operator()(const TupletData &d1, const TupletData &d2)
{
return (d1.len > d2.len);
}
} comparator;
// sort by tuplet length in desc order
sort(tupletsData.begin(), tupletsData.end(), comparator);
return tupletsData;
}
//---------------------------------------------------------
// fillGapsWithRests
// check for gap and fill with rest
//---------------------------------------------------------
void fillGapsWithRests(Score* score, int ctick, int restLen, int track)
void MTrack::fillGapsWithRests(Score* score, int voice, int ctick, int restLen, int track)
{
bool useDots = preferences.midiImportOperations.currentTrackOperations().useDots;
while (restLen > 0) {
@ -397,7 +435,7 @@ void fillGapsWithRests(Score* score, int ctick, int restLen, int track)
restLen = 0;
break;
}
// split rest on measure boundary
// split rest on measure boundary
if ((ctick + len) > measure->tick() + measure->ticks())
len = measure->tick() + measure->ticks() - ctick;
if (len >= measure->ticks()) {
@ -412,11 +450,19 @@ void fillGapsWithRests(Score* score, int ctick, int restLen, int track)
ctick += len;
}
else {
QList<TDuration> dl = Meter::toDurationList(ctick - measure->tick(),
ctick + len - measure->tick(),
// find tuplets over which duration is go
std::vector<TupletData> tupletsData = findTupletsForDuration(voice, measure->tick(),
ctick, len);
int startTickInBar = ctick - measure->tick();
int endTickInBar = startTickInBar + len;
QList<TDuration> dl = Meter::toDurationList(startTickInBar,
endTickInBar,
measure->timesig(),
tupletsData,
Meter::DurationType::REST,
useDots);
if (dl.isEmpty()) {
qDebug("cannot create duration list for len %d", len);
restLen = 0; // fake
@ -457,13 +503,19 @@ void MTrack::processPendingNotes(QList<MidiChord>& chords, int voice, int ctick,
len = c.duration;
}
Measure* measure = score->tick2measure(tick);
// split notes on measure boundary
// split notes on measure boundary
if ((tick + len) > measure->tick() + measure->ticks())
len = measure->tick() + measure->ticks() - tick;
// find tuplets over which duration is go
std::vector<TupletData> tupletsData = findTupletsForDuration(voice, measure->tick(),
tick, len);
QList<TDuration> dl = Meter::toDurationList(tick - measure->tick(),
tick + len - measure->tick(),
int startTickInBar = tick - measure->tick();
int endTickInBar = startTickInBar + len;
QList<TDuration> dl = Meter::toDurationList(startTickInBar,
endTickInBar,
measure->timesig(),
tupletsData,
Meter::DurationType::NOTE,
useDots);
@ -533,8 +585,8 @@ void MTrack::processPendingNotes(QList<MidiChord>& chords, int voice, int ctick,
ctick += len;
}
if (voice == 0)
fillGapsWithRests(score, ctick, t - ctick, track);
// if (voice == 0)
fillGapsWithRests(score, voice, ctick, t - ctick, track);
}
//---------------------------------------------------------
@ -552,22 +604,22 @@ void MTrack::convertTrack(int lastTick)
QList<MidiChord> notes;
int ctick = 0;
for (auto i = chords.begin(); i != chords.end();) {
const MidiChord& e = i->second;
for (auto it = chords.begin(); it != chords.end();) {
const MidiChord& e = it->second;
if (e.voice != voice) {
++i;
++it;
continue;
}
processPendingNotes(notes, voice, ctick, i->first);
processPendingNotes(notes, voice, ctick, it->first);
//
// collect all notes on current
// tick position
//
ctick = i->first; // debug
for (;i != chords.end(); ++i) {
const MidiChord& e = i->second;
if (i->first != ctick)
ctick = it->first; // debug
for (;it != chords.end(); ++it) {
const MidiChord& e = it->second;
if (it->first != ctick)
break;
if (e.voice != voice)
continue;
@ -585,9 +637,9 @@ void MTrack::convertTrack(int lastTick)
ks.setAccidentalType(key);
(*km)[0] = ks;
}
for (auto i = km->begin(); i != km->end(); ++i) {
int tick = i->first;
KeySigEvent key = i->second;
for (auto it = km->begin(); it != km->end(); ++it) {
int tick = it->first;
KeySigEvent key = it->second;
KeySig* ks = new KeySig(score);
ks->setTrack(track);
ks->setGenerated(false);
@ -721,17 +773,16 @@ void splitIntoLRHands_HandWidth(QList<MTrack> &tracks, int &trackIndex)
sortNotesByPitch(srcTrack.chords);
const int OCTAVE = 12;
std::multimap<int, MidiChord> leftHandChords;
// chords after MIDI import are sorted by onTime values
// chords after MIDI import are sorted by onTime values
for (auto i = srcTrack.chords.begin(); i != srcTrack.chords.end(); ++i) {
auto &notes = i->second.notes;
QList<MidiNote> leftHandNotes;
int minPitch = notes.front().pitch;
int maxPitch = notes.back().pitch;
if (maxPitch - minPitch > OCTAVE) {
// need both hands
// assign all chords in range [minPitch .. minPitch + OCTAVE] to left hand
// and assign all other chords to right hand
// need both hands
// assign all chords in range [minPitch .. minPitch + OCTAVE] to left hand
// and assign all other chords to right hand
for (auto j = notes.begin(); j != notes.end(); ) {
auto &note = *j;
if (note.pitch <= minPitch + OCTAVE) {
@ -744,8 +795,8 @@ void splitIntoLRHands_HandWidth(QList<MTrack> &tracks, int &trackIndex)
// chords to another, third track
}
}
else { // check - use two hands or one hand will be enough (right or left?)
// assign top chord for right hand, all the rest - to left hand
else { // check - use two hands or one hand will be enough (right or left?)
// assign top chord for right hand, all the rest - to left hand
while (notes.size() > 1) {
leftHandNotes.push_back(notes.front());
notes.erase(notes.begin());
@ -791,21 +842,14 @@ void createMTrackList(int& lastTick, TimeSigMap* sigmap, QList<MTrack>& tracks,
MTrack track;
track.mtrack = t;
int events = 0;
// - create time signature list from meta events
// - create MidiChord list
// - extract some information from track: program, min/max pitch
// - create time signature list from meta events
// - create MidiChord list
// - extract some information from track: program, min/max pitch
for (auto i : t->events()) {
const MidiEvent& e = i.second;
//
// change division to MScore::division
//
// change division to MScore::division
int tick = (i.first * MScore::division + mf->division()/2) / mf->division();
//
// remove time signature events
//
// remove time signature events
if ((e.type() == ME_META) && (e.metaType() == META_TIME_SIGNATURE))
sigmap->add(tick, metaTimeSignature(e));
else if (e.type() == ME_NOTE) {
@ -866,7 +910,7 @@ void createInstruments(Score* score, QList<MTrack>& tracks)
track.staff = s;
if (track.mtrack->drumTrack()) {
// drum track
// drum track
s->setInitialClef(CLEF_PERC);
part->instr()->setDrumset(smDrumset);
}
@ -874,8 +918,8 @@ void createInstruments(Score* score, QList<MTrack>& tracks)
if ((idx < (ntracks-1))
&& (tracks.at(idx+1).mtrack->outChannel() == track.mtrack->outChannel())
&& (track.program == 0)) {
// assume that the current track and the next track
// form a piano part
// assume that the current track and the next track
// form a piano part
Staff* ss = new Staff(score, part, 1);
part->insertStaff(ss);
score->staves().push_back(ss);
@ -888,7 +932,7 @@ void createInstruments(Score* score, QList<MTrack>& tracks)
tracks[idx].staff = ss;
}
else {
// other track type
// other track type
ClefType ct = track.medPitch < 58 ? CLEF_F : CLEF_G;
s->setInitialClef(ct);
}
@ -902,7 +946,7 @@ void createMeasures(int& lastTick, Score* score)
int bars, beat, tick;
score->sigmap()->tickValues(lastTick, &bars, &beat, &tick);
if (beat > 0 || tick > 0)
++bars; // convert bar index to number of bars
++bars; // convert bar index to number of bars
for (int i = 0; i < bars; ++i) {
Measure* measure = new Measure(score);
@ -977,9 +1021,8 @@ void createNotes(int lastTick, QList<MTrack>& tracks, MidiFile* mf)
mt.processMeta(ie.first, e);
}
setTrackInfo(mf, mt);
// pass current track index to the convertTrack function
// through MidiImportOperations
// pass current track index to the convertTrack function
// through MidiImportOperations
preferences.midiImportOperations.setCurrentTrack(i);
mt.convertTrack(lastTick);
@ -1020,7 +1063,7 @@ void convertMidi(Score* score, MidiFile* mf)
mf->separateChannel();
createMTrackList(lastTick, sigmap, tracks, mf);
collectChords(tracks, MScore::division / 32); // tol = 1/128 note
collectChords(tracks, MScore::division / 32); // tol = 1/128 note
quantizeAllTracks(tracks, sigmap, lastTick);
removeOverlappingNotes(tracks);
splitIntoLeftRightHands(tracks);

View file

@ -2,6 +2,7 @@
#include "libmscore/fraction.h"
#include "libmscore/durationtype.h"
#include "libmscore/mscore.h"
#include "importmidi_tupletdata.h"
#include <memory>
@ -9,6 +10,10 @@
namespace Ms {
namespace Meter {
// max level for tuplets: duration cannot go over the tuplet boundary
// this level should be greater than any other level
const int TUPLET_BOUNDARY_LEVEL = 10;
struct MaxLevel
{
int level = 0; // 0 - the biggest, whole bar level; other: -1, -2, ...
@ -23,12 +28,12 @@ bool isSimple(const Fraction &barFraction) // 2/2, 3/4, 4/4, ...
bool isCompound(const Fraction &barFraction) // 6/8, 12/4, ...
{
return (barFraction.numerator() % 3 == 0 && barFraction.numerator() > 3);
return barFraction.numerator() % 3 == 0 && barFraction.numerator() > 3;
}
bool isComplex(const Fraction &barFraction) // 5/4, 7/8, ...
{
return !isSimple(barFraction) && !isCompound(barFraction);
return barFraction.numerator() == 5 || barFraction.numerator() == 7;
}
bool isDuple(const Fraction &barFraction) // 2/2, 6/8, ...
@ -46,106 +51,204 @@ bool isQuadruple(const Fraction &barFraction) // 4/4, 12/8, ...
return barFraction.numerator() % 4 == 0;
}
int levelOfTick(int tick, const QList<int> &divLevels)
int minAllowedDuration()
{
return MScore::division / 32; // smallest allowed duration is 1/128
}
struct DivLengthInfo
{
int len;
int level;
};
struct DivisionInfo
{
int onTime = 0; // division start tick (tick is counted from the beginning of bar)
int len = 0; // length of this whole division
bool isTuplet = false;
std::vector<DivLengthInfo> divLengths; // lengths of len subdivisions
};
// list of bar division lengths in ticks (whole bar len, half bar len, ...)
// and its corresponding levels
DivisionInfo metricDivisionsOfBar(const Fraction &barFraction)
{
int barLen = barFraction.ticks();
DivisionInfo barDivInfo;
barDivInfo.onTime = 0;
barDivInfo.len = barLen;
// first value of each element in list is a length (in ticks) of every part of bar
// on which bar is subdivided on each level
// the level value is a second value of each element
auto &divLengths = barDivInfo.divLengths;
int level = 0;
for (const auto &divLen: divLevels) {
if (divLen > 0) {
if (tick % divLen == 0)
return level;
divLengths.push_back({barLen, level});
// pulse-level division
if (Meter::isDuple(barFraction))
divLengths.push_back({barLen / 2, --level});
else if (Meter::isTriple(barFraction))
divLengths.push_back({barLen / 3, --level});
else if (Meter::isQuadruple(barFraction)) {
divLengths.push_back({barLen / 2, --level}); // additional central accent
divLengths.push_back({barLen / 4, --level});
}
else {
// if complex meter - not a complete solution: pos of central accent is unknown
divLengths.push_back({barLen / barFraction.numerator(), --level});
}
if (Meter::isCompound(barFraction)) {
--level; // additional min level for pulse divisions
// subdivide pulse of compound meter into 3 parts
divLengths.push_back({divLengths.back().len / 3, --level});
}
while (divLengths.back().len >= 2 * minAllowedDuration())
divLengths.push_back({divLengths.back().len / 2, --level});
return barDivInfo;
}
DivisionInfo metricDivisionsOfTuplet(const TupletData &tuplet,
int startLevel)
{
DivisionInfo tupletDivInfo;
tupletDivInfo.onTime = tuplet.onTime;
tupletDivInfo.len = tuplet.len;
tupletDivInfo.isTuplet = true;
int divLen = tuplet.len / tuplet.tupletNumber;
tupletDivInfo.divLengths.push_back({divLen, TUPLET_BOUNDARY_LEVEL});
while (tupletDivInfo.divLengths.back().len >= 2 * minAllowedDuration())
tupletDivInfo.divLengths.push_back({
tupletDivInfo.divLengths.back().len / 2, --startLevel
});
return tupletDivInfo;
}
int beatLength(const Fraction &barFraction)
{
int barLen = barFraction.ticks();
int beatLen = barLen / 4;
if (Meter::isDuple(barFraction))
beatLen = barLen / 2;
else if (Meter::isTriple(barFraction))
beatLen = barLen / 3;
else if (Meter::isQuadruple(barFraction))
beatLen = barLen / 4;
else if (Meter::isComplex(barFraction))
beatLen = barLen / barFraction.numerator();
return beatLen;
}
std::vector<int> divisionsOfBarForTuplets(const Fraction &barFraction)
{
DivisionInfo info = metricDivisionsOfBar(barFraction);
std::vector<int> divLengths;
int beatLen = beatLength(barFraction);
for (const auto &i: info.divLengths) {
// in compound meter tuplet starts from beat level, not the whole bar
if (Meter::isCompound(barFraction) && i.len > beatLen)
continue;
divLengths.push_back(i.len);
}
return divLengths;
}
// result in vector: first - all tuplets info, at the end - one bar division info
std::vector<DivisionInfo> divisionInfo(const Fraction &barFraction,
const std::vector<TupletData> &tupletsInBar)
{
std::vector<DivisionInfo> divsInfo;
auto barDivisionInfo = metricDivisionsOfBar(barFraction);
for (const auto &tuplet: tupletsInBar) {
int tupletStartLevel = 0;
for (const auto &divLenInfo: barDivisionInfo.divLengths) {
if (divLenInfo.len == tuplet.len) {
tupletStartLevel = divLenInfo.level;
break;
}
}
divsInfo.push_back(metricDivisionsOfTuplet(tuplet, tupletStartLevel));
}
divsInfo.push_back(barDivisionInfo);
return divsInfo;
}
// tick is counted from the beginning of bar
int levelOfTick(int tick, const std::vector<DivisionInfo> &divsInfo)
{
for (const auto &divInfo: divsInfo) {
if (tick < divInfo.onTime || tick > divInfo.onTime + divInfo.len)
continue;
for (const auto &divLenInfo: divInfo.divLengths) {
if ((tick - divInfo.onTime) % divLenInfo.len == 0)
return divLenInfo.level;
}
}
return 0;
}
Meter::MaxLevel maxLevelBetween(int startTickInDivision,
int endTickInDivision,
const DivisionInfo &divInfo)
{
Meter::MaxLevel level;
for (const auto &divLengthInfo: divInfo.divLengths) {
int divLen = divLengthInfo.len;
int maxEndRaster = (endTickInDivision / divLen) * divLen;
if (maxEndRaster == endTickInDivision)
maxEndRaster -= divLen;
if (startTickInDivision < maxEndRaster) {
// max level is found
level.lastPos = maxEndRaster;
int maxStartRaster = (startTickInDivision / divLen) * divLen;
level.levelCount = (maxEndRaster - maxStartRaster) / divLen;
level.level = divLengthInfo.level;
break;
}
--level;
}
return level;
}
std::vector<int> metricLevelsOfBar(const Fraction &barFraction,
const QList<int> &divLevels,
int minDuration)
{
if (minDuration <= 0)
minDuration = MScore::division / 32;
int ticksInBar = barFraction.ticks();
std::vector<int> levels;
for (int tick = 0; tick <= ticksInBar; tick += minDuration)
levels.push_back(levelOfTick(tick, divLevels));
return levels;
}
// list of bar division lengths in ticks (whole bar len, half bar len, ...)
QList<int> metricDivisionsOfBar(const Fraction &barFraction)
{
int barLen = barFraction.ticks();
// value in list is a length (in ticks) of every part of bar
// on which bar is subdivided on each level
QList<int> divLevels;
divLevels.append(barLen);
// pulse-level division
if (Meter::isDuple(barFraction))
divLevels.append(barLen / 2);
else if (Meter::isTriple(barFraction))
divLevels.append(barLen / 3);
else if (Meter::isQuadruple(barFraction)) {
divLevels.append(barLen / 2); // additional central accent
divLevels.append(barLen / 4);
}
else {
// if complex meter - not a complete solution: pos of central accent is unknown
// so select the pos closest to center
divLevels.append(barLen / (barFraction.numerator() / 2)); // additional central accent
divLevels.append(barLen / barFraction.numerator());
}
if (Meter::isCompound(barFraction)) {
// add invalid level to make further levels -1 smaller
// (not so significant compared to beats)
int pulseLen = divLevels.last();
divLevels.append(-1);
// subdivide pulse of compound meter into 3 parts
divLevels.append(pulseLen / 3);
}
// smallest allowed subdivision is 1/128
const int minDuration = MScore::division / 32;
// subdivide duration further
while ((divLevels.last() % 2 == 0) && (divLevels.last() > minDuration))
divLevels.append(divLevels.last() / 2);
return divLevels;
}
std::vector<int> metricLevelsOfBar(const Fraction &barFraction, int minDuration)
{
QList<int> divLevels = metricDivisionsOfBar(barFraction);
return metricLevelsOfBar(barFraction, divLevels, minDuration);
}
Meter::MaxLevel maxLevelBetween(int startTickInBar, int endTickInBar,
const QList<int> &divLevels)
Meter::MaxLevel maxLevelBetween(int startTickInBar,
int endTickInBar,
const std::vector<DivisionInfo> &divsInfo)
{
Meter::MaxLevel level;
// (begin() + 1) because bar start (max level) cannot be between
// level = -1 because we start from half bar division
level.level = -1;
for (auto p = divLevels.begin() + 1; p != divLevels.end(); ++p) {
int divLen = *p;
if (divLen > 0) {
int maxEndRaster = (endTickInBar / divLen) * divLen;
if (maxEndRaster == endTickInBar)
maxEndRaster -= divLen;
if (startTickInBar < maxEndRaster) {
// max level found
level.lastPos = maxEndRaster;
int maxStartRaster = (startTickInBar / divLen) * divLen;
level.levelCount = (maxEndRaster - maxStartRaster) / divLen;
for (const auto &divInfo: divsInfo) {
if (divInfo.isTuplet) {
if (startTickInBar < divInfo.onTime
&& endTickInBar >= divInfo.onTime + divInfo.len) {
level.level = TUPLET_BOUNDARY_LEVEL;
level.levelCount = 1;
level.lastPos = divInfo.onTime;
break;
}
if (startTickInBar == divInfo.onTime
&& endTickInBar > divInfo.onTime + divInfo.len) {
level.level = TUPLET_BOUNDARY_LEVEL;
level.levelCount = 1;
level.lastPos = divInfo.onTime + divInfo.len;
break;
}
if (startTickInBar == divInfo.onTime
&& endTickInBar == divInfo.onTime + divInfo.len) {
level = maxLevelBetween(startTickInBar - divInfo.onTime,
endTickInBar - divInfo.onTime,
divInfo);
break;
}
}
--level.level;
else
level = maxLevelBetween(startTickInBar, endTickInBar, divInfo);
}
return level;
}
@ -171,24 +274,12 @@ bool isQuarterDuration(int ticks)
return (f.numerator() == 1 && f.denominator() == 4);
}
int beatLength(const Fraction &barFraction)
{
int barLen = barFraction.ticks();
int beatLen = barLen / 4;
if (Meter::isDuple(barFraction))
beatLen = barLen / 2;
else if (Meter::isTriple(barFraction))
beatLen = barLen / 3;
else if (Meter::isQuadruple(barFraction))
beatLen = barLen / 4;
return beatLen;
}
// If last 2/3 of beat in compound meter is rest,
// it should be splitted into 2 rests
bool is23EndOfBeatInCompoundMeter(int startTickInBar, int endTickInBar,
const Fraction &barFraction)
bool is23EndOfBeatInCompoundMeter(int startTickInBar,
int endTickInBar,
const Fraction &barFraction)
{
if (endTickInBar - startTickInBar <= 0)
return false;
@ -213,8 +304,9 @@ bool isHalfDuration(int ticks)
// 3/4: if half rest starts from beg of bar or ends on bar end
// then it is a bad practice - need to split rest into 2 quarter rests
bool isHalfRestOn34(int startTickInBar, int endTickInBar,
const Fraction &barFraction)
bool isHalfRestOn34(int startTickInBar,
int endTickInBar,
const Fraction &barFraction)
{
if (endTickInBar - startTickInBar <= 0)
return false;
@ -246,7 +338,9 @@ struct Node
std::unique_ptr<Node> right;
};
void treeToDurationList(Node *node, QList<TDuration> &dl, bool useDots)
void treeToDurationList(Node *node,
QList<TDuration> &dl,
bool useDots)
{
if (node->left != nullptr && node->right != nullptr) {
treeToDurationList(node->left.get(), dl, useDots);
@ -259,45 +353,42 @@ void treeToDurationList(Node *node, QList<TDuration> &dl, bool useDots)
// duration start/end should be quantisized at least to 1/128 note
QList<TDuration> toDurationList(int startTickInBar, int endTickInBar,
const Fraction &barFraction, DurationType durationType,
QList<TDuration> toDurationList(int startTickInBar,
int endTickInBar,
const Fraction &barFraction,
const std::vector<TupletData> &tupletsInBar,
DurationType durationType,
bool useDots)
{
QList<TDuration> durations;
if (startTickInBar < 0 || endTickInBar <= startTickInBar
|| endTickInBar > barFraction.ticks())
return durations;
// analyse mectric structure of bar
QList<int> divLevels = metricDivisionsOfBar(barFraction);
// create a root for binary tree of durationsstd::multimap<int, MidiChord>
// analyse mectric structure of bar
auto divInfo = divisionInfo(barFraction, tupletsInBar);
// create a root for binary tree of durationsstd::multimap<int, MidiChord>
std::unique_ptr<Node> root(new Node(startTickInBar, endTickInBar,
levelOfTick(startTickInBar, divLevels),
levelOfTick(endTickInBar, divLevels)));
QQueue<Node *> q;
q.enqueue(root.get());
// max allowed difference between start/end level of duration and split point level
levelOfTick(startTickInBar, divInfo),
levelOfTick(endTickInBar, divInfo)));
QQueue<Node *> nodesToProcess;
nodesToProcess.enqueue(root.get());
// max allowed difference between start/end level of duration and split point level
int tol = 0;
if (durationType == DurationType::NOTE)
tol = 1;
else if (durationType == DurationType::REST)
tol = 0;
// the smallest allowed duration of node before splitting is 1/64
// the lowest division level duration is 1/128
// so each child node duration after division is not less than 1/128
const int minDuration = MScore::division / 16;
// build duration tree such that durations don't go across strong beat divisions
while (!q.isEmpty()) {
Node *node = q.dequeue();
// don't split node if its duration is less than minDuration
// each child node duration after division is not less than minDuration()
const int minDuration = minAllowedDuration() * 2;
// build duration tree such that durations don't go across strong beat divisions
while (!nodesToProcess.isEmpty()) {
Node *node = nodesToProcess.dequeue();
// don't split node if its duration is less than minDuration
if (node->endTick - node->startTick < minDuration)
continue;
auto splitPoint = maxLevelBetween(node->startTick, node->endTick, divLevels);
// sum levels if there are several positions (beats) with max level value
// for example, 8th + half duration + 8th in 3/4, and half is over two beats
auto splitPoint = maxLevelBetween(node->startTick, node->endTick, divInfo);
// sum levels if there are several positions (beats) with max level value
// for example, 8th + half duration + 8th in 3/4, and half is over two beats
int effectiveLevel = splitPoint.level + splitPoint.levelCount - 1;
if (effectiveLevel - node->startLevel > tol
|| effectiveLevel - node->endLevel > tol
@ -306,21 +397,19 @@ QList<TDuration> toDurationList(int startTickInBar, int endTickInBar,
&& is23EndOfBeatInCompoundMeter(node->startTick, node->endTick, barFraction))
)
{
// split duration by splitPoint position
// split duration in splitPoint position
node->left.reset(new Node(node->startTick, splitPoint.lastPos,
node->startLevel, splitPoint.level));
node->left->parent = node;
q.enqueue(node->left.get());
nodesToProcess.enqueue(node->left.get());
node->right.reset(new Node(splitPoint.lastPos, node->endTick,
splitPoint.level, node->endLevel));
node->right->parent = node;
q.enqueue(node->right.get());
nodesToProcess.enqueue(node->right.get());
}
}
// collect the resulting durations
// collect the resulting durations
treeToDurationList(root.get(), durations, useDots);
return durations;
}

View file

@ -6,6 +6,7 @@ namespace Ms {
class Fraction;
class TDuration;
struct TupletData;
namespace Meter {
@ -22,13 +23,18 @@ bool isDuple(const Fraction &barFraction);
bool isTriple(const Fraction &barFraction);
bool isQuadruple(const Fraction &barFraction);
int beatLength(const Fraction &barFraction);
// list of levels (0, -1, ...) of all ticks in bar that are multiples of minDuration
// if minDuration <= 0 then minDuration will be set to min allowed note length (1/128)
std::vector<int> metricLevelsOfBar(const Fraction &barFraction, int minDuration);
// division lengths of bar, each can be a tuplet length
std::vector<int> divisionsOfBarForTuplets(const Fraction &barFraction);
QList<TDuration> toDurationList(int startTickInBar, int endTickInBar,
const Fraction &barFraction, DurationType durationType,
// duration and all tuplets should belong to the same voice
// nested tuplets are not allowed
QList<TDuration> toDurationList(int startTickInBar,
int endTickInBar,
const Fraction &barFraction,
const std::vector<TupletData> &tupletsInBar,
DurationType durationType,
bool useDots);
} // namespace Meter

View file

@ -30,13 +30,9 @@ struct MidiOperation
SHORTEST_IN_BAR = 0,
FROM_PREFERENCES,
N_4,
// N_4_triplet,
N_8,
// N_8_triplet,
N_16,
// N_16_triplet,
N_32,
// N_32_triplet,
N_64
};

View file

@ -21,7 +21,7 @@ struct LHRHSeparation
MidiOperation::Note splitPitchNote = MidiOperation::Note::E;
};
// bool and enum-like elementary operations (itself and inside structs) allowed
// bool and enum-like elementary operations (itself and inside structs) allowed
struct TrackOperations
{
bool doImport = true;

View file

@ -31,10 +31,8 @@ OperationsModel::OperationsModel()
, controller(std::unique_ptr<Controller>(new Controller()))
{
beginResetModel();
// - initialize opeations with their default values
// - string lists below should match Operation enum values
// - initialize opeations with their default values
// - string lists below should match Operation enum values
Node *quantValue = new Node;
quantValue->name = "Quantization";
quantValue->oper.type = MidiOperation::Type::QUANT_VALUE;
@ -42,13 +40,9 @@ OperationsModel::OperationsModel()
quantValue->values.push_back("Shortest note in bar");
quantValue->values.push_back("Value from preferences");
quantValue->values.push_back("1/4");
// quantValue->values.push_back("1/4 triplet");
quantValue->values.push_back("1/8");
// quantValue->values.push_back("1/8 triplet");
quantValue->values.push_back("1/16");
// quantValue->values.push_back("1/16 triplet");
quantValue->values.push_back("1/32");
// quantValue->values.push_back("1/32 triplet");
quantValue->values.push_back("1/64");
quantValue->parent = root.get();
root->children.push_back(std::unique_ptr<Node>(quantValue));
@ -167,7 +161,7 @@ QModelIndex OperationsModel::index(int row, int column, const QModelIndex &paren
return QModelIndex();
if (parent_node->children.empty() || row >= (int)parent_node->children.size())
return QModelIndex();
// find new row in connection with invisible items
// find new row in connection with invisible items
int shift = 0;
for (int i = 0; i <= row + shift; ++i) {
if (i >= (int)parent_node->children.size())
@ -206,7 +200,7 @@ int OperationsModel::rowCount(const QModelIndex &parent) const
Node *parent_node = nodeFromIndex(parent);
if (!parent_node)
return 0;
// take only visible nodes into account
// take only visible nodes into account
size_t counter = 0;
for (const auto &p: parent_node->children)
if (p->visible)
@ -221,6 +215,7 @@ int OperationsModel::columnCount(const QModelIndex &parent) const
// All nodes can have either bool value or list of possible values
// also node value can be undefined (QVariant()), for example grayed checkbox
QVariant OperationsModel::data(const QModelIndex &index, int role) const
{
Node *node = nodeFromIndex(index);
@ -241,15 +236,15 @@ QVariant OperationsModel::data(const QModelIndex &index, int role) const
if (!node->values.empty()) {
if (!node->oper.value.isValid()) // undefined operation value
return " . . . ";
// list contains names of possible string values
// like {"1/4", "1/8"}
// valid node value is one of enum items
// -> use enum item as index
// list contains names of possible string values
// like {"1/4", "1/8"}
// valid node value is one of enum items
// -> use enum item as index
int indexOfValue = node->oper.value.toInt();
if (indexOfValue < node->values.size() && indexOfValue >= 0)
return node->values.at(indexOfValue);
}
// otherwise return nothing because it's a checkbox
// otherwise return nothing because it's a checkbox
break;
default:
break;
@ -304,9 +299,9 @@ Qt::ItemFlags OperationsModel::flags(const QModelIndex &index) const
return Qt::ItemFlags();
Qt::ItemFlags flags = Qt::ItemFlags(Qt::ItemIsEnabled);
if (index.column() == OperationCol::VALUE) {
if (node->values.empty()) // node value is bool - a checkbox
if (node->values.empty()) // node value is bool - a checkbox
flags |= Qt::ItemIsUserCheckable;
else // node has list of values
else // node has list of values
flags |= Qt::ItemIsEditable;
}
return flags;
@ -325,7 +320,7 @@ bool OperationsModel::setData(const QModelIndex &index, const QVariant &value, i
result = true;
break;
case Qt::EditRole:
// set enum value from value == list index
// set enum value from value == list index
node->oper.value = value.toInt();
result = true;
break;
@ -371,7 +366,7 @@ void setNodeOperations(Node *node, const DefinedTrackOperations &opers)
void OperationsModel::setTrackData(const QString &trackLabel, const DefinedTrackOperations &opers)
{
this->trackLabel = trackLabel;
// set new operations values
// set new operations values
beginResetModel();
for (const auto &nodePtr: root->children)
setNodeOperations(nodePtr.get(), opers);

View file

@ -134,7 +134,7 @@ void ImportMidiPanel::updateUi()
ui->lineEditMidiFile->setStyleSheet("QLineEdit{color: black;}");
ui->lineEditMidiFile->setToolTip(midiFile);
}
else { // midi file not exists
else { // midi file does not exist
ui->lineEditMidiFile->setStyleSheet("QLineEdit{color: red;}");
ui->lineEditMidiFile->setToolTip(tr("MIDI file not found"));
}

View file

@ -6,6 +6,9 @@
#include "preferences.h"
#include "importmidi_chord.h"
#include "importmidi_meter.h"
#include "importmidi_tupletdata.h"
#include <set>
namespace Ms {
@ -27,19 +30,15 @@ int shortestNoteInBar(const std::multimap<int, MidiChord> &chords,
{
int division = MScore::division;
int minDuration = division;
// find shortest note in measure
//
for (auto i = start; i != chords.end(); ++i) {
if (i->first >= endBarTick)
// find shortest note in measure
for (auto it = start; it != chords.end(); ++it) {
if (it->first >= endBarTick)
break;
for (const auto &note: i->second.notes)
for (const auto &note: it->second.notes)
minDuration = qMin(minDuration, note.len);
}
//
// determine suitable quantization value based
// on shortest note in measure
//
// determine suitable quantization value based
// on shortest note in measure
int div = division;
if (minDuration <= division / 16) // minimum duration is 1/64
div = division / 16;
@ -69,32 +68,20 @@ int userQuantNoteToTicks(MidiOperation::QuantValue quantNote)
{
int division = MScore::division;
int userQuantValue = preferences.shortestNote;
// specified quantization value
// specified quantization value
switch (quantNote) {
case MidiOperation::QuantValue::N_4:
userQuantValue = division;
break;
// case MidiOperation::QuantValue::N_4_triplet:
// userQuantValue = division * 2 / 3;
// break;
case MidiOperation::QuantValue::N_8:
userQuantValue = division / 2;
break;
// case MidiOperation::QuantValue::N_8_triplet:
// userQuantValue = division / 3;
// break;
case MidiOperation::QuantValue::N_16:
userQuantValue = division / 4;
break;
// case MidiOperation::QuantValue::N_16_triplet:
// userQuantValue = division / 6;
// break;
case MidiOperation::QuantValue::N_32:
userQuantValue = division / 8;
break;
// case MidiOperation::QuantValue::N_32_triplet:
// userQuantValue = division / 12;
// break;
case MidiOperation::QuantValue::N_64:
userQuantValue = division / 16;
break;
@ -113,14 +100,13 @@ int findQuantRaster(const std::multimap<int, MidiChord> &chords,
{
int raster;
auto operations = preferences.midiImportOperations.currentTrackOperations();
// find raster value for quantization
// find raster value for quantization
if (operations.quantize.value == MidiOperation::QuantValue::SHORTEST_IN_BAR)
raster = shortestNoteInBar(chords, startChordIter, 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 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 = qMin(userQuantValue, raster);
@ -131,6 +117,8 @@ int findQuantRaster(const std::multimap<int, MidiChord> &chords,
return raster;
}
// 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,
@ -138,14 +126,16 @@ void doGridQuantizationOfBar(const std::multimap<int, MidiChord> &chords,
int endBarTick)
{
int raster2 = raster >> 1;
for (auto i = startChordIter; i != chords.end(); ++i) {
if (i->first >= endBarTick)
for (auto it = startChordIter; it != chords.end(); ++it) {
if (it->first >= endBarTick)
break;
auto chord = i->second;
if (quantizedChords.find(it->first) != quantizedChords.end())
continue;
auto chord = it->second;
chord.onTime = ((chord.onTime + raster2) / raster) * raster;
for (auto &note: chord.notes) {
note.onTime = chord.onTime;
note.len = quantizeLen(note.len, raster);
note.len = quantizeLen(note.len, raster);
}
quantizedChords.insert({chord.onTime, chord});
}
@ -154,15 +144,15 @@ 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)
{
auto i = chords.begin();
for (; i != chords.end(); ++i) {
if (i->first >= startBarTick) {
if (i->first >= endBarTick)
auto it = chords.begin();
for (; it != chords.end(); ++it) {
if (it->first >= startBarTick) {
if (it->first >= endBarTick)
return chords.end();
break;
}
}
return i;
return it;
}
void quantizeChordsOfBar(const std::multimap<int, MidiChord> &chords,
@ -171,7 +161,7 @@ void quantizeChordsOfBar(const std::multimap<int, MidiChord> &chords,
int endBarTick)
{
auto startChordIter = findFirstChordInBar(chords, startBarTick, endBarTick);
if (startChordIter == chords.end()) // if no chords found in this bar
if (startChordIter == chords.end()) // if no chords found in this bar
return;
int raster = findQuantRaster(chords, startChordIter, endBarTick);
doGridQuantizationOfBar(chords, quantizedChords, startChordIter, raster, endBarTick);
@ -183,14 +173,435 @@ void applyGridQuant(std::multimap<int, MidiChord> &chords,
{
std::multimap<int, MidiChord> quantizedChords;
int startBarTick = 0;
for (int i = 1;; ++i) { // iterate over all measures by indexes
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;
}
chords = quantizedChords;
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)
{
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 (auto num: nums) {
if (tupletNumber == num && tupletInfo.chords.size() == 1
&& tupletInfo.chords.begin()->first == 0) {
auto &chordEventIt = tupletInfo.chords.begin()->second;
for (const auto &note: chordEventIt->second.notes) {
if (std::abs(note.len - divLen / 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) {
return false;
}
int tupletNoteLen = tupletInfo.len / tupletInfo.tupletNumber;
for (const auto &tupletChord: tupletInfo.chords) {
for (const auto &note: tupletChord.second->second.notes) {
if (note.len >= tupletNoteLen / 2)
return true;
}
}
return false;
}
std::vector<int> findTupletNumbers(int divLen, const Fraction &barFraction)
{
std::vector<int> tupletNumbers;
if (Meter::isCompound(barFraction) && divLen == Meter::beatLength(barFraction))
tupletNumbers = {2, 4}; // duplets and quadruplets
else
tupletNumbers = {3, 5, 7};
return tupletNumbers;
}
int findOnTimeRegularError(int onTime, int quantValue)
{
int regularPos = ((onTime + quantValue / 2) / quantValue) * quantValue;
return std::abs(onTime - regularPos);
}
TupletInfo findTupletApproximation(int tupletNumber,
int tupletNoteLen,
int quantValue,
int startDivTime,
const std::multimap<int, MidiChord>::iterator &startDivChordIt,
const std::multimap<int, MidiChord>::iterator &endDivChordIt)
{
TupletInfo tupletInfo;
tupletInfo.tupletNumber = tupletNumber;
auto startTupletChordIt = startDivChordIt;
for (int k = 0; k != tupletNumber; ++k) {
int tupletNotePos = startDivTime + k * tupletNoteLen;
// choose the best chord, if any, for this tuplet note
BestChord bestChord = findBestChordForTupletNote(tupletNotePos, quantValue,
startTupletChordIt, endDivChordIt);
if (bestChord.chordIter == endDivChordIt)
continue; // no chord fits to this tuplet note position
// chord can be in tuplet
tupletInfo.chords.insert({k, bestChord.chordIter});
tupletInfo.tupletOnTimeSumError += bestChord.minTupletError;
// for next tuplet note we start from the next chord
// because chord for the next tuplet note cannot be earlier
startTupletChordIt = bestChord.chordIter;
++startTupletChordIt;
// find chord quant error for a regular grid
int regularError = findOnTimeRegularError(bestChord.chordIter->first, quantValue);
tupletInfo.regularSumError += regularError;
}
return tupletInfo;
}
std::multimap<double, TupletInfo>
findTupletCandidatesOfBar(int startBarTick,
int endBarTick,
const Fraction &barFraction,
std::multimap<int, MidiChord> &chords)
{
std::multimap<double, TupletInfo> tupletCandidates; // average error, TupletInfo
if (chords.empty() || startBarTick >= endBarTick) // invalid cases
return tupletCandidates;
auto startBarChordIt = findFirstChordInBar(startBarTick, endBarTick, chords);
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);
int barLen = barFraction.ticks();
int quantValue = findQuantRaster(chords, startBarChordIt, endBarTick);
auto divLengths = Meter::divisionsOfBarForTuplets(barFraction);
for (const auto &divLen: divLengths) {
auto tupletNumbers = findTupletNumbers(divLen, barFraction);
int divCount = barLen / divLen;
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);
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);
// 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))
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
}
}
return tupletCandidates;
}
void markChordsAsUsed(std::map<int, int> &usedFirstTupletNotes,
std::set<int> &usedChords,
const std::map<int, std::multimap<int, MidiChord>::iterator> &tupletChords)
{
if (tupletChords.empty())
return;
auto i = tupletChords.begin();
auto chordEventIt = i->second;
int chordOnTime = chordEventIt->first;
int voice = 0;
// check is the note of the first tuplet chord in use
auto ii = usedFirstTupletNotes.find(chordOnTime);
if (ii == usedFirstTupletNotes.end())
ii = usedFirstTupletNotes.insert({chordOnTime, 1}).first;
else {
voice = ii->second; // voice = counter - 1
++(ii->second); // increase chord note counter
}
++i; // start from the second chord
for ( ; i != tupletChords.end(); ++i) {
// mark the chord as used
chordEventIt = i->second;
chordOnTime = chordEventIt->first;
usedChords.insert(chordOnTime);
chordEventIt->second.voice = voice;
}
}
bool isChordInUse(const std::map<int, int> &usedFirstTupletNotes,
const std::set<int> &usedChords,
const std::map<int, std::multimap<int, MidiChord>::iterator> &tupletChords)
{
auto i = tupletChords.begin();
// check are first tuplet notes all in use (1 note - 1 voice)
int chordOnTime = i->first;
auto ii = usedFirstTupletNotes.find(chordOnTime);
if (ii != usedFirstTupletNotes.end()) {
if (ii->second >= VOICES) {
// need to choose next tuplet candidate - no more available voices
return true;
}
}
++i; // start from the second chord
for ( ; i != tupletChords.end(); ++i) {
chordOnTime = i->first;
if (usedChords.find(chordOnTime) != usedChords.end()) {
// the chord note is in use - cannot use this chord again
return true;
}
}
return false;
}
// use case for this: first chord in tuplet can belong
// to any other tuplet at the same time
// if there are enough notes in this first chord
// to be splitted to different voices
// the same is for duplet second chord: it can belong to 4-tuplet
void filterTuplets(std::multimap<double, TupletInfo> &tuplets)
{
// structure of map: <tick, count of use of first tuplet chord with this tick>
std::map<int, int> usedFirstTupletNotes;
// onTime values - tick - of already used chords
std::set<int> usedChords;
// select tuplets with min average error
for (auto tc = tuplets.begin(); tc != tuplets.end(); ) { // tc - tuplet candidate
auto &tupletChords = tc->second.chords;
// check for chords notes already used in another tuplets
if (tupletChords.empty()
|| isChordInUse(usedFirstTupletNotes, usedChords, tupletChords)) {
tc = tuplets.erase(tc);
continue;
}
// we can use this tuplet
markChordsAsUsed(usedFirstTupletNotes, usedChords, tupletChords);
const auto &chordEventIt = tupletChords.begin()->second;
tc->second.voice = chordEventIt->second.voice;
++tc;
}
}
void quantizeTupletChord(MidiChord &midiChord, int onTime, const TupletInfo &tupletInfo)
{
midiChord.onTime = onTime;
midiChord.voice = tupletInfo.voice;
for (auto &note: midiChord.notes) {
int raster;
if (note.onTime + note.len <= tupletInfo.onTime + tupletInfo.len) {
// if offTime is inside the tuplet - quant by tuplet grid
raster = tupletInfo.tupletQuantValue;
}
else { // if offTime is outside the tuplet - quant by regular grid
raster = tupletInfo.regularQuantValue;
}
int offTime = ((note.onTime + note.len + raster / 2) / raster) * raster;
note.onTime = onTime;
note.len = offTime - onTime;
}
}
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
void quantizeChordsAndFindTuplets(std::multimap<int, TupletData> &tupletEvents,
std::multimap<int, MidiChord> &chords,
const TimeSigMap* sigmap,
int lastTick)
{
std::multimap<int, MidiChord> quantizedChords; // set of already quantized onTime values
// quantize chords in tuplets
int startBarTick = 0;
for (int i = 1;; ++i) { // iterate over all measures by indexes
int endBarTick = sigmap->bar2tick(i, 0);
Fraction barFraction = sigmap->timesig(startBarTick).timesig();
auto tuplets = findTupletCandidatesOfBar(startBarTick, endBarTick, barFraction, chords);
filterTuplets(tuplets);
for (auto &tuplet: tuplets) {
auto &tupletInfo = tuplet.second;
auto &infoChords = tupletInfo.chords;
for (auto &tupletChord: infoChords) {
int tupletNoteNum = tupletChord.first;
int onTime = tupletInfo.onTime + tupletNoteNum
* (tupletInfo.len / tupletInfo.tupletNumber);
std::multimap<int, MidiChord>::iterator &midiChordEventIt = tupletChord.second;
// quantize chord to onTime value
MidiChord midiChord = midiChordEventIt->second;
quantizeTupletChord(midiChord, onTime, tupletInfo);
quantizedChords.insert({onTime, midiChord});
}
TupletData tupletData = {tupletInfo.voice, tupletInfo.onTime,
tupletInfo.len, tupletInfo.tupletNumber};
tupletEvents.insert({tupletInfo.onTime, tupletData});
}
if (endBarTick > lastTick)
break;
startBarTick = endBarTick;
}
// quantize non-tuplet (remaining) chords with ordinary grid
quantizeNonTupletChords(chords, quantizedChords, lastTick, sigmap);
std::swap(chords, quantizedChords);
}
} // namespace Quantize

View file

@ -6,6 +6,7 @@ namespace Ms {
class MidiChord;
class TimeSigMap;
struct TupletData;
namespace Quantize {
@ -15,6 +16,11 @@ void applyGridQuant(std::multimap<int, MidiChord> &chords,
const TimeSigMap* sigmap,
int lastTick);
void quantizeChordsAndFindTuplets(std::multimap<int, TupletData> &tupletEvents,
std::multimap<int, MidiChord> &chords,
const TimeSigMap* sigmap,
int lastTick);
} // namespace Quantize
} // namespace Ms

View file

@ -0,0 +1,18 @@
#ifndef IMPORTMIDI_TUPLETDATA_H
#define IMPORTMIDI_TUPLETDATA_H
namespace Ms {
struct TupletData
{
int voice;
int onTime;
int len;
int tupletNumber;
};
} // namespace Ms
#endif // IMPORTMIDI_TUPLETDATA_H