Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MIDI import: optimization for speed #815

Merged
merged 54 commits into from
Apr 2, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
4f127ad
Bool flag instead of int counter in filterTuplets function
trig-ger Mar 15, 2014
b8ec722
Optimization: move tuplet interval search outside the loop
trig-ger Mar 15, 2014
a6539fa
Further optimization: precalculate all tuplet intervals
trig-ger Mar 15, 2014
6da90b0
Use regular raster from TupletInfo, not from function parameter
trig-ger Mar 15, 2014
13fb133
Fix addition of unused tuplet to tuplet intervals
trig-ger Mar 15, 2014
041b616
Optimize voice count calculation for tuplet error
trig-ger Mar 15, 2014
4160f75
Reorder conditions for small optimization
trig-ger Mar 16, 2014
0c3f1a2
Refactoring of isMoreTupletVoicesAllowed function
trig-ger Mar 16, 2014
1b74028
Less line breaks
trig-ger Mar 16, 2014
83b3af4
Reduce fraction only if numerator or denominator > some limit
trig-ger Mar 16, 2014
c92659e
Check fraction overflow only in Debug mode
trig-ger Mar 16, 2014
1b32fa0
Optimize tuplet validation
trig-ger Mar 16, 2014
e7443fa
Remove empty lines
trig-ger Mar 16, 2014
2447965
Optimize assert function (tuplet validation)
trig-ger Mar 16, 2014
18841b1
Fix of possible bug: now reset all usedVoices values
trig-ger Mar 16, 2014
2ef5e8c
Fix multiple times excluded chords error estimation
trig-ger Mar 16, 2014
4172afb
New findCommonIndexes function to find 3 and more tuplet groups
trig-ger Mar 17, 2014
4e2c430
Set permutation limit exactly to 8!
trig-ger Mar 17, 2014
d1d7f4f
Print debug message if tuplet permutation limit was exceeded
trig-ger Mar 17, 2014
6b8b0ff
Fix chords^2 execution time of removeOverlappingNotes function
trig-ger Mar 17, 2014
9002a29
Correct function description
trig-ger Mar 17, 2014
56f1392
Refactor condition
trig-ger Mar 18, 2014
1087de9
Small optimization of tuplet error calculation
trig-ger Mar 18, 2014
6020225
Refactor clef changes code
trig-ger Mar 20, 2014
5586a11
Remove check for chord emptiness in tuplets
trig-ger Mar 20, 2014
24064a4
Remove penalty array dependency from create clefs function
trig-ger Mar 22, 2014
d09968d
Reduce memory usage for penalties array (clef changes)
trig-ger Mar 22, 2014
dea29ff
Refactoring of filterTuplets: extract findCommonIndexes function
trig-ger Mar 22, 2014
44a0964
Refactoring of filterTuplets: extract findTupletIntervals function
trig-ger Mar 22, 2014
89aa2a6
Remove check for empty tuplets (it's done earlier)
trig-ger Mar 22, 2014
af84af8
Refactoring of tuplet error setting for shorter code
trig-ger Mar 22, 2014
ca4433e
Refactoring of filterTuplets: extract findBestTuplets function
trig-ger Mar 22, 2014
aca734d
Refactoring of tuplet filtering functions for clearer code
trig-ger Mar 22, 2014
4f0c713
Fix permutation limit condition
trig-ger Mar 22, 2014
a6e9c1c
Recursively find common tuplet groups to reduce permutation count
trig-ger Mar 22, 2014
b8c53b6
Fix chords^2 running time for doNotesOverlap debug function
trig-ger Mar 22, 2014
2e14731
New - faster tuplet filtering algorithm (instead of permutations)
trig-ger Mar 25, 2014
09377ea
Remove sorting of tuplets because there was no bonus speed
trig-ger Mar 27, 2014
5b3ddce
Remove saved tuplet indexes because tuplet ordering is unchanged
trig-ger Mar 27, 2014
79f9c99
More full recursive algorithm for tuplet filtering
trig-ger Mar 28, 2014
d7235ae
Remove not necessary function argument
trig-ger Mar 28, 2014
092118c
Refactoring of tuplet filtering code
trig-ger Mar 28, 2014
9ac8bf5
Always use max tuplet count possible - fix mtest
trig-ger Mar 29, 2014
21ad626
Filter group of totally common tuplets differently
trig-ger Mar 29, 2014
5f49b81
Refactoring: extract prepare voice intervals function
trig-ger Mar 29, 2014
38a3cf1
Fix quantization scheme
trig-ger Mar 30, 2014
407e5d1
Move check for emptiness into Q_ASSERT
trig-ger Mar 31, 2014
fca1664
Off time instead of note length
trig-ger Apr 1, 2014
9ec893c
Add const
trig-ger Apr 1, 2014
63e19bf
Information for quantization is taken from tuplets search stage
trig-ger Apr 1, 2014
d6b7e69
Fix first chord in tuplet that was placed outside the tuplet
trig-ger Apr 1, 2014
8e48cbd
Fix tuplet note staccato offTime
trig-ger Apr 2, 2014
8ca7067
Move overlapping notes check function
trig-ger Apr 2, 2014
d8e07ad
Move variable reset under if condition
trig-ger Apr 2, 2014
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
123 changes: 65 additions & 58 deletions mscore/importmidi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ void cleanUpMidiEvents(std::multimap<int, MTrack> &tracks)
for (auto chordIt = mtrack.chords.begin(); chordIt != mtrack.chords.end(); ) {
MidiChord &ch = chordIt->second;
for (auto noteIt = ch.notes.begin(); noteIt != ch.notes.end(); ) {
if ((noteIt->len < MChord::minAllowedDuration())
|| (!reduce && noteIt->len < raster / 2)) {
if ((noteIt->offTime - chordIt->first < MChord::minAllowedDuration())
|| (!reduce && noteIt->offTime - chordIt->first < raster / 2)) {
noteIt = ch.notes.erase(noteIt);
continue;
}
Expand All @@ -93,6 +93,57 @@ void cleanUpMidiEvents(std::multimap<int, MTrack> &tracks)
}
}


#ifdef QT_DEBUG

bool doNotesOverlap(const MTrack &track)
{
const auto &chords = track.chords;
for (auto i1 = chords.begin(); i1 != chords.end(); ++i1) {
const auto &chord1 = i1->second;
for (const auto &note1: chord1.notes) {
for (auto i2 = std::next(i1); i2 != chords.end(); ++i2) {
if (i2->first >= note1.offTime)
break;
const auto &chord2 = i2->second;
if (chord1.voice != chord2.voice)
continue;
for (const auto &note2: chord2.notes) {
if (note2.pitch != note1.pitch)
continue;
return true;
}
}
}
}
return false;
}

bool doNotesOverlap(const std::multimap<int, MTrack> &tracks)
{
bool result = false;
for (const auto &track: tracks)
result = doNotesOverlap(track.second);
return result;
}

bool noTooShortNotes(const std::multimap<int, MTrack> &tracks)
{
for (const auto &track: tracks) {
const auto &chords = track.second.chords;
for (const auto &chord: chords) {
for (const auto &note: chord.second.notes) {
if (note.offTime - chord.first < MChord::minAllowedDuration())
return false;
}
}
}
return true;
}

#endif


void quantizeAllTracks(std::multimap<int, MTrack> &tracks,
TimeSigMap *sigmap,
const ReducedFraction &lastTick)
Expand All @@ -105,6 +156,11 @@ void quantizeAllTracks(std::multimap<int, MTrack> &tracks,
opers.setCurrentTrack(mtrack.indexOfOperation);
opers.adaptForPercussion(mtrack.indexOfOperation, mtrack.mtrack->drumTrack());
mtrack.tuplets = MidiTuplet::findAllTuplets(mtrack.chords, sigmap, lastTick);

Q_ASSERT_X(!doNotesOverlap(track.second),
"quantizeAllTracks",
"There are overlapping notes of the same voice that is incorrect");

Quantize::quantizeChords(mtrack.chords, mtrack.tuplets, sigmap);
}
}
Expand Down Expand Up @@ -387,7 +443,7 @@ void MTrack::processPendingNotes(QList<MidiChord> &midiChords,
ReducedFraction len = nextChordTick - tick;
if (len <= ReducedFraction(0, 1))
break;
len = MChord::findMinDuration(midiChords, len);
len = MChord::findMinDuration(tick, midiChords, len);
Measure* measure = score->tick2measure(tick.ticks());
len = splitDurationOnBarBoundary(len, tick, measure);

Expand Down Expand Up @@ -418,14 +474,12 @@ void MTrack::processPendingNotes(QList<MidiChord> &midiChords,
MidiChord& midiChord = midiChords[k];
setMusicNotesFromMidi(score, midiChord.notes, startChordTick,
len, chord, tick, drumset, useDrumset);
if (!midiChord.notes.empty() && midiChord.notes.first().len <= len) {
if (!midiChord.notes.empty() && midiChord.notes.first().offTime - tick <= len) {
midiChords.removeAt(k);
--k;
continue;
}
setTies(chord, score, midiChord.notes);
for (auto &midiNote: midiChord.notes)
midiNote.len -= len;
}
startChordTick += len;
}
Expand Down Expand Up @@ -561,7 +615,7 @@ std::multimap<int, MTrack> createMTrackList(ReducedFraction &lastTick,
MidiNote n;
n.pitch = pitch;
n.velo = e.velo();
n.len = len;
n.offTime = tick + len;

MidiChord c;
c.notes.push_back(n);
Expand Down Expand Up @@ -860,54 +914,6 @@ QList<TrackMeta> getTracksMeta(const QList<MTrack> &tracks,
return tracksMeta;
}


#ifdef QT_DEBUG

bool doNotesOverlap(const std::multimap<int, MTrack> &tracks)
{
for (const auto &track: tracks) {
const auto &chords = track.second.chords;
for (auto it = chords.begin(); it != chords.end(); ++it) {
const auto &firstChord = it->second;
const auto &firstOnTime = it->first;
for (const auto &note1: firstChord.notes) {
auto ii = std::next(it);
for (; ii != chords.end(); ++ii) {
const auto &secondChord = ii->second;
if (firstChord.voice != secondChord.voice)
continue;
const auto &secondOnTime = ii->first;
for (const auto &note2: secondChord.notes) {
if (note2.pitch != note1.pitch)
continue;
if (secondOnTime >= (firstOnTime + note1.len))
continue;
return true;
}
}
}
}
}
return false;
}

bool noTooShortNotes(const std::multimap<int, MTrack> &tracks)
{
for (const auto &track: tracks) {
const auto &chords = track.second.chords;
for (const auto &chord: chords) {
for (const auto &note: chord.second.notes) {
if (note.len < MChord::minAllowedDuration())
return false;
}
}
}
return true;
}

#endif


void convertMidi(Score *score, const MidiFile *mf)
{
ReducedFraction lastTick;
Expand All @@ -919,14 +925,15 @@ void convertMidi(Score *score, const MidiFile *mf)
MChord::removeOverlappingNotes(tracks);

Q_ASSERT_X(!doNotesOverlap(tracks),
"convertMidi:", "There are overlapping notes of the same voice that is incorrect");
"convertMidi", "There are overlapping notes of the same voice that is incorrect");

quantizeAllTracks(tracks, sigmap, lastTick);

Q_ASSERT_X(!doNotesOverlap(tracks),
"convertMidi:", "There are overlapping notes of the same voice that is incorrect");
"convertMidi", "There are overlapping notes of the same voice that is incorrect");

Q_ASSERT_X(noTooShortNotes(tracks),
"convertMidi:", "There are notes of length < min allowed duration");
"convertMidi", "There are notes of length < min allowed duration");

MChord::mergeChordsWithEqualOnTimeAndVoice(tracks);
LRHand::splitIntoLeftRightHands(tracks);
Expand Down
79 changes: 40 additions & 39 deletions mscore/importmidi_chord.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ const ReducedFraction& minAllowedDuration()
return minDuration;
}

ReducedFraction maxNoteLen(const QList<MidiNote> &notes)
ReducedFraction maxNoteOffTime(const QList<MidiNote> &notes)
{
ReducedFraction maxLen(0, 1);
ReducedFraction maxOffTime(0, 1);
for (const auto &note: notes) {
if (note.len > maxLen)
maxLen = note.len;
if (note.offTime > maxOffTime)
maxOffTime = note.offTime;
}
return maxLen;
return maxOffTime;
}

// remove overlapping notes with the same pitch
Expand All @@ -33,32 +33,31 @@ void removeOverlappingNotes(std::multimap<int, MTrack> &tracks)
{
for (auto &track: tracks) {
auto &chords = track.second.chords;
for (auto it = chords.begin(); it != chords.end(); ++it) {
auto &firstChord = it->second;
const auto &firstOnTime = it->first;
for (auto &note1: firstChord.notes) {
auto ii = std::next(it);
for (; ii != chords.end(); ++ii) {
auto &secondChord = ii->second;
if (firstChord.voice != secondChord.voice)
for (auto i1 = chords.begin(); i1 != chords.end(); ++i1) {
auto &chord1 = i1->second;
const auto &onTime1 = i1->first;
for (auto &note1: chord1.notes) {
for (auto i2 = std::next(i1); i2 != chords.end(); ++i2) {
const auto &onTime2 = i2->first;
if (onTime2 >= note1.offTime)
break;
auto &chord2 = i2->second;
if (chord1.voice != chord2.voice)
continue;
const auto &secondOnTime = ii->first;
for (auto &note2: secondChord.notes) {
for (auto &note2: chord2.notes) {
if (note2.pitch != note1.pitch)
continue;
if (secondOnTime >= (firstOnTime + note1.len))
continue;
qDebug("Midi import: overlapping events: %d+%d %d+%d",
firstOnTime.ticks(), note1.len.ticks(),
secondOnTime.ticks(), note2.len.ticks());
note1.len = secondOnTime - firstOnTime;
ii = std::prev(chords.end());
onTime1.ticks(), note1.offTime.ticks(),
onTime2.ticks(), note2.offTime.ticks());
note1.offTime = onTime2;
i2 = std::prev(chords.end());
break;
}
}
if (note1.len <= ReducedFraction(0, 1)) {
qDebug("Midi import: duration <= 0: drop note at %d",
firstOnTime.ticks());
if (note1.offTime <= ReducedFraction(0, 1)) {
qDebug("removeOverlappingNotes: duration <= 0: drop note at %d",
onTime1.ticks());
continue;
}
}
Expand Down Expand Up @@ -139,7 +138,7 @@ void collectChords(std::multimap<int, MTrack> &tracks)
const auto &note = it->second.notes[0];

// short events with len < minAllowedDuration must be cleaned up
Q_ASSERT_X(note.len >= minAllowedDuration(),
Q_ASSERT_X(note.offTime - it->first >= minAllowedDuration(),
"MChord: collectChords", "Note length is less than min allowed duration");

if (it->first <= currentChordStart + curThreshTime) {
Expand All @@ -156,21 +155,21 @@ void collectChords(std::multimap<int, MTrack> &tracks)
&& curThreshTime == threshTime) {
curThreshTime += threshExtTime;
}
if (it->first + note.len > maxOffTime)
maxOffTime = it->first + note.len;
if (note.offTime > maxOffTime)
maxOffTime = note.offTime;
it = chords.erase(it);
continue;
}
}
currentChordStart = it->first;
maxOffTime = currentChordStart + note.len;
maxOffTime = note.offTime;
curThreshTime = threshTime;
++it;
}

Q_ASSERT_X(areOnTimeValuesDifferent(chords),
"MChord: collectChords", "onTime values of chords are equal "
"but should be different");
"MChord: collectChords",
"onTime values of chords are equal but should be different");
}
}

Expand All @@ -195,12 +194,12 @@ void sortNotesByLength(std::multimap<ReducedFraction, MidiChord> &chords)
struct {
bool operator()(const MidiNote &note1, const MidiNote &note2)
{
return note1.len < note2.len;
return note1.offTime < note2.offTime;
}
} lenSort;

for (auto &chordEvent: chords) {
// in each chord sort notes by pitches
// in each chord sort notes by lengths
auto &notes = chordEvent.second.notes;
qSort(notes.begin(), notes.end(), lenSort);
}
Expand All @@ -219,13 +218,13 @@ void splitUnequalChords(std::multimap<int, MTrack> &tracks)
for (auto &chordEvent: chords) {
auto &chord = chordEvent.second;
auto &notes = chord.notes;
ReducedFraction len;
ReducedFraction offTime;
for (auto it = notes.begin(); it != notes.end(); ) {
if (it == notes.begin())
len = it->len;
offTime = it->offTime;
else {
ReducedFraction newLen = it->len;
if (newLen != len) {
ReducedFraction newOffTime = it->offTime;
if (newOffTime != offTime) {
MidiChord newChord(chord);
newChord.notes.clear();
for (int j = it - notes.begin(); j > 0; --j)
Expand All @@ -243,14 +242,16 @@ void splitUnequalChords(std::multimap<int, MTrack> &tracks)
}
}

ReducedFraction findMinDuration(const QList<MidiChord> &midiChords,
ReducedFraction findMinDuration(const ReducedFraction &onTime,
const QList<MidiChord> &midiChords,
const ReducedFraction &length)
{
ReducedFraction len = length;
for (const auto &chord: midiChords) {
for (const auto &note: chord.notes) {
if ((note.len < len) && (note.len != ReducedFraction(0, 1)))
len = note.len;
if ((note.offTime - onTime < len)
&& (note.offTime - onTime != ReducedFraction(0, 1)))
len = note.offTime - onTime;
}
}
return len;
Expand Down
10 changes: 6 additions & 4 deletions mscore/importmidi_chord.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ class MidiNote {
public:
int pitch;
int velo;
ReducedFraction len;
ReducedFraction offTime;
ReducedFraction quantizedOffTime = {-1, 1}; // invalid
Tie* tie = nullptr;
bool staccato = false;
};
Expand All @@ -21,8 +22,8 @@ class MidiChord {
public:
int voice = 0;
bool isInTuplet = false;
ReducedFraction quantizedOnTime = {-1, 1}; // invalid
QList<MidiNote> notes;

bool isStaccato() const
{
for (const auto &note: notes)
Expand Down Expand Up @@ -66,9 +67,10 @@ Iter findEndChordInRange(const ReducedFraction &endRangeTick,
return it;
}

ReducedFraction maxNoteLen(const QList<MidiNote> &notes);
ReducedFraction maxNoteOffTime(const QList<MidiNote> &notes);
const ReducedFraction& minAllowedDuration();
ReducedFraction findMinDuration(const QList<MidiChord> &midiChords,
ReducedFraction findMinDuration(const ReducedFraction &onTime,
const QList<MidiChord> &midiChords,
const ReducedFraction &length);
void sortNotesByPitch(std::multimap<ReducedFraction, MidiChord> &chords);
void collectChords(std::multimap<int, MTrack> &tracks);
Expand Down