2013-09-05 16:37:49 +02:00
|
|
|
//=============================================================================
|
|
|
|
// MuseScore
|
|
|
|
// Music Composition & Notation
|
|
|
|
//
|
|
|
|
// Copyright (C) 2013 Werner Schweer
|
|
|
|
//
|
|
|
|
// This program is free software; you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU General Public License version 2
|
|
|
|
// as published by the Free Software Foundation and appearing in
|
|
|
|
// the file LICENCE.GPL
|
|
|
|
//=============================================================================
|
|
|
|
|
2013-08-23 20:53:42 +02:00
|
|
|
#include "importmidi_clef.h"
|
|
|
|
#include "libmscore/score.h"
|
|
|
|
#include "libmscore/staff.h"
|
|
|
|
#include "libmscore/measure.h"
|
|
|
|
#include "libmscore/segment.h"
|
|
|
|
#include "libmscore/clef.h"
|
|
|
|
#include "libmscore/chordrest.h"
|
|
|
|
#include "libmscore/chord.h"
|
|
|
|
#include "libmscore/note.h"
|
|
|
|
#include "libmscore/slur.h"
|
2014-03-01 18:00:30 +01:00
|
|
|
#include "importmidi_tie.h"
|
2013-08-23 20:53:42 +02:00
|
|
|
#include "preferences.h"
|
|
|
|
|
|
|
|
#include <set>
|
|
|
|
|
|
|
|
|
|
|
|
namespace Ms {
|
|
|
|
|
|
|
|
extern Preferences preferences;
|
|
|
|
|
|
|
|
namespace MidiClef {
|
|
|
|
|
|
|
|
|
|
|
|
class AveragePitch
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
AveragePitch() : sumPitch_(0), count_(0) {}
|
|
|
|
AveragePitch(int sumPitch, int count) : sumPitch_(sumPitch), count_(count) {}
|
|
|
|
|
2014-03-02 12:23:27 +01:00
|
|
|
int pitch() const { return qRound(sumPitch_ * 1.0 / count_); }
|
2013-08-23 20:53:42 +02:00
|
|
|
void addPitch(int pitch)
|
|
|
|
{
|
|
|
|
sumPitch_ += pitch;
|
|
|
|
++count_;
|
|
|
|
}
|
|
|
|
AveragePitch& operator+=(const AveragePitch &other)
|
|
|
|
{
|
2014-03-14 18:50:02 +01:00
|
|
|
sumPitch_ += other.sumPitch_;
|
|
|
|
count_ += other.count_;
|
2013-08-23 20:53:42 +02:00
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
private:
|
|
|
|
int sumPitch_;
|
|
|
|
int count_;
|
|
|
|
};
|
|
|
|
|
2014-03-14 18:50:02 +01:00
|
|
|
class MinMaxPitch
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
MinMaxPitch() : minPitch_(std::numeric_limits<int>::max()), maxPitch_(-1) {}
|
|
|
|
MinMaxPitch(int minPitch, int maxPitch) : minPitch_(minPitch), maxPitch_(maxPitch) {}
|
|
|
|
|
|
|
|
int minPitch() const { return minPitch_; }
|
|
|
|
int maxPitch() const { return maxPitch_; }
|
|
|
|
int empty() const { return minPitch_ == std::numeric_limits<int>::max() || maxPitch_ == -1; }
|
|
|
|
void addPitch(int pitch)
|
|
|
|
{
|
|
|
|
if (pitch < minPitch_)
|
|
|
|
minPitch_ = pitch;
|
|
|
|
if (pitch > maxPitch_)
|
|
|
|
maxPitch_ = pitch;
|
|
|
|
}
|
|
|
|
private:
|
|
|
|
int minPitch_;
|
|
|
|
int maxPitch_;
|
|
|
|
};
|
|
|
|
|
2013-08-23 20:53:42 +02:00
|
|
|
|
2014-03-14 14:38:16 +01:00
|
|
|
int clefMidPitch()
|
2014-03-01 18:00:30 +01:00
|
|
|
{
|
2014-03-14 14:38:16 +01:00
|
|
|
static const int midPitch = 60;
|
|
|
|
return midPitch;
|
2014-03-01 18:00:30 +01:00
|
|
|
}
|
2013-08-23 20:53:42 +02:00
|
|
|
|
|
|
|
ClefType clefTypeFromAveragePitch(int averagePitch)
|
|
|
|
{
|
2014-03-14 14:38:16 +01:00
|
|
|
return averagePitch < clefMidPitch() ? ClefType::F : ClefType::G;
|
2013-08-23 20:53:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void createClef(ClefType clefType, Staff* staff, int tick, bool isSmall = false)
|
|
|
|
{
|
|
|
|
Clef* clef = new Clef(staff->score());
|
|
|
|
clef->setClefType(clefType);
|
|
|
|
const int track = staff->idx() * VOICES;
|
|
|
|
clef->setTrack(track);
|
|
|
|
clef->setGenerated(false);
|
|
|
|
clef->setMag(staff->mag());
|
|
|
|
clef->setSmall(isSmall);
|
|
|
|
Measure* m = staff->score()->tick2measure(tick);
|
|
|
|
Segment* seg = m->getSegment(clef, tick);
|
|
|
|
seg->add(clef);
|
|
|
|
}
|
|
|
|
|
|
|
|
void createSmallClef(ClefType clefType, Segment *chordRestSeg, Staff *staff)
|
|
|
|
{
|
|
|
|
const int strack = staff->idx() * VOICES;
|
|
|
|
const int tick = chordRestSeg->tick();
|
|
|
|
Segment *clefSeg = chordRestSeg->measure()->findSegment(Segment::SegClef, tick);
|
|
|
|
// remove clef if it is not the staff clef
|
|
|
|
if (clefSeg && clefSeg != chordRestSeg->score()->firstSegment(Segment::SegClef)) {
|
|
|
|
Clef *c = static_cast<Clef *>(clefSeg->element(strack)); // voice == 0 for clefs
|
|
|
|
if (c) {
|
|
|
|
clefSeg->remove(c);
|
|
|
|
delete c;
|
|
|
|
if (clefSeg->isEmpty()) {
|
|
|
|
chordRestSeg->measure()->remove(clefSeg);
|
|
|
|
delete clefSeg;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
createClef(clefType, staff, tick, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
AveragePitch findAverageSegPitch(const Segment *seg, int strack)
|
|
|
|
{
|
2014-03-14 18:50:02 +01:00
|
|
|
AveragePitch averagePitch;
|
|
|
|
for (int voice = 0; voice < VOICES; ++voice) {
|
|
|
|
ChordRest *cr = static_cast<ChordRest *>(seg->element(strack + voice));
|
|
|
|
if (cr && cr->type() == Element::CHORD) {
|
|
|
|
Chord *chord = static_cast<Chord *>(cr);
|
|
|
|
const auto ¬es = chord->notes();
|
|
|
|
for (const Note *note: notes)
|
|
|
|
averagePitch.addPitch(note->pitch());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return averagePitch;
|
|
|
|
}
|
|
|
|
|
|
|
|
MinMaxPitch findMinMaxSegPitch(const Segment *seg, int strack)
|
|
|
|
{
|
|
|
|
MinMaxPitch minMaxPitch;
|
2013-08-23 20:53:42 +02:00
|
|
|
for (int voice = 0; voice < VOICES; ++voice) {
|
|
|
|
ChordRest *cr = static_cast<ChordRest *>(seg->element(strack + voice));
|
|
|
|
if (cr && cr->type() == Element::CHORD) {
|
|
|
|
Chord *chord = static_cast<Chord *>(cr);
|
|
|
|
const auto ¬es = chord->notes();
|
|
|
|
for (const Note *note: notes)
|
2014-03-14 18:50:02 +01:00
|
|
|
minMaxPitch.addPitch(note->pitch());
|
2013-08-23 20:53:42 +02:00
|
|
|
}
|
|
|
|
}
|
2014-03-14 18:50:02 +01:00
|
|
|
return minMaxPitch;
|
2013-08-23 20:53:42 +02:00
|
|
|
}
|
|
|
|
|
2014-03-01 16:25:28 +01:00
|
|
|
|
|
|
|
#ifdef QT_DEBUG
|
|
|
|
|
|
|
|
bool doesClefBreakTie(const Staff *staff)
|
|
|
|
{
|
|
|
|
const int strack = staff->idx() * VOICES;
|
|
|
|
|
|
|
|
for (int voice = 0; voice < VOICES; ++voice) {
|
2014-03-01 18:05:35 +01:00
|
|
|
bool currentTie = false;
|
2014-03-01 16:25:28 +01:00
|
|
|
for (Segment *seg = staff->score()->firstSegment(); seg; seg = seg->next1()) {
|
|
|
|
if (seg->segmentType() == Segment::SegChordRest) {
|
2014-03-01 18:00:30 +01:00
|
|
|
if (MidiTie::isTiedBack(seg, strack, voice))
|
2014-03-01 18:05:35 +01:00
|
|
|
currentTie = false;
|
|
|
|
if (MidiTie::isTiedFor(seg, strack, voice))
|
|
|
|
currentTie = true;
|
2014-03-01 16:25:28 +01:00
|
|
|
}
|
|
|
|
else if (seg->segmentType() == Segment::SegClef && seg->element(strack)) {
|
2014-03-01 18:05:35 +01:00
|
|
|
if (currentTie) {
|
2014-03-01 16:25:28 +01:00
|
|
|
qDebug() << "Clef breaks tie; measure number (from 1):"
|
|
|
|
<< seg->measure()->no() + 1
|
|
|
|
<< ", staff index (from 0):" << staff->idx();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
2014-03-14 18:50:02 +01:00
|
|
|
|
|
|
|
// clef index: 0 - treble, 1 - bass
|
2014-03-01 16:25:28 +01:00
|
|
|
|
2014-03-14 14:38:16 +01:00
|
|
|
size_t findPitchPenaltyForClef(int pitch, int clefIndex)
|
2013-08-23 20:53:42 +02:00
|
|
|
{
|
2014-03-14 14:38:16 +01:00
|
|
|
static const size_t farPitchPenalty = 10000;
|
|
|
|
static const size_t approxPitchPenalty = 1;
|
|
|
|
static const int dx = 5;
|
|
|
|
|
|
|
|
static const int midPitch = clefMidPitch(); // all notes equal or upper - better in G clef
|
|
|
|
static const int highPitch = midPitch + dx; // all notes equal or upper - in G clef
|
|
|
|
static const int lowPitch = midPitch - dx; // all notes lower - in F clef
|
|
|
|
|
|
|
|
switch (clefIndex) {
|
|
|
|
case 0:
|
|
|
|
if (pitch < lowPitch)
|
|
|
|
return farPitchPenalty;
|
|
|
|
else if (pitch < midPitch)
|
|
|
|
return approxPitchPenalty;
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
if (pitch >= highPitch)
|
|
|
|
return farPitchPenalty;
|
|
|
|
else if (pitch >= midPitch)
|
|
|
|
return approxPitchPenalty;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
Q_ASSERT_X(false, "MidiClef::pitchPenalty", "Unknown clef type");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
2013-08-23 20:53:42 +02:00
|
|
|
|
2014-03-14 18:50:02 +01:00
|
|
|
size_t findClefChangePenalty(
|
|
|
|
int pos,
|
|
|
|
int clefIndex,
|
|
|
|
const std::vector<std::vector<int>> &trebleBassPath)
|
2014-03-14 14:38:16 +01:00
|
|
|
{
|
|
|
|
static const size_t clefChangePenalty = 1000;
|
|
|
|
static const int notesBetweenClefs = 5; // should be >= 2
|
2013-08-23 20:53:42 +02:00
|
|
|
|
2014-03-14 14:38:16 +01:00
|
|
|
if (pos == 0)
|
|
|
|
return 0;
|
|
|
|
if (pos - 1 == 0)
|
|
|
|
return clefChangePenalty;
|
|
|
|
|
|
|
|
for (int j = pos - 1; j != pos - notesBetweenClefs; --j) {
|
|
|
|
if (j == 0 || trebleBassPath[clefIndex][j] != clefIndex)
|
|
|
|
return clefChangePenalty;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
ClefType clefFromIndex(int index)
|
|
|
|
{
|
|
|
|
return (index == 0) ? ClefType::G : ClefType::F;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t findTiePenalty(MidiTie::TieStateMachine::State tieState)
|
|
|
|
{
|
|
|
|
static const size_t tieBreakagePenalty = 10000000;
|
|
|
|
return (tieState == MidiTie::TieStateMachine::State::TIED_BACK
|
|
|
|
|| tieState == MidiTie::TieStateMachine::State::TIED_BOTH)
|
|
|
|
? tieBreakagePenalty : 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void makeDynamicProgrammingStep(std::vector<std::vector<size_t>> &penalties,
|
|
|
|
std::vector<std::vector<int>> &optimalPaths,
|
|
|
|
int pos,
|
|
|
|
MidiTie::TieStateMachine::State tieState,
|
2014-03-14 18:50:02 +01:00
|
|
|
const MinMaxPitch &minMaxPitch)
|
2014-03-14 14:38:16 +01:00
|
|
|
{
|
|
|
|
for (int clefIndex = 0; clefIndex != 2; ++clefIndex) {
|
|
|
|
penalties[clefIndex].resize(pos + 1);
|
|
|
|
optimalPaths[clefIndex].resize(pos + 1);
|
|
|
|
}
|
|
|
|
const size_t tiePenalty = findTiePenalty(tieState);
|
|
|
|
|
|
|
|
for (int clefIndex = 0; clefIndex != 2; ++clefIndex) {
|
2014-03-14 18:50:02 +01:00
|
|
|
int significantPitch = (clefIndex == 0)
|
|
|
|
? minMaxPitch.minPitch() : minMaxPitch.maxPitch();
|
|
|
|
const size_t pitchPenalty = findPitchPenaltyForClef(significantPitch, clefIndex);
|
2014-03-14 14:38:16 +01:00
|
|
|
|
|
|
|
const size_t prevSameClefPenalty = (pos == 0)
|
|
|
|
? 0 : penalties[clefIndex][pos - 1];
|
|
|
|
const size_t sumPenaltySameClef = pitchPenalty + prevSameClefPenalty;
|
|
|
|
|
|
|
|
const size_t prevDiffClefPenalty = (pos == 0)
|
|
|
|
? 0 : penalties[1 - clefIndex][pos - 1];
|
2014-03-14 18:50:02 +01:00
|
|
|
const size_t clefPenalty = findClefChangePenalty(pos, 1 - clefIndex, optimalPaths);
|
2014-03-14 14:38:16 +01:00
|
|
|
const size_t sumPenaltyDiffClef
|
|
|
|
= tiePenalty + pitchPenalty + prevDiffClefPenalty + clefPenalty;
|
|
|
|
|
|
|
|
if (sumPenaltySameClef <= sumPenaltyDiffClef) {
|
|
|
|
penalties[clefIndex][pos] = sumPenaltySameClef;
|
|
|
|
if (pos > 0)
|
|
|
|
optimalPaths[clefIndex][pos] = clefIndex;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
penalties[clefIndex][pos] = sumPenaltyDiffClef;
|
|
|
|
if (pos > 0)
|
|
|
|
optimalPaths[clefIndex][pos] = 1 - clefIndex;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void createClefs(Staff *staff,
|
|
|
|
const std::vector<std::vector<size_t>> &penalties,
|
|
|
|
const std::vector<std::vector<int>> &optimalPaths,
|
|
|
|
std::vector<Segment *> segments,
|
|
|
|
ClefType *mainClef)
|
|
|
|
{
|
|
|
|
const size_t chordCount = penalties[0].size();
|
|
|
|
if (chordCount != 0) {
|
|
|
|
// create clefs
|
|
|
|
int currentClef = 0;
|
|
|
|
if (penalties[1][chordCount - 1] < penalties[0][chordCount - 1])
|
|
|
|
currentClef = 1;
|
|
|
|
for (size_t i = chordCount - 1; i; --i) {
|
|
|
|
const int prevClef = optimalPaths[currentClef][i];
|
|
|
|
if (prevClef != currentClef) {
|
|
|
|
createSmallClef(clefFromIndex(currentClef), segments[i], staff);
|
|
|
|
currentClef = prevClef;
|
2013-08-23 20:53:42 +02:00
|
|
|
}
|
|
|
|
}
|
2014-03-14 22:06:25 +01:00
|
|
|
*mainClef = clefFromIndex(currentClef);
|
2014-03-14 14:38:16 +01:00
|
|
|
}
|
|
|
|
}
|
2013-08-23 20:53:42 +02:00
|
|
|
|
2014-03-14 14:38:16 +01:00
|
|
|
void createClefs(Staff *staff, int indexOfOperation, bool isDrumTrack)
|
|
|
|
{
|
|
|
|
if (isDrumTrack) {
|
|
|
|
createClef(ClefType::PERC, staff, 0);
|
|
|
|
return;
|
|
|
|
}
|
2013-08-23 20:53:42 +02:00
|
|
|
|
2014-03-14 14:38:16 +01:00
|
|
|
ClefType mainClef = staff->clefTypeList(0)._concertClef;
|
|
|
|
const int strack = staff->idx() * VOICES;
|
|
|
|
const auto trackOpers = preferences.midiImportOperations.trackOperations(indexOfOperation);
|
|
|
|
|
|
|
|
if (trackOpers.changeClef) {
|
|
|
|
MidiTie::TieStateMachine tieTracker;
|
|
|
|
|
|
|
|
// find optimal clef changes by dynamic programming
|
|
|
|
std::vector<std::vector<size_t>> penalties(2); // 0 - treble, 1 - bass
|
|
|
|
std::vector<std::vector<int>> optimalPaths(2); // first col is unused
|
|
|
|
std::vector<Segment *> segments;
|
|
|
|
|
|
|
|
int pos = 0;
|
|
|
|
for (Segment *seg = staff->score()->firstSegment(Segment::SegChordRest); seg;
|
|
|
|
seg = seg->next1(Segment::SegChordRest)) {
|
|
|
|
|
2014-03-14 18:50:02 +01:00
|
|
|
const auto minMaxPitch = findMinMaxSegPitch(seg, strack);
|
|
|
|
if (minMaxPitch.empty()) // no chords
|
2014-03-14 14:38:16 +01:00
|
|
|
continue;
|
|
|
|
tieTracker.addSeg(seg, strack);
|
|
|
|
segments.push_back(seg);
|
|
|
|
|
|
|
|
makeDynamicProgrammingStep(penalties, optimalPaths, pos,
|
2014-03-14 18:50:02 +01:00
|
|
|
tieTracker.state(), minMaxPitch);
|
2014-03-14 14:38:16 +01:00
|
|
|
++pos;
|
2013-08-23 20:53:42 +02:00
|
|
|
}
|
2014-03-14 14:38:16 +01:00
|
|
|
// get the optimal clef changes found by dynamic programming
|
|
|
|
createClefs(staff, penalties, optimalPaths, segments, &mainClef);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
AveragePitch allAveragePitch;
|
|
|
|
for (Segment *seg = staff->score()->firstSegment(Segment::SegChordRest); seg;
|
|
|
|
seg = seg->next1(Segment::SegChordRest)) {
|
|
|
|
allAveragePitch += findAverageSegPitch(seg, strack);
|
2013-08-23 20:53:42 +02:00
|
|
|
}
|
2014-03-14 14:38:16 +01:00
|
|
|
mainClef = MidiClef::clefTypeFromAveragePitch(allAveragePitch.pitch());
|
2013-08-23 20:53:42 +02:00
|
|
|
}
|
2014-03-01 16:25:28 +01:00
|
|
|
|
2014-03-14 14:38:16 +01:00
|
|
|
staff->setClef(0, mainClef); // set main clef
|
|
|
|
createClef(mainClef, staff, 0);
|
|
|
|
|
2014-03-01 16:25:28 +01:00
|
|
|
Q_ASSERT_X(!doesClefBreakTie(staff), "MidiClef::createClefs", "Clef breaks the tie");
|
2013-08-23 20:53:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace MidiClef
|
|
|
|
} // namespace Ms
|
|
|
|
|