246 lines
11 KiB
C++
246 lines
11 KiB
C++
#include "importmidi_simplify.h"
|
|
#include "importmidi_chord.h"
|
|
#include "importmidi_inner.h"
|
|
#include "importmidi_meter.h"
|
|
#include "importmidi_tuplet.h"
|
|
#include "importmidi_quant.h"
|
|
#include "importmidi_voice.h"
|
|
#include "importmidi_operations.h"
|
|
#include "preferences.h"
|
|
#include "libmscore/sig.h"
|
|
#include "libmscore/durationtype.h"
|
|
#include "importmidi_tuplet_voice.h"
|
|
#include "midi/midifile.h"
|
|
|
|
|
|
namespace Ms {
|
|
namespace Simplify {
|
|
|
|
bool hasComplexBeamedDurations(const QList<std::pair<ReducedFraction, TDuration> > &list)
|
|
{
|
|
for (const auto &d: list) {
|
|
if (d.second == TDuration::DurationType::V_16TH
|
|
|| d.second == TDuration::DurationType::V_32ND
|
|
|| d.second == TDuration::DurationType::V_64TH
|
|
|| d.second == TDuration::DurationType::V_128TH) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
#ifdef QT_DEBUG
|
|
|
|
bool areDurationsEqual(
|
|
const QList<std::pair<ReducedFraction, TDuration> > &durations,
|
|
const ReducedFraction &desiredLen)
|
|
{
|
|
ReducedFraction sum(0, 1);
|
|
for (const auto &d: durations)
|
|
sum += ReducedFraction(d.second.fraction()) / d.first;
|
|
|
|
return desiredLen == desiredLen;
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
void lengthenNote(
|
|
MidiNote ¬e,
|
|
int voice,
|
|
const ReducedFraction ¬eOnTime,
|
|
const ReducedFraction &durationStart,
|
|
const ReducedFraction &endTime,
|
|
const ReducedFraction &barStart,
|
|
const ReducedFraction &barFraction,
|
|
const std::multimap<ReducedFraction, MidiTuplet::TupletData> &tuplets)
|
|
{
|
|
if (endTime <= note.offTime)
|
|
return;
|
|
|
|
const auto &opers = preferences.midiImportOperations.data()->trackOpers;
|
|
const int currentTrack = preferences.midiImportOperations.currentTrack();
|
|
|
|
const bool useDots = opers.useDots.value(currentTrack);
|
|
const auto tupletsForDuration = MidiTuplet::findTupletsInBarForDuration(
|
|
voice, barStart, note.offTime, endTime - note.offTime, tuplets);
|
|
|
|
Q_ASSERT_X(note.quant != ReducedFraction(-1, 1),
|
|
"Simplify::lengthenNote", "Note quant value was not set");
|
|
|
|
const auto origNoteDurations = Meter::toDurationList(
|
|
durationStart - barStart, note.offTime - barStart, barFraction,
|
|
tupletsForDuration, Meter::DurationType::NOTE, useDots, false);
|
|
|
|
const auto origRestDurations = Meter::toDurationList(
|
|
note.offTime - barStart, endTime - barStart, barFraction,
|
|
tupletsForDuration, Meter::DurationType::REST, useDots, false);
|
|
|
|
Q_ASSERT_X(areDurationsEqual(origNoteDurations, note.offTime - durationStart),
|
|
"Simplify::lengthenNote", "Too short note durations remaining");
|
|
Q_ASSERT_X(areDurationsEqual(origRestDurations, endTime - note.offTime),
|
|
"Simplify::lengthenNote", "Too short rest durations remaining");
|
|
|
|
// double - because can be + 0.5 for dots
|
|
double minNoteDurationCount = MidiDuration::durationCount(origNoteDurations);
|
|
double minRestDurationCount = MidiDuration::durationCount(origRestDurations);
|
|
|
|
ReducedFraction bestOffTime(-1, 1);
|
|
|
|
for (ReducedFraction offTime = note.offTime + note.quant;
|
|
offTime <= endTime; offTime += note.quant) {
|
|
double noteDurationCount = 0;
|
|
double restDurationCount = 0;
|
|
|
|
const auto noteDurations = Meter::toDurationList(
|
|
durationStart - barStart, offTime - barStart, barFraction,
|
|
tupletsForDuration, Meter::DurationType::NOTE, useDots, false);
|
|
|
|
Q_ASSERT_X(areDurationsEqual(noteDurations, offTime - durationStart),
|
|
"Simplify::lengthenNote", "Too short note durations remaining");
|
|
|
|
noteDurationCount += MidiDuration::durationCount(noteDurations);
|
|
|
|
if (offTime < endTime) {
|
|
const auto restDurations = Meter::toDurationList(
|
|
offTime - barStart, endTime - barStart, barFraction,
|
|
tupletsForDuration, Meter::DurationType::REST, useDots, false);
|
|
|
|
Q_ASSERT_X(areDurationsEqual(restDurations, endTime - offTime),
|
|
"Simplify::lengthenNote", "Too short rest durations remaining");
|
|
|
|
restDurationCount += MidiDuration::durationCount(restDurations);
|
|
}
|
|
|
|
if (noteDurationCount + restDurationCount
|
|
< minNoteDurationCount + minRestDurationCount) {
|
|
if (opers.isHumanPerformance.value() || noteDurationCount <= 1.5) {
|
|
minNoteDurationCount = noteDurationCount;
|
|
minRestDurationCount = restDurationCount;
|
|
bestOffTime = offTime;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bestOffTime == ReducedFraction(-1, 1))
|
|
return;
|
|
|
|
// check for staccato:
|
|
// don't apply staccato if note is tied
|
|
// (case noteOnTime != durationStart - another bar, for example)
|
|
|
|
bool hasLossOfAccuracy = false;
|
|
const double addedPart = ((bestOffTime - note.offTime)
|
|
/ (bestOffTime - durationStart)).toDouble();
|
|
const double STACCATO_TOL = 0.3;
|
|
|
|
if (addedPart >= STACCATO_TOL) {
|
|
if (noteOnTime == durationStart && minNoteDurationCount <= 1.5)
|
|
note.staccato = true;
|
|
else
|
|
hasLossOfAccuracy = true;
|
|
}
|
|
|
|
// if the difference is only in one note/rest and there is some loss of accuracy -
|
|
// discard change because it silently reduces duration accuracy
|
|
// without significant improvement of readability
|
|
|
|
if (!opers.isHumanPerformance.value()
|
|
&& (origNoteDurations.size() + origRestDurations.size())
|
|
- (minNoteDurationCount + minRestDurationCount) <= 1
|
|
&& !hasComplexBeamedDurations(origNoteDurations)
|
|
&& !hasComplexBeamedDurations(origRestDurations)
|
|
&& hasLossOfAccuracy) {
|
|
return;
|
|
}
|
|
|
|
note.offTime = bestOffTime;
|
|
}
|
|
|
|
void minimizeNumberOfRests(
|
|
std::multimap<ReducedFraction, MidiChord> &chords,
|
|
const TimeSigMap *sigmap,
|
|
const std::multimap<ReducedFraction, MidiTuplet::TupletData> &tuplets)
|
|
{
|
|
for (auto it = chords.begin(); it != chords.end(); ++it) {
|
|
for (MidiNote ¬e: it->second.notes) {
|
|
const auto barStart = MidiBar::findBarStart(note.offTime, sigmap);
|
|
const auto barFraction = ReducedFraction(
|
|
sigmap->timesig(barStart.ticks()).timesig());
|
|
auto durationStart = (it->first > barStart) ? it->first : barStart;
|
|
if (it->second.isInTuplet) {
|
|
const auto &tuplet = it->second.tuplet->second;
|
|
if (note.offTime >= tuplet.onTime + tuplet.len)
|
|
durationStart = tuplet.onTime + tuplet.len;
|
|
}
|
|
auto endTime = (barStart == note.offTime)
|
|
? barStart : barStart + barFraction;
|
|
if (note.isInTuplet) {
|
|
const auto &tuplet = note.tuplet->second;
|
|
if (note.offTime == tuplet.onTime + tuplet.len)
|
|
continue;
|
|
endTime = barStart + Quantize::quantizeToLarge(
|
|
note.offTime - barStart, tuplet.len / tuplet.tupletNumber);
|
|
}
|
|
|
|
const auto beatLen = Meter::beatLength(barFraction);
|
|
const auto beatTime = barStart + Quantize::quantizeToLarge(
|
|
note.offTime - barStart, beatLen);
|
|
if (endTime > beatTime)
|
|
endTime = beatTime;
|
|
|
|
auto next = std::next(it);
|
|
while (next != chords.end()
|
|
&& (next->second.voice != it->second.voice
|
|
|| next->first < note.offTime)) {
|
|
++next;
|
|
}
|
|
if (next != chords.end()) {
|
|
if (next->first < endTime)
|
|
endTime = next->first;
|
|
if (next->second.isInTuplet && !note.isInTuplet) {
|
|
const auto &tuplet = next->second.tuplet->second;
|
|
if (tuplet.onTime < endTime)
|
|
endTime = tuplet.onTime;
|
|
}
|
|
}
|
|
|
|
lengthenNote(note, it->second.voice, it->first, durationStart, endTime,
|
|
barStart, barFraction, tuplets);
|
|
}
|
|
}
|
|
}
|
|
|
|
void simplifyDurations(std::multimap<int, MTrack> &tracks, const TimeSigMap *sigmap)
|
|
{
|
|
auto &opers = preferences.midiImportOperations;
|
|
|
|
for (auto &track: tracks) {
|
|
MTrack &mtrack = track.second;
|
|
if (mtrack.mtrack->drumTrack())
|
|
continue;
|
|
auto &chords = track.second.chords;
|
|
if (chords.empty())
|
|
continue;
|
|
|
|
if (opers.data()->trackOpers.simplifyDurations.value(mtrack.indexOfOperation)) {
|
|
MidiOperations::CurrentTrackSetter setCurrentTrack{opers, mtrack.indexOfOperation};
|
|
|
|
Q_ASSERT_X(MidiTuplet::areTupletRangesOk(chords, mtrack.tuplets),
|
|
"Simplify::simplifyDurations", "Tuplet chord/note is outside tuplet "
|
|
"or non-tuplet chord/note is inside tuplet before simplification");
|
|
|
|
minimizeNumberOfRests(chords, sigmap, mtrack.tuplets);
|
|
// empty tuplets may appear after simplification
|
|
MidiTuplet::removeEmptyTuplets(mtrack);
|
|
|
|
Q_ASSERT_X(MidiTuplet::areTupletRangesOk(chords, mtrack.tuplets),
|
|
"Simplify::simplifyDurations", "Tuplet chord/note is outside tuplet "
|
|
"or non-tuplet chord/note is inside tuplet after simplification");
|
|
}
|
|
}
|
|
}
|
|
|
|
} // Simplify
|
|
} // Ms
|